mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-12 10:01:17 +00:00
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:
@@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user