Deprecate Currency; introduce holds and freezing into fungible traits (#12951)

* First reworking of fungibles API

* New API and docs

* More fungible::* API improvements

* New ref-counting logic for old API

* Missing files

* Fixes

* Use the new transfer logic

* Use fungibles for the dispatchables

* Use shelve/restore names

* Locking works with total balance.

* repotting and removal

* Separate Holds from Reserves

* Introduce freezes

* Missing files

* Tests for freezing

* Fix hold+freeze combo

* More tests

* Fee-free dispatchable for upgrading accounts

* Benchmarks and a few fixes

* Another test

* Docs and refactor to avoid blanket impls

* Repot

* Fit out ItemOf fully

* Add events to Balanced traits

* Introduced events into Hold traits

* Fix Assets pallet tests

* Assets benchmarks pass

* Missing files and fixes

* Fixes

* Fixes

* Benchmarks fixes

* Fix balance benchmarks

* Formatting

* Expose fungible sub modules

* Move NIS to fungible API

* Fix broken impl and add test

* Fix tests

* API for `transfer_and_hold`

* Use composite APIs

* Formatting

* Upgraded event

* Fixes

* Fixes

* Fixes

* Fixes

* Repot tests and some fixed

* Fix some bits

* Fix dust tests

* Rename `set_balance`

- `Balances::set_balance` becomes `Balances::force_set_balance`
- `Unbalanced::set_balance` becomes `Unbalances::write_balance`

* becomes

* Move dust handling to fungibles API

* Formatting

* Fixes and more refactoring

* Fixes

* Fixes

* Fixes

* Fixes

* Fixes

* Fixes

* Fixes

* Fixes

* Fixes

* Use reducible_balance for better correctness on fees

* Reducing hold to zero should remove entry.

* Add test

* Docs

* Update frame/support/src/traits/tokens/fungibles/hold.rs

Co-authored-by: Muharem Ismailov <ismailov.m.h@gmail.com>

* Update frame/support/src/traits/tokens/fungibles/regular.rs

Co-authored-by: Muharem Ismailov <ismailov.m.h@gmail.com>

* Update frame/support/src/traits/tokens/fungible/hold.rs

Co-authored-by: Muharem Ismailov <ismailov.m.h@gmail.com>

* Update frame/support/src/traits/tokens/fungible/regular.rs

Co-authored-by: Muharem Ismailov <ismailov.m.h@gmail.com>

* Docs

* Docs

* Docs

* Fix NIS benchmarks

* Doc comment

* Remove post_mutation

* Fix some tests

* Fix some grumbles

* Enumify bool args to fungible(s) functions

* Fix up assets and balances

* Formatting

* Fix contracts

* Fix tests & benchmarks build

* Typify minted boolean arg

* Typify on_hold boolean arg; renames

* Fix numerous tests

* Fix dependency issue

* Privatize dangerous API mutate_account

* Fix contracts (@alext - please check this commit)

* Remove println

* Fix tests for contracts

* Fix broken rename

* Fix broken rename

* Fix broken rename

* Docs

* Update frame/support/src/traits/tokens/fungible/hold.rs

Co-authored-by: Anthony Alaribe <anthonyalaribe@gmail.com>

* remove from_ref_time

* Update frame/executive/src/lib.rs

Co-authored-by: Anthony Alaribe <anthonyalaribe@gmail.com>

* Update frame/executive/src/lib.rs

Co-authored-by: Anthony Alaribe <anthonyalaribe@gmail.com>

* Reenable test

* Update frame/support/src/traits/tokens/fungibles/hold.rs

Co-authored-by: Anthony Alaribe <anthonyalaribe@gmail.com>

* Update frame/support/src/traits/tokens/fungible/hold.rs

Co-authored-by: Anthony Alaribe <anthonyalaribe@gmail.com>

* Update frame/support/src/traits/tokens/fungible/hold.rs

Co-authored-by: Anthony Alaribe <anthonyalaribe@gmail.com>

* Update frame/support/src/traits/tokens/fungible/hold.rs

Co-authored-by: Anthony Alaribe <anthonyalaribe@gmail.com>

* Update frame/support/src/traits/tokens/currency.rs

Co-authored-by: Anthony Alaribe <anthonyalaribe@gmail.com>

* Update frame/lottery/src/tests.rs

Co-authored-by: Anthony Alaribe <anthonyalaribe@gmail.com>

* Update frame/support/src/traits/tokens/fungible/mod.rs

Co-authored-by: Anthony Alaribe <anthonyalaribe@gmail.com>

* Update frame/support/src/traits/tokens/fungible/regular.rs

Co-authored-by: Anthony Alaribe <anthonyalaribe@gmail.com>

* Update frame/support/src/traits/tokens/fungibles/freeze.rs

Co-authored-by: Anthony Alaribe <anthonyalaribe@gmail.com>

* Update frame/support/src/traits/tokens/fungible/regular.rs

Co-authored-by: Anthony Alaribe <anthonyalaribe@gmail.com>

* Update frame/support/src/traits/tokens/fungibles/hold.rs

Co-authored-by: Anthony Alaribe <anthonyalaribe@gmail.com>

* Update frame/support/src/traits/tokens/fungibles/hold.rs

Co-authored-by: Anthony Alaribe <anthonyalaribe@gmail.com>

* Update frame/support/src/traits/tokens/fungibles/hold.rs

Co-authored-by: Anthony Alaribe <anthonyalaribe@gmail.com>

* Rename UnwantedRemoval to UnwantedAccountRemoval

* Docs

* Formatting

* Update frame/balances/src/lib.rs

Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Update primitives/runtime/src/lib.rs

Co-authored-by: Keith Yeung <kungfukeith11@gmail.com>

* handle_raw_dust oes nothing

* Formatting

* Fixes

* Grumble

* Fixes

* Add test

* Add test

* Tests for reducible_balance

* Fixes

* Fix Salary

* Fixes

* Disable broken test

* Disable nicely

* Fixes

* Fixes

* Fixes

* Rename some events

* Fix nomination pools breakage

* Add compatibility stub for transfer tx

* Reinstate a safely compatible version of Balances set_balance

* Fixes

* Grumble

* Update frame/nis/src/lib.rs

Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>

* ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_balances

* disable flakey tests

* Update frame/balances/src/lib.rs

Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>

* Grumbles

* Grumble

---------

Co-authored-by: Muharem Ismailov <ismailov.m.h@gmail.com>
Co-authored-by: Alexander Theißen <alex.theissen@me.com>
Co-authored-by: Anthony Alaribe <anthonyalaribe@gmail.com>
Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
Co-authored-by: Keith Yeung <kungfukeith11@gmail.com>
Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>
Co-authored-by: command-bot <>
This commit is contained in:
Gavin Wood
2023-03-18 14:47:55 +00:00
committed by GitHub
parent c699876ab8
commit 5d81f23f8f
129 changed files with 8370 additions and 6164 deletions
@@ -17,7 +17,7 @@
//! Traits and associated datatypes for managing abstract stored values.
use crate::{storage::StorageMap, traits::misc::HandleLifetime};
use crate::storage::StorageMap;
use codec::FullCodec;
use sp_runtime::DispatchError;
@@ -81,48 +81,29 @@ pub trait StoredMap<K, T: Default> {
/// be the default value), or where the account is being removed or reset back to the default value
/// where previously it did exist (though may have been in a default state). This works well with
/// system module's `CallOnCreatedAccount` and `CallKillAccount`.
pub struct StorageMapShim<S, L, K, T>(sp_std::marker::PhantomData<(S, L, K, T)>);
impl<
S: StorageMap<K, T, Query = T>,
L: HandleLifetime<K>,
K: FullCodec,
T: FullCodec + Default,
> StoredMap<K, T> for StorageMapShim<S, L, K, T>
pub struct StorageMapShim<S, K, T>(sp_std::marker::PhantomData<(S, K, T)>);
impl<S: StorageMap<K, T, Query = T>, K: FullCodec, T: FullCodec + Default> StoredMap<K, T>
for StorageMapShim<S, K, T>
{
fn get(k: &K) -> T {
S::get(k)
}
fn insert(k: &K, t: T) -> Result<(), DispatchError> {
if !S::contains_key(&k) {
L::created(k)?;
}
S::insert(k, t);
Ok(())
}
fn remove(k: &K) -> Result<(), DispatchError> {
if S::contains_key(&k) {
L::killed(k)?;
S::remove(k);
}
Ok(())
}
fn mutate<R>(k: &K, f: impl FnOnce(&mut T) -> R) -> Result<R, DispatchError> {
if !S::contains_key(&k) {
L::created(k)?;
}
Ok(S::mutate(k, f))
}
fn mutate_exists<R>(k: &K, f: impl FnOnce(&mut Option<T>) -> R) -> Result<R, DispatchError> {
S::try_mutate_exists(k, |maybe_value| {
let existed = maybe_value.is_some();
let r = f(maybe_value);
let exists = maybe_value.is_some();
if !existed && exists {
L::created(k)?;
} else if existed && !exists {
L::killed(k)?;
}
Ok(r)
})
}
@@ -131,15 +112,7 @@ impl<
f: impl FnOnce(&mut Option<T>) -> Result<R, E>,
) -> Result<R, E> {
S::try_mutate_exists(k, |maybe_value| {
let existed = maybe_value.is_some();
let r = f(maybe_value)?;
let exists = maybe_value.is_some();
if !existed && exists {
L::created(k).map_err(E::from)?;
} else if existed && !exists {
L::killed(k).map_err(E::from)?;
}
Ok(r)
})
}
+2 -1
View File
@@ -30,6 +30,7 @@ pub use imbalance::Imbalance;
pub mod pay;
pub use misc::{
AssetId, Balance, BalanceConversion, BalanceStatus, ConvertRank, DepositConsequence,
ExistenceRequirement, GetSalary, Locker, WithdrawConsequence, WithdrawReasons,
ExistenceRequirement, Fortitude, GetSalary, Locker, Precision, Preservation, Provenance,
Restriction, WithdrawConsequence, WithdrawReasons,
};
pub use pay::{Pay, PayFromAccount, PaymentStatus};
@@ -1,367 +0,0 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! The traits for dealing with a single fungible token class and any associated types.
use super::{
misc::{Balance, DepositConsequence, WithdrawConsequence},
*,
};
use crate::{
dispatch::{DispatchError, DispatchResult},
traits::misc::Get,
};
use sp_runtime::traits::Saturating;
mod balanced;
mod imbalance;
pub use balanced::{Balanced, Unbalanced};
pub use imbalance::{CreditOf, DebtOf, HandleImbalanceDrop, Imbalance};
/// Trait for providing balance-inspection access to a fungible asset.
pub trait Inspect<AccountId> {
/// Scalar type for representing balance of an account.
type Balance: Balance;
/// The total amount of issuance in the system.
fn total_issuance() -> Self::Balance;
/// The total amount of issuance in the system excluding those which are controlled by the
/// system.
fn active_issuance() -> Self::Balance {
Self::total_issuance()
}
/// The minimum balance any single account may have.
fn minimum_balance() -> Self::Balance;
/// Get the balance of `who`.
fn balance(who: &AccountId) -> Self::Balance;
/// Get the maximum amount that `who` can withdraw/transfer successfully.
fn reducible_balance(who: &AccountId, keep_alive: bool) -> Self::Balance;
/// Returns `true` if the balance of `who` may be increased by `amount`.
///
/// - `who`: The account of which the balance should be increased by `amount`.
/// - `amount`: How much should the balance be increased?
/// - `mint`: Will `amount` be minted to deposit it into `account`?
fn can_deposit(who: &AccountId, amount: Self::Balance, mint: bool) -> DepositConsequence;
/// Returns `Failed` if the balance of `who` may not be decreased by `amount`, otherwise
/// the consequence.
fn can_withdraw(who: &AccountId, amount: Self::Balance) -> WithdrawConsequence<Self::Balance>;
}
/// Trait for providing an ERC-20 style fungible asset.
pub trait Mutate<AccountId>: Inspect<AccountId> {
/// Increase the balance of `who` by exactly `amount`, minting new tokens. If that isn't
/// possible then an `Err` is returned and nothing is changed.
fn mint_into(who: &AccountId, amount: Self::Balance) -> DispatchResult;
/// Decrease the balance of `who` by at least `amount`, possibly slightly more in the case of
/// minimum_balance requirements, burning the tokens. If that isn't possible then an `Err` is
/// returned and nothing is changed. If successful, the amount of tokens reduced is returned.
fn burn_from(who: &AccountId, amount: Self::Balance) -> Result<Self::Balance, DispatchError>;
// TODO: Remove.
/// Attempt to reduce the balance of `who` by as much as possible up to `amount`, and possibly
/// slightly more due to minimum_balance requirements. If no decrease is possible then an `Err`
/// is returned and nothing is changed. If successful, the amount of tokens reduced is returned.
///
/// The default implementation just uses `withdraw` along with `reducible_balance` to ensure
/// that it doesn't fail.
fn slash(who: &AccountId, amount: Self::Balance) -> Result<Self::Balance, DispatchError> {
Self::burn_from(who, Self::reducible_balance(who, false).min(amount))
}
/// Transfer funds from one account into another. The default implementation uses `mint_into`
/// and `burn_from` and may generate unwanted events.
fn teleport(
source: &AccountId,
dest: &AccountId,
amount: Self::Balance,
) -> Result<Self::Balance, DispatchError> {
let extra = Self::can_withdraw(&source, amount).into_result()?;
// As we first burn and then mint, we don't need to check if `mint` fits into the supply.
// If we can withdraw/burn it, we can also mint it again.
Self::can_deposit(dest, amount.saturating_add(extra), false).into_result()?;
let actual = Self::burn_from(source, amount)?;
debug_assert!(
actual == amount.saturating_add(extra),
"can_withdraw must agree with withdraw; qed"
);
match Self::mint_into(dest, actual) {
Ok(_) => Ok(actual),
Err(err) => {
debug_assert!(false, "can_deposit returned true previously; qed");
// attempt to return the funds back to source
let revert = Self::mint_into(source, actual);
debug_assert!(revert.is_ok(), "withdrew funds previously; qed");
Err(err)
},
}
}
}
/// Trait for providing a fungible asset which can only be transferred.
pub trait Transfer<AccountId>: Inspect<AccountId> {
/// Transfer funds from one account into another.
fn transfer(
source: &AccountId,
dest: &AccountId,
amount: Self::Balance,
keep_alive: bool,
) -> Result<Self::Balance, DispatchError>;
/// Reduce the active issuance by some amount.
fn deactivate(_: Self::Balance) {}
/// Increase the active issuance by some amount, up to the outstanding amount reduced.
fn reactivate(_: Self::Balance) {}
}
/// Trait for inspecting a fungible asset which can be reserved.
pub trait InspectHold<AccountId>: Inspect<AccountId> {
/// Amount of funds held in reserve by `who`.
fn balance_on_hold(who: &AccountId) -> Self::Balance;
/// Check to see if some `amount` of funds of `who` may be placed on hold.
fn can_hold(who: &AccountId, amount: Self::Balance) -> bool;
}
// TODO: Introduce `HoldReason`.
/// Trait for mutating a fungible asset which can be reserved.
pub trait MutateHold<AccountId>: InspectHold<AccountId> + Transfer<AccountId> {
/// Hold some funds in an account.
fn hold(who: &AccountId, amount: Self::Balance) -> DispatchResult;
/// Release up to `amount` held funds in an account.
///
/// The actual amount released is returned with `Ok`.
///
/// If `best_effort` is `true`, then the amount actually unreserved and returned as the inner
/// value of `Ok` may be smaller than the `amount` passed.
fn release(
who: &AccountId,
amount: Self::Balance,
best_effort: bool,
) -> Result<Self::Balance, DispatchError>;
// TODO: Introduce repatriate_held
/// Transfer held funds into a destination account.
///
/// If `on_hold` is `true`, then the destination account must already exist and the assets
/// transferred will still be on hold in the destination account. If not, then the destination
/// account need not already exist, but must be creatable.
///
/// If `best_effort` is `true`, then an amount less than `amount` may be transferred without
/// error.
///
/// The actual amount transferred is returned, or `Err` in the case of error and nothing is
/// changed.
fn transfer_held(
source: &AccountId,
dest: &AccountId,
amount: Self::Balance,
best_effort: bool,
on_held: bool,
) -> Result<Self::Balance, DispatchError>;
}
/// Trait for slashing a fungible asset which can be reserved.
pub trait BalancedHold<AccountId>: Balanced<AccountId> + MutateHold<AccountId> {
/// Reduce the balance of some funds on hold in an account.
///
/// The resulting imbalance is the first item of the tuple returned.
///
/// As much funds that are on hold up to `amount` will be deducted as possible. If this is less
/// than `amount`, then a non-zero second item will be returned.
fn slash_held(
who: &AccountId,
amount: Self::Balance,
) -> (CreditOf<AccountId, Self>, Self::Balance);
}
impl<AccountId, T: Balanced<AccountId> + MutateHold<AccountId>> BalancedHold<AccountId> for T {
// TODO: This should be implemented properly, and `slash` should be removed.
fn slash_held(
who: &AccountId,
amount: Self::Balance,
) -> (CreditOf<AccountId, Self>, Self::Balance) {
let actual = match Self::release(who, amount, true) {
Ok(x) => x,
Err(_) => return (Imbalance::default(), amount),
};
<Self as fungible::Balanced<AccountId>>::slash(who, actual)
}
}
/// Convert a `fungibles` trait implementation into a `fungible` trait implementation by identifying
/// a single item.
pub struct ItemOf<
F: fungibles::Inspect<AccountId>,
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
AccountId,
>(sp_std::marker::PhantomData<(F, A, AccountId)>);
impl<
F: fungibles::Inspect<AccountId>,
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
AccountId,
> Inspect<AccountId> for ItemOf<F, A, AccountId>
{
type Balance = <F as fungibles::Inspect<AccountId>>::Balance;
fn total_issuance() -> Self::Balance {
<F as fungibles::Inspect<AccountId>>::total_issuance(A::get())
}
fn active_issuance() -> Self::Balance {
<F as fungibles::Inspect<AccountId>>::active_issuance(A::get())
}
fn minimum_balance() -> Self::Balance {
<F as fungibles::Inspect<AccountId>>::minimum_balance(A::get())
}
fn balance(who: &AccountId) -> Self::Balance {
<F as fungibles::Inspect<AccountId>>::balance(A::get(), who)
}
fn reducible_balance(who: &AccountId, keep_alive: bool) -> Self::Balance {
<F as fungibles::Inspect<AccountId>>::reducible_balance(A::get(), who, keep_alive)
}
fn can_deposit(who: &AccountId, amount: Self::Balance, mint: bool) -> DepositConsequence {
<F as fungibles::Inspect<AccountId>>::can_deposit(A::get(), who, amount, mint)
}
fn can_withdraw(who: &AccountId, amount: Self::Balance) -> WithdrawConsequence<Self::Balance> {
<F as fungibles::Inspect<AccountId>>::can_withdraw(A::get(), who, amount)
}
}
impl<
F: fungibles::Mutate<AccountId>,
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
AccountId,
> Mutate<AccountId> for ItemOf<F, A, AccountId>
{
fn mint_into(who: &AccountId, amount: Self::Balance) -> DispatchResult {
<F as fungibles::Mutate<AccountId>>::mint_into(A::get(), who, amount)
}
fn burn_from(who: &AccountId, amount: Self::Balance) -> Result<Self::Balance, DispatchError> {
<F as fungibles::Mutate<AccountId>>::burn_from(A::get(), who, amount)
}
}
impl<
F: fungibles::Transfer<AccountId>,
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
AccountId,
> Transfer<AccountId> for ItemOf<F, A, AccountId>
{
fn transfer(
source: &AccountId,
dest: &AccountId,
amount: Self::Balance,
keep_alive: bool,
) -> Result<Self::Balance, DispatchError> {
<F as fungibles::Transfer<AccountId>>::transfer(A::get(), source, dest, amount, keep_alive)
}
fn deactivate(amount: Self::Balance) {
<F as fungibles::Transfer<AccountId>>::deactivate(A::get(), amount)
}
fn reactivate(amount: Self::Balance) {
<F as fungibles::Transfer<AccountId>>::reactivate(A::get(), amount)
}
}
impl<
F: fungibles::InspectHold<AccountId>,
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
AccountId,
> InspectHold<AccountId> for ItemOf<F, A, AccountId>
{
fn balance_on_hold(who: &AccountId) -> Self::Balance {
<F as fungibles::InspectHold<AccountId>>::balance_on_hold(A::get(), who)
}
fn can_hold(who: &AccountId, amount: Self::Balance) -> bool {
<F as fungibles::InspectHold<AccountId>>::can_hold(A::get(), who, amount)
}
}
impl<
F: fungibles::MutateHold<AccountId>,
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
AccountId,
> MutateHold<AccountId> for ItemOf<F, A, AccountId>
{
fn hold(who: &AccountId, amount: Self::Balance) -> DispatchResult {
<F as fungibles::MutateHold<AccountId>>::hold(A::get(), who, amount)
}
fn release(
who: &AccountId,
amount: Self::Balance,
best_effort: bool,
) -> Result<Self::Balance, DispatchError> {
<F as fungibles::MutateHold<AccountId>>::release(A::get(), who, amount, best_effort)
}
fn transfer_held(
source: &AccountId,
dest: &AccountId,
amount: Self::Balance,
best_effort: bool,
on_hold: bool,
) -> Result<Self::Balance, DispatchError> {
<F as fungibles::MutateHold<AccountId>>::transfer_held(
A::get(),
source,
dest,
amount,
best_effort,
on_hold,
)
}
}
impl<
F: fungibles::Unbalanced<AccountId>,
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
AccountId,
> Unbalanced<AccountId> for ItemOf<F, A, AccountId>
{
fn set_balance(who: &AccountId, amount: Self::Balance) -> DispatchResult {
<F as fungibles::Unbalanced<AccountId>>::set_balance(A::get(), who, amount)
}
fn set_total_issuance(amount: Self::Balance) -> () {
<F as fungibles::Unbalanced<AccountId>>::set_total_issuance(A::get(), amount)
}
fn decrease_balance(
who: &AccountId,
amount: Self::Balance,
) -> Result<Self::Balance, DispatchError> {
<F as fungibles::Unbalanced<AccountId>>::decrease_balance(A::get(), who, amount)
}
fn decrease_balance_at_most(who: &AccountId, amount: Self::Balance) -> Self::Balance {
<F as fungibles::Unbalanced<AccountId>>::decrease_balance_at_most(A::get(), who, amount)
}
fn increase_balance(
who: &AccountId,
amount: Self::Balance,
) -> Result<Self::Balance, DispatchError> {
<F as fungibles::Unbalanced<AccountId>>::increase_balance(A::get(), who, amount)
}
fn increase_balance_at_most(who: &AccountId, amount: Self::Balance) -> Self::Balance {
<F as fungibles::Unbalanced<AccountId>>::increase_balance_at_most(A::get(), who, amount)
}
}
@@ -1,354 +0,0 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! The trait and associated types for sets of fungible tokens that manage total issuance without
//! requiring atomic balanced operations.
use super::{super::Imbalance as ImbalanceT, *};
use crate::{
dispatch::{DispatchError, DispatchResult},
traits::misc::{SameOrOther, TryDrop},
};
use sp_runtime::{
traits::{CheckedAdd, Zero},
ArithmeticError, TokenError,
};
use sp_std::marker::PhantomData;
/// A fungible token class where any creation and deletion of tokens is semi-explicit and where the
/// total supply is maintained automatically.
///
/// This is auto-implemented when a token class has `Unbalanced` implemented.
pub trait Balanced<AccountId>: Inspect<AccountId> {
/// The type for managing what happens when an instance of `Debt` is dropped without being used.
type OnDropDebt: HandleImbalanceDrop<Self::Balance>;
/// The type for managing what happens when an instance of `Credit` is dropped without being
/// used.
type OnDropCredit: HandleImbalanceDrop<Self::Balance>;
/// Reduce the total issuance by `amount` and return the according imbalance. The imbalance will
/// typically be used to reduce an account by the same amount with e.g. `settle`.
///
/// This is infallible, but doesn't guarantee that the entire `amount` is burnt, for example
/// in the case of underflow.
fn rescind(amount: Self::Balance) -> DebtOf<AccountId, Self>;
/// Increase the total issuance by `amount` and return the according imbalance. The imbalance
/// will typically be used to increase an account by the same amount with e.g.
/// `resolve_into_existing` or `resolve_creating`.
///
/// This is infallible, but doesn't guarantee that the entire `amount` is issued, for example
/// in the case of overflow.
fn issue(amount: Self::Balance) -> CreditOf<AccountId, Self>;
/// Produce a pair of imbalances that cancel each other out exactly.
///
/// This is just the same as burning and issuing the same amount and has no effect on the
/// total issuance.
fn pair(amount: Self::Balance) -> (DebtOf<AccountId, Self>, CreditOf<AccountId, Self>) {
(Self::rescind(amount), Self::issue(amount))
}
/// Deducts up to `value` from the combined balance of `who`. This function cannot fail.
///
/// The resulting imbalance is the first item of the tuple returned.
///
/// As much funds up to `value` will be deducted as possible. If this is less than `value`,
/// then a non-zero second item will be returned.
fn slash(who: &AccountId, amount: Self::Balance) -> (CreditOf<AccountId, Self>, Self::Balance);
/// Mints exactly `value` into the account of `who`.
///
/// If `who` doesn't exist, nothing is done and an `Err` returned. This could happen because it
/// the account doesn't yet exist and it isn't possible to create it under the current
/// circumstances and with `value` in it.
fn deposit(
who: &AccountId,
value: Self::Balance,
) -> Result<DebtOf<AccountId, Self>, DispatchError>;
/// Removes `value` balance from `who` account if possible.
///
/// If the removal is not possible, then it returns `Err` and nothing is changed.
///
/// If the operation is successful, this will return `Ok` with a `NegativeImbalance` whose value
/// is no less than `value`. It may be more in the case that removing it reduced it below
/// `Self::minimum_balance()`.
fn withdraw(
who: &AccountId,
value: Self::Balance,
// TODO: liveness: ExistenceRequirement,
) -> Result<CreditOf<AccountId, Self>, DispatchError>;
/// The balance of `who` is increased in order to counter `credit`. If the whole of `credit`
/// cannot be countered, then nothing is changed and the original `credit` is returned in an
/// `Err`.
///
/// Please note: If `credit.peek()` is less than `Self::minimum_balance()`, then `who` must
/// already exist for this to succeed.
fn resolve(
who: &AccountId,
credit: CreditOf<AccountId, Self>,
) -> Result<(), CreditOf<AccountId, Self>> {
let v = credit.peek();
let debt = match Self::deposit(who, v) {
Err(_) => return Err(credit),
Ok(d) => d,
};
let result = credit.offset(debt).try_drop();
debug_assert!(result.is_ok(), "ok deposit return must be equal to credit value; qed");
Ok(())
}
/// The balance of `who` is decreased in order to counter `debt`. If the whole of `debt`
/// cannot be countered, then nothing is changed and the original `debt` is returned in an
/// `Err`.
fn settle(
who: &AccountId,
debt: DebtOf<AccountId, Self>,
// TODO: liveness: ExistenceRequirement,
) -> Result<CreditOf<AccountId, Self>, DebtOf<AccountId, Self>> {
let amount = debt.peek();
let credit = match Self::withdraw(who, amount) {
Err(_) => return Err(debt),
Ok(d) => d,
};
match credit.offset(debt) {
SameOrOther::None => Ok(CreditOf::<AccountId, Self>::zero()),
SameOrOther::Same(dust) => Ok(dust),
SameOrOther::Other(rest) => {
debug_assert!(false, "ok withdraw return must be at least debt value; qed");
Err(rest)
},
}
}
}
/// A fungible token class where the balance can be set arbitrarily.
///
/// **WARNING**
/// Do not use this directly unless you want trouble, since it allows you to alter account balances
/// without keeping the issuance up to date. It has no safeguards against accidentally creating
/// token imbalances in your system leading to accidental imflation or deflation. It's really just
/// for the underlying datatype to implement so the user gets the much safer `Balanced` trait to
/// use.
pub trait Unbalanced<AccountId>: Inspect<AccountId> {
/// Set the balance of `who` to `amount`. If this cannot be done for some reason (e.g.
/// because the account cannot be created or an overflow) then an `Err` is returned.
fn set_balance(who: &AccountId, amount: Self::Balance) -> DispatchResult;
/// Set the total issuance to `amount`.
fn set_total_issuance(amount: Self::Balance);
/// Reduce the balance of `who` by `amount`. If it cannot be reduced by that amount for
/// some reason, return `Err` and don't reduce it at all. If Ok, return the imbalance.
///
/// Minimum balance will be respected and the returned imbalance may be up to
/// `Self::minimum_balance() - 1` greater than `amount`.
fn decrease_balance(
who: &AccountId,
amount: Self::Balance,
) -> Result<Self::Balance, DispatchError> {
let old_balance = Self::balance(who);
let (mut new_balance, mut amount) = if Self::reducible_balance(who, false) < amount {
return Err(TokenError::NoFunds.into())
} else {
(old_balance - amount, amount)
};
if new_balance < Self::minimum_balance() {
amount = amount.saturating_add(new_balance);
new_balance = Zero::zero();
}
// Defensive only - this should not fail now.
Self::set_balance(who, new_balance)?;
Ok(amount)
}
/// Reduce the balance of `who` by the most that is possible, up to `amount`.
///
/// Minimum balance will be respected and the returned imbalance may be up to
/// `Self::minimum_balance() - 1` greater than `amount`.
///
/// Return the imbalance by which the account was reduced.
fn decrease_balance_at_most(who: &AccountId, amount: Self::Balance) -> Self::Balance {
let old_balance = Self::balance(who);
let old_free_balance = Self::reducible_balance(who, false);
let (mut new_balance, mut amount) = if old_free_balance < amount {
(old_balance.saturating_sub(old_free_balance), old_free_balance)
} else {
(old_balance - amount, amount)
};
let minimum_balance = Self::minimum_balance();
if new_balance < minimum_balance {
amount = amount.saturating_add(new_balance);
new_balance = Zero::zero();
}
let mut r = Self::set_balance(who, new_balance);
if r.is_err() {
// Some error, probably because we tried to destroy an account which cannot be
// destroyed.
if new_balance.is_zero() && amount >= minimum_balance {
new_balance = minimum_balance;
amount -= minimum_balance;
r = Self::set_balance(who, new_balance);
}
if r.is_err() {
// Still an error. Apparently it's not possible to reduce at all.
amount = Zero::zero();
}
}
amount
}
/// Increase the balance of `who` by `amount`. If it cannot be increased by that amount
/// for some reason, return `Err` and don't increase it at all. If Ok, return the imbalance.
///
/// Minimum balance will be respected and an error will be returned if
/// `amount < Self::minimum_balance()` when the account of `who` is zero.
fn increase_balance(
who: &AccountId,
amount: Self::Balance,
) -> Result<Self::Balance, DispatchError> {
let old_balance = Self::balance(who);
let new_balance = old_balance.checked_add(&amount).ok_or(ArithmeticError::Overflow)?;
if new_balance < Self::minimum_balance() {
return Err(TokenError::BelowMinimum.into())
}
if old_balance != new_balance {
Self::set_balance(who, new_balance)?;
}
Ok(amount)
}
/// Increase the balance of `who` by the most that is possible, up to `amount`.
///
/// Minimum balance will be respected and the returned imbalance will be zero in the case that
/// `amount < Self::minimum_balance()`.
///
/// Return the imbalance by which the account was increased.
fn increase_balance_at_most(who: &AccountId, amount: Self::Balance) -> Self::Balance {
let old_balance = Self::balance(who);
let mut new_balance = old_balance.saturating_add(amount);
let mut amount = new_balance - old_balance;
if new_balance < Self::minimum_balance() {
new_balance = Zero::zero();
amount = Zero::zero();
}
if old_balance == new_balance || Self::set_balance(who, new_balance).is_ok() {
amount
} else {
Zero::zero()
}
}
}
/// Simple handler for an imbalance drop which increases the total issuance of the system by the
/// imbalance amount. Used for leftover debt.
pub struct IncreaseIssuance<AccountId, U>(PhantomData<(AccountId, U)>);
impl<AccountId, U: Unbalanced<AccountId>> HandleImbalanceDrop<U::Balance>
for IncreaseIssuance<AccountId, U>
{
fn handle(amount: U::Balance) {
U::set_total_issuance(U::total_issuance().saturating_add(amount))
}
}
/// Simple handler for an imbalance drop which decreases the total issuance of the system by the
/// imbalance amount. Used for leftover credit.
pub struct DecreaseIssuance<AccountId, U>(PhantomData<(AccountId, U)>);
impl<AccountId, U: Unbalanced<AccountId>> HandleImbalanceDrop<U::Balance>
for DecreaseIssuance<AccountId, U>
{
fn handle(amount: U::Balance) {
U::set_total_issuance(U::total_issuance().saturating_sub(amount))
}
}
/// An imbalance type which uses `DecreaseIssuance` to deal with anything `Drop`ed.
///
/// Basically means that funds in someone's account have been removed and not yet placed anywhere
/// else. If it gets dropped, then those funds will be assumed to be "burned" and the total supply
/// will be accordingly decreased to ensure it equals the sum of the balances of all accounts.
type Credit<AccountId, U> = Imbalance<
<U as Inspect<AccountId>>::Balance,
DecreaseIssuance<AccountId, U>,
IncreaseIssuance<AccountId, U>,
>;
/// An imbalance type which uses `IncreaseIssuance` to deal with anything `Drop`ed.
///
/// Basically means that there are funds in someone's account whose origin is as yet unaccounted
/// for. If it gets dropped, then those funds will be assumed to be "minted" and the total supply
/// will be accordingly increased to ensure it equals the sum of the balances of all accounts.
type Debt<AccountId, U> = Imbalance<
<U as Inspect<AccountId>>::Balance,
IncreaseIssuance<AccountId, U>,
DecreaseIssuance<AccountId, U>,
>;
/// Create some `Credit` item. Only for internal use.
fn credit<AccountId, U: Unbalanced<AccountId>>(amount: U::Balance) -> Credit<AccountId, U> {
Imbalance::new(amount)
}
/// Create some `Debt` item. Only for internal use.
fn debt<AccountId, U: Unbalanced<AccountId>>(amount: U::Balance) -> Debt<AccountId, U> {
Imbalance::new(amount)
}
impl<AccountId, U: Unbalanced<AccountId>> Balanced<AccountId> for U {
type OnDropCredit = DecreaseIssuance<AccountId, U>;
type OnDropDebt = IncreaseIssuance<AccountId, U>;
fn rescind(amount: Self::Balance) -> Debt<AccountId, Self> {
let old = U::total_issuance();
let new = old.saturating_sub(amount);
U::set_total_issuance(new);
debt(old - new)
}
fn issue(amount: Self::Balance) -> Credit<AccountId, Self> {
let old = U::total_issuance();
let new = old.saturating_add(amount);
U::set_total_issuance(new);
credit(new - old)
}
fn slash(who: &AccountId, amount: Self::Balance) -> (Credit<AccountId, Self>, Self::Balance) {
let slashed = U::decrease_balance_at_most(who, amount);
// `slashed` could be less than, greater than or equal to `amount`.
// If slashed == amount, it means the account had at least amount in it and it could all be
// removed without a problem.
// If slashed > amount, it means the account had more than amount in it, but not enough more
// to push it over minimum_balance.
// If slashed < amount, it means the account didn't have enough in it to be reduced by
// `amount` without being destroyed.
(credit(slashed), amount.saturating_sub(slashed))
}
fn deposit(
who: &AccountId,
amount: Self::Balance,
) -> Result<Debt<AccountId, Self>, DispatchError> {
let increase = U::increase_balance(who, amount)?;
Ok(debt(increase))
}
fn withdraw(
who: &AccountId,
amount: Self::Balance,
// TODO: liveness: ExistenceRequirement,
) -> Result<Credit<AccountId, Self>, DispatchError> {
let decrease = U::decrease_balance(who, amount)?;
Ok(credit(decrease))
}
}
@@ -0,0 +1,68 @@
// This file is part of Substrate.
// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! The traits for putting freezes within a single fungible token class.
use scale_info::TypeInfo;
use sp_runtime::DispatchResult;
/// Trait for inspecting a fungible asset which can be frozen. Freezing is essentially setting a
/// minimum balance bellow which the total balance (inclusive of any funds placed on hold) may not
/// be normally allowed to drop. Generally, freezers will provide an "update" function such that
/// if the total balance does drop below the limit, then the freezer can update their housekeeping
/// accordingly.
pub trait Inspect<AccountId>: super::Inspect<AccountId> {
/// An identifier for a freeze.
type Id: codec::Encode + TypeInfo + 'static;
/// Amount of funds held in reserve by `who` for the given `id`.
fn balance_frozen(id: &Self::Id, who: &AccountId) -> Self::Balance;
/// The amount of the balance which can become frozen. Defaults to `total_balance()`.
fn balance_freezable(who: &AccountId) -> Self::Balance {
Self::total_balance(who)
}
/// Returns `true` if it's possible to introduce a freeze for the given `id` onto the
/// account of `who`. This will be true as long as the implementor supports as many
/// concurrent freeze locks as there are possible values of `id`.
fn can_freeze(id: &Self::Id, who: &AccountId) -> bool;
}
/// Trait for introducing, altering and removing locks to freeze an account's funds so they never
/// go below a set minimum.
pub trait Mutate<AccountId>: Inspect<AccountId> {
/// Prevent actions which would reduce the balance of the account of `who` below the given
/// `amount` and identify this restriction though the given `id`. Unlike `extend_freeze`, any
/// outstanding freeze in place for `who` under the `id` are dropped.
///
/// If `amount` is zero, it is equivalent to using `thaw`.
///
/// Note that `amount` can be greater than the total balance, if desired.
fn set_freeze(id: &Self::Id, who: &AccountId, amount: Self::Balance) -> DispatchResult;
/// Prevent the balance of the account of `who` from being reduced below the given `amount` and
/// identify this restriction though the given `id`. Unlike `set_freeze`, this does not
/// counteract any pre-existing freezes in place for `who` under the `id`. Also unlike
/// `set_freeze`, in the case that `amount` is zero, this is no-op and never fails.
///
/// Note that more funds can be locked than the total balance, if desired.
fn extend_freeze(id: &Self::Id, who: &AccountId, amount: Self::Balance) -> DispatchResult;
/// Remove an existing lock.
fn thaw(id: &Self::Id, who: &AccountId) -> DispatchResult;
}
@@ -0,0 +1,393 @@
// This file is part of Substrate.
// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! The traits for putting holds within a single fungible token class.
use crate::{
ensure,
traits::tokens::{
DepositConsequence::Success,
Fortitude::{self, Force},
Precision::{self, BestEffort, Exact},
Preservation::{self, Protect},
Provenance::Extant,
Restriction::{self, Free, OnHold},
},
};
use scale_info::TypeInfo;
use sp_arithmetic::{
traits::{CheckedAdd, CheckedSub, Zero},
ArithmeticError,
};
use sp_runtime::{DispatchError, DispatchResult, Saturating, TokenError};
use super::*;
/// Trait for inspecting a fungible asset whose accounts support partitioning and slashing.
pub trait Inspect<AccountId>: super::Inspect<AccountId> {
/// An identifier for a hold. Used for disambiguating different holds so that
/// they can be individually replaced or removed and funds from one hold don't accidentally
/// become unreserved or slashed for another.
type Reason: codec::Encode + TypeInfo + 'static;
/// Amount of funds on hold (for all hold reasons) of `who`.
fn total_balance_on_hold(who: &AccountId) -> Self::Balance;
/// Get the maximum amount that the `total_balance_on_hold` of `who` can be reduced successfully
/// based on whether we are willing to force the reduction and potentially go below user-level
/// restrictions on the minimum amount of the account. Note: This cannot bring the account into
/// an inconsistent state with regards any required existential deposit.
///
/// Always less than `total_balance_on_hold()`.
fn reducible_total_balance_on_hold(who: &AccountId, force: Fortitude) -> Self::Balance;
/// Amount of funds on hold (for the given reason) of `who`.
fn balance_on_hold(reason: &Self::Reason, who: &AccountId) -> Self::Balance;
/// Returns `true` if it's possible to place (additional) funds under a hold of a given
/// `reason`. This may fail if the account has exhausted a limited number of concurrent
/// holds or if it cannot be made to exist (e.g. there is no provider reference).
///
/// NOTE: This does not take into account changes which could be made to the account of `who`
/// (such as removing a provider reference) after this call is made. Any usage of this should
/// therefore ensure the account is already in the appropriate state prior to calling it.
fn hold_available(reason: &Self::Reason, who: &AccountId) -> bool;
/// Check to see if some `amount` of funds of `who` may be placed on hold with the given
/// `reason`. Reasons why this may not be true:
///
/// - The implementor supports only a limited number of concurrent holds on an account which is
/// the possible values of `reason`;
/// - The total balance of the account is less than `amount`;
/// - Removing `amount` from the total balance would kill the account and remove the only
/// provider reference.
///
/// Note: we pass `true` as the third argument to `reducible_balance` since we assume that if
/// needed the balance can slashed. If we are using a simple non-forcing reserve-transfer, then
/// we really ought to check that we are not reducing the funds below the freeze-limit (if any).
///
/// NOTE: This does not take into account changes which could be made to the account of `who`
/// (such as removing a provider reference) after this call is made. Any usage of this should
/// therefore ensure the account is already in the appropriate state prior to calling it.
fn ensure_can_hold(
reason: &Self::Reason,
who: &AccountId,
amount: Self::Balance,
) -> DispatchResult {
ensure!(Self::hold_available(reason, who), TokenError::CannotCreateHold);
ensure!(
amount <= Self::reducible_balance(who, Protect, Force),
TokenError::FundsUnavailable
);
Ok(())
}
/// Check to see if some `amount` of funds of `who` may be placed on hold for the given
/// `reason`. Reasons why this may not be true:
///
/// - The implementor supports only a limited number of concurrernt holds on an account which is
/// the possible values of `reason`;
/// - The main balance of the account is less than `amount`;
/// - Removing `amount` from the main balance would kill the account and remove the only
/// provider reference.
///
/// NOTE: This does not take into account changes which could be made to the account of `who`
/// (such as removing a provider reference) after this call is made. Any usage of this should
/// therefore ensure the account is already in the appropriate state prior to calling it.
fn can_hold(reason: &Self::Reason, who: &AccountId, amount: Self::Balance) -> bool {
Self::ensure_can_hold(reason, who, amount).is_ok()
}
}
/// A fungible, holdable token class where the balance on hold can be set arbitrarily.
///
/// **WARNING**
/// Do not use this directly unless you want trouble, since it allows you to alter account balances
/// without keeping the issuance up to date. It has no safeguards against accidentally creating
/// token imbalances in your system leading to accidental imflation or deflation. It's really just
/// for the underlying datatype to implement so the user gets the much safer `Balanced` trait to
/// use.
pub trait Unbalanced<AccountId>: Inspect<AccountId> {
/// Forcefully set the balance on hold of `who` to `amount`. This is independent of any other
/// balances on hold or the main ("free") balance.
///
/// If this call executes successfully, you can `assert_eq!(Self::balance_on_hold(), amount);`.
///
/// This function does its best to force the balance change through, but will not break system
/// invariants such as any Existential Deposits needed or overflows/underflows.
/// If this cannot be done for some reason (e.g. because the account doesn't exist) then an
/// `Err` is returned.
// Implmentation note: This should increment the consumer refs if it moves total on hold from
// zero to non-zero and decrement in the opposite direction.
//
// Since this was not done in the previous logic, this will need either a migration or a
// state item which tracks whether the account is on the old logic or new.
fn set_balance_on_hold(
reason: &Self::Reason,
who: &AccountId,
amount: Self::Balance,
) -> DispatchResult;
/// Reduce the balance on hold of `who` by `amount`.
///
/// If `precision` is `Exact` and it cannot be reduced by that amount for
/// some reason, return `Err` and don't reduce it at all. If `precision` is `BestEffort`, then
/// reduce the balance of `who` by the most that is possible, up to `amount`.
///
/// In either case, if `Ok` is returned then the inner is the amount by which is was reduced.
fn decrease_balance_on_hold(
reason: &Self::Reason,
who: &AccountId,
mut amount: Self::Balance,
precision: Precision,
) -> Result<Self::Balance, DispatchError> {
let old_balance = Self::balance_on_hold(reason, who);
if let BestEffort = precision {
amount = amount.min(old_balance);
}
let new_balance = old_balance.checked_sub(&amount).ok_or(TokenError::FundsUnavailable)?;
Self::set_balance_on_hold(reason, who, new_balance)?;
Ok(amount)
}
/// Increase the balance on hold of `who` by `amount`.
///
/// If it cannot be increased by that amount for some reason, return `Err` and don't increase
/// it at all. If Ok, return the imbalance.
fn increase_balance_on_hold(
reason: &Self::Reason,
who: &AccountId,
amount: Self::Balance,
precision: Precision,
) -> Result<Self::Balance, DispatchError> {
let old_balance = Self::balance_on_hold(reason, who);
let new_balance = if let BestEffort = precision {
old_balance.saturating_add(amount)
} else {
old_balance.checked_add(&amount).ok_or(ArithmeticError::Overflow)?
};
let amount = new_balance.saturating_sub(old_balance);
if !amount.is_zero() {
Self::set_balance_on_hold(reason, who, new_balance)?;
}
Ok(amount)
}
}
/// Trait for mutating a fungible asset which can be placed on hold.
pub trait Mutate<AccountId>:
Inspect<AccountId> + super::Unbalanced<AccountId> + Unbalanced<AccountId>
{
/// Hold some funds in an account. If a hold for `reason` is already in place, then this
/// will increase it.
fn hold(reason: &Self::Reason, who: &AccountId, amount: Self::Balance) -> DispatchResult {
// NOTE: This doesn't change the total balance of the account so there's no need to
// check liquidity.
Self::ensure_can_hold(reason, who, amount)?;
// Should be infallible now, but we proceed softly anyway.
Self::decrease_balance(who, amount, Exact, Protect, Force)?;
Self::increase_balance_on_hold(reason, who, amount, BestEffort)?;
Self::done_hold(reason, who, amount);
Ok(())
}
/// Release up to `amount` held funds in an account.
///
/// The actual amount released is returned with `Ok`.
///
/// If `precision` is `BestEffort`, then the amount actually unreserved and returned as the
/// inner value of `Ok` may be smaller than the `amount` passed.
///
/// NOTE! The inner of the `Ok` result variant returns the *actual* amount released. This is the
/// opposite of the `ReservableCurrency::unreserve()` result, which gives the amount not able
/// to be released!
fn release(
reason: &Self::Reason,
who: &AccountId,
amount: Self::Balance,
precision: Precision,
) -> Result<Self::Balance, DispatchError> {
// NOTE: This doesn't change the total balance of the account so there's no need to
// check liquidity.
// We want to make sure we can deposit the amount in advance. If we can't then something is
// very wrong.
ensure!(Self::can_deposit(who, amount, Extant) == Success, TokenError::CannotCreate);
// Get the amount we can actually take from the hold. This might be less than what we want
// if we're only doing a best-effort.
let amount = Self::decrease_balance_on_hold(reason, who, amount, precision)?;
// Increase the main balance by what we took. We always do a best-effort here because we
// already checked that we can deposit before.
let actual = Self::increase_balance(who, amount, BestEffort)?;
Self::done_release(reason, who, actual);
Ok(actual)
}
/// Attempt to decrease the balance of `who` which is held for the given `reason` by `amount`.
///
/// If `precision` is `BestEffort`, then as much as possible is reduced, up to `amount`, and the
/// amount of tokens reduced is returned. Otherwise, if the total amount can be reduced, then it
/// is and the amount returned, and if not, then nothing changes and `Err` is returned.
///
/// If `force` is `Force`, then locks/freezes will be ignored. This should only be used when
/// conducting slashing or other activity which materially disadvantages the account holder
/// since it could provide a means of circumventing freezes.
fn burn_held(
reason: &Self::Reason,
who: &AccountId,
mut amount: Self::Balance,
precision: Precision,
force: Fortitude,
) -> Result<Self::Balance, DispatchError> {
// We must check total-balance requirements if `!force`.
let liquid = Self::reducible_total_balance_on_hold(who, force);
if let BestEffort = precision {
amount = amount.min(liquid);
} else {
ensure!(amount <= liquid, TokenError::Frozen);
}
let amount = Self::decrease_balance_on_hold(reason, who, amount, precision)?;
Self::set_total_issuance(Self::total_issuance().saturating_sub(amount));
Self::done_burn_held(reason, who, amount);
Ok(amount)
}
/// Transfer held funds into a destination account.
///
/// If `on_hold` is `true`, then the destination account must already exist and the assets
/// transferred will still be on hold in the destination account. If not, then the destination
/// account need not already exist, but must be creatable.
///
/// If `precision` is `BestEffort`, then an amount less than `amount` may be transferred without
/// error.
///
/// If `force` is `Force`, then other fund-locking mechanisms may be disregarded. It should be
/// left as `Polite` in most circumstances, but when you want the same power as a `slash`, it
/// may be `Force`.
///
/// The actual amount transferred is returned, or `Err` in the case of error and nothing is
/// changed.
fn transfer_on_hold(
reason: &Self::Reason,
source: &AccountId,
dest: &AccountId,
mut amount: Self::Balance,
precision: Precision,
mode: Restriction,
force: Fortitude,
) -> Result<Self::Balance, DispatchError> {
// We must check total-balance requirements if `force` is `Fortitude::Polite`.
let have = Self::balance_on_hold(reason, source);
let liquid = Self::reducible_total_balance_on_hold(source, force);
if let BestEffort = precision {
amount = amount.min(liquid).min(have);
} else {
ensure!(amount <= liquid, TokenError::Frozen);
ensure!(amount <= have, TokenError::FundsUnavailable);
}
// We want to make sure we can deposit the amount in advance. If we can't then something is
// very wrong.
ensure!(Self::can_deposit(dest, amount, Extant) == Success, TokenError::CannotCreate);
ensure!(mode == Free || Self::hold_available(reason, dest), TokenError::CannotCreateHold);
let amount = Self::decrease_balance_on_hold(reason, source, amount, precision)?;
let actual = if mode == OnHold {
Self::increase_balance_on_hold(reason, dest, amount, precision)?
} else {
Self::increase_balance(dest, amount, precision)?
};
Self::done_transfer_on_hold(reason, source, dest, actual);
Ok(actual)
}
/// Transfer some `amount` of free balance from `source` to become owned by `dest` but on hold
/// for `reason`.
///
/// If `precision` is `BestEffort`, then an amount less than `amount` may be transferred without
/// error.
///
/// `source` must obey the requirements of `keep_alive`.
///
/// If `force` is `Force`, then other fund-locking mechanisms may be disregarded. It should be
/// left as `Polite` in most circumstances, but when you want the same power as a `slash`, it
/// may be `Force`.
///
/// The amount placed on hold is returned or `Err` in the case of error and nothing is changed.
///
/// WARNING: This may return an error after a partial storage mutation. It should be used only
/// inside a transactional storage context and an `Err` result must imply a storage rollback.
fn transfer_and_hold(
reason: &Self::Reason,
source: &AccountId,
dest: &AccountId,
amount: Self::Balance,
precision: Precision,
expendability: Preservation,
force: Fortitude,
) -> Result<Self::Balance, DispatchError> {
ensure!(Self::hold_available(reason, dest), TokenError::CannotCreateHold);
ensure!(Self::can_deposit(dest, amount, Extant) == Success, TokenError::CannotCreate);
let actual = Self::decrease_balance(source, amount, precision, expendability, force)?;
Self::increase_balance_on_hold(reason, dest, actual, precision)?;
Self::done_transfer_on_hold(reason, source, dest, actual);
Ok(actual)
}
fn done_hold(_reason: &Self::Reason, _who: &AccountId, _amount: Self::Balance) {}
fn done_release(_reason: &Self::Reason, _who: &AccountId, _amount: Self::Balance) {}
fn done_burn_held(_reason: &Self::Reason, _who: &AccountId, _amount: Self::Balance) {}
fn done_transfer_on_hold(
_reason: &Self::Reason,
_source: &AccountId,
_dest: &AccountId,
_amount: Self::Balance,
) {
}
fn done_transfer_and_hold(
_reason: &Self::Reason,
_source: &AccountId,
_dest: &AccountId,
_transferred: Self::Balance,
) {
}
}
/// Trait for slashing a fungible asset which can be place on hold.
pub trait Balanced<AccountId>: super::Balanced<AccountId> + Unbalanced<AccountId> {
/// Reduce the balance of some funds on hold in an account.
///
/// The resulting imbalance is the first item of the tuple returned.
///
/// As much funds that are on hold up to `amount` will be deducted as possible. If this is less
/// than `amount`, then a non-zero second item will be returned.
fn slash(
reason: &Self::Reason,
who: &AccountId,
amount: Self::Balance,
) -> (Credit<AccountId, Self>, Self::Balance) {
let decrease = Self::decrease_balance_on_hold(reason, who, amount, BestEffort)
.unwrap_or(Default::default());
let credit =
Imbalance::<Self::Balance, Self::OnDropCredit, Self::OnDropDebt>::new(decrease);
Self::done_slash(reason, who, decrease);
(credit, amount.saturating_sub(decrease))
}
fn done_slash(_reason: &Self::Reason, _who: &AccountId, _amount: Self::Balance) {}
}
@@ -18,8 +18,11 @@
//! The imbalance type and its associates, which handles keeps everything adding up properly with
//! unbalanced operations.
use super::{super::Imbalance as ImbalanceT, balanced::Balanced, misc::Balance, *};
use crate::traits::misc::{SameOrOther, TryDrop};
use super::{super::Imbalance as ImbalanceT, Balanced, *};
use crate::traits::{
misc::{SameOrOther, TryDrop},
tokens::Balance,
};
use sp_runtime::{traits::Zero, RuntimeDebug};
use sp_std::marker::PhantomData;
@@ -30,6 +33,10 @@ pub trait HandleImbalanceDrop<Balance> {
fn handle(amount: Balance);
}
impl<Balance> HandleImbalanceDrop<Balance> for () {
fn handle(_: Balance) {}
}
/// An imbalance in the system, representing a divergence of recorded token supply from the sum of
/// the balances of all accounts. This is `must_use` in order to ensure it gets handled (placing
/// into an account, settling from an account or altering the supply).
@@ -135,7 +142,7 @@ impl<B: Balance, OnDrop: HandleImbalanceDrop<B>, OppositeOnDrop: HandleImbalance
}
/// Imbalance implying that the total_issuance value is less than the sum of all account balances.
pub type DebtOf<AccountId, B> = Imbalance<
pub type Debt<AccountId, B> = Imbalance<
<B as Inspect<AccountId>>::Balance,
// This will generally be implemented by increasing the total_issuance value.
<B as Balanced<AccountId>>::OnDropDebt,
@@ -144,7 +151,7 @@ pub type DebtOf<AccountId, B> = Imbalance<
/// Imbalance implying that the total_issuance value is greater than the sum of all account
/// balances.
pub type CreditOf<AccountId, B> = Imbalance<
pub type Credit<AccountId, B> = Imbalance<
<B as Inspect<AccountId>>::Balance,
// This will generally be implemented by decreasing the total_issuance value.
<B as Balanced<AccountId>>::OnDropCredit,
@@ -0,0 +1,451 @@
// This file is part of Substrate.
// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Adapter to use `fungibles::*` implementations as `fungible::*`.
use sp_core::Get;
use sp_runtime::{DispatchError, DispatchResult};
use super::*;
use crate::traits::tokens::{
fungibles, DepositConsequence, Fortitude, Imbalance as ImbalanceT, Precision, Preservation,
Provenance, Restriction, WithdrawConsequence,
};
/// Convert a `fungibles` trait implementation into a `fungible` trait implementation by identifying
/// a single item.
pub struct ItemOf<
F: fungibles::Inspect<AccountId>,
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
AccountId,
>(sp_std::marker::PhantomData<(F, A, AccountId)>);
impl<
F: fungibles::Inspect<AccountId>,
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
AccountId,
> Inspect<AccountId> for ItemOf<F, A, AccountId>
{
type Balance = <F as fungibles::Inspect<AccountId>>::Balance;
fn total_issuance() -> Self::Balance {
<F as fungibles::Inspect<AccountId>>::total_issuance(A::get())
}
fn active_issuance() -> Self::Balance {
<F as fungibles::Inspect<AccountId>>::active_issuance(A::get())
}
fn minimum_balance() -> Self::Balance {
<F as fungibles::Inspect<AccountId>>::minimum_balance(A::get())
}
fn balance(who: &AccountId) -> Self::Balance {
<F as fungibles::Inspect<AccountId>>::balance(A::get(), who)
}
fn total_balance(who: &AccountId) -> Self::Balance {
<F as fungibles::Inspect<AccountId>>::total_balance(A::get(), who)
}
fn reducible_balance(
who: &AccountId,
preservation: Preservation,
force: Fortitude,
) -> Self::Balance {
<F as fungibles::Inspect<AccountId>>::reducible_balance(A::get(), who, preservation, force)
}
fn can_deposit(
who: &AccountId,
amount: Self::Balance,
provenance: Provenance,
) -> DepositConsequence {
<F as fungibles::Inspect<AccountId>>::can_deposit(A::get(), who, amount, provenance)
}
fn can_withdraw(who: &AccountId, amount: Self::Balance) -> WithdrawConsequence<Self::Balance> {
<F as fungibles::Inspect<AccountId>>::can_withdraw(A::get(), who, amount)
}
}
impl<
F: fungibles::InspectHold<AccountId>,
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
AccountId,
> InspectHold<AccountId> for ItemOf<F, A, AccountId>
{
type Reason = F::Reason;
fn reducible_total_balance_on_hold(who: &AccountId, force: Fortitude) -> Self::Balance {
<F as fungibles::InspectHold<AccountId>>::reducible_total_balance_on_hold(
A::get(),
who,
force,
)
}
fn hold_available(reason: &Self::Reason, who: &AccountId) -> bool {
<F as fungibles::InspectHold<AccountId>>::hold_available(A::get(), reason, who)
}
fn total_balance_on_hold(who: &AccountId) -> Self::Balance {
<F as fungibles::InspectHold<AccountId>>::total_balance_on_hold(A::get(), who)
}
fn balance_on_hold(reason: &Self::Reason, who: &AccountId) -> Self::Balance {
<F as fungibles::InspectHold<AccountId>>::balance_on_hold(A::get(), reason, who)
}
fn can_hold(reason: &Self::Reason, who: &AccountId, amount: Self::Balance) -> bool {
<F as fungibles::InspectHold<AccountId>>::can_hold(A::get(), reason, who, amount)
}
}
impl<
F: fungibles::InspectFreeze<AccountId>,
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
AccountId,
> InspectFreeze<AccountId> for ItemOf<F, A, AccountId>
{
type Id = F::Id;
fn balance_frozen(id: &Self::Id, who: &AccountId) -> Self::Balance {
<F as fungibles::InspectFreeze<AccountId>>::balance_frozen(A::get(), id, who)
}
fn balance_freezable(who: &AccountId) -> Self::Balance {
<F as fungibles::InspectFreeze<AccountId>>::balance_freezable(A::get(), who)
}
fn can_freeze(id: &Self::Id, who: &AccountId) -> bool {
<F as fungibles::InspectFreeze<AccountId>>::can_freeze(A::get(), id, who)
}
}
impl<
F: fungibles::Unbalanced<AccountId>,
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
AccountId,
> Unbalanced<AccountId> for ItemOf<F, A, AccountId>
{
fn handle_dust(dust: regular::Dust<AccountId, Self>)
where
Self: Sized,
{
<F as fungibles::Unbalanced<AccountId>>::handle_dust(fungibles::Dust(A::get(), dust.0))
}
fn write_balance(
who: &AccountId,
amount: Self::Balance,
) -> Result<Option<Self::Balance>, DispatchError> {
<F as fungibles::Unbalanced<AccountId>>::write_balance(A::get(), who, amount)
}
fn set_total_issuance(amount: Self::Balance) -> () {
<F as fungibles::Unbalanced<AccountId>>::set_total_issuance(A::get(), amount)
}
fn decrease_balance(
who: &AccountId,
amount: Self::Balance,
precision: Precision,
preservation: Preservation,
force: Fortitude,
) -> Result<Self::Balance, DispatchError> {
<F as fungibles::Unbalanced<AccountId>>::decrease_balance(
A::get(),
who,
amount,
precision,
preservation,
force,
)
}
fn increase_balance(
who: &AccountId,
amount: Self::Balance,
precision: Precision,
) -> Result<Self::Balance, DispatchError> {
<F as fungibles::Unbalanced<AccountId>>::increase_balance(A::get(), who, amount, precision)
}
}
impl<
F: fungibles::UnbalancedHold<AccountId>,
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
AccountId,
> UnbalancedHold<AccountId> for ItemOf<F, A, AccountId>
{
fn set_balance_on_hold(
reason: &Self::Reason,
who: &AccountId,
amount: Self::Balance,
) -> DispatchResult {
<F as fungibles::UnbalancedHold<AccountId>>::set_balance_on_hold(
A::get(),
reason,
who,
amount,
)
}
fn decrease_balance_on_hold(
reason: &Self::Reason,
who: &AccountId,
amount: Self::Balance,
precision: Precision,
) -> Result<Self::Balance, DispatchError> {
<F as fungibles::UnbalancedHold<AccountId>>::decrease_balance_on_hold(
A::get(),
reason,
who,
amount,
precision,
)
}
fn increase_balance_on_hold(
reason: &Self::Reason,
who: &AccountId,
amount: Self::Balance,
precision: Precision,
) -> Result<Self::Balance, DispatchError> {
<F as fungibles::UnbalancedHold<AccountId>>::increase_balance_on_hold(
A::get(),
reason,
who,
amount,
precision,
)
}
}
impl<
F: fungibles::Mutate<AccountId>,
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
AccountId,
> Mutate<AccountId> for ItemOf<F, A, AccountId>
{
fn mint_into(who: &AccountId, amount: Self::Balance) -> Result<Self::Balance, DispatchError> {
<F as fungibles::Mutate<AccountId>>::mint_into(A::get(), who, amount)
}
fn burn_from(
who: &AccountId,
amount: Self::Balance,
precision: Precision,
force: Fortitude,
) -> Result<Self::Balance, DispatchError> {
<F as fungibles::Mutate<AccountId>>::burn_from(A::get(), who, amount, precision, force)
}
fn shelve(who: &AccountId, amount: Self::Balance) -> Result<Self::Balance, DispatchError> {
<F as fungibles::Mutate<AccountId>>::shelve(A::get(), who, amount)
}
fn restore(who: &AccountId, amount: Self::Balance) -> Result<Self::Balance, DispatchError> {
<F as fungibles::Mutate<AccountId>>::restore(A::get(), who, amount)
}
fn transfer(
source: &AccountId,
dest: &AccountId,
amount: Self::Balance,
preservation: Preservation,
) -> Result<Self::Balance, DispatchError> {
<F as fungibles::Mutate<AccountId>>::transfer(A::get(), source, dest, amount, preservation)
}
fn set_balance(who: &AccountId, amount: Self::Balance) -> Self::Balance {
<F as fungibles::Mutate<AccountId>>::set_balance(A::get(), who, amount)
}
}
impl<
F: fungibles::MutateHold<AccountId>,
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
AccountId,
> MutateHold<AccountId> for ItemOf<F, A, AccountId>
{
fn hold(reason: &Self::Reason, who: &AccountId, amount: Self::Balance) -> DispatchResult {
<F as fungibles::MutateHold<AccountId>>::hold(A::get(), reason, who, amount)
}
fn release(
reason: &Self::Reason,
who: &AccountId,
amount: Self::Balance,
precision: Precision,
) -> Result<Self::Balance, DispatchError> {
<F as fungibles::MutateHold<AccountId>>::release(A::get(), reason, who, amount, precision)
}
fn burn_held(
reason: &Self::Reason,
who: &AccountId,
amount: Self::Balance,
precision: Precision,
force: Fortitude,
) -> Result<Self::Balance, DispatchError> {
<F as fungibles::MutateHold<AccountId>>::burn_held(
A::get(),
reason,
who,
amount,
precision,
force,
)
}
fn transfer_on_hold(
reason: &Self::Reason,
source: &AccountId,
dest: &AccountId,
amount: Self::Balance,
precision: Precision,
mode: Restriction,
force: Fortitude,
) -> Result<Self::Balance, DispatchError> {
<F as fungibles::MutateHold<AccountId>>::transfer_on_hold(
A::get(),
reason,
source,
dest,
amount,
precision,
mode,
force,
)
}
fn transfer_and_hold(
reason: &Self::Reason,
source: &AccountId,
dest: &AccountId,
amount: Self::Balance,
precision: Precision,
preservation: Preservation,
force: Fortitude,
) -> Result<Self::Balance, DispatchError> {
<F as fungibles::MutateHold<AccountId>>::transfer_and_hold(
A::get(),
reason,
source,
dest,
amount,
precision,
preservation,
force,
)
}
}
impl<
F: fungibles::MutateFreeze<AccountId>,
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
AccountId,
> MutateFreeze<AccountId> for ItemOf<F, A, AccountId>
{
fn set_freeze(id: &Self::Id, who: &AccountId, amount: Self::Balance) -> DispatchResult {
<F as fungibles::MutateFreeze<AccountId>>::set_freeze(A::get(), id, who, amount)
}
fn extend_freeze(id: &Self::Id, who: &AccountId, amount: Self::Balance) -> DispatchResult {
<F as fungibles::MutateFreeze<AccountId>>::extend_freeze(A::get(), id, who, amount)
}
fn thaw(id: &Self::Id, who: &AccountId) -> DispatchResult {
<F as fungibles::MutateFreeze<AccountId>>::thaw(A::get(), id, who)
}
}
pub struct ConvertImbalanceDropHandler<AccountId, Balance, AssetIdType, AssetId, Handler>(
sp_std::marker::PhantomData<(AccountId, Balance, AssetIdType, AssetId, Handler)>,
);
impl<
AccountId,
Balance,
AssetIdType,
AssetId: Get<AssetIdType>,
Handler: crate::traits::tokens::fungibles::HandleImbalanceDrop<AssetIdType, Balance>,
> HandleImbalanceDrop<Balance>
for ConvertImbalanceDropHandler<AccountId, Balance, AssetIdType, AssetId, Handler>
{
fn handle(amount: Balance) {
Handler::handle(AssetId::get(), amount)
}
}
impl<
F: fungibles::Inspect<AccountId>
+ fungibles::Unbalanced<AccountId>
+ fungibles::Balanced<AccountId>,
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
AccountId,
> Balanced<AccountId> for ItemOf<F, A, AccountId>
{
type OnDropDebt =
ConvertImbalanceDropHandler<AccountId, Self::Balance, F::AssetId, A, F::OnDropDebt>;
type OnDropCredit =
ConvertImbalanceDropHandler<AccountId, Self::Balance, F::AssetId, A, F::OnDropCredit>;
fn deposit(
who: &AccountId,
value: Self::Balance,
precision: Precision,
) -> Result<Debt<AccountId, Self>, DispatchError> {
<F as fungibles::Balanced<AccountId>>::deposit(A::get(), who, value, precision)
.map(|debt| Imbalance::new(debt.peek()))
}
fn issue(amount: Self::Balance) -> Credit<AccountId, Self> {
Imbalance::new(<F as fungibles::Balanced<AccountId>>::issue(A::get(), amount).peek())
}
fn pair(amount: Self::Balance) -> (Debt<AccountId, Self>, Credit<AccountId, Self>) {
let (a, b) = <F as fungibles::Balanced<AccountId>>::pair(A::get(), amount);
(Imbalance::new(a.peek()), Imbalance::new(b.peek()))
}
fn rescind(amount: Self::Balance) -> Debt<AccountId, Self> {
Imbalance::new(<F as fungibles::Balanced<AccountId>>::rescind(A::get(), amount).peek())
}
fn resolve(
who: &AccountId,
credit: Credit<AccountId, Self>,
) -> Result<(), Credit<AccountId, Self>> {
let credit = fungibles::Imbalance::new(A::get(), credit.peek());
<F as fungibles::Balanced<AccountId>>::resolve(who, credit)
.map_err(|credit| Imbalance::new(credit.peek()))
}
fn settle(
who: &AccountId,
debt: Debt<AccountId, Self>,
preservation: Preservation,
) -> Result<Credit<AccountId, Self>, Debt<AccountId, Self>> {
let debt = fungibles::Imbalance::new(A::get(), debt.peek());
<F as fungibles::Balanced<AccountId>>::settle(who, debt, preservation)
.map(|credit| Imbalance::new(credit.peek()))
.map_err(|debt| Imbalance::new(debt.peek()))
}
fn withdraw(
who: &AccountId,
value: Self::Balance,
precision: Precision,
preservation: Preservation,
force: Fortitude,
) -> Result<Credit<AccountId, Self>, DispatchError> {
<F as fungibles::Balanced<AccountId>>::withdraw(
A::get(),
who,
value,
precision,
preservation,
force,
)
.map(|credit| Imbalance::new(credit.peek()))
}
}
impl<
F: fungibles::BalancedHold<AccountId>,
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
AccountId,
> BalancedHold<AccountId> for ItemOf<F, A, AccountId>
{
fn slash(
reason: &Self::Reason,
who: &AccountId,
amount: Self::Balance,
) -> (Credit<AccountId, Self>, Self::Balance) {
let (credit, amount) =
<F as fungibles::BalancedHold<AccountId>>::slash(A::get(), reason, who, amount);
(Imbalance::new(credit.peek()), amount)
}
}
#[test]
fn test() {}
@@ -0,0 +1,56 @@
// This file is part of Substrate.
// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! The traits for dealing with a single fungible token class and any associated types.
//!
//! ### User-implememted traits
//! - `Inspect`: Regular balance inspector functions.
//! - `Unbalanced`: Low-level balance mutating functions. Does not guarantee proper book-keeping and
//! so should not be called into directly from application code. Other traits depend on this and
//! provide default implementations based on it.
//! - `UnbalancedHold`: Low-level balance mutating functions for balances placed on hold. Does not
//! guarantee proper book-keeping and so should not be called into directly from application code.
//! Other traits depend on this and provide default implementations based on it.
//! - `Mutate`: Regular balance mutator functions. Pre-implemented using `Unbalanced`, though the
//! `done_*` functions should likely be reimplemented in case you want to do something following
//! the operation such as emit events.
//! - `InspectHold`: Inspector functions for balances on hold.
//! - `MutateHold`: Mutator functions for balances on hold. Mostly pre-implemented using
//! `UnbalancedHold`.
//! - `InspectFreeze`: Inspector functions for frozen balance.
//! - `MutateFreeze`: Mutator functions for frozen balance.
//! - `Balanced`: One-sided mutator functions for regular balances, which return imbalance objects
//! which guarantee eventual book-keeping. May be useful for some sophisticated operations where
//! funds must be removed from an account before it is known precisely what should be done with
//! them.
pub mod freeze;
pub mod hold;
mod imbalance;
mod item_of;
mod regular;
pub use freeze::{Inspect as InspectFreeze, Mutate as MutateFreeze};
pub use hold::{
Balanced as BalancedHold, Inspect as InspectHold, Mutate as MutateHold,
Unbalanced as UnbalancedHold,
};
pub use imbalance::{Credit, Debt, HandleImbalanceDrop, Imbalance};
pub use item_of::ItemOf;
pub use regular::{
Balanced, DecreaseIssuance, Dust, IncreaseIssuance, Inspect, Mutate, Unbalanced,
};
@@ -0,0 +1,506 @@
// This file is part of Substrate.
// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! `Inspect` and `Mutate` traits for working with regular balances.
use crate::{
dispatch::DispatchError,
ensure,
traits::{
tokens::{
misc::{
Balance, DepositConsequence,
Fortitude::{self, Force, Polite},
Precision::{self, BestEffort, Exact},
Preservation::{self, Expendable},
Provenance::{self, Extant},
WithdrawConsequence,
},
Imbalance as ImbalanceT,
},
SameOrOther, TryDrop,
},
};
use sp_arithmetic::traits::{CheckedAdd, CheckedSub, One};
use sp_runtime::{traits::Saturating, ArithmeticError, TokenError};
use sp_std::marker::PhantomData;
use super::{Credit, Debt, HandleImbalanceDrop, Imbalance};
/// Trait for providing balance-inspection access to a fungible asset.
pub trait Inspect<AccountId>: Sized {
/// Scalar type for representing balance of an account.
type Balance: Balance;
/// The total amount of issuance in the system.
fn total_issuance() -> Self::Balance;
/// The total amount of issuance in the system excluding those which are controlled by the
/// system.
fn active_issuance() -> Self::Balance {
Self::total_issuance()
}
/// The minimum balance any single account may have.
fn minimum_balance() -> Self::Balance;
/// Get the total amount of funds whose ultimate bneficial ownership can be determined as `who`.
///
/// This may include funds which are wholly inaccessible to `who`, either temporarily or even
/// indefinitely.
///
/// For the amount of the balance which is currently free to be removed from the account without
/// error, use `reducible_balance`.
///
/// For the amount of the balance which may eventually be free to be removed from the account,
/// use `balance()`.
fn total_balance(who: &AccountId) -> Self::Balance;
/// Get the balance of `who` which does not include funds which are exclusively allocated to
/// subsystems of the chain ("on hold" or "reserved").
///
/// In general this isn't especially useful outside of tests, and for practical purposes, you'll
/// want to use `reducible_balance()`.
fn balance(who: &AccountId) -> Self::Balance;
/// Get the maximum amount that `who` can withdraw/transfer successfully based on whether the
/// account should be kept alive (`preservation`) or whether we are willing to force the
/// reduction and potentially go below user-level restrictions on the minimum amount of the
/// account.
///
/// Always less than or equal to `balance()`.
fn reducible_balance(
who: &AccountId,
preservation: Preservation,
force: Fortitude,
) -> Self::Balance;
/// Returns `true` if the balance of `who` may be increased by `amount`.
///
/// - `who`: The account of which the balance should be increased by `amount`.
/// - `amount`: How much should the balance be increased?
/// - `provenance`: Will `amount` be minted to deposit it into `account` or is it already in the
/// system?
fn can_deposit(
who: &AccountId,
amount: Self::Balance,
provenance: Provenance,
) -> DepositConsequence;
/// Returns `Success` if the balance of `who` may be decreased by `amount`, otherwise
/// the consequence.
fn can_withdraw(who: &AccountId, amount: Self::Balance) -> WithdrawConsequence<Self::Balance>;
}
/// Special dust type which can be type-safely converted into a `Credit`.
#[must_use]
pub struct Dust<A, T: Inspect<A>>(pub(crate) T::Balance);
impl<A, T: Balanced<A>> Dust<A, T> {
/// Convert `Dust` into an instance of `Credit`.
pub fn into_credit(self) -> Credit<A, T> {
Credit::<A, T>::new(self.0)
}
}
/// A fungible token class where the balance can be set arbitrarily.
///
/// **WARNING**
/// Do not use this directly unless you want trouble, since it allows you to alter account balances
/// without keeping the issuance up to date. It has no safeguards against accidentally creating
/// token imbalances in your system leading to accidental imflation or deflation. It's really just
/// for the underlying datatype to implement so the user gets the much safer `Balanced` trait to
/// use.
pub trait Unbalanced<AccountId>: Inspect<AccountId> {
/// Create some dust and handle it with `Self::handle_dust`. This is an unbalanced operation
/// and it must only be used when an account is modified in a raw fashion, outside of the entire
/// fungibles API. The `amount` is capped at `Self::minimum_balance() - 1`.
///
/// This should not be reimplemented.
fn handle_raw_dust(amount: Self::Balance) {
Self::handle_dust(Dust(amount.min(Self::minimum_balance().saturating_sub(One::one()))))
}
/// Do something with the dust which has been destroyed from the system. `Dust` can be converted
/// into a `Credit` with the `Balanced` trait impl.
fn handle_dust(dust: Dust<AccountId, Self>);
/// Forcefully set the balance of `who` to `amount`.
///
/// If this call executes successfully, you can `assert_eq!(Self::balance(), amount);`.
///
/// For implementations which include one or more balances on hold, then these are *not*
/// included in the `amount`.
///
/// This function does its best to force the balance change through, but will not break system
/// invariants such as any Existential Deposits needed or overflows/underflows.
/// If this cannot be done for some reason (e.g. because the account cannot be created, deleted
/// or would overflow) then an `Err` is returned.
///
/// If `Ok` is returned then its inner, if `Some` is the amount which was discarded as dust due
/// to existential deposit requirements. The default implementation of `decrease_balance` and
/// `increase_balance` converts this into an `Imbalance` and then passes it into `handle_dust`.
fn write_balance(
who: &AccountId,
amount: Self::Balance,
) -> Result<Option<Self::Balance>, DispatchError>;
/// Set the total issuance to `amount`.
fn set_total_issuance(amount: Self::Balance);
/// Reduce the balance of `who` by `amount`.
///
/// If `precision` is `Exact` and it cannot be reduced by that amount for
/// some reason, return `Err` and don't reduce it at all. If `precision` is `BestEffort`, then
/// reduce the balance of `who` by the most that is possible, up to `amount`.
///
/// In either case, if `Ok` is returned then the inner is the amount by which is was reduced.
/// Minimum balance will be respected and thus the returned amount may be up to
/// `Self::minimum_balance() - 1` greater than `amount` in the case that the reduction caused
/// the account to be deleted.
fn decrease_balance(
who: &AccountId,
mut amount: Self::Balance,
precision: Precision,
preservation: Preservation,
force: Fortitude,
) -> Result<Self::Balance, DispatchError> {
let old_balance = Self::balance(who);
let free = Self::reducible_balance(who, preservation, force);
if let BestEffort = precision {
amount = amount.min(free);
}
let new_balance = old_balance.checked_sub(&amount).ok_or(TokenError::FundsUnavailable)?;
if let Some(dust) = Self::write_balance(who, new_balance)? {
Self::handle_dust(Dust(dust));
}
Ok(old_balance.saturating_sub(new_balance))
}
/// Increase the balance of `who` by `amount`.
///
/// If it cannot be increased by that amount for some reason, return `Err` and don't increase
/// it at all. If Ok, return the imbalance.
/// Minimum balance will be respected and an error will be returned if
/// `amount < Self::minimum_balance()` when the account of `who` is zero.
fn increase_balance(
who: &AccountId,
amount: Self::Balance,
precision: Precision,
) -> Result<Self::Balance, DispatchError> {
let old_balance = Self::balance(who);
let new_balance = if let BestEffort = precision {
old_balance.saturating_add(amount)
} else {
old_balance.checked_add(&amount).ok_or(ArithmeticError::Overflow)?
};
if new_balance < Self::minimum_balance() {
// Attempt to increase from 0 to below minimum -> stays at zero.
if let BestEffort = precision {
Ok(Default::default())
} else {
Err(TokenError::BelowMinimum.into())
}
} else {
if new_balance == old_balance {
Ok(Default::default())
} else {
if let Some(dust) = Self::write_balance(who, new_balance)? {
Self::handle_dust(Dust(dust));
}
Ok(new_balance.saturating_sub(old_balance))
}
}
}
/// Reduce the active issuance by some amount.
fn deactivate(_: Self::Balance) {}
/// Increase the active issuance by some amount, up to the outstanding amount reduced.
fn reactivate(_: Self::Balance) {}
}
/// Trait for providing a basic fungible asset.
pub trait Mutate<AccountId>: Inspect<AccountId> + Unbalanced<AccountId> {
/// Increase the balance of `who` by exactly `amount`, minting new tokens. If that isn't
/// possible then an `Err` is returned and nothing is changed.
fn mint_into(who: &AccountId, amount: Self::Balance) -> Result<Self::Balance, DispatchError> {
Self::total_issuance().checked_add(&amount).ok_or(ArithmeticError::Overflow)?;
let actual = Self::increase_balance(who, amount, Exact)?;
Self::set_total_issuance(Self::total_issuance().saturating_add(actual));
Self::done_mint_into(who, amount);
Ok(actual)
}
/// Decrease the balance of `who` by at least `amount`, possibly slightly more in the case of
/// minimum-balance requirements, burning the tokens. If that isn't possible then an `Err` is
/// returned and nothing is changed. If successful, the amount of tokens reduced is returned.
fn burn_from(
who: &AccountId,
amount: Self::Balance,
precision: Precision,
force: Fortitude,
) -> Result<Self::Balance, DispatchError> {
let actual = Self::reducible_balance(who, Expendable, force).min(amount);
ensure!(actual == amount || precision == BestEffort, TokenError::FundsUnavailable);
Self::total_issuance().checked_sub(&actual).ok_or(ArithmeticError::Overflow)?;
let actual = Self::decrease_balance(who, actual, BestEffort, Expendable, force)?;
Self::set_total_issuance(Self::total_issuance().saturating_sub(actual));
Self::done_burn_from(who, actual);
Ok(actual)
}
/// Attempt to decrease the `asset` balance of `who` by `amount`.
///
/// Equivalent to `burn_from`, except with an expectation that within the bounds of some
/// universal issuance, the total assets `suspend`ed and `resume`d will be equivalent. The
/// implementation may be configured such that the total assets suspended may never be less than
/// the total assets resumed (which is the invariant for an issuing system), or the reverse
/// (which the invariant in a non-issuing system).
///
/// Because of this expectation, any metadata associated with the asset is expected to survive
/// the suspect-resume cycle.
fn shelve(who: &AccountId, amount: Self::Balance) -> Result<Self::Balance, DispatchError> {
let actual = Self::reducible_balance(who, Expendable, Polite).min(amount);
ensure!(actual == amount, TokenError::FundsUnavailable);
Self::total_issuance().checked_sub(&actual).ok_or(ArithmeticError::Overflow)?;
let actual = Self::decrease_balance(who, actual, BestEffort, Expendable, Polite)?;
Self::set_total_issuance(Self::total_issuance().saturating_sub(actual));
Self::done_shelve(who, actual);
Ok(actual)
}
/// Attempt to increase the `asset` balance of `who` by `amount`.
///
/// Equivalent to `mint_into`, except with an expectation that within the bounds of some
/// universal issuance, the total assets `suspend`ed and `resume`d will be equivalent. The
/// implementation may be configured such that the total assets suspended may never be less than
/// the total assets resumed (which is the invariant for an issuing system), or the reverse
/// (which the invariant in a non-issuing system).
///
/// Because of this expectation, any metadata associated with the asset is expected to survive
/// the suspect-resume cycle.
fn restore(who: &AccountId, amount: Self::Balance) -> Result<Self::Balance, DispatchError> {
Self::total_issuance().checked_add(&amount).ok_or(ArithmeticError::Overflow)?;
let actual = Self::increase_balance(who, amount, Exact)?;
Self::set_total_issuance(Self::total_issuance().saturating_add(actual));
Self::done_restore(who, amount);
Ok(actual)
}
/// Transfer funds from one account into another.
fn transfer(
source: &AccountId,
dest: &AccountId,
amount: Self::Balance,
preservation: Preservation,
) -> Result<Self::Balance, DispatchError> {
let _extra = Self::can_withdraw(source, amount).into_result(preservation != Expendable)?;
Self::can_deposit(dest, amount, Extant).into_result()?;
Self::decrease_balance(source, amount, BestEffort, preservation, Polite)?;
// This should never fail as we checked `can_deposit` earlier. But we do a best-effort
// anyway.
let _ = Self::increase_balance(dest, amount, BestEffort);
Self::done_transfer(source, dest, amount);
Ok(amount)
}
/// Simple infallible function to force an account to have a particular balance, good for use
/// in tests and benchmarks but not recommended for production code owing to the lack of
/// error reporting.
///
/// Returns the new balance.
fn set_balance(who: &AccountId, amount: Self::Balance) -> Self::Balance {
let b = Self::balance(who);
if b > amount {
Self::burn_from(who, b - amount, BestEffort, Force).map(|d| amount.saturating_sub(d))
} else {
Self::mint_into(who, amount - b).map(|d| amount.saturating_add(d))
}
.unwrap_or(b)
}
fn done_mint_into(_who: &AccountId, _amount: Self::Balance) {}
fn done_burn_from(_who: &AccountId, _amount: Self::Balance) {}
fn done_shelve(_who: &AccountId, _amount: Self::Balance) {}
fn done_restore(_who: &AccountId, _amount: Self::Balance) {}
fn done_transfer(_source: &AccountId, _dest: &AccountId, _amount: Self::Balance) {}
}
/// Simple handler for an imbalance drop which increases the total issuance of the system by the
/// imbalance amount. Used for leftover debt.
pub struct IncreaseIssuance<AccountId, U>(PhantomData<(AccountId, U)>);
impl<AccountId, U: Unbalanced<AccountId>> HandleImbalanceDrop<U::Balance>
for IncreaseIssuance<AccountId, U>
{
fn handle(amount: U::Balance) {
U::set_total_issuance(U::total_issuance().saturating_add(amount))
}
}
/// Simple handler for an imbalance drop which decreases the total issuance of the system by the
/// imbalance amount. Used for leftover credit.
pub struct DecreaseIssuance<AccountId, U>(PhantomData<(AccountId, U)>);
impl<AccountId, U: Unbalanced<AccountId>> HandleImbalanceDrop<U::Balance>
for DecreaseIssuance<AccountId, U>
{
fn handle(amount: U::Balance) {
U::set_total_issuance(U::total_issuance().saturating_sub(amount))
}
}
/// A fungible token class where any creation and deletion of tokens is semi-explicit and where the
/// total supply is maintained automatically.
///
/// This is auto-implemented when a token class has `Unbalanced` implemented.
pub trait Balanced<AccountId>: Inspect<AccountId> + Unbalanced<AccountId> {
/// The type for managing what happens when an instance of `Debt` is dropped without being used.
type OnDropDebt: HandleImbalanceDrop<Self::Balance>;
/// The type for managing what happens when an instance of `Credit` is dropped without being
/// used.
type OnDropCredit: HandleImbalanceDrop<Self::Balance>;
/// Reduce the total issuance by `amount` and return the according imbalance. The imbalance will
/// typically be used to reduce an account by the same amount with e.g. `settle`.
///
/// This is infallible, but doesn't guarantee that the entire `amount` is burnt, for example
/// in the case of underflow.
fn rescind(amount: Self::Balance) -> Debt<AccountId, Self> {
let old = Self::total_issuance();
let new = old.saturating_sub(amount);
Self::set_total_issuance(new);
let delta = old - new;
Self::done_rescind(delta);
Imbalance::<Self::Balance, Self::OnDropDebt, Self::OnDropCredit>::new(delta)
}
/// Increase the total issuance by `amount` and return the according imbalance. The imbalance
/// will typically be used to increase an account by the same amount with e.g.
/// `resolve_into_existing` or `resolve_creating`.
///
/// This is infallible, but doesn't guarantee that the entire `amount` is issued, for example
/// in the case of overflow.
fn issue(amount: Self::Balance) -> Credit<AccountId, Self> {
let old = Self::total_issuance();
let new = old.saturating_add(amount);
Self::set_total_issuance(new);
let delta = new - old;
Self::done_issue(delta);
Imbalance::<Self::Balance, Self::OnDropCredit, Self::OnDropDebt>::new(delta)
}
/// Produce a pair of imbalances that cancel each other out exactly.
///
/// This is just the same as burning and issuing the same amount and has no effect on the
/// total issuance.
fn pair(amount: Self::Balance) -> (Debt<AccountId, Self>, Credit<AccountId, Self>) {
(Self::rescind(amount), Self::issue(amount))
}
/// Mints `value` into the account of `who`, creating it as needed.
///
/// If `precision` is `BestEffort` and `value` in full could not be minted (e.g. due to
/// overflow), then the maximum is minted, up to `value`. If `precision` is `Exact`, then
/// exactly `value` must be minted into the account of `who` or the operation will fail with an
/// `Err` and nothing will change.
///
/// If the operation is successful, this will return `Ok` with a `Debt` of the total value
/// added to the account.
fn deposit(
who: &AccountId,
value: Self::Balance,
precision: Precision,
) -> Result<Debt<AccountId, Self>, DispatchError> {
let increase = Self::increase_balance(who, value, precision)?;
Self::done_deposit(who, increase);
Ok(Imbalance::<Self::Balance, Self::OnDropDebt, Self::OnDropCredit>::new(increase))
}
/// Removes `value` balance from `who` account if possible.
///
/// If `precision` is `BestEffort` and `value` in full could not be removed (e.g. due to
/// underflow), then the maximum is removed, up to `value`. If `precision` is `Exact`, then
/// exactly `value` must be removed from the account of `who` or the operation will fail with an
/// `Err` and nothing will change.
///
/// If the removal is needed but not possible, then it returns `Err` and nothing is changed.
/// If the account needed to be deleted, then slightly more than `value` may be removed from the
/// account owning since up to (but not including) minimum balance may also need to be removed.
///
/// If the operation is successful, this will return `Ok` with a `Credit` of the total value
/// removed from the account.
fn withdraw(
who: &AccountId,
value: Self::Balance,
precision: Precision,
preservation: Preservation,
force: Fortitude,
) -> Result<Credit<AccountId, Self>, DispatchError> {
let decrease = Self::decrease_balance(who, value, precision, preservation, force)?;
Self::done_withdraw(who, decrease);
Ok(Imbalance::<Self::Balance, Self::OnDropCredit, Self::OnDropDebt>::new(decrease))
}
/// The balance of `who` is increased in order to counter `credit`. If the whole of `credit`
/// cannot be countered, then nothing is changed and the original `credit` is returned in an
/// `Err`.
///
/// Please note: If `credit.peek()` is less than `Self::minimum_balance()`, then `who` must
/// already exist for this to succeed.
fn resolve(
who: &AccountId,
credit: Credit<AccountId, Self>,
) -> Result<(), Credit<AccountId, Self>> {
let v = credit.peek();
let debt = match Self::deposit(who, v, Exact) {
Err(_) => return Err(credit),
Ok(d) => d,
};
let result = credit.offset(debt).try_drop();
debug_assert!(result.is_ok(), "ok deposit return must be equal to credit value; qed");
Ok(())
}
/// The balance of `who` is decreased in order to counter `debt`. If the whole of `debt`
/// cannot be countered, then nothing is changed and the original `debt` is returned in an
/// `Err`.
fn settle(
who: &AccountId,
debt: Debt<AccountId, Self>,
preservation: Preservation,
) -> Result<Credit<AccountId, Self>, Debt<AccountId, Self>> {
let amount = debt.peek();
let credit = match Self::withdraw(who, amount, Exact, preservation, Polite) {
Err(_) => return Err(debt),
Ok(d) => d,
};
match credit.offset(debt) {
SameOrOther::None => Ok(Credit::<AccountId, Self>::zero()),
SameOrOther::Same(dust) => Ok(dust),
SameOrOther::Other(rest) => {
debug_assert!(false, "ok withdraw return must be at least debt value; qed");
Err(rest)
},
}
}
fn done_rescind(_amount: Self::Balance) {}
fn done_issue(_amount: Self::Balance) {}
fn done_deposit(_who: &AccountId, _amount: Self::Balance) {}
fn done_withdraw(_who: &AccountId, _amount: Self::Balance) {}
}
@@ -1,332 +0,0 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! The traits for sets of fungible tokens and any associated types.
use super::{
misc::{AssetId, Balance},
*,
};
use crate::dispatch::{DispatchError, DispatchResult};
use sp_runtime::traits::Saturating;
use sp_std::vec::Vec;
pub mod approvals;
mod balanced;
pub mod enumerable;
pub use enumerable::InspectEnumerable;
pub mod metadata;
pub use balanced::{Balanced, Unbalanced};
mod imbalance;
pub use imbalance::{CreditOf, DebtOf, HandleImbalanceDrop, Imbalance};
pub mod roles;
/// Trait for providing balance-inspection access to a set of named fungible assets.
pub trait Inspect<AccountId> {
/// Means of identifying one asset class from another.
type AssetId: AssetId;
/// Scalar type for representing balance of an account.
type Balance: Balance;
/// The total amount of issuance in the system.
fn total_issuance(asset: Self::AssetId) -> Self::Balance;
/// The total amount of issuance in the system excluding those which are controlled by the
/// system.
fn active_issuance(asset: Self::AssetId) -> Self::Balance {
Self::total_issuance(asset)
}
/// The minimum balance any single account may have.
fn minimum_balance(asset: Self::AssetId) -> Self::Balance;
/// Get the `asset` balance of `who`.
fn balance(asset: Self::AssetId, who: &AccountId) -> Self::Balance;
/// Get the maximum amount of `asset` that `who` can withdraw/transfer successfully.
fn reducible_balance(asset: Self::AssetId, who: &AccountId, keep_alive: bool) -> Self::Balance;
/// Returns `true` if the `asset` balance of `who` may be increased by `amount`.
///
/// - `asset`: The asset that should be deposited.
/// - `who`: The account of which the balance should be increased by `amount`.
/// - `amount`: How much should the balance be increased?
/// - `mint`: Will `amount` be minted to deposit it into `account`?
fn can_deposit(
asset: Self::AssetId,
who: &AccountId,
amount: Self::Balance,
mint: bool,
) -> DepositConsequence;
/// Returns `Failed` if the `asset` balance of `who` may not be decreased by `amount`, otherwise
/// the consequence.
fn can_withdraw(
asset: Self::AssetId,
who: &AccountId,
amount: Self::Balance,
) -> WithdrawConsequence<Self::Balance>;
/// Returns `true` if an `asset` exists.
fn asset_exists(asset: Self::AssetId) -> bool;
}
/// Trait for reading metadata from a fungible asset.
pub trait InspectMetadata<AccountId>: Inspect<AccountId> {
/// Return the name of an asset.
fn name(asset: &Self::AssetId) -> Vec<u8>;
/// Return the symbol of an asset.
fn symbol(asset: &Self::AssetId) -> Vec<u8>;
/// Return the decimals of an asset.
fn decimals(asset: &Self::AssetId) -> u8;
}
/// Trait for providing a set of named fungible assets which can be created and destroyed.
pub trait Mutate<AccountId>: Inspect<AccountId> {
/// Attempt to increase the `asset` balance of `who` by `amount`.
///
/// If not possible then don't do anything. Possible reasons for failure include:
/// - Minimum balance not met.
/// - Account cannot be created (e.g. because there is no provider reference and/or the asset
/// isn't considered worth anything).
///
/// Since this is an operation which should be possible to take alone, if successful it will
/// increase the overall supply of the underlying token.
fn mint_into(asset: Self::AssetId, who: &AccountId, amount: Self::Balance) -> DispatchResult;
/// Attempt to reduce the `asset` balance of `who` by `amount`.
///
/// If not possible then don't do anything. Possible reasons for failure include:
/// - Less funds in the account than `amount`
/// - Liquidity requirements (locks, reservations) prevent the funds from being removed
/// - Operation would require destroying the account and it is required to stay alive (e.g.
/// because it's providing a needed provider reference).
///
/// Since this is an operation which should be possible to take alone, if successful it will
/// reduce the overall supply of the underlying token.
///
/// Due to minimum balance requirements, it's possible that the amount withdrawn could be up to
/// `Self::minimum_balance() - 1` more than the `amount`. The total amount withdrawn is returned
/// in an `Ok` result. This may be safely ignored if you don't mind the overall supply reducing.
fn burn_from(
asset: Self::AssetId,
who: &AccountId,
amount: Self::Balance,
) -> Result<Self::Balance, DispatchError>;
/// Attempt to reduce the `asset` balance of `who` by as much as possible up to `amount`, and
/// possibly slightly more due to minimum_balance requirements. If no decrease is possible then
/// an `Err` is returned and nothing is changed. If successful, the amount of tokens reduced is
/// returned.
///
/// The default implementation just uses `withdraw` along with `reducible_balance` to ensure
/// that is doesn't fail.
fn slash(
asset: Self::AssetId,
who: &AccountId,
amount: Self::Balance,
) -> Result<Self::Balance, DispatchError> {
Self::burn_from(asset, who, Self::reducible_balance(asset, who, false).min(amount))
}
/// Transfer funds from one account into another. The default implementation uses `mint_into`
/// and `burn_from` and may generate unwanted events.
fn teleport(
asset: Self::AssetId,
source: &AccountId,
dest: &AccountId,
amount: Self::Balance,
) -> Result<Self::Balance, DispatchError> {
let extra = Self::can_withdraw(asset, &source, amount).into_result()?;
// As we first burn and then mint, we don't need to check if `mint` fits into the supply.
// If we can withdraw/burn it, we can also mint it again.
Self::can_deposit(asset, dest, amount.saturating_add(extra), false).into_result()?;
let actual = Self::burn_from(asset, source, amount)?;
debug_assert!(
actual == amount.saturating_add(extra),
"can_withdraw must agree with withdraw; qed"
);
match Self::mint_into(asset, dest, actual) {
Ok(_) => Ok(actual),
Err(err) => {
debug_assert!(false, "can_deposit returned true previously; qed");
// attempt to return the funds back to source
let revert = Self::mint_into(asset, source, actual);
debug_assert!(revert.is_ok(), "withdrew funds previously; qed");
Err(err)
},
}
}
}
/// Trait for providing a set of named fungible assets which can only be transferred.
pub trait Transfer<AccountId>: Inspect<AccountId> {
/// Transfer funds from one account into another.
fn transfer(
asset: Self::AssetId,
source: &AccountId,
dest: &AccountId,
amount: Self::Balance,
keep_alive: bool,
) -> Result<Self::Balance, DispatchError>;
/// Reduce the active issuance by some amount.
fn deactivate(_: Self::AssetId, _: Self::Balance) {}
/// Increase the active issuance by some amount, up to the outstanding amount reduced.
fn reactivate(_: Self::AssetId, _: Self::Balance) {}
}
/// Trait for inspecting a set of named fungible assets which can be placed on hold.
pub trait InspectHold<AccountId>: Inspect<AccountId> {
/// Amount of funds held in hold.
fn balance_on_hold(asset: Self::AssetId, who: &AccountId) -> Self::Balance;
/// Check to see if some `amount` of `asset` may be held on the account of `who`.
fn can_hold(asset: Self::AssetId, who: &AccountId, amount: Self::Balance) -> bool;
}
/// Trait for mutating a set of named fungible assets which can be placed on hold.
pub trait MutateHold<AccountId>: InspectHold<AccountId> + Transfer<AccountId> {
/// Hold some funds in an account.
fn hold(asset: Self::AssetId, who: &AccountId, amount: Self::Balance) -> DispatchResult;
/// Release some funds in an account from being on hold.
///
/// If `best_effort` is `true`, then the amount actually released and returned as the inner
/// value of `Ok` may be smaller than the `amount` passed.
fn release(
asset: Self::AssetId,
who: &AccountId,
amount: Self::Balance,
best_effort: bool,
) -> Result<Self::Balance, DispatchError>;
/// Transfer held funds into a destination account.
///
/// If `on_hold` is `true`, then the destination account must already exist and the assets
/// transferred will still be on hold in the destination account. If not, then the destination
/// account need not already exist, but must be creatable.
///
/// If `best_effort` is `true`, then an amount less than `amount` may be transferred without
/// error.
///
/// The actual amount transferred is returned, or `Err` in the case of error and nothing is
/// changed.
fn transfer_held(
asset: Self::AssetId,
source: &AccountId,
dest: &AccountId,
amount: Self::Balance,
best_effort: bool,
on_hold: bool,
) -> Result<Self::Balance, DispatchError>;
}
/// Trait for mutating one of several types of fungible assets which can be held.
pub trait BalancedHold<AccountId>: Balanced<AccountId> + MutateHold<AccountId> {
/// Release and slash some funds in an account.
///
/// The resulting imbalance is the first item of the tuple returned.
///
/// As much funds up to `amount` will be deducted as possible. If this is less than `amount`,
/// then a non-zero second item will be returned.
fn slash_held(
asset: Self::AssetId,
who: &AccountId,
amount: Self::Balance,
) -> (CreditOf<AccountId, Self>, Self::Balance);
}
impl<AccountId, T: Balanced<AccountId> + MutateHold<AccountId>> BalancedHold<AccountId> for T {
fn slash_held(
asset: Self::AssetId,
who: &AccountId,
amount: Self::Balance,
) -> (CreditOf<AccountId, Self>, Self::Balance) {
let actual = match Self::release(asset, who, amount, true) {
Ok(x) => x,
Err(_) => return (Imbalance::zero(asset), amount),
};
<Self as fungibles::Balanced<AccountId>>::slash(asset, who, actual)
}
}
/// Trait for providing the ability to create new fungible assets.
pub trait Create<AccountId>: Inspect<AccountId> {
/// Create a new fungible asset.
fn create(
id: Self::AssetId,
admin: AccountId,
is_sufficient: bool,
min_balance: Self::Balance,
) -> DispatchResult;
}
/// Trait for providing the ability to destroy existing fungible assets.
pub trait Destroy<AccountId>: Inspect<AccountId> {
/// Start the destruction an existing fungible asset.
/// * `id`: The `AssetId` to be destroyed. successfully.
/// * `maybe_check_owner`: An optional account id that can be used to authorize the destroy
/// command. If not provided, no authorization checks will be performed before destroying
/// asset.
fn start_destroy(id: Self::AssetId, maybe_check_owner: Option<AccountId>) -> DispatchResult;
/// Destroy all accounts associated with a given asset.
/// `destroy_accounts` should only be called after `start_destroy` has been called, and the
/// asset is in a `Destroying` state
///
/// * `id`: The identifier of the asset to be destroyed. This must identify an existing asset.
/// * `max_items`: The maximum number of accounts to be destroyed for a given call of the
/// function. This value should be small enough to allow the operation fit into a logical
/// block.
///
/// Response:
/// * u32: Total number of approvals which were actually destroyed
///
/// Due to weight restrictions, this function may need to be called multiple
/// times to fully destroy all approvals. It will destroy `max_items` approvals at a
/// time.
fn destroy_accounts(id: Self::AssetId, max_items: u32) -> Result<u32, DispatchError>;
/// Destroy all approvals associated with a given asset up to the `max_items`
/// `destroy_approvals` should only be called after `start_destroy` has been called, and the
/// asset is in a `Destroying` state
///
/// * `id`: The identifier of the asset to be destroyed. This must identify an existing asset.
/// * `max_items`: The maximum number of accounts to be destroyed for a given call of the
/// function. This value should be small enough to allow the operation fit into a logical
/// block.
///
/// Response:
/// * u32: Total number of approvals which were actually destroyed
///
/// Due to weight restrictions, this function may need to be called multiple
/// times to fully destroy all approvals. It will destroy `max_items` approvals at a
/// time.
fn destroy_approvals(id: Self::AssetId, max_items: u32) -> Result<u32, DispatchError>;
/// Complete destroying asset and unreserve currency.
/// `finish_destroy` should only be called after `start_destroy` has been called, and the
/// asset is in a `Destroying` state. All accounts or approvals should be destroyed before
/// hand.
///
/// * `id`: The identifier of the asset to be destroyed. This must identify an existing asset.
fn finish_destroy(id: Self::AssetId) -> DispatchResult;
}
@@ -1,394 +0,0 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! The trait and associated types for sets of fungible tokens that manage total issuance without
//! requiring atomic balanced operations.
use super::*;
use crate::{
dispatch::{DispatchError, DispatchResult},
traits::misc::{SameOrOther, TryDrop},
};
use sp_arithmetic::traits::Saturating;
use sp_runtime::{
traits::{CheckedAdd, Zero},
ArithmeticError, TokenError,
};
use sp_std::marker::PhantomData;
/// A fungible token class where any creation and deletion of tokens is semi-explicit and where the
/// total supply is maintained automatically.
///
/// This is auto-implemented when a token class has `Unbalanced` implemented.
pub trait Balanced<AccountId>: Inspect<AccountId> {
/// The type for managing what happens when an instance of `Debt` is dropped without being used.
type OnDropDebt: HandleImbalanceDrop<Self::AssetId, Self::Balance>;
/// The type for managing what happens when an instance of `Credit` is dropped without being
/// used.
type OnDropCredit: HandleImbalanceDrop<Self::AssetId, Self::Balance>;
/// Reduce the total issuance by `amount` and return the according imbalance. The imbalance will
/// typically be used to reduce an account by the same amount with e.g. `settle`.
///
/// This is infallible, but doesn't guarantee that the entire `amount` is burnt, for example
/// in the case of underflow.
fn rescind(asset: Self::AssetId, amount: Self::Balance) -> DebtOf<AccountId, Self>;
/// Increase the total issuance by `amount` and return the according imbalance. The imbalance
/// will typically be used to increase an account by the same amount with e.g.
/// `resolve_into_existing` or `resolve_creating`.
///
/// This is infallible, but doesn't guarantee that the entire `amount` is issued, for example
/// in the case of overflow.
fn issue(asset: Self::AssetId, amount: Self::Balance) -> CreditOf<AccountId, Self>;
/// Produce a pair of imbalances that cancel each other out exactly.
///
/// This is just the same as burning and issuing the same amount and has no effect on the
/// total issuance.
fn pair(
asset: Self::AssetId,
amount: Self::Balance,
) -> (DebtOf<AccountId, Self>, CreditOf<AccountId, Self>) {
(Self::rescind(asset, amount), Self::issue(asset, amount))
}
/// Deducts up to `value` from the combined balance of `who`, preferring to deduct from the
/// free balance. This function cannot fail.
///
/// The resulting imbalance is the first item of the tuple returned.
///
/// As much funds up to `value` will be deducted as possible. If this is less than `value`,
/// then a non-zero second item will be returned.
fn slash(
asset: Self::AssetId,
who: &AccountId,
amount: Self::Balance,
) -> (CreditOf<AccountId, Self>, Self::Balance);
/// Mints exactly `value` into the `asset` account of `who`.
///
/// If `who` doesn't exist, nothing is done and an `Err` returned. This could happen because it
/// the account doesn't yet exist and it isn't possible to create it under the current
/// circumstances and with `value` in it.
fn deposit(
asset: Self::AssetId,
who: &AccountId,
value: Self::Balance,
) -> Result<DebtOf<AccountId, Self>, DispatchError>;
/// Removes `value` free `asset` balance from `who` account if possible.
///
/// If the removal is not possible, then it returns `Err` and nothing is changed.
///
/// If the operation is successful, this will return `Ok` with a `NegativeImbalance` whose value
/// is no less than `value`. It may be more in the case that removing it reduced it below
/// `Self::minimum_balance()`.
fn withdraw(
asset: Self::AssetId,
who: &AccountId,
value: Self::Balance,
// TODO: liveness: ExistenceRequirement,
) -> Result<CreditOf<AccountId, Self>, DispatchError>;
/// The balance of `who` is increased in order to counter `credit`. If the whole of `credit`
/// cannot be countered, then nothing is changed and the original `credit` is returned in an
/// `Err`.
///
/// Please note: If `credit.peek()` is less than `Self::minimum_balance()`, then `who` must
/// already exist for this to succeed.
fn resolve(
who: &AccountId,
credit: CreditOf<AccountId, Self>,
) -> Result<(), CreditOf<AccountId, Self>> {
let v = credit.peek();
let debt = match Self::deposit(credit.asset(), who, v) {
Err(_) => return Err(credit),
Ok(d) => d,
};
if let Ok(result) = credit.offset(debt) {
let result = result.try_drop();
debug_assert!(result.is_ok(), "ok deposit return must be equal to credit value; qed");
} else {
debug_assert!(false, "debt.asset is credit.asset; qed");
}
Ok(())
}
/// The balance of `who` is decreased in order to counter `debt`. If the whole of `debt`
/// cannot be countered, then nothing is changed and the original `debt` is returned in an
/// `Err`.
fn settle(
who: &AccountId,
debt: DebtOf<AccountId, Self>,
// TODO: liveness: ExistenceRequirement,
) -> Result<CreditOf<AccountId, Self>, DebtOf<AccountId, Self>> {
let amount = debt.peek();
let asset = debt.asset();
let credit = match Self::withdraw(asset, who, amount) {
Err(_) => return Err(debt),
Ok(d) => d,
};
match credit.offset(debt) {
Ok(SameOrOther::None) => Ok(CreditOf::<AccountId, Self>::zero(asset)),
Ok(SameOrOther::Same(dust)) => Ok(dust),
Ok(SameOrOther::Other(rest)) => {
debug_assert!(false, "ok withdraw return must be at least debt value; qed");
Err(rest)
},
Err(_) => {
debug_assert!(false, "debt.asset is credit.asset; qed");
Ok(CreditOf::<AccountId, Self>::zero(asset))
},
}
}
}
/// A fungible token class where the balance can be set arbitrarily.
///
/// **WARNING**
/// Do not use this directly unless you want trouble, since it allows you to alter account balances
/// without keeping the issuance up to date. It has no safeguards against accidentally creating
/// token imbalances in your system leading to accidental inflation or deflation. It's really just
/// for the underlying datatype to implement so the user gets the much safer `Balanced` trait to
/// use.
pub trait Unbalanced<AccountId>: Inspect<AccountId> {
/// Set the `asset` balance of `who` to `amount`. If this cannot be done for some reason (e.g.
/// because the account cannot be created or an overflow) then an `Err` is returned.
fn set_balance(asset: Self::AssetId, who: &AccountId, amount: Self::Balance) -> DispatchResult;
/// Set the total issuance of `asset` to `amount`.
fn set_total_issuance(asset: Self::AssetId, amount: Self::Balance);
/// Reduce the `asset` balance of `who` by `amount`. If it cannot be reduced by that amount for
/// some reason, return `Err` and don't reduce it at all. If Ok, return the imbalance.
///
/// Minimum balance will be respected and the returned imbalance may be up to
/// `Self::minimum_balance() - 1` greater than `amount`.
fn decrease_balance(
asset: Self::AssetId,
who: &AccountId,
amount: Self::Balance,
) -> Result<Self::Balance, DispatchError> {
let old_balance = Self::balance(asset, who);
let (mut new_balance, mut amount) = if Self::reducible_balance(asset, who, false) < amount {
return Err(TokenError::NoFunds.into())
} else {
(old_balance - amount, amount)
};
if new_balance < Self::minimum_balance(asset) {
amount = amount.saturating_add(new_balance);
new_balance = Zero::zero();
}
// Defensive only - this should not fail now.
Self::set_balance(asset, who, new_balance)?;
Ok(amount)
}
/// Reduce the `asset` balance of `who` by the most that is possible, up to `amount`.
///
/// Minimum balance will be respected and the returned imbalance may be up to
/// `Self::minimum_balance() - 1` greater than `amount`.
///
/// Return the imbalance by which the account was reduced.
fn decrease_balance_at_most(
asset: Self::AssetId,
who: &AccountId,
amount: Self::Balance,
) -> Self::Balance {
let old_balance = Self::balance(asset, who);
let old_free_balance = Self::reducible_balance(asset, who, false);
let (mut new_balance, mut amount) = if old_free_balance < amount {
(old_balance.saturating_sub(old_free_balance), old_free_balance)
} else {
(old_balance - amount, amount)
};
let minimum_balance = Self::minimum_balance(asset);
if new_balance < minimum_balance {
amount = amount.saturating_add(new_balance);
new_balance = Zero::zero();
}
let mut r = Self::set_balance(asset, who, new_balance);
if r.is_err() {
// Some error, probably because we tried to destroy an account which cannot be
// destroyed.
if new_balance.is_zero() && amount >= minimum_balance {
new_balance = minimum_balance;
amount -= minimum_balance;
r = Self::set_balance(asset, who, new_balance);
}
if r.is_err() {
// Still an error. Apparently it's not possible to reduce at all.
amount = Zero::zero();
}
}
amount
}
/// Increase the `asset` balance of `who` by `amount`. If it cannot be increased by that amount
/// for some reason, return `Err` and don't increase it at all. If Ok, return the imbalance.
///
/// Minimum balance will be respected and an error will be returned if
/// `amount < Self::minimum_balance()` when the account of `who` is zero.
fn increase_balance(
asset: Self::AssetId,
who: &AccountId,
amount: Self::Balance,
) -> Result<Self::Balance, DispatchError> {
let old_balance = Self::balance(asset, who);
let new_balance = old_balance.checked_add(&amount).ok_or(ArithmeticError::Overflow)?;
if new_balance < Self::minimum_balance(asset) {
return Err(TokenError::BelowMinimum.into())
}
if old_balance != new_balance {
Self::set_balance(asset, who, new_balance)?;
}
Ok(amount)
}
/// Increase the `asset` balance of `who` by the most that is possible, up to `amount`.
///
/// Minimum balance will be respected and the returned imbalance will be zero in the case that
/// `amount < Self::minimum_balance()`.
///
/// Return the imbalance by which the account was increased.
fn increase_balance_at_most(
asset: Self::AssetId,
who: &AccountId,
amount: Self::Balance,
) -> Self::Balance {
let old_balance = Self::balance(asset, who);
let mut new_balance = old_balance.saturating_add(amount);
let mut amount = new_balance - old_balance;
if new_balance < Self::minimum_balance(asset) {
new_balance = Zero::zero();
amount = Zero::zero();
}
if old_balance == new_balance || Self::set_balance(asset, who, new_balance).is_ok() {
amount
} else {
Zero::zero()
}
}
}
/// Simple handler for an imbalance drop which increases the total issuance of the system by the
/// imbalance amount. Used for leftover debt.
pub struct IncreaseIssuance<AccountId, U>(PhantomData<(AccountId, U)>);
impl<AccountId, U: Unbalanced<AccountId>> HandleImbalanceDrop<U::AssetId, U::Balance>
for IncreaseIssuance<AccountId, U>
{
fn handle(asset: U::AssetId, amount: U::Balance) {
U::set_total_issuance(asset, U::total_issuance(asset).saturating_add(amount))
}
}
/// Simple handler for an imbalance drop which decreases the total issuance of the system by the
/// imbalance amount. Used for leftover credit.
pub struct DecreaseIssuance<AccountId, U>(PhantomData<(AccountId, U)>);
impl<AccountId, U: Unbalanced<AccountId>> HandleImbalanceDrop<U::AssetId, U::Balance>
for DecreaseIssuance<AccountId, U>
{
fn handle(asset: U::AssetId, amount: U::Balance) {
U::set_total_issuance(asset, U::total_issuance(asset).saturating_sub(amount))
}
}
/// An imbalance type which uses `DecreaseIssuance` to deal with anything `Drop`ed.
///
/// Basically means that funds in someone's account have been removed and not yet placed anywhere
/// else. If it gets dropped, then those funds will be assumed to be "burned" and the total supply
/// will be accordingly decreased to ensure it equals the sum of the balances of all accounts.
type Credit<AccountId, U> = Imbalance<
<U as Inspect<AccountId>>::AssetId,
<U as Inspect<AccountId>>::Balance,
DecreaseIssuance<AccountId, U>,
IncreaseIssuance<AccountId, U>,
>;
/// An imbalance type which uses `IncreaseIssuance` to deal with anything `Drop`ed.
///
/// Basically means that there are funds in someone's account whose origin is as yet unaccounted
/// for. If it gets dropped, then those funds will be assumed to be "minted" and the total supply
/// will be accordingly increased to ensure it equals the sum of the balances of all accounts.
type Debt<AccountId, U> = Imbalance<
<U as Inspect<AccountId>>::AssetId,
<U as Inspect<AccountId>>::Balance,
IncreaseIssuance<AccountId, U>,
DecreaseIssuance<AccountId, U>,
>;
/// Create some `Credit` item. Only for internal use.
fn credit<AccountId, U: Unbalanced<AccountId>>(
asset: U::AssetId,
amount: U::Balance,
) -> Credit<AccountId, U> {
Imbalance::new(asset, amount)
}
/// Create some `Debt` item. Only for internal use.
fn debt<AccountId, U: Unbalanced<AccountId>>(
asset: U::AssetId,
amount: U::Balance,
) -> Debt<AccountId, U> {
Imbalance::new(asset, amount)
}
impl<AccountId, U: Unbalanced<AccountId>> Balanced<AccountId> for U {
type OnDropCredit = DecreaseIssuance<AccountId, U>;
type OnDropDebt = IncreaseIssuance<AccountId, U>;
fn rescind(asset: Self::AssetId, amount: Self::Balance) -> Debt<AccountId, Self> {
U::set_total_issuance(asset, U::total_issuance(asset).saturating_sub(amount));
debt(asset, amount)
}
fn issue(asset: Self::AssetId, amount: Self::Balance) -> Credit<AccountId, Self> {
U::set_total_issuance(asset, U::total_issuance(asset).saturating_add(amount));
credit(asset, amount)
}
fn slash(
asset: Self::AssetId,
who: &AccountId,
amount: Self::Balance,
) -> (Credit<AccountId, Self>, Self::Balance) {
let slashed = U::decrease_balance_at_most(asset, who, amount);
// `slashed` could be less than, greater than or equal to `amount`.
// If slashed == amount, it means the account had at least amount in it and it could all be
// removed without a problem.
// If slashed > amount, it means the account had more than amount in it, but not enough more
// to push it over minimum_balance.
// If slashed < amount, it means the account didn't have enough in it to be reduced by
// `amount` without being destroyed.
(credit(asset, slashed), amount.saturating_sub(slashed))
}
fn deposit(
asset: Self::AssetId,
who: &AccountId,
amount: Self::Balance,
) -> Result<Debt<AccountId, Self>, DispatchError> {
let increase = U::increase_balance(asset, who, amount)?;
Ok(debt(asset, increase))
}
fn withdraw(
asset: Self::AssetId,
who: &AccountId,
amount: Self::Balance,
// TODO: liveness: ExistenceRequirement,
) -> Result<Credit<AccountId, Self>, DispatchError> {
let decrease = U::decrease_balance(asset, who, amount)?;
Ok(credit(asset, decrease))
}
}
@@ -15,10 +15,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::traits::fungibles::Inspect;
/// Interface for enumerating assets in existence or owned by a given account.
pub trait InspectEnumerable<AccountId>: Inspect<AccountId> {
pub trait Inspect<AccountId>: super::Inspect<AccountId> {
type AssetsIterator;
/// Returns an iterator of the collections in existence.
@@ -0,0 +1,78 @@
// This file is part of Substrate.
// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! The traits for putting freezes within a single fungible token class.
use scale_info::TypeInfo;
use sp_runtime::DispatchResult;
/// Trait for inspecting a fungible asset which can be frozen. Freezing is essentially setting a
/// minimum balance below which the total balance (inclusive of any funds placed on hold) may not
/// be normally allowed to drop. Generally, freezers will provide an "update" function such that
/// if the total balance does drop below the limit, then the freezer can update their housekeeping
/// accordingly.
pub trait Inspect<AccountId>: super::Inspect<AccountId> {
/// An identifier for a freeze.
type Id: codec::Encode + TypeInfo + 'static;
/// Amount of funds held in reserve by `who` for the given `id`.
fn balance_frozen(asset: Self::AssetId, id: &Self::Id, who: &AccountId) -> Self::Balance;
/// The amount of the balance which can become frozen. Defaults to `total_balance()`.
fn balance_freezable(asset: Self::AssetId, who: &AccountId) -> Self::Balance {
Self::total_balance(asset, who)
}
/// Returns `true` if it's possible to introduce a freeze for the given `id` onto the
/// account of `who`. This will be true as long as the implementor supports as many
/// concurrent freeze locks as there are possible values of `id`.
fn can_freeze(asset: Self::AssetId, id: &Self::Id, who: &AccountId) -> bool;
}
/// Trait for introducing, altering and removing locks to freeze an account's funds so they never
/// go below a set minimum.
pub trait Mutate<AccountId>: Inspect<AccountId> {
/// Prevent actions which would reduce the balance of the account of `who` below the given
/// `amount` and identify this restriction though the given `id`. Unlike `extend_freeze`, any
/// outstanding freeze in place for `who` under the `id` are dropped.
///
/// If `amount` is zero, it is equivalent to using `thaw`.
///
/// Note that `amount` can be greater than the total balance, if desired.
fn set_freeze(
asset: Self::AssetId,
id: &Self::Id,
who: &AccountId,
amount: Self::Balance,
) -> DispatchResult;
/// Prevent the balance of the account of `who` from being reduced below the given `amount` and
/// identify this restriction though the given `id`. Unlike `set_freeze`, this does not
/// counteract any pre-existing freezes in place for `who` under the `id`. Also unlike
/// `set_freeze`, in the case that `amount` is zero, this is no-op and never fails.
///
/// Note that more funds can be locked than the total balance, if desired.
fn extend_freeze(
asset: Self::AssetId,
id: &Self::Id,
who: &AccountId,
amount: Self::Balance,
) -> DispatchResult;
/// Remove an existing lock.
fn thaw(asset: Self::AssetId, id: &Self::Id, who: &AccountId) -> DispatchResult;
}
@@ -0,0 +1,457 @@
// This file is part of Substrate.
// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! The traits for putting holds within a single fungible token class.
use crate::{
ensure,
traits::tokens::{
DepositConsequence::Success,
Fortitude::{self, Force},
Precision::{self, BestEffort, Exact},
Preservation::{self, Protect},
Provenance::Extant,
Restriction::{self, Free, OnHold},
},
};
use scale_info::TypeInfo;
use sp_arithmetic::{
traits::{CheckedAdd, CheckedSub, Zero},
ArithmeticError,
};
use sp_runtime::{DispatchError, DispatchResult, Saturating, TokenError};
use super::*;
/// Trait for inspecting a fungible asset whose accounts support partitioning and slashing.
pub trait Inspect<AccountId>: super::Inspect<AccountId> {
/// An identifier for a hold. Used for disambiguating different holds so that
/// they can be individually replaced or removed and funds from one hold don't accidentally
/// become unreserved or slashed for another.
type Reason: codec::Encode + TypeInfo + 'static;
/// Amount of funds on hold (for all hold reasons) of `who`.
fn total_balance_on_hold(asset: Self::AssetId, who: &AccountId) -> Self::Balance;
/// Get the maximum amount that the `total_balance_on_hold` of `who` can be reduced successfully
/// based on whether we are willing to force the reduction and potentially go below user-level
/// restrictions on the minimum amount of the account. Note: This cannot bring the account into
/// an inconsistent state with regards any required existential deposit.
///
/// Always less than `total_balance_on_hold()`.
fn reducible_total_balance_on_hold(
asset: Self::AssetId,
who: &AccountId,
force: Fortitude,
) -> Self::Balance;
/// Amount of funds on hold (for the given reason) of `who`.
fn balance_on_hold(
asset: Self::AssetId,
reason: &Self::Reason,
who: &AccountId,
) -> Self::Balance;
/// Returns `true` if it's possible to place (additional) funds under a hold of a given
/// `reason`. This may fail if the account has exhausted a limited number of concurrent
/// holds or if it cannot be made to exist (e.g. there is no provider reference).
///
/// NOTE: This does not take into account changes which could be made to the account of `who`
/// (such as removing a provider reference) after this call is made. Any usage of this should
/// therefore ensure the account is already in the appropriate state prior to calling it.
fn hold_available(asset: Self::AssetId, reason: &Self::Reason, who: &AccountId) -> bool;
/// Check to see if some `amount` of funds of `who` may be placed on hold with the given
/// `reason`. Reasons why this may not be true:
///
/// - The implementor supports only a limited number of concurrent holds on an account which is
/// the possible values of `reason`;
/// - The total balance of the account is less than `amount`;
/// - Removing `amount` from the total balance would kill the account and remove the only
/// provider reference.
///
/// Note: we pass `Fortitude::Force` as the last argument to `reducible_balance` since we assume
/// that if needed the balance can slashed. If we are using a simple non-forcing
/// reserve-transfer, then we really ought to check that we are not reducing the funds below the
/// freeze-limit (if any).
///
/// NOTE: This does not take into account changes which could be made to the account of `who`
/// (such as removing a provider reference) after this call is made. Any usage of this should
/// therefore ensure the account is already in the appropriate state prior to calling it.
fn ensure_can_hold(
asset: Self::AssetId,
reason: &Self::Reason,
who: &AccountId,
amount: Self::Balance,
) -> DispatchResult {
ensure!(Self::hold_available(asset, reason, who), TokenError::CannotCreateHold);
ensure!(
amount <= Self::reducible_balance(asset, who, Protect, Force),
TokenError::FundsUnavailable
);
Ok(())
}
/// Check to see if some `amount` of funds of `who` may be placed on hold for the given
/// `reason`. Reasons why this may not be true:
///
/// - The implementor supports only a limited number of concurrent holds on an account which is
/// the possible values of `reason`;
/// - The main balance of the account is less than `amount`;
/// - Removing `amount` from the main balance would kill the account and remove the only
/// provider reference.
///
/// NOTE: This does not take into account changes which could be made to the account of `who`
/// (such as removing a provider reference) after this call is made. Any usage of this should
/// therefore ensure the account is already in the appropriate state prior to calling it.
fn can_hold(
asset: Self::AssetId,
reason: &Self::Reason,
who: &AccountId,
amount: Self::Balance,
) -> bool {
Self::ensure_can_hold(asset, reason, who, amount).is_ok()
}
}
/// A fungible, holdable token class where the balance on hold can be set arbitrarily.
///
/// **WARNING**
/// Do not use this directly unless you want trouble, since it allows you to alter account balances
/// without keeping the issuance up to date. It has no safeguards against accidentally creating
/// token imbalances in your system leading to accidental imflation or deflation. It's really just
/// for the underlying datatype to implement so the user gets the much safer `Balanced` trait to
/// use.
pub trait Unbalanced<AccountId>: Inspect<AccountId> {
/// Forcefully set the balance on hold of `who` to `amount`. This is independent of any other
/// balances on hold or the main ("free") balance.
///
/// If this call executes successfully, you can `assert_eq!(Self::balance_on_hold(), amount);`.
///
/// This function does its best to force the balance change through, but will not break system
/// invariants such as any Existential Deposits needed or overflows/underflows.
/// If this cannot be done for some reason (e.g. because the account doesn't exist) then an
/// `Err` is returned.
// Implmentation note: This should increment the consumer refs if it moves total on hold from
// zero to non-zero and decrement in the opposite direction.
//
// Since this was not done in the previous logic, this will need either a migration or a
// state item which tracks whether the account is on the old logic or new.
fn set_balance_on_hold(
asset: Self::AssetId,
reason: &Self::Reason,
who: &AccountId,
amount: Self::Balance,
) -> DispatchResult;
/// Reduce the balance on hold of `who` by `amount`.
///
/// If `precision` is `Precision::Exact` and it cannot be reduced by that amount for
/// some reason, return `Err` and don't reduce it at all. If `precision` is
/// `Precision::BestEffort`, then reduce the balance of `who` by the most that is possible, up
/// to `amount`.
///
/// In either case, if `Ok` is returned then the inner is the amount by which is was reduced.
fn decrease_balance_on_hold(
asset: Self::AssetId,
reason: &Self::Reason,
who: &AccountId,
mut amount: Self::Balance,
precision: Precision,
) -> Result<Self::Balance, DispatchError> {
let old_balance = Self::balance_on_hold(asset, reason, who);
if let BestEffort = precision {
amount = amount.min(old_balance);
}
let new_balance = old_balance.checked_sub(&amount).ok_or(TokenError::FundsUnavailable)?;
Self::set_balance_on_hold(asset, reason, who, new_balance)?;
Ok(amount)
}
/// Increase the balance on hold of `who` by `amount`.
///
/// If it cannot be increased by that amount for some reason, return `Err` and don't increase
/// it at all. If Ok, return the imbalance.
fn increase_balance_on_hold(
asset: Self::AssetId,
reason: &Self::Reason,
who: &AccountId,
amount: Self::Balance,
precision: Precision,
) -> Result<Self::Balance, DispatchError> {
let old_balance = Self::balance_on_hold(asset, reason, who);
let new_balance = if let BestEffort = precision {
old_balance.saturating_add(amount)
} else {
old_balance.checked_add(&amount).ok_or(ArithmeticError::Overflow)?
};
let amount = new_balance.saturating_sub(old_balance);
if !amount.is_zero() {
Self::set_balance_on_hold(asset, reason, who, new_balance)?;
}
Ok(amount)
}
}
/// Trait for slashing a fungible asset which can be place on hold.
pub trait Balanced<AccountId>: super::Balanced<AccountId> + Unbalanced<AccountId> {
/// Reduce the balance of some funds on hold in an account.
///
/// The resulting imbalance is the first item of the tuple returned.
///
/// As much funds that are on hold up to `amount` will be deducted as possible. If this is less
/// than `amount`, then a non-zero second item will be returned.
fn slash(
asset: Self::AssetId,
reason: &Self::Reason,
who: &AccountId,
amount: Self::Balance,
) -> (Credit<AccountId, Self>, Self::Balance) {
let decrease = Self::decrease_balance_on_hold(asset, reason, who, amount, BestEffort)
.unwrap_or(Default::default());
let credit =
Imbalance::<Self::AssetId, Self::Balance, Self::OnDropCredit, Self::OnDropDebt>::new(
asset, decrease,
);
Self::done_slash(asset, reason, who, decrease);
(credit, amount.saturating_sub(decrease))
}
fn done_slash(
_asset: Self::AssetId,
_reason: &Self::Reason,
_who: &AccountId,
_amount: Self::Balance,
) {
}
}
/// Trait for mutating a fungible asset which can be placed on hold.
pub trait Mutate<AccountId>:
Inspect<AccountId> + super::Unbalanced<AccountId> + Unbalanced<AccountId>
{
/// Hold some funds in an account. If a hold for `reason` is already in place, then this
/// will increase it.
fn hold(
asset: Self::AssetId,
reason: &Self::Reason,
who: &AccountId,
amount: Self::Balance,
) -> DispatchResult {
// NOTE: This doesn't change the total balance of the account so there's no need to
// check liquidity.
Self::ensure_can_hold(asset, reason, who, amount)?;
// Should be infallible now, but we proceed softly anyway.
Self::decrease_balance(asset, who, amount, Exact, Protect, Force)?;
Self::increase_balance_on_hold(asset, reason, who, amount, BestEffort)?;
Self::done_hold(asset, reason, who, amount);
Ok(())
}
/// Release up to `amount` held funds in an account.
///
/// The actual amount released is returned with `Ok`.
///
/// If `precision` is `BestEffort`, then the amount actually unreserved and returned as the
/// inner value of `Ok` may be smaller than the `amount` passed.
fn release(
asset: Self::AssetId,
reason: &Self::Reason,
who: &AccountId,
amount: Self::Balance,
precision: Precision,
) -> Result<Self::Balance, DispatchError> {
// NOTE: This doesn't change the total balance of the account so there's no need to
// check liquidity.
// We want to make sure we can deposit the amount in advance. If we can't then something is
// very wrong.
ensure!(Self::can_deposit(asset, who, amount, Extant) == Success, TokenError::CannotCreate);
// Get the amount we can actually take from the hold. This might be less than what we want
// if we're only doing a best-effort.
let amount = Self::decrease_balance_on_hold(asset, reason, who, amount, precision)?;
// Increase the main balance by what we took. We always do a best-effort here because we
// already checked that we can deposit before.
let actual = Self::increase_balance(asset, who, amount, BestEffort)?;
Self::done_release(asset, reason, who, actual);
Ok(actual)
}
/// Attempt to decrease the balance of `who` which is held for the given `reason` by `amount`.
///
/// If `precision` is true, then as much as possible is reduced, up to `amount`, and the
/// amount of tokens reduced is returned. Otherwise, if the total amount can be reduced, then it
/// is and the amount returned, and if not, then nothing changes and `Err` is returned.
///
/// If `force` is `Force`, then locks/freezes will be ignored. This should only be used when
/// conducting slashing or other activity which materially disadvantages the account holder
/// since it could provide a means of circumventing freezes.
fn burn_held(
asset: Self::AssetId,
reason: &Self::Reason,
who: &AccountId,
mut amount: Self::Balance,
precision: Precision,
force: Fortitude,
) -> Result<Self::Balance, DispatchError> {
// We must check total-balance requirements if `!force`.
let liquid = Self::reducible_total_balance_on_hold(asset, who, force);
if let BestEffort = precision {
amount = amount.min(liquid);
} else {
ensure!(amount <= liquid, TokenError::Frozen);
}
let amount = Self::decrease_balance_on_hold(asset, reason, who, amount, precision)?;
Self::set_total_issuance(asset, Self::total_issuance(asset).saturating_sub(amount));
Self::done_burn_held(asset, reason, who, amount);
Ok(amount)
}
/// Transfer held funds into a destination account.
///
/// If `on_hold` is `true`, then the destination account must already exist and the assets
/// transferred will still be on hold in the destination account. If not, then the destination
/// account need not already exist, but must be creatable.
///
/// If `precision` is `BestEffort`, then an amount less than `amount` may be transferred without
/// error.
///
/// If `force` is `Force`, then other fund-locking mechanisms may be disregarded. It should be
/// left as `Regular` in most circumstances, but when you want the same power as a `slash`, it
/// may be `Force`.
///
/// The actual amount transferred is returned, or `Err` in the case of error and nothing is
/// changed.
fn transfer_on_hold(
asset: Self::AssetId,
reason: &Self::Reason,
source: &AccountId,
dest: &AccountId,
mut amount: Self::Balance,
precision: Precision,
mode: Restriction,
force: Fortitude,
) -> Result<Self::Balance, DispatchError> {
// We must check total-balance requirements if `!force`.
let have = Self::balance_on_hold(asset, reason, source);
let liquid = Self::reducible_total_balance_on_hold(asset, source, force);
if let BestEffort = precision {
amount = amount.min(liquid).min(have);
} else {
ensure!(amount <= liquid, TokenError::Frozen);
ensure!(amount <= have, TokenError::FundsUnavailable);
}
// We want to make sure we can deposit the amount in advance. If we can't then something is
// very wrong.
ensure!(
Self::can_deposit(asset, dest, amount, Extant) == Success,
TokenError::CannotCreate
);
ensure!(
mode == Free || Self::hold_available(asset, reason, dest),
TokenError::CannotCreateHold
);
let amount = Self::decrease_balance_on_hold(asset, reason, source, amount, precision)?;
let actual = if mode == OnHold {
Self::increase_balance_on_hold(asset, reason, dest, amount, precision)?
} else {
Self::increase_balance(asset, dest, amount, precision)?
};
Self::done_transfer_on_hold(asset, reason, source, dest, actual);
Ok(actual)
}
/// Transfer some `amount` of free balance from `source` to become owned by `dest` but on hold
/// for `reason`.
/// for `reason`.
///
/// If `precision` is `BestEffort`, then an amount less than `amount` may be transferred without
/// error.
///
/// `source` must obey the requirements of `keep_alive`.
///
/// If `force` is `Force`, then other fund-locking mechanisms may be disregarded. It should be
/// left as `Regular` in most circumstances, but when you want the same power as a `slash`, it
/// may be `Force`.
///
/// The amount placed on hold is returned or `Err` in the case of error and nothing is changed.
///
/// WARNING: This may return an error after a partial storage mutation. It should be used only
/// inside a transactional storage context and an `Err` result must imply a storage rollback.
fn transfer_and_hold(
asset: Self::AssetId,
reason: &Self::Reason,
source: &AccountId,
dest: &AccountId,
amount: Self::Balance,
precision: Precision,
expendability: Preservation,
force: Fortitude,
) -> Result<Self::Balance, DispatchError> {
ensure!(Self::hold_available(asset, reason, dest), TokenError::CannotCreateHold);
ensure!(
Self::can_deposit(asset, dest, amount, Extant) == Success,
TokenError::CannotCreate
);
let actual =
Self::decrease_balance(asset, source, amount, precision, expendability, force)?;
Self::increase_balance_on_hold(asset, reason, dest, actual, precision)?;
Self::done_transfer_on_hold(asset, reason, source, dest, actual);
Ok(actual)
}
fn done_hold(
_asset: Self::AssetId,
_reason: &Self::Reason,
_who: &AccountId,
_amount: Self::Balance,
) {
}
fn done_release(
_asset: Self::AssetId,
_reason: &Self::Reason,
_who: &AccountId,
_amount: Self::Balance,
) {
}
fn done_burn_held(
_asset: Self::AssetId,
_reason: &Self::Reason,
_who: &AccountId,
_amount: Self::Balance,
) {
}
fn done_transfer_on_hold(
_asset: Self::AssetId,
_reason: &Self::Reason,
_source: &AccountId,
_dest: &AccountId,
_amount: Self::Balance,
) {
}
fn done_transfer_and_hold(
_asset: Self::AssetId,
_reason: &Self::Reason,
_source: &AccountId,
_dest: &AccountId,
_transferred: Self::Balance,
) {
}
}
@@ -18,12 +18,11 @@
//! The imbalance type and its associates, which handles keeps everything adding up properly with
//! unbalanced operations.
use super::{
balanced::Balanced,
fungibles::{AssetId, Balance},
*,
use super::*;
use crate::traits::{
misc::{SameOrOther, TryDrop},
tokens::{AssetId, Balance},
};
use crate::traits::misc::{SameOrOther, TryDrop};
use sp_runtime::{traits::Zero, RuntimeDebug};
use sp_std::marker::PhantomData;
@@ -160,7 +159,7 @@ impl<
}
/// Imbalance implying that the total_issuance value is less than the sum of all account balances.
pub type DebtOf<AccountId, B> = Imbalance<
pub type Debt<AccountId, B> = Imbalance<
<B as Inspect<AccountId>>::AssetId,
<B as Inspect<AccountId>>::Balance,
// This will generally be implemented by increasing the total_issuance value.
@@ -170,7 +169,7 @@ pub type DebtOf<AccountId, B> = Imbalance<
/// Imbalance implying that the total_issuance value is greater than the sum of all account
/// balances.
pub type CreditOf<AccountId, B> = Imbalance<
pub type Credit<AccountId, B> = Imbalance<
<B as Inspect<AccountId>>::AssetId,
<B as Inspect<AccountId>>::Balance,
// This will generally be implemented by decreasing the total_issuance value.
@@ -0,0 +1,84 @@
// This file is part of Substrate.
// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Traits for creating and destroying assets.
use sp_runtime::{DispatchError, DispatchResult};
use super::Inspect;
/// Trait for providing the ability to create new fungible assets.
pub trait Create<AccountId>: Inspect<AccountId> {
/// Create a new fungible asset.
fn create(
id: Self::AssetId,
admin: AccountId,
is_sufficient: bool,
min_balance: Self::Balance,
) -> DispatchResult;
}
/// Trait for providing the ability to destroy existing fungible assets.
pub trait Destroy<AccountId>: Inspect<AccountId> {
/// Start the destruction an existing fungible asset.
/// * `id`: The `AssetId` to be destroyed. successfully.
/// * `maybe_check_owner`: An optional account id that can be used to authorize the destroy
/// command. If not provided, no authorization checks will be performed before destroying
/// asset.
fn start_destroy(id: Self::AssetId, maybe_check_owner: Option<AccountId>) -> DispatchResult;
/// Destroy all accounts associated with a given asset.
/// `destroy_accounts` should only be called after `start_destroy` has been called, and the
/// asset is in a `Destroying` state
///
/// * `id`: The identifier of the asset to be destroyed. This must identify an existing asset.
/// * `max_items`: The maximum number of accounts to be destroyed for a given call of the
/// function. This value should be small enough to allow the operation fit into a logical
/// block.
///
/// Response:
/// * u32: Total number of approvals which were actually destroyed
///
/// Due to weight restrictions, this function may need to be called multiple
/// times to fully destroy all approvals. It will destroy `max_items` approvals at a
/// time.
fn destroy_accounts(id: Self::AssetId, max_items: u32) -> Result<u32, DispatchError>;
/// Destroy all approvals associated with a given asset up to the `max_items`
/// `destroy_approvals` should only be called after `start_destroy` has been called, and the
/// asset is in a `Destroying` state
///
/// * `id`: The identifier of the asset to be destroyed. This must identify an existing asset.
/// * `max_items`: The maximum number of accounts to be destroyed for a given call of the
/// function. This value should be small enough to allow the operation fit into a logical
/// block.
///
/// Response:
/// * u32: Total number of approvals which were actually destroyed
///
/// Due to weight restrictions, this function may need to be called multiple
/// times to fully destroy all approvals. It will destroy `max_items` approvals at a
/// time.
fn destroy_approvals(id: Self::AssetId, max_items: u32) -> Result<u32, DispatchError>;
/// Complete destroying asset and unreserve currency.
/// `finish_destroy` should only be called after `start_destroy` has been called, and the
/// asset is in a `Destroying` state. All accounts or approvals should be destroyed before
/// hand.
///
/// * `id`: The identifier of the asset to be destroyed. This must identify an existing asset.
fn finish_destroy(id: Self::AssetId) -> DispatchResult;
}
@@ -0,0 +1,40 @@
// This file is part of Substrate.
// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! The traits for sets of fungible tokens and any associated types.
pub mod approvals;
mod enumerable;
pub mod freeze;
pub mod hold;
mod imbalance;
mod lifetime;
pub mod metadata;
mod regular;
pub mod roles;
pub use enumerable::Inspect as InspectEnumerable;
pub use freeze::{Inspect as InspectFreeze, Mutate as MutateFreeze};
pub use hold::{
Balanced as BalancedHold, Inspect as InspectHold, Mutate as MutateHold,
Unbalanced as UnbalancedHold,
};
pub use imbalance::{Credit, Debt, HandleImbalanceDrop, Imbalance};
pub use lifetime::{Create, Destroy};
pub use regular::{
Balanced, DecreaseIssuance, Dust, IncreaseIssuance, Inspect, Mutate, Unbalanced,
};
@@ -0,0 +1,571 @@
// This file is part of Substrate.
// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! `Inspect` and `Mutate` traits for working with regular balances.
use sp_std::marker::PhantomData;
use crate::{
dispatch::DispatchError,
ensure,
traits::{
tokens::{
misc::{
Balance, DepositConsequence,
Fortitude::{self, Force, Polite},
Precision::{self, BestEffort, Exact},
Preservation::{self, Expendable},
Provenance::{self, Extant},
WithdrawConsequence,
},
AssetId,
},
SameOrOther, TryDrop,
},
};
use sp_arithmetic::traits::{CheckedAdd, CheckedSub, One};
use sp_runtime::{traits::Saturating, ArithmeticError, TokenError};
use super::{Credit, Debt, HandleImbalanceDrop, Imbalance};
/// Trait for providing balance-inspection access to a set of named fungible assets.
pub trait Inspect<AccountId>: Sized {
/// Means of identifying one asset class from another.
type AssetId: AssetId;
/// Scalar type for representing balance of an account.
type Balance: Balance;
/// The total amount of issuance in the system.
fn total_issuance(asset: Self::AssetId) -> Self::Balance;
/// The total amount of issuance in the system excluding those which are controlled by the
/// system.
fn active_issuance(asset: Self::AssetId) -> Self::Balance {
Self::total_issuance(asset)
}
/// The minimum balance any single account may have.
fn minimum_balance(asset: Self::AssetId) -> Self::Balance;
/// Get the total amount of funds whose ultimate bneficial ownership can be determined as `who`.
///
/// This may include funds which are wholly inaccessible to `who`, either temporarily or even
/// indefinitely.
///
/// For the amount of the balance which is currently free to be removed from the account without
/// error, use `reducible_balance`.
///
/// For the amount of the balance which may eventually be free to be removed from the account,
/// use `balance()`.
fn total_balance(asset: Self::AssetId, who: &AccountId) -> Self::Balance;
/// Get the balance of `who` which does not include funds which are exclusively allocated to
/// subsystems of the chain ("on hold" or "reserved").
///
/// In general this isn't especially useful outside of tests, and for practical purposes, you'll
/// want to use `reducible_balance()`.
fn balance(asset: Self::AssetId, who: &AccountId) -> Self::Balance;
/// Get the maximum amount that `who` can withdraw/transfer successfully based on whether the
/// account should be kept alive (`preservation`) or whether we are willing to force the
/// transfer and potentially go below user-level restrictions on the minimum amount of the
/// account.
///
/// Always less than `free_balance()`.
fn reducible_balance(
asset: Self::AssetId,
who: &AccountId,
preservation: Preservation,
force: Fortitude,
) -> Self::Balance;
/// Returns `true` if the `asset` balance of `who` may be increased by `amount`.
///
/// - `asset`: The asset that should be deposited.
/// - `who`: The account of which the balance should be increased by `amount`.
/// - `amount`: How much should the balance be increased?
/// - `mint`: Will `amount` be minted to deposit it into `account`?
fn can_deposit(
asset: Self::AssetId,
who: &AccountId,
amount: Self::Balance,
provenance: Provenance,
) -> DepositConsequence;
/// Returns `Failed` if the `asset` balance of `who` may not be decreased by `amount`, otherwise
/// the consequence.
fn can_withdraw(
asset: Self::AssetId,
who: &AccountId,
amount: Self::Balance,
) -> WithdrawConsequence<Self::Balance>;
/// Returns `true` if an `asset` exists.
fn asset_exists(asset: Self::AssetId) -> bool;
}
/// Special dust type which can be type-safely converted into a `Credit`.
#[must_use]
pub struct Dust<A, T: Unbalanced<A>>(pub(crate) T::AssetId, pub(crate) T::Balance);
impl<A, T: Balanced<A>> Dust<A, T> {
/// Convert `Dust` into an instance of `Credit`.
pub fn into_credit(self) -> Credit<A, T> {
Credit::<A, T>::new(self.0, self.1)
}
}
/// A fungible token class where the balance can be set arbitrarily.
///
/// **WARNING**
/// Do not use this directly unless you want trouble, since it allows you to alter account balances
/// without keeping the issuance up to date. It has no safeguards against accidentally creating
/// token imbalances in your system leading to accidental imflation or deflation. It's really just
/// for the underlying datatype to implement so the user gets the much safer `Balanced` trait to
/// use.
pub trait Unbalanced<AccountId>: Inspect<AccountId> {
/// Create some dust and handle it with `Self::handle_dust`. This is an unbalanced operation
/// and it must only be used when an account is modified in a raw fashion, outside of the entire
/// fungibles API. The `amount` is capped at `Self::minimum_balance() - 1`.
///
/// This should not be reimplemented.
fn handle_raw_dust(asset: Self::AssetId, amount: Self::Balance) {
Self::handle_dust(Dust(
asset,
amount.min(Self::minimum_balance(asset).saturating_sub(One::one())),
))
}
/// Do something with the dust which has been destroyed from the system. `Dust` can be converted
/// into a `Credit` with the `Balanced` trait impl.
fn handle_dust(dust: Dust<AccountId, Self>);
/// Forcefully set the balance of `who` to `amount`.
///
/// If this call executes successfully, you can `assert_eq!(Self::balance(), amount);`.
///
/// For implementations which include one or more balances on hold, then these are *not*
/// included in the `amount`.
///
/// This function does its best to force the balance change through, but will not break system
/// invariants such as any Existential Deposits needed or overflows/underflows.
/// If this cannot be done for some reason (e.g. because the account cannot be created, deleted
/// or would overflow) then an `Err` is returned.
fn write_balance(
asset: Self::AssetId,
who: &AccountId,
amount: Self::Balance,
) -> Result<Option<Self::Balance>, DispatchError>;
/// Set the total issuance to `amount`.
fn set_total_issuance(asset: Self::AssetId, amount: Self::Balance);
/// Reduce the balance of `who` by `amount`.
///
/// If `precision` is `Exact` and it cannot be reduced by that amount for
/// some reason, return `Err` and don't reduce it at all. If `precision` is `BestEffort`, then
/// reduce the balance of `who` by the most that is possible, up to `amount`.
///
/// In either case, if `Ok` is returned then the inner is the amount by which is was reduced.
/// Minimum balance will be respected and thus the returned amount may be up to
/// `Self::minimum_balance() - 1` greater than `amount` in the case that the reduction caused
/// the account to be deleted.
fn decrease_balance(
asset: Self::AssetId,
who: &AccountId,
mut amount: Self::Balance,
precision: Precision,
preservation: Preservation,
force: Fortitude,
) -> Result<Self::Balance, DispatchError> {
let old_balance = Self::balance(asset, who);
let free = Self::reducible_balance(asset, who, preservation, force);
if let BestEffort = precision {
amount = amount.min(free);
}
let new_balance = old_balance.checked_sub(&amount).ok_or(TokenError::FundsUnavailable)?;
if let Some(dust) = Self::write_balance(asset, who, new_balance)? {
Self::handle_dust(Dust(asset, dust));
}
Ok(old_balance.saturating_sub(new_balance))
}
/// Increase the balance of `who` by `amount`.
///
/// If it cannot be increased by that amount for some reason, return `Err` and don't increase
/// it at all. If Ok, return the imbalance.
/// Minimum balance will be respected and an error will be returned if
/// `amount < Self::minimum_balance()` when the account of `who` is zero.
fn increase_balance(
asset: Self::AssetId,
who: &AccountId,
amount: Self::Balance,
precision: Precision,
) -> Result<Self::Balance, DispatchError> {
let old_balance = Self::balance(asset, who);
let new_balance = if let BestEffort = precision {
old_balance.saturating_add(amount)
} else {
old_balance.checked_add(&amount).ok_or(ArithmeticError::Overflow)?
};
if new_balance < Self::minimum_balance(asset) {
// Attempt to increase from 0 to below minimum -> stays at zero.
if let BestEffort = precision {
Ok(Self::Balance::default())
} else {
Err(TokenError::BelowMinimum.into())
}
} else {
if new_balance == old_balance {
Ok(Self::Balance::default())
} else {
if let Some(dust) = Self::write_balance(asset, who, new_balance)? {
Self::handle_dust(Dust(asset, dust));
}
Ok(new_balance.saturating_sub(old_balance))
}
}
}
/// Reduce the active issuance by some amount.
fn deactivate(_asset: Self::AssetId, _: Self::Balance) {}
/// Increase the active issuance by some amount, up to the outstanding amount reduced.
fn reactivate(_asset: Self::AssetId, _: Self::Balance) {}
}
/// Trait for providing a basic fungible asset.
pub trait Mutate<AccountId>: Inspect<AccountId> + Unbalanced<AccountId> {
/// Increase the balance of `who` by exactly `amount`, minting new tokens. If that isn't
/// possible then an `Err` is returned and nothing is changed.
fn mint_into(
asset: Self::AssetId,
who: &AccountId,
amount: Self::Balance,
) -> Result<Self::Balance, DispatchError> {
Self::total_issuance(asset)
.checked_add(&amount)
.ok_or(ArithmeticError::Overflow)?;
let actual = Self::increase_balance(asset, who, amount, Exact)?;
Self::set_total_issuance(asset, Self::total_issuance(asset).saturating_add(actual));
Self::done_mint_into(asset, who, amount);
Ok(actual)
}
/// Decrease the balance of `who` by at least `amount`, possibly slightly more in the case of
/// minimum-balance requirements, burning the tokens. If that isn't possible then an `Err` is
/// returned and nothing is changed. If successful, the amount of tokens reduced is returned.
fn burn_from(
asset: Self::AssetId,
who: &AccountId,
amount: Self::Balance,
precision: Precision,
force: Fortitude,
) -> Result<Self::Balance, DispatchError> {
let actual = Self::reducible_balance(asset, who, Expendable, force).min(amount);
ensure!(actual == amount || precision == BestEffort, TokenError::FundsUnavailable);
Self::total_issuance(asset)
.checked_sub(&actual)
.ok_or(ArithmeticError::Overflow)?;
let actual = Self::decrease_balance(asset, who, actual, BestEffort, Expendable, force)?;
Self::set_total_issuance(asset, Self::total_issuance(asset).saturating_sub(actual));
Self::done_burn_from(asset, who, actual);
Ok(actual)
}
/// Attempt to decrease the `asset` balance of `who` by `amount`.
///
/// Equivalent to `burn_from`, except with an expectation that within the bounds of some
/// universal issuance, the total assets `suspend`ed and `resume`d will be equivalent. The
/// implementation may be configured such that the total assets suspended may never be less than
/// the total assets resumed (which is the invariant for an issuing system), or the reverse
/// (which the invariant in a non-issuing system).
///
/// Because of this expectation, any metadata associated with the asset is expected to survive
/// the suspect-resume cycle.
fn shelve(
asset: Self::AssetId,
who: &AccountId,
amount: Self::Balance,
) -> Result<Self::Balance, DispatchError> {
let actual = Self::reducible_balance(asset, who, Expendable, Polite).min(amount);
ensure!(actual == amount, TokenError::FundsUnavailable);
Self::total_issuance(asset)
.checked_sub(&actual)
.ok_or(ArithmeticError::Overflow)?;
let actual = Self::decrease_balance(asset, who, actual, BestEffort, Expendable, Polite)?;
Self::set_total_issuance(asset, Self::total_issuance(asset).saturating_sub(actual));
Self::done_shelve(asset, who, actual);
Ok(actual)
}
/// Attempt to increase the `asset` balance of `who` by `amount`.
///
/// Equivalent to `mint_into`, except with an expectation that within the bounds of some
/// universal issuance, the total assets `suspend`ed and `resume`d will be equivalent. The
/// implementation may be configured such that the total assets suspended may never be less than
/// the total assets resumed (which is the invariant for an issuing system), or the reverse
/// (which the invariant in a non-issuing system).
///
/// Because of this expectation, any metadata associated with the asset is expected to survive
/// the suspect-resume cycle.
fn restore(
asset: Self::AssetId,
who: &AccountId,
amount: Self::Balance,
) -> Result<Self::Balance, DispatchError> {
Self::total_issuance(asset)
.checked_add(&amount)
.ok_or(ArithmeticError::Overflow)?;
let actual = Self::increase_balance(asset, who, amount, Exact)?;
Self::set_total_issuance(asset, Self::total_issuance(asset).saturating_add(actual));
Self::done_restore(asset, who, amount);
Ok(actual)
}
/// Transfer funds from one account into another.
fn transfer(
asset: Self::AssetId,
source: &AccountId,
dest: &AccountId,
amount: Self::Balance,
preservation: Preservation,
) -> Result<Self::Balance, DispatchError> {
let _extra =
Self::can_withdraw(asset, source, amount).into_result(preservation != Expendable)?;
Self::can_deposit(asset, dest, amount, Extant).into_result()?;
Self::decrease_balance(asset, source, amount, BestEffort, preservation, Polite)?;
// This should never fail as we checked `can_deposit` earlier. But we do a best-effort
// anyway.
let _ = Self::increase_balance(asset, dest, amount, BestEffort);
Self::done_transfer(asset, source, dest, amount);
Ok(amount)
}
/// Simple infallible function to force an account to have a particular balance, good for use
/// in tests and benchmarks but not recommended for production code owing to the lack of
/// error reporting.
///
/// Returns the new balance.
fn set_balance(asset: Self::AssetId, who: &AccountId, amount: Self::Balance) -> Self::Balance {
let b = Self::balance(asset, who);
if b > amount {
Self::burn_from(asset, who, b - amount, BestEffort, Force)
.map(|d| amount.saturating_sub(d))
} else {
Self::mint_into(asset, who, amount - b).map(|d| amount.saturating_add(d))
}
.unwrap_or(b)
}
fn done_mint_into(_asset: Self::AssetId, _who: &AccountId, _amount: Self::Balance) {}
fn done_burn_from(_asset: Self::AssetId, _who: &AccountId, _amount: Self::Balance) {}
fn done_shelve(_asset: Self::AssetId, _who: &AccountId, _amount: Self::Balance) {}
fn done_restore(_asset: Self::AssetId, _who: &AccountId, _amount: Self::Balance) {}
fn done_transfer(
_asset: Self::AssetId,
_source: &AccountId,
_dest: &AccountId,
_amount: Self::Balance,
) {
}
}
/// Simple handler for an imbalance drop which increases the total issuance of the system by the
/// imbalance amount. Used for leftover debt.
pub struct IncreaseIssuance<AccountId, U>(PhantomData<(AccountId, U)>);
impl<AccountId, U: Unbalanced<AccountId>> HandleImbalanceDrop<U::AssetId, U::Balance>
for IncreaseIssuance<AccountId, U>
{
fn handle(asset: U::AssetId, amount: U::Balance) {
U::set_total_issuance(asset, U::total_issuance(asset).saturating_add(amount))
}
}
/// Simple handler for an imbalance drop which decreases the total issuance of the system by the
/// imbalance amount. Used for leftover credit.
pub struct DecreaseIssuance<AccountId, U>(PhantomData<(AccountId, U)>);
impl<AccountId, U: Unbalanced<AccountId>> HandleImbalanceDrop<U::AssetId, U::Balance>
for DecreaseIssuance<AccountId, U>
{
fn handle(asset: U::AssetId, amount: U::Balance) {
U::set_total_issuance(asset, U::total_issuance(asset).saturating_sub(amount))
}
}
/// A fungible token class where any creation and deletion of tokens is semi-explicit and where the
/// total supply is maintained automatically.
///
/// This is auto-implemented when a token class has `Unbalanced` implemented.
pub trait Balanced<AccountId>: Inspect<AccountId> + Unbalanced<AccountId> {
/// The type for managing what happens when an instance of `Debt` is dropped without being used.
type OnDropDebt: HandleImbalanceDrop<Self::AssetId, Self::Balance>;
/// The type for managing what happens when an instance of `Credit` is dropped without being
/// used.
type OnDropCredit: HandleImbalanceDrop<Self::AssetId, Self::Balance>;
/// Reduce the total issuance by `amount` and return the according imbalance. The imbalance will
/// typically be used to reduce an account by the same amount with e.g. `settle`.
///
/// This is infallible, but doesn't guarantee that the entire `amount` is burnt, for example
/// in the case of underflow.
fn rescind(asset: Self::AssetId, amount: Self::Balance) -> Debt<AccountId, Self> {
let old = Self::total_issuance(asset);
let new = old.saturating_sub(amount);
Self::set_total_issuance(asset, new);
let delta = old - new;
Self::done_rescind(asset, delta);
Imbalance::<Self::AssetId, Self::Balance, Self::OnDropDebt, Self::OnDropCredit>::new(
asset, delta,
)
}
/// Increase the total issuance by `amount` and return the according imbalance. The imbalance
/// will typically be used to increase an account by the same amount with e.g.
/// `resolve_into_existing` or `resolve_creating`.
///
/// This is infallible, but doesn't guarantee that the entire `amount` is issued, for example
/// in the case of overflow.
fn issue(asset: Self::AssetId, amount: Self::Balance) -> Credit<AccountId, Self> {
let old = Self::total_issuance(asset);
let new = old.saturating_add(amount);
Self::set_total_issuance(asset, new);
let delta = new - old;
Self::done_issue(asset, delta);
Imbalance::<Self::AssetId, Self::Balance, Self::OnDropCredit, Self::OnDropDebt>::new(
asset, delta,
)
}
/// Produce a pair of imbalances that cancel each other out exactly.
///
/// This is just the same as burning and issuing the same amount and has no effect on the
/// total issuance.
fn pair(
asset: Self::AssetId,
amount: Self::Balance,
) -> (Debt<AccountId, Self>, Credit<AccountId, Self>) {
(Self::rescind(asset, amount), Self::issue(asset, amount))
}
/// Mints `value` into the account of `who`, creating it as needed.
///
/// If `precision` is `BestEffort` and `value` in full could not be minted (e.g. due to
/// overflow), then the maximum is minted, up to `value`. If `precision` is `Exact`, then
/// exactly `value` must be minted into the account of `who` or the operation will fail with an
/// `Err` and nothing will change.
///
/// If the operation is successful, this will return `Ok` with a `Debt` of the total value
/// added to the account.
fn deposit(
asset: Self::AssetId,
who: &AccountId,
value: Self::Balance,
precision: Precision,
) -> Result<Debt<AccountId, Self>, DispatchError> {
let increase = Self::increase_balance(asset, who, value, precision)?;
Self::done_deposit(asset, who, increase);
Ok(Imbalance::<Self::AssetId, Self::Balance, Self::OnDropDebt, Self::OnDropCredit>::new(
asset, increase,
))
}
/// Removes `value` balance from `who` account if possible.
///
/// If `precision` is `BestEffort` and `value` in full could not be removed (e.g. due to
/// underflow), then the maximum is removed, up to `value`. If `precision` is `Exact`, then
/// exactly `value` must be removed from the account of `who` or the operation will fail with an
/// `Err` and nothing will change.
///
/// If the removal is needed but not possible, then it returns `Err` and nothing is changed.
/// If the account needed to be deleted, then slightly more than `value` may be removed from the
/// account owning since up to (but not including) minimum balance may also need to be removed.
///
/// If the operation is successful, this will return `Ok` with a `Credit` of the total value
/// removed from the account.
fn withdraw(
asset: Self::AssetId,
who: &AccountId,
value: Self::Balance,
precision: Precision,
preservation: Preservation,
force: Fortitude,
) -> Result<Credit<AccountId, Self>, DispatchError> {
let decrease = Self::decrease_balance(asset, who, value, precision, preservation, force)?;
Self::done_withdraw(asset, who, decrease);
Ok(Imbalance::<Self::AssetId, Self::Balance, Self::OnDropCredit, Self::OnDropDebt>::new(
asset, decrease,
))
}
/// The balance of `who` is increased in order to counter `credit`. If the whole of `credit`
/// cannot be countered, then nothing is changed and the original `credit` is returned in an
/// `Err`.
///
/// Please note: If `credit.peek()` is less than `Self::minimum_balance()`, then `who` must
/// already exist for this to succeed.
fn resolve(
who: &AccountId,
credit: Credit<AccountId, Self>,
) -> Result<(), Credit<AccountId, Self>> {
let v = credit.peek();
let debt = match Self::deposit(credit.asset(), who, v, Exact) {
Err(_) => return Err(credit),
Ok(d) => d,
};
if let Ok(result) = credit.offset(debt) {
let result = result.try_drop();
debug_assert!(result.is_ok(), "ok deposit return must be equal to credit value; qed");
} else {
debug_assert!(false, "debt.asset is credit.asset; qed");
}
Ok(())
}
/// The balance of `who` is decreased in order to counter `debt`. If the whole of `debt`
/// cannot be countered, then nothing is changed and the original `debt` is returned in an
/// `Err`.
fn settle(
who: &AccountId,
debt: Debt<AccountId, Self>,
preservation: Preservation,
) -> Result<Credit<AccountId, Self>, Debt<AccountId, Self>> {
let amount = debt.peek();
let asset = debt.asset();
let credit = match Self::withdraw(asset, who, amount, Exact, preservation, Polite) {
Err(_) => return Err(debt),
Ok(d) => d,
};
match credit.offset(debt) {
Ok(SameOrOther::None) => Ok(Credit::<AccountId, Self>::zero(asset)),
Ok(SameOrOther::Same(dust)) => Ok(dust),
Ok(SameOrOther::Other(rest)) => {
debug_assert!(false, "ok withdraw return must be at least debt value; qed");
Err(rest)
},
Err(_) => {
debug_assert!(false, "debt.asset is credit.asset; qed");
Ok(Credit::<AccountId, Self>::zero(asset))
},
}
}
fn done_rescind(_asset: Self::AssetId, _amount: Self::Balance) {}
fn done_issue(_asset: Self::AssetId, _amount: Self::Balance) {}
fn done_deposit(_asset: Self::AssetId, _who: &AccountId, _amount: Self::Balance) {}
fn done_withdraw(_asset: Self::AssetId, _who: &AccountId, _amount: Self::Balance) {}
}
@@ -23,12 +23,65 @@ use sp_core::RuntimeDebug;
use sp_runtime::{traits::Convert, ArithmeticError, DispatchError, TokenError};
use sp_std::fmt::Debug;
/// The origin of funds to be used for a deposit operation.
#[derive(Copy, Clone, RuntimeDebug, Eq, PartialEq)]
pub enum Provenance {
/// The funds will be minted into the system, increasing total issuance (and potentially
/// causing an overflow there).
Minted,
/// The funds already exist in the system, therefore will not affect total issuance.
Extant,
}
/// The mode under which usage of funds may be restricted.
#[derive(Copy, Clone, RuntimeDebug, Eq, PartialEq)]
pub enum Restriction {
/// Funds are under the normal conditions.
Free,
/// Funds are on hold.
OnHold,
}
/// The mode by which we describe whether an operation should keep an account alive.
#[derive(Copy, Clone, RuntimeDebug, Eq, PartialEq)]
pub enum Preservation {
/// We don't care if the account gets killed by this operation.
Expendable,
/// The account may not be killed, but we don't care if the balance gets dusted.
Protect,
/// The account may not be killed and our provider reference must remain (in the context of
/// tokens, this means that the account may not be dusted).
Preserve,
}
/// The privilege with which a withdraw operation is conducted.
#[derive(Copy, Clone, RuntimeDebug, Eq, PartialEq)]
pub enum Fortitude {
/// The operation should execute with regular privilege.
Polite,
/// The operation should be forced to succeed if possible. This is usually employed for system-
/// level security-critical events such as slashing.
Force,
}
/// The precision required of an operation generally involving some aspect of quantitative fund
/// withdrawal or transfer.
#[derive(Copy, Clone, RuntimeDebug, Eq, PartialEq)]
pub enum Precision {
/// The operation should must either proceed either exactly according to the amounts involved
/// or not at all.
Exact,
/// The operation may be considered successful even if less than the specified amounts are
/// available to be used. In this case a best effort will be made.
BestEffort,
}
/// One of a number of consequences of withdrawing a fungible from an account.
#[derive(Copy, Clone, RuntimeDebug, Eq, PartialEq)]
pub enum WithdrawConsequence<Balance> {
/// Withdraw could not happen since the amount to be withdrawn is less than the total funds in
/// the account.
NoFunds,
BalanceLow,
/// The withdraw would mean the account dying when it needs to exist (usually because it is a
/// provider and there are consumer references on it).
WouldDie,
@@ -53,15 +106,16 @@ pub enum WithdrawConsequence<Balance> {
impl<Balance: Zero> WithdrawConsequence<Balance> {
/// Convert the type into a `Result` with `DispatchError` as the error or the additional
/// `Balance` by which the account will be reduced.
pub fn into_result(self) -> Result<Balance, DispatchError> {
pub fn into_result(self, keep_nonzero: bool) -> Result<Balance, DispatchError> {
use WithdrawConsequence::*;
match self {
NoFunds => Err(TokenError::NoFunds.into()),
WouldDie => Err(TokenError::WouldDie.into()),
BalanceLow => Err(TokenError::FundsUnavailable.into()),
WouldDie => Err(TokenError::OnlyProvider.into()),
UnknownAsset => Err(TokenError::UnknownAsset.into()),
Underflow => Err(ArithmeticError::Underflow.into()),
Overflow => Err(ArithmeticError::Overflow.into()),
Frozen => Err(TokenError::Frozen.into()),
ReducedToZero(_) if keep_nonzero => Err(TokenError::NotExpendable.into()),
ReducedToZero(result) => Ok(result),
Success => Ok(Zero::zero()),
}
@@ -22,7 +22,7 @@ use scale_info::TypeInfo;
use sp_core::{RuntimeDebug, TypedGet};
use sp_std::fmt::Debug;
use super::{fungible, Balance};
use super::{fungible, Balance, Preservation::Expendable};
/// Can be implemented by `PayFromAccount` using a `fungible` impl, but can also be implemented with
/// XCM/MultiAsset and made generic over assets.
@@ -76,9 +76,7 @@ pub enum PaymentStatus {
/// Simple implementation of `Pay` which makes a payment from a "pot" - i.e. a single account.
pub struct PayFromAccount<F, A>(sp_std::marker::PhantomData<(F, A)>);
impl<A: TypedGet, F: fungible::Transfer<A::Type> + fungible::Mutate<A::Type>> Pay
for PayFromAccount<F, A>
{
impl<A: TypedGet, F: fungible::Mutate<A::Type>> Pay for PayFromAccount<F, A> {
type Balance = F::Balance;
type Beneficiary = A::Type;
type AssetKind = ();
@@ -88,7 +86,7 @@ impl<A: TypedGet, F: fungible::Transfer<A::Type> + fungible::Mutate<A::Type>> Pa
_: Self::AssetKind,
amount: Self::Balance,
) -> Result<Self::Id, ()> {
<F as fungible::Transfer<_>>::transfer(&A::get(), who, amount, false).map_err(|_| ())?;
<F as fungible::Mutate<_>>::transfer(&A::get(), who, amount, Expendable).map_err(|_| ())?;
Ok(())
}
fn check_payment(_: ()) -> PaymentStatus {