mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-11 23:31:07 +00:00
Implement fungible::* for Balances (#8454)
* Reservable, Transferrable Fungible(s), plus adapters. * Repot into new dir * Imbalances for Fungibles * Repot and balanced fungible. * Clean up names and bridge-over Imbalanced. * Repot frame_support::trait. Finally. * Make build. * Docs * Good errors * Fix tests. Implement fungible::Inspect for Balances. * Implement additional traits for Balances. * Revert UI test "fixes" * Fix UI error * Fix UI test * More work on fungibles * Fixes * More work. * Update lock * Make fungible::reserved work for Balances * Introduce Freezer to Assets, ready for a reserve & locks pallet. Some renaming/refactoring. * Cleanup errors * Imbalances working with Assets * Test for freezer. * Grumbles * Grumbles * Fixes * Extra "side-car" data for a user's asset balance. * Fix * Fix test * Fixes * Line lengths * Comments * Update frame/assets/src/tests.rs Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com> * Update frame/support/src/traits/tokens/fungibles.rs Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com> * Update frame/assets/src/lib.rs Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com> * Update frame/support/src/traits/tokens/fungible.rs Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com> * Introduce `transfer_reserved` * Rename fungible Reserve -> Hold, add flag structs * Avoid the `melted` API - its too complex and gives little help * Repot Assets pallet Co-authored-by: Bastian Köcher <info@kchr.de> Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>
This commit is contained in:
Generated
+16
@@ -4635,6 +4635,22 @@ dependencies = [
|
||||
"sp-std",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pallet-assets-freezer"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"frame-benchmarking",
|
||||
"frame-support",
|
||||
"frame-system",
|
||||
"pallet-assets",
|
||||
"parity-scale-codec 2.0.1",
|
||||
"serde",
|
||||
"sp-core",
|
||||
"sp-io",
|
||||
"sp-runtime",
|
||||
"sp-std",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pallet-atomic-swap"
|
||||
version = "3.0.0"
|
||||
|
||||
@@ -1045,6 +1045,8 @@ impl pallet_assets::Config for Runtime {
|
||||
type MetadataDepositPerByte = MetadataDepositPerByte;
|
||||
type ApprovalDeposit = ApprovalDeposit;
|
||||
type StringLimit = StringLimit;
|
||||
type Freezer = ();
|
||||
type Extra = ();
|
||||
type WeightInfo = pallet_assets::weights::SubstrateWeight<Runtime>;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2017-2021 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.
|
||||
|
||||
//! Datatype for easy mutation of the extra "sidecar" data.
|
||||
|
||||
use super::*;
|
||||
|
||||
/// A mutator type allowing inspection and possible modification of the extra "sidecar" data.
|
||||
///
|
||||
/// This may be used as a `Deref` for the pallet's extra data. If mutated (using `DerefMut`), then
|
||||
/// any uncommitted changes (see `commit` function) will be automatically committed to storage when
|
||||
/// dropped. Changes, even after committed, may be reverted to their original values with the
|
||||
/// `revert` function.
|
||||
pub struct ExtraMutator<T: Config> {
|
||||
id: T::AssetId,
|
||||
who: T::AccountId,
|
||||
original: T::Extra,
|
||||
pending: Option<T::Extra>,
|
||||
}
|
||||
|
||||
impl<T: Config> Drop for ExtraMutator<T> {
|
||||
fn drop(&mut self) {
|
||||
debug_assert!(self.commit().is_ok(), "attempt to write to non-existent asset account");
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> sp_std::ops::Deref for ExtraMutator<T> {
|
||||
type Target = T::Extra;
|
||||
fn deref(&self) -> &T::Extra {
|
||||
match self.pending {
|
||||
Some(ref value) => value,
|
||||
None => &self.original,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> sp_std::ops::DerefMut for ExtraMutator<T> {
|
||||
fn deref_mut(&mut self) -> &mut T::Extra {
|
||||
if self.pending.is_none() {
|
||||
self.pending = Some(self.original.clone());
|
||||
}
|
||||
self.pending.as_mut().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> ExtraMutator<T> {
|
||||
pub(super) fn maybe_new(id: T::AssetId, who: impl sp_std::borrow::Borrow<T::AccountId>)
|
||||
-> Option<ExtraMutator<T>>
|
||||
{
|
||||
if Account::<T>::contains_key(id, who.borrow()) {
|
||||
Some(ExtraMutator::<T> {
|
||||
id,
|
||||
who: who.borrow().clone(),
|
||||
original: Account::<T>::get(id, who.borrow()).extra,
|
||||
pending: None,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Commit any changes to storage.
|
||||
pub fn commit(&mut self) -> Result<(), ()> {
|
||||
if let Some(extra) = self.pending.take() {
|
||||
Account::<T>::try_mutate_exists(self.id, self.who.borrow(), |maybe_account|
|
||||
if let Some(ref mut account) = maybe_account {
|
||||
account.extra = extra;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Revert any changes, even those already committed by `self` and drop self.
|
||||
pub fn revert(mut self) -> Result<(), ()> {
|
||||
self.pending = None;
|
||||
Account::<T>::try_mutate_exists(self.id, self.who.borrow(), |maybe_account|
|
||||
if let Some(ref mut account) = maybe_account {
|
||||
account.extra = self.original.clone();
|
||||
Ok(())
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,469 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2017-2021 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.
|
||||
|
||||
//! Functions for the Assets pallet.
|
||||
|
||||
use super::*;
|
||||
|
||||
// The main implementation block for the module.
|
||||
impl<T: Config> Pallet<T> {
|
||||
// Public immutables
|
||||
|
||||
/// Return the extra "sid-car" data for `id`/`who`, or `None` if the account doesn't exist.
|
||||
pub fn adjust_extra(id: T::AssetId, who: impl sp_std::borrow::Borrow<T::AccountId>)
|
||||
-> Option<ExtraMutator<T>>
|
||||
{
|
||||
ExtraMutator::maybe_new(id, who)
|
||||
}
|
||||
|
||||
/// Get the asset `id` balance of `who`.
|
||||
pub fn balance(id: T::AssetId, who: impl sp_std::borrow::Borrow<T::AccountId>) -> T::Balance {
|
||||
Account::<T>::get(id, who.borrow()).balance
|
||||
}
|
||||
|
||||
/// Get the total supply of an asset `id`.
|
||||
pub fn total_supply(id: T::AssetId) -> T::Balance {
|
||||
Asset::<T>::get(id).map(|x| x.supply).unwrap_or_else(Zero::zero)
|
||||
}
|
||||
|
||||
pub(super) fn new_account(
|
||||
who: &T::AccountId,
|
||||
d: &mut AssetDetails<T::Balance, T::AccountId, DepositBalanceOf<T>>,
|
||||
) -> Result<bool, DispatchError> {
|
||||
let accounts = d.accounts.checked_add(1).ok_or(Error::<T>::Overflow)?;
|
||||
let is_sufficient = if d.is_sufficient {
|
||||
frame_system::Pallet::<T>::inc_sufficients(who);
|
||||
d.sufficients += 1;
|
||||
true
|
||||
} else {
|
||||
frame_system::Pallet::<T>::inc_consumers(who).map_err(|_| Error::<T>::NoProvider)?;
|
||||
false
|
||||
};
|
||||
d.accounts = accounts;
|
||||
Ok(is_sufficient)
|
||||
}
|
||||
|
||||
pub(super) fn dead_account(
|
||||
what: T::AssetId,
|
||||
who: &T::AccountId,
|
||||
d: &mut AssetDetails<T::Balance, T::AccountId, DepositBalanceOf<T>>,
|
||||
sufficient: bool,
|
||||
) {
|
||||
if sufficient {
|
||||
d.sufficients = d.sufficients.saturating_sub(1);
|
||||
frame_system::Pallet::<T>::dec_sufficients(who);
|
||||
} else {
|
||||
frame_system::Pallet::<T>::dec_consumers(who);
|
||||
}
|
||||
d.accounts = d.accounts.saturating_sub(1);
|
||||
T::Freezer::died(what, who)
|
||||
}
|
||||
|
||||
pub(super) fn can_increase(id: T::AssetId, who: &T::AccountId, amount: T::Balance) -> DepositConsequence {
|
||||
let details = match Asset::<T>::get(id) {
|
||||
Some(details) => details,
|
||||
None => return DepositConsequence::UnknownAsset,
|
||||
};
|
||||
if details.supply.checked_add(&amount).is_none() {
|
||||
return DepositConsequence::Overflow
|
||||
}
|
||||
let account = Account::<T>::get(id, who);
|
||||
if account.balance.checked_add(&amount).is_none() {
|
||||
return DepositConsequence::Overflow
|
||||
}
|
||||
if account.balance.is_zero() {
|
||||
if amount < details.min_balance {
|
||||
return DepositConsequence::BelowMinimum
|
||||
}
|
||||
if !details.is_sufficient && frame_system::Pallet::<T>::providers(who) == 0 {
|
||||
return DepositConsequence::CannotCreate
|
||||
}
|
||||
if details.is_sufficient && details.sufficients.checked_add(1).is_none() {
|
||||
return DepositConsequence::Overflow
|
||||
}
|
||||
}
|
||||
|
||||
DepositConsequence::Success
|
||||
}
|
||||
|
||||
/// Return the consequence of a withdraw.
|
||||
pub(super) fn can_decrease(
|
||||
id: T::AssetId,
|
||||
who: &T::AccountId,
|
||||
amount: T::Balance,
|
||||
keep_alive: bool,
|
||||
) -> WithdrawConsequence<T::Balance> {
|
||||
use WithdrawConsequence::*;
|
||||
let details = match Asset::<T>::get(id) {
|
||||
Some(details) => details,
|
||||
None => return UnknownAsset,
|
||||
};
|
||||
if details.supply.checked_sub(&amount).is_none() {
|
||||
return Underflow
|
||||
}
|
||||
if details.is_frozen {
|
||||
return Frozen
|
||||
}
|
||||
let account = Account::<T>::get(id, who);
|
||||
if account.is_frozen {
|
||||
return Frozen
|
||||
}
|
||||
if let Some(rest) = account.balance.checked_sub(&amount) {
|
||||
if let Some(frozen) = T::Freezer::frozen_balance(id, who) {
|
||||
match frozen.checked_add(&details.min_balance) {
|
||||
Some(required) if rest < required => return Frozen,
|
||||
None => return Overflow,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
let is_provider = false;
|
||||
let is_required = is_provider && !frame_system::Pallet::<T>::can_dec_provider(who);
|
||||
let must_keep_alive = keep_alive || is_required;
|
||||
|
||||
if rest < details.min_balance {
|
||||
if must_keep_alive {
|
||||
WouldDie
|
||||
} else {
|
||||
ReducedToZero(rest)
|
||||
}
|
||||
} else {
|
||||
Success
|
||||
}
|
||||
} else {
|
||||
NoFunds
|
||||
}
|
||||
}
|
||||
|
||||
// Maximum `amount` that can be passed into `can_withdraw` to result in a `WithdrawConsequence`
|
||||
// of `Success`.
|
||||
pub(super) fn reducible_balance(
|
||||
id: T::AssetId,
|
||||
who: &T::AccountId,
|
||||
keep_alive: bool,
|
||||
) -> Result<T::Balance, Error<T>> {
|
||||
let details = match Asset::<T>::get(id) {
|
||||
Some(details) => details,
|
||||
None => return Err(Error::<T>::Unknown),
|
||||
};
|
||||
ensure!(!details.is_frozen, Error::<T>::Frozen);
|
||||
|
||||
let account = Account::<T>::get(id, who);
|
||||
ensure!(!account.is_frozen, Error::<T>::Frozen);
|
||||
|
||||
let amount = if let Some(frozen) = T::Freezer::frozen_balance(id, who) {
|
||||
// Frozen balance: account CANNOT be deleted
|
||||
let required = frozen.checked_add(&details.min_balance).ok_or(Error::<T>::Overflow)?;
|
||||
account.balance.saturating_sub(required)
|
||||
} else {
|
||||
let is_provider = false;
|
||||
let is_required = is_provider && !frame_system::Pallet::<T>::can_dec_provider(who);
|
||||
if keep_alive || is_required {
|
||||
// We want to keep the account around.
|
||||
account.balance.saturating_sub(details.min_balance)
|
||||
} else {
|
||||
// Don't care if the account dies
|
||||
account.balance
|
||||
}
|
||||
};
|
||||
Ok(amount.min(details.supply))
|
||||
}
|
||||
|
||||
/// Make preparatory checks for debiting some funds from an account. Flags indicate requirements
|
||||
/// of the debit.
|
||||
///
|
||||
/// - `amount`: The amount desired to be debited. The actual amount returned for debit may be
|
||||
/// less (in the case of `best_effort` being `true`) or greater by up to the minimum balance
|
||||
/// less one.
|
||||
/// - `keep_alive`: Require that `target` must stay alive.
|
||||
/// - `respect_freezer`: Respect any freezes on the account or token (or not).
|
||||
/// - `best_effort`: The debit amount may be less than `amount`.
|
||||
///
|
||||
/// On success, the amount which should be debited (this will always be at least `amount` unless
|
||||
/// `best_effort` is `true`) together with an optional value indicating the argument which must
|
||||
/// be passed into the `melted` function of the `T::Freezer` if `Some`.
|
||||
///
|
||||
/// If no valid debit can be made then return an `Err`.
|
||||
pub(super) fn prep_debit(
|
||||
id: T::AssetId,
|
||||
target: &T::AccountId,
|
||||
amount: T::Balance,
|
||||
f: DebitFlags,
|
||||
) -> Result<T::Balance, DispatchError> {
|
||||
let actual = Self::reducible_balance(id, target, f.keep_alive)?
|
||||
.min(amount);
|
||||
ensure!(f.best_effort || actual >= amount, Error::<T>::BalanceLow);
|
||||
|
||||
let conseq = Self::can_decrease(id, target, actual, f.keep_alive);
|
||||
let actual = match conseq.into_result() {
|
||||
Ok(dust) => actual.saturating_add(dust), //< guaranteed by reducible_balance
|
||||
Err(e) => {
|
||||
debug_assert!(false, "passed from reducible_balance; qed");
|
||||
return Err(e.into())
|
||||
}
|
||||
};
|
||||
|
||||
Ok(actual)
|
||||
}
|
||||
|
||||
/// Make preparatory checks for crediting some funds from an account. Flags indicate
|
||||
/// requirements of the credit.
|
||||
///
|
||||
/// - `amount`: The amount desired to be credited.
|
||||
/// - `debit`: The amount by which some other account has been debited. If this is greater than
|
||||
/// `amount`, then the `burn_dust` parameter takes effect.
|
||||
/// - `burn_dust`: Indicates that in the case of debit being greater than amount, the additional
|
||||
/// (dust) value should be burned, rather than credited.
|
||||
///
|
||||
/// On success, the amount which should be credited (this will always be at least `amount`)
|
||||
/// together with an optional value indicating the value which should be burned. The latter
|
||||
/// will always be `None` as long as `burn_dust` is `false` or `debit` is no greater than
|
||||
/// `amount`.
|
||||
///
|
||||
/// If no valid credit can be made then return an `Err`.
|
||||
pub(super) fn prep_credit(
|
||||
id: T::AssetId,
|
||||
dest: &T::AccountId,
|
||||
amount: T::Balance,
|
||||
debit: T::Balance,
|
||||
burn_dust: bool,
|
||||
) -> Result<(T::Balance, Option<T::Balance>), DispatchError> {
|
||||
let (credit, maybe_burn) = match (burn_dust, debit.checked_sub(&amount)) {
|
||||
(true, Some(dust)) => (amount, Some(dust)),
|
||||
_ => (debit, None),
|
||||
};
|
||||
Self::can_increase(id, &dest, credit).into_result()?;
|
||||
Ok((credit, maybe_burn))
|
||||
}
|
||||
|
||||
/// Increases the asset `id` balance of `beneficiary` by `amount`.
|
||||
///
|
||||
/// This alters the registered supply of the asset and emits an event.
|
||||
///
|
||||
/// Will return an error or will increase the amount by exactly `amount`.
|
||||
pub(super) fn do_mint(
|
||||
id: T::AssetId,
|
||||
beneficiary: &T::AccountId,
|
||||
amount: T::Balance,
|
||||
maybe_check_issuer: Option<T::AccountId>,
|
||||
) -> DispatchResult {
|
||||
Self::increase_balance(id, beneficiary, amount, |details| -> DispatchResult {
|
||||
if let Some(check_issuer) = maybe_check_issuer {
|
||||
ensure!(&check_issuer == &details.issuer, Error::<T>::NoPermission);
|
||||
}
|
||||
debug_assert!(T::Balance::max_value() - details.supply >= amount, "checked in prep; qed");
|
||||
details.supply = details.supply.saturating_add(amount);
|
||||
Ok(())
|
||||
})?;
|
||||
Self::deposit_event(Event::Issued(id, beneficiary.clone(), amount));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Increases the asset `id` balance of `beneficiary` by `amount`.
|
||||
///
|
||||
/// LOW-LEVEL: Does not alter the supply of asset or emit an event. Use `do_mint` if you need
|
||||
/// that. This is not intended to be used alone.
|
||||
///
|
||||
/// Will return an error or will increase the amount by exactly `amount`.
|
||||
pub(super) fn increase_balance(
|
||||
id: T::AssetId,
|
||||
beneficiary: &T::AccountId,
|
||||
amount: T::Balance,
|
||||
check: impl FnOnce(&mut AssetDetails<T::Balance, T::AccountId, DepositBalanceOf<T>>) -> DispatchResult,
|
||||
) -> DispatchResult {
|
||||
if amount.is_zero() { return Ok(()) }
|
||||
|
||||
Self::can_increase(id, beneficiary, amount).into_result()?;
|
||||
Asset::<T>::try_mutate(id, |maybe_details| -> DispatchResult {
|
||||
let details = maybe_details.as_mut().ok_or(Error::<T>::Unknown)?;
|
||||
|
||||
check(details)?;
|
||||
|
||||
Account::<T>::try_mutate(id, beneficiary, |t| -> DispatchResult {
|
||||
let new_balance = t.balance.saturating_add(amount);
|
||||
ensure!(new_balance >= details.min_balance, TokenError::BelowMinimum);
|
||||
if t.balance.is_zero() {
|
||||
t.sufficient = Self::new_account(beneficiary, details)?;
|
||||
}
|
||||
t.balance = new_balance;
|
||||
Ok(())
|
||||
})?;
|
||||
Ok(())
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Reduces asset `id` balance of `target` by `amount`. Flags `f` can be given to alter whether
|
||||
/// it attempts a `best_effort` or makes sure to `keep_alive` the account.
|
||||
///
|
||||
/// This alters the registered supply of the asset and emits an event.
|
||||
///
|
||||
/// Will return an error and do nothing or will decrease the amount and return the amount
|
||||
/// reduced by.
|
||||
pub(super) fn do_burn(
|
||||
id: T::AssetId,
|
||||
target: &T::AccountId,
|
||||
amount: T::Balance,
|
||||
maybe_check_admin: Option<T::AccountId>,
|
||||
f: DebitFlags,
|
||||
) -> Result<T::Balance, DispatchError> {
|
||||
let actual = Self::decrease_balance(id, target, amount, f, |actual, details| {
|
||||
// Check admin rights.
|
||||
if let Some(check_admin) = maybe_check_admin {
|
||||
ensure!(&check_admin == &details.admin, Error::<T>::NoPermission);
|
||||
}
|
||||
|
||||
debug_assert!(details.supply >= actual, "checked in prep; qed");
|
||||
details.supply = details.supply.saturating_sub(actual);
|
||||
|
||||
Ok(())
|
||||
})?;
|
||||
Self::deposit_event(Event::Burned(id, target.clone(), actual));
|
||||
Ok(actual)
|
||||
}
|
||||
|
||||
/// Reduces asset `id` balance of `target` by `amount`. Flags `f` can be given to alter whether
|
||||
/// it attempts a `best_effort` or makes sure to `keep_alive` the account.
|
||||
///
|
||||
/// LOW-LEVEL: Does not alter the supply of asset or emit an event. Use `do_burn` if you need
|
||||
/// that. This is not intended to be used alone.
|
||||
///
|
||||
/// Will return an error and do nothing or will decrease the amount and return the amount
|
||||
/// reduced by.
|
||||
pub(super) fn decrease_balance(
|
||||
id: T::AssetId,
|
||||
target: &T::AccountId,
|
||||
amount: T::Balance,
|
||||
f: DebitFlags,
|
||||
check: impl FnOnce(
|
||||
T::Balance,
|
||||
&mut AssetDetails<T::Balance, T::AccountId, DepositBalanceOf<T>>,
|
||||
) -> DispatchResult,
|
||||
) -> Result<T::Balance, DispatchError> {
|
||||
if amount.is_zero() { return Ok(amount) }
|
||||
|
||||
let actual = Self::prep_debit(id, target, amount, f)?;
|
||||
|
||||
Asset::<T>::try_mutate(id, |maybe_details| -> DispatchResult {
|
||||
let details = maybe_details.as_mut().ok_or(Error::<T>::Unknown)?;
|
||||
|
||||
check(actual, details)?;
|
||||
|
||||
Account::<T>::try_mutate_exists(id, target, |maybe_account| -> DispatchResult {
|
||||
let mut account = maybe_account.take().unwrap_or_default();
|
||||
debug_assert!(account.balance >= actual, "checked in prep; qed");
|
||||
|
||||
// Make the debit.
|
||||
account.balance = account.balance.saturating_sub(actual);
|
||||
*maybe_account = if account.balance < details.min_balance {
|
||||
debug_assert!(account.balance.is_zero(), "checked in prep; qed");
|
||||
Self::dead_account(id, target, details, account.sufficient);
|
||||
None
|
||||
} else {
|
||||
Some(account)
|
||||
};
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
Ok(actual)
|
||||
}
|
||||
|
||||
/// Reduces the asset `id` balance of `source` by some `amount` and increases the balance of
|
||||
/// `dest` by (similar) amount.
|
||||
///
|
||||
/// Returns the actual amount placed into `dest`. Exact semantics are determined by the flags
|
||||
/// `f`.
|
||||
///
|
||||
/// Will fail if the amount transferred is so small that it cannot create the destination due
|
||||
/// to minimum balance requirements.
|
||||
pub(super) fn do_transfer(
|
||||
id: T::AssetId,
|
||||
source: &T::AccountId,
|
||||
dest: &T::AccountId,
|
||||
amount: T::Balance,
|
||||
maybe_need_admin: Option<T::AccountId>,
|
||||
f: TransferFlags,
|
||||
) -> Result<T::Balance, DispatchError> {
|
||||
// Early exist if no-op.
|
||||
if amount.is_zero() {
|
||||
Self::deposit_event(Event::Transferred(id, source.clone(), dest.clone(), amount));
|
||||
return Ok(amount)
|
||||
}
|
||||
|
||||
// Figure out the debit and credit, together with side-effects.
|
||||
let debit = Self::prep_debit(id, &source, amount, f.into())?;
|
||||
let (credit, maybe_burn) = Self::prep_credit(id, &dest, amount, debit, f.burn_dust)?;
|
||||
|
||||
let mut source_account = Account::<T>::get(id, &source);
|
||||
|
||||
Asset::<T>::try_mutate(id, |maybe_details| -> DispatchResult {
|
||||
let details = maybe_details.as_mut().ok_or(Error::<T>::Unknown)?;
|
||||
|
||||
// Check admin rights.
|
||||
if let Some(need_admin) = maybe_need_admin {
|
||||
ensure!(&need_admin == &details.admin, Error::<T>::NoPermission);
|
||||
}
|
||||
|
||||
// Skip if source == dest
|
||||
if source == dest {
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
// Burn any dust if needed.
|
||||
if let Some(burn) = maybe_burn {
|
||||
// Debit dust from supply; this will not saturate since it's already checked in prep.
|
||||
debug_assert!(details.supply >= burn, "checked in prep; qed");
|
||||
details.supply = details.supply.saturating_sub(burn);
|
||||
}
|
||||
|
||||
// Debit balance from source; this will not saturate since it's already checked in prep.
|
||||
debug_assert!(source_account.balance >= debit, "checked in prep; qed");
|
||||
source_account.balance = source_account.balance.saturating_sub(debit);
|
||||
|
||||
Account::<T>::try_mutate(id, &dest, |a| -> DispatchResult {
|
||||
// Calculate new balance; this will not saturate since it's already checked in prep.
|
||||
debug_assert!(a.balance.checked_add(&credit).is_some(), "checked in prep; qed");
|
||||
let new_balance = a.balance.saturating_add(credit);
|
||||
|
||||
// Create a new account if there wasn't one already.
|
||||
if a.balance.is_zero() {
|
||||
a.sufficient = Self::new_account(&dest, details)?;
|
||||
}
|
||||
|
||||
a.balance = new_balance;
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
// Remove source account if it's now dead.
|
||||
if source_account.balance < details.min_balance {
|
||||
debug_assert!(source_account.balance.is_zero(), "checked in prep; qed");
|
||||
Self::dead_account(id, &source, details, source_account.sufficient);
|
||||
Account::<T>::remove(id, &source);
|
||||
} else {
|
||||
Account::<T>::insert(id, &source, &source_account)
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
Self::deposit_event(Event::Transferred(id, source.clone(), dest.clone(), credit));
|
||||
Ok(credit)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2017-2021 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.
|
||||
|
||||
//! Implementations for fungibles trait.
|
||||
|
||||
use super::*;
|
||||
|
||||
impl<T: Config> fungibles::Inspect<<T as SystemConfig>::AccountId> for Pallet<T> {
|
||||
type AssetId = T::AssetId;
|
||||
type Balance = T::Balance;
|
||||
|
||||
fn total_issuance(asset: Self::AssetId) -> Self::Balance {
|
||||
Asset::<T>::get(asset).map(|x| x.supply).unwrap_or_else(Zero::zero)
|
||||
}
|
||||
|
||||
fn minimum_balance(asset: Self::AssetId) -> Self::Balance {
|
||||
Asset::<T>::get(asset).map(|x| x.min_balance).unwrap_or_else(Zero::zero)
|
||||
}
|
||||
|
||||
fn balance(
|
||||
asset: Self::AssetId,
|
||||
who: &<T as SystemConfig>::AccountId,
|
||||
) -> Self::Balance {
|
||||
Pallet::<T>::balance(asset, who)
|
||||
}
|
||||
|
||||
fn reducible_balance(
|
||||
asset: Self::AssetId,
|
||||
who: &<T as SystemConfig>::AccountId,
|
||||
keep_alive: bool,
|
||||
) -> Self::Balance {
|
||||
Pallet::<T>::reducible_balance(asset, who, keep_alive).unwrap_or(Zero::zero())
|
||||
}
|
||||
|
||||
fn can_deposit(
|
||||
asset: Self::AssetId,
|
||||
who: &<T as SystemConfig>::AccountId,
|
||||
amount: Self::Balance,
|
||||
) -> DepositConsequence {
|
||||
Pallet::<T>::can_increase(asset, who, amount)
|
||||
}
|
||||
|
||||
fn can_withdraw(
|
||||
asset: Self::AssetId,
|
||||
who: &<T as SystemConfig>::AccountId,
|
||||
amount: Self::Balance,
|
||||
) -> WithdrawConsequence<Self::Balance> {
|
||||
Pallet::<T>::can_decrease(asset, who, amount, false)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> fungibles::Mutate<<T as SystemConfig>::AccountId> for Pallet<T> {
|
||||
fn mint_into(
|
||||
asset: Self::AssetId,
|
||||
who: &<T as SystemConfig>::AccountId,
|
||||
amount: Self::Balance,
|
||||
) -> DispatchResult {
|
||||
Self::do_mint(asset, who, amount, None)
|
||||
}
|
||||
|
||||
fn burn_from(
|
||||
asset: Self::AssetId,
|
||||
who: &<T as SystemConfig>::AccountId,
|
||||
amount: Self::Balance,
|
||||
) -> Result<Self::Balance, DispatchError> {
|
||||
let f = DebitFlags {
|
||||
keep_alive: false,
|
||||
best_effort: false,
|
||||
};
|
||||
Self::do_burn(asset, who, amount, None, f)
|
||||
}
|
||||
|
||||
fn slash(
|
||||
asset: Self::AssetId,
|
||||
who: &<T as SystemConfig>::AccountId,
|
||||
amount: Self::Balance,
|
||||
) -> Result<Self::Balance, DispatchError> {
|
||||
let f = DebitFlags {
|
||||
keep_alive: false,
|
||||
best_effort: true,
|
||||
};
|
||||
Self::do_burn(asset, who, amount, None, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> fungibles::Transfer<T::AccountId> for Pallet<T> {
|
||||
fn transfer(
|
||||
asset: Self::AssetId,
|
||||
source: &T::AccountId,
|
||||
dest: &T::AccountId,
|
||||
amount: T::Balance,
|
||||
keep_alive: bool,
|
||||
) -> Result<T::Balance, DispatchError> {
|
||||
let f = TransferFlags {
|
||||
keep_alive,
|
||||
best_effort: false,
|
||||
burn_dust: false
|
||||
};
|
||||
Self::do_transfer(asset, source, dest, amount, None, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> fungibles::Unbalanced<T::AccountId> for Pallet<T> {
|
||||
fn set_balance(_: Self::AssetId, _: &T::AccountId, _: Self::Balance) -> DispatchResult {
|
||||
unreachable!("set_balance is not used if other functions are impl'd");
|
||||
}
|
||||
fn set_total_issuance(id: T::AssetId, amount: Self::Balance) {
|
||||
Asset::<T>::mutate_exists(id, |maybe_asset| if let Some(ref mut asset) = maybe_asset {
|
||||
asset.supply = amount
|
||||
});
|
||||
}
|
||||
fn decrease_balance(asset: T::AssetId, who: &T::AccountId, amount: Self::Balance)
|
||||
-> Result<Self::Balance, DispatchError>
|
||||
{
|
||||
let f = DebitFlags { keep_alive: false, best_effort: false };
|
||||
Self::decrease_balance(asset, who, amount, f, |_, _| Ok(()))
|
||||
}
|
||||
fn decrease_balance_at_most(asset: T::AssetId, who: &T::AccountId, amount: Self::Balance)
|
||||
-> Self::Balance
|
||||
{
|
||||
let f = DebitFlags { keep_alive: false, best_effort: true };
|
||||
Self::decrease_balance(asset, who, amount, f, |_, _| Ok(()))
|
||||
.unwrap_or(Zero::zero())
|
||||
}
|
||||
fn increase_balance(asset: T::AssetId, who: &T::AccountId, amount: Self::Balance)
|
||||
-> Result<Self::Balance, DispatchError>
|
||||
{
|
||||
Self::increase_balance(asset, who, amount, |_| Ok(()))?;
|
||||
Ok(amount)
|
||||
}
|
||||
fn increase_balance_at_most(asset: T::AssetId, who: &T::AccountId, amount: Self::Balance)
|
||||
-> Self::Balance
|
||||
{
|
||||
match Self::increase_balance(asset, who, amount, |_| Ok(())) {
|
||||
Ok(()) => amount,
|
||||
Err(_) => Zero::zero(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2017-2021 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.
|
||||
|
||||
//! Assets pallet's `StoredMap` implementation.
|
||||
|
||||
use super::*;
|
||||
|
||||
impl<T: Config> StoredMap<(T::AssetId, T::AccountId), T::Extra> for Pallet<T> {
|
||||
fn get(id_who: &(T::AssetId, T::AccountId)) -> T::Extra {
|
||||
let &(id, ref who) = id_who;
|
||||
if Account::<T>::contains_key(id, who) {
|
||||
Account::<T>::get(id, who).extra
|
||||
} else {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn try_mutate_exists<R, E: From<StoredMapError>>(
|
||||
id_who: &(T::AssetId, T::AccountId),
|
||||
f: impl FnOnce(&mut Option<T::Extra>) -> Result<R, E>,
|
||||
) -> Result<R, E> {
|
||||
let &(id, ref who) = id_who;
|
||||
let mut maybe_extra = Some(Account::<T>::get(id, who).extra);
|
||||
let r = f(&mut maybe_extra)?;
|
||||
// They want to write some value or delete it.
|
||||
// If the account existed and they want to write a value, then we write.
|
||||
// If the account didn't exist and they want to delete it, then we let it pass.
|
||||
// Otherwise, we fail.
|
||||
Account::<T>::try_mutate_exists(id, who, |maybe_account| {
|
||||
if let Some(extra) = maybe_extra {
|
||||
// They want to write a value. Let this happen only if the account actually exists.
|
||||
if let Some(ref mut account) = maybe_account {
|
||||
account.extra = extra;
|
||||
} else {
|
||||
Err(StoredMapError::NoProviders)?;
|
||||
}
|
||||
} else {
|
||||
// They want to delete it. Let this pass if the item never existed anyway.
|
||||
ensure!(maybe_account.is_none(), StoredMapError::ConsumerRemaining);
|
||||
}
|
||||
Ok(r)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -130,191 +130,30 @@ pub mod mock;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use sp_std::prelude::*;
|
||||
mod extra_mutator;
|
||||
pub use extra_mutator::*;
|
||||
mod impl_stored_map;
|
||||
mod impl_fungibles;
|
||||
mod functions;
|
||||
mod types;
|
||||
pub use types::*;
|
||||
|
||||
use sp_std::{prelude::*, borrow::Borrow};
|
||||
use sp_runtime::{
|
||||
RuntimeDebug,
|
||||
traits::{
|
||||
AtLeast32BitUnsigned, Zero, StaticLookup, Saturating, CheckedSub, CheckedAdd,
|
||||
RuntimeDebug, TokenError, traits::{
|
||||
AtLeast32BitUnsigned, Zero, StaticLookup, Saturating, CheckedSub, CheckedAdd, Bounded,
|
||||
StoredMapError,
|
||||
}
|
||||
};
|
||||
use codec::{Encode, Decode, HasCompact};
|
||||
use frame_support::{ensure, dispatch::{DispatchError, DispatchResult}};
|
||||
use frame_support::traits::{Currency, ReservableCurrency, BalanceStatus::Reserved};
|
||||
use frame_support::traits::{Currency, ReservableCurrency, BalanceStatus::Reserved, StoredMap};
|
||||
use frame_support::traits::tokens::{WithdrawConsequence, DepositConsequence, fungibles};
|
||||
use frame_system::Config as SystemConfig;
|
||||
|
||||
pub use weights::WeightInfo;
|
||||
pub use pallet::*;
|
||||
|
||||
impl<T: Config> fungibles::Inspect<<T as SystemConfig>::AccountId> for Pallet<T> {
|
||||
type AssetId = T::AssetId;
|
||||
type Balance = T::Balance;
|
||||
|
||||
fn total_issuance(asset: Self::AssetId) -> Self::Balance {
|
||||
Asset::<T>::get(asset).map(|x| x.supply).unwrap_or_else(Zero::zero)
|
||||
}
|
||||
|
||||
fn minimum_balance(asset: Self::AssetId) -> Self::Balance {
|
||||
Asset::<T>::get(asset).map(|x| x.min_balance).unwrap_or_else(Zero::zero)
|
||||
}
|
||||
|
||||
fn balance(
|
||||
asset: Self::AssetId,
|
||||
who: &<T as SystemConfig>::AccountId,
|
||||
) -> Self::Balance {
|
||||
Pallet::<T>::balance(asset, who)
|
||||
}
|
||||
|
||||
fn can_deposit(
|
||||
asset: Self::AssetId,
|
||||
who: &<T as SystemConfig>::AccountId,
|
||||
amount: Self::Balance,
|
||||
) -> DepositConsequence {
|
||||
Pallet::<T>::can_deposit(asset, who, amount)
|
||||
}
|
||||
|
||||
fn can_withdraw(
|
||||
asset: Self::AssetId,
|
||||
who: &<T as SystemConfig>::AccountId,
|
||||
amount: Self::Balance,
|
||||
) -> WithdrawConsequence<Self::Balance> {
|
||||
Pallet::<T>::can_withdraw(asset, who, amount)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> fungibles::Mutate<<T as SystemConfig>::AccountId> for Pallet<T> {
|
||||
fn deposit(
|
||||
asset: Self::AssetId,
|
||||
who: &<T as SystemConfig>::AccountId,
|
||||
amount: Self::Balance,
|
||||
) -> DispatchResult {
|
||||
Pallet::<T>::increase_balance(asset, who.clone(), amount, None)
|
||||
}
|
||||
|
||||
fn withdraw(
|
||||
asset: Self::AssetId,
|
||||
who: &<T as SystemConfig>::AccountId,
|
||||
amount: Self::Balance,
|
||||
) -> Result<Self::Balance, DispatchError> {
|
||||
Pallet::<T>::reduce_balance(asset, who.clone(), amount, None)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> fungibles::Transfer<T::AccountId> for Pallet<T> {
|
||||
fn transfer(
|
||||
asset: Self::AssetId,
|
||||
source: &T::AccountId,
|
||||
dest: &T::AccountId,
|
||||
amount: T::Balance,
|
||||
) -> Result<T::Balance, DispatchError> {
|
||||
<Self as fungibles::Mutate::<T::AccountId>>::transfer(asset, source, dest, amount)
|
||||
}
|
||||
}
|
||||
|
||||
type DepositBalanceOf<T> = <<T as Config>::Currency as Currency<<T as SystemConfig>::AccountId>>::Balance;
|
||||
|
||||
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug)]
|
||||
pub struct AssetDetails<
|
||||
Balance,
|
||||
AccountId,
|
||||
DepositBalance,
|
||||
> {
|
||||
/// Can change `owner`, `issuer`, `freezer` and `admin` accounts.
|
||||
owner: AccountId,
|
||||
/// Can mint tokens.
|
||||
issuer: AccountId,
|
||||
/// Can thaw tokens, force transfers and burn tokens from any account.
|
||||
admin: AccountId,
|
||||
/// Can freeze tokens.
|
||||
freezer: AccountId,
|
||||
/// The total supply across all accounts.
|
||||
supply: Balance,
|
||||
/// The balance deposited for this asset. This pays for the data stored here.
|
||||
deposit: DepositBalance,
|
||||
/// The ED for virtual accounts.
|
||||
min_balance: Balance,
|
||||
/// If `true`, then any account with this asset is given a provider reference. Otherwise, it
|
||||
/// requires a consumer reference.
|
||||
is_sufficient: bool,
|
||||
/// The total number of accounts.
|
||||
accounts: u32,
|
||||
/// The total number of accounts for which we have placed a self-sufficient reference.
|
||||
sufficients: u32,
|
||||
/// The total number of approvals.
|
||||
approvals: u32,
|
||||
/// Whether the asset is frozen for non-admin transfers.
|
||||
is_frozen: bool,
|
||||
}
|
||||
|
||||
impl<Balance, AccountId, DepositBalance> AssetDetails<Balance, AccountId, DepositBalance> {
|
||||
pub fn destroy_witness(&self) -> DestroyWitness {
|
||||
DestroyWitness {
|
||||
accounts: self.accounts,
|
||||
sufficients: self.sufficients,
|
||||
approvals: self.approvals,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A pair to act as a key for the approval storage map.
|
||||
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug)]
|
||||
pub struct ApprovalKey<AccountId> {
|
||||
/// The owner of the funds that are being approved.
|
||||
owner: AccountId,
|
||||
/// The party to whom transfer of the funds is being delegated.
|
||||
delegate: AccountId,
|
||||
}
|
||||
|
||||
/// Data concerning an approval.
|
||||
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default)]
|
||||
pub struct Approval<Balance, DepositBalance> {
|
||||
/// The amount of funds approved for the balance transfer from the owner to some delegated
|
||||
/// target.
|
||||
amount: Balance,
|
||||
/// The amount reserved on the owner's account to hold this item in storage.
|
||||
deposit: DepositBalance,
|
||||
}
|
||||
|
||||
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default)]
|
||||
pub struct AssetBalance<Balance> {
|
||||
/// The balance.
|
||||
balance: Balance,
|
||||
/// Whether the account is frozen.
|
||||
is_frozen: bool,
|
||||
/// `true` if this balance gave the account a self-sufficient reference.
|
||||
sufficient: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default)]
|
||||
pub struct AssetMetadata<DepositBalance> {
|
||||
/// The balance deposited for this metadata.
|
||||
///
|
||||
/// This pays for the data stored in this struct.
|
||||
deposit: DepositBalance,
|
||||
/// The user friendly name of this asset. Limited in length by `StringLimit`.
|
||||
name: Vec<u8>,
|
||||
/// The ticker symbol for this asset. Limited in length by `StringLimit`.
|
||||
symbol: Vec<u8>,
|
||||
/// The number of decimals this asset uses to represent one unit.
|
||||
decimals: u8,
|
||||
/// Whether the asset metadata may be changed by a non Force origin.
|
||||
is_frozen: bool,
|
||||
}
|
||||
|
||||
/// Witness data for the destroy transactions.
|
||||
#[derive(Copy, Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug)]
|
||||
pub struct DestroyWitness {
|
||||
/// The number of accounts holding the asset.
|
||||
#[codec(compact)]
|
||||
accounts: u32,
|
||||
/// The number of accounts holding the asset with a self-sufficient reference.
|
||||
#[codec(compact)]
|
||||
sufficients: u32,
|
||||
/// The number of transfer-approvals of the asset.
|
||||
#[codec(compact)]
|
||||
approvals: u32,
|
||||
}
|
||||
|
||||
#[frame_support::pallet]
|
||||
pub mod pallet {
|
||||
use frame_support::{
|
||||
@@ -363,6 +202,13 @@ pub mod pallet {
|
||||
/// The maximum length of a name or symbol stored on-chain.
|
||||
type StringLimit: Get<u32>;
|
||||
|
||||
/// A hook to allow a per-asset, per-account minimum balance to be enforced. This must be
|
||||
/// respected in all permissionless operations.
|
||||
type Freezer: FrozenBalance<Self::AssetId, Self::AccountId, Self::Balance>;
|
||||
|
||||
/// Additional data to be stored with an account's asset balance.
|
||||
type Extra: Member + Parameter + Default;
|
||||
|
||||
/// Weight information for extrinsics in this pallet.
|
||||
type WeightInfo: WeightInfo;
|
||||
}
|
||||
@@ -384,7 +230,7 @@ pub mod pallet {
|
||||
T::AssetId,
|
||||
Blake2_128Concat,
|
||||
T::AccountId,
|
||||
AssetBalance<T::Balance>,
|
||||
AssetBalance<T::Balance, T::Extra>,
|
||||
ValueQuery,
|
||||
>;
|
||||
|
||||
@@ -637,7 +483,7 @@ pub mod pallet {
|
||||
ensure!(details.approvals == witness.approvals, Error::<T>::BadWitness);
|
||||
|
||||
for (who, v) in Account::<T>::drain_prefix(id) {
|
||||
Self::dead_account(&who, &mut details, v.sufficient);
|
||||
Self::dead_account(id, &who, &mut details, v.sufficient);
|
||||
}
|
||||
debug_assert_eq!(details.accounts, 0);
|
||||
debug_assert_eq!(details.sufficients, 0);
|
||||
@@ -674,7 +520,9 @@ pub mod pallet {
|
||||
) -> DispatchResult {
|
||||
let origin = ensure_signed(origin)?;
|
||||
let beneficiary = T::Lookup::lookup(beneficiary)?;
|
||||
Self::increase_balance(id, beneficiary, amount, Some(origin))
|
||||
Self::do_mint(id, &beneficiary, amount, Some(origin))?;
|
||||
Self::deposit_event(Event::Issued(id, beneficiary, amount));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Reduce the balance of `who` by as much as possible up to `amount` assets of `id`.
|
||||
@@ -702,7 +550,10 @@ pub mod pallet {
|
||||
let origin = ensure_signed(origin)?;
|
||||
let who = T::Lookup::lookup(who)?;
|
||||
|
||||
Self::reduce_balance(id, who, amount, Some(origin)).map(|_| ())
|
||||
let f = DebitFlags { keep_alive: false, best_effort: true };
|
||||
let burned = Self::do_burn(id, &who, amount, Some(origin), f)?;
|
||||
Self::deposit_event(Event::Burned(id, who, burned));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Move some assets from the sender account to another.
|
||||
@@ -733,7 +584,12 @@ pub mod pallet {
|
||||
let origin = ensure_signed(origin)?;
|
||||
let dest = T::Lookup::lookup(target)?;
|
||||
|
||||
Self::do_transfer(id, origin, dest, amount, None, false)
|
||||
let f = TransferFlags {
|
||||
keep_alive: false,
|
||||
best_effort: false,
|
||||
burn_dust: false
|
||||
};
|
||||
Self::do_transfer(id, &origin, &dest, amount, None, f).map(|_| ())
|
||||
}
|
||||
|
||||
/// Move some assets from the sender account to another, keeping the sender account alive.
|
||||
@@ -761,10 +617,15 @@ pub mod pallet {
|
||||
target: <T::Lookup as StaticLookup>::Source,
|
||||
#[pallet::compact] amount: T::Balance
|
||||
) -> DispatchResult {
|
||||
let origin = ensure_signed(origin)?;
|
||||
let source = ensure_signed(origin)?;
|
||||
let dest = T::Lookup::lookup(target)?;
|
||||
|
||||
Self::do_transfer(id, origin, dest, amount, None, true)
|
||||
let f = TransferFlags {
|
||||
keep_alive: true,
|
||||
best_effort: false,
|
||||
burn_dust: false
|
||||
};
|
||||
Self::do_transfer(id, &source, &dest, amount, None, f).map(|_| ())
|
||||
}
|
||||
|
||||
/// Move some assets from one account to another.
|
||||
@@ -798,7 +659,12 @@ pub mod pallet {
|
||||
let source = T::Lookup::lookup(source)?;
|
||||
let dest = T::Lookup::lookup(dest)?;
|
||||
|
||||
Self::do_transfer(id, source, dest, amount, Some(origin), false)
|
||||
let f = TransferFlags {
|
||||
keep_alive: false,
|
||||
best_effort: false,
|
||||
burn_dust: false
|
||||
};
|
||||
Self::do_transfer(id, &source, &dest, amount, Some(origin), f).map(|_| ())
|
||||
}
|
||||
|
||||
/// Disallow further unprivileged transfers from an account.
|
||||
@@ -1351,7 +1217,12 @@ pub mod pallet {
|
||||
let mut approved = maybe_approved.take().ok_or(Error::<T>::Unapproved)?;
|
||||
let remaining = approved.amount.checked_sub(&amount).ok_or(Error::<T>::Unapproved)?;
|
||||
|
||||
Self::do_transfer(id, key.owner.clone(), destination, amount, None, false)?;
|
||||
let f = TransferFlags {
|
||||
keep_alive: false,
|
||||
best_effort: false,
|
||||
burn_dust: false
|
||||
};
|
||||
Self::do_transfer(id, &key.owner, &destination, amount, None, f)?;
|
||||
|
||||
if remaining.is_zero() {
|
||||
T::Currency::unreserve(&key.owner, approved.deposit);
|
||||
@@ -1365,222 +1236,3 @@ pub mod pallet {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The main implementation block for the module.
|
||||
impl<T: Config> Pallet<T> {
|
||||
// Public immutables
|
||||
|
||||
/// Get the asset `id` balance of `who`.
|
||||
pub fn balance(id: T::AssetId, who: impl sp_std::borrow::Borrow<T::AccountId>) -> T::Balance {
|
||||
Account::<T>::get(id, who.borrow()).balance
|
||||
}
|
||||
|
||||
/// Get the total supply of an asset `id`.
|
||||
pub fn total_supply(id: T::AssetId) -> T::Balance {
|
||||
Asset::<T>::get(id).map(|x| x.supply).unwrap_or_else(Zero::zero)
|
||||
}
|
||||
|
||||
fn new_account(
|
||||
who: &T::AccountId,
|
||||
d: &mut AssetDetails<T::Balance, T::AccountId, DepositBalanceOf<T>>,
|
||||
) -> Result<bool, DispatchError> {
|
||||
let accounts = d.accounts.checked_add(1).ok_or(Error::<T>::Overflow)?;
|
||||
let is_sufficient = if d.is_sufficient {
|
||||
frame_system::Pallet::<T>::inc_sufficients(who);
|
||||
d.sufficients += 1;
|
||||
true
|
||||
} else {
|
||||
frame_system::Pallet::<T>::inc_consumers(who).map_err(|_| Error::<T>::NoProvider)?;
|
||||
false
|
||||
};
|
||||
d.accounts = accounts;
|
||||
Ok(is_sufficient)
|
||||
}
|
||||
|
||||
fn dead_account(
|
||||
who: &T::AccountId,
|
||||
d: &mut AssetDetails<T::Balance, T::AccountId, DepositBalanceOf<T>>,
|
||||
sufficient: bool,
|
||||
) {
|
||||
if sufficient {
|
||||
d.sufficients = d.sufficients.saturating_sub(1);
|
||||
frame_system::Pallet::<T>::dec_sufficients(who);
|
||||
} else {
|
||||
frame_system::Pallet::<T>::dec_consumers(who);
|
||||
}
|
||||
d.accounts = d.accounts.saturating_sub(1);
|
||||
}
|
||||
|
||||
fn can_deposit(id: T::AssetId, who: &T::AccountId, amount: T::Balance) -> DepositConsequence {
|
||||
let details = match Asset::<T>::get(id) {
|
||||
Some(details) => details,
|
||||
None => return DepositConsequence::UnknownAsset,
|
||||
};
|
||||
if details.supply.checked_add(&amount).is_none() {
|
||||
return DepositConsequence::Overflow
|
||||
}
|
||||
let account = Account::<T>::get(id, who);
|
||||
if account.balance.checked_add(&amount).is_none() {
|
||||
return DepositConsequence::Overflow
|
||||
}
|
||||
if account.balance.is_zero() {
|
||||
if amount < details.min_balance {
|
||||
return DepositConsequence::BelowMinimum
|
||||
}
|
||||
if !details.is_sufficient && frame_system::Pallet::<T>::providers(who) == 0 {
|
||||
return DepositConsequence::CannotCreate
|
||||
}
|
||||
if details.is_sufficient && details.sufficients.checked_add(1).is_none() {
|
||||
return DepositConsequence::Overflow
|
||||
}
|
||||
}
|
||||
|
||||
DepositConsequence::Success
|
||||
}
|
||||
|
||||
fn can_withdraw(
|
||||
id: T::AssetId,
|
||||
who: &T::AccountId,
|
||||
amount: T::Balance,
|
||||
) -> WithdrawConsequence<T::Balance> {
|
||||
let details = match Asset::<T>::get(id) {
|
||||
Some(details) => details,
|
||||
None => return WithdrawConsequence::UnknownAsset,
|
||||
};
|
||||
if details.supply.checked_sub(&amount).is_none() {
|
||||
return WithdrawConsequence::Underflow
|
||||
}
|
||||
let account = Account::<T>::get(id, who);
|
||||
if let Some(rest) = account.balance.checked_sub(&amount) {
|
||||
if rest < details.min_balance {
|
||||
WithdrawConsequence::ReducedToZero(rest)
|
||||
} else {
|
||||
// NOTE: this assumes (correctly) that the token won't be a provider. If that ever
|
||||
// changes, this will need to change.
|
||||
WithdrawConsequence::Success
|
||||
}
|
||||
} else {
|
||||
WithdrawConsequence::NoFunds
|
||||
}
|
||||
}
|
||||
|
||||
fn increase_balance(
|
||||
id: T::AssetId,
|
||||
beneficiary: T::AccountId,
|
||||
amount: T::Balance,
|
||||
maybe_check_issuer: Option<T::AccountId>,
|
||||
) -> DispatchResult {
|
||||
Asset::<T>::try_mutate(id, |maybe_details| {
|
||||
let details = maybe_details.as_mut().ok_or(Error::<T>::Unknown)?;
|
||||
|
||||
if let Some(check_issuer) = maybe_check_issuer {
|
||||
ensure!(&check_issuer == &details.issuer, Error::<T>::NoPermission);
|
||||
}
|
||||
details.supply = details.supply.checked_add(&amount).ok_or(Error::<T>::Overflow)?;
|
||||
|
||||
Account::<T>::try_mutate(id, &beneficiary, |t| -> DispatchResult {
|
||||
let new_balance = t.balance.saturating_add(amount);
|
||||
ensure!(new_balance >= details.min_balance, Error::<T>::BalanceLow);
|
||||
if t.balance.is_zero() {
|
||||
t.sufficient = Self::new_account(&beneficiary, details)?;
|
||||
}
|
||||
t.balance = new_balance;
|
||||
Ok(())
|
||||
})?;
|
||||
Self::deposit_event(Event::Issued(id, beneficiary, amount));
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn reduce_balance(
|
||||
id: T::AssetId,
|
||||
target: T::AccountId,
|
||||
amount: T::Balance,
|
||||
maybe_check_admin: Option<T::AccountId>,
|
||||
) -> Result<T::Balance, DispatchError> {
|
||||
Asset::<T>::try_mutate(id, |maybe_details| {
|
||||
let d = maybe_details.as_mut().ok_or(Error::<T>::Unknown)?;
|
||||
if let Some(check_admin) = maybe_check_admin {
|
||||
ensure!(&check_admin == &d.admin, Error::<T>::NoPermission);
|
||||
}
|
||||
|
||||
let burned = Account::<T>::try_mutate_exists(
|
||||
id,
|
||||
&target,
|
||||
|maybe_account| -> Result<T::Balance, DispatchError> {
|
||||
let mut account = maybe_account.take().ok_or(Error::<T>::BalanceZero)?;
|
||||
let mut burned = amount.min(account.balance);
|
||||
account.balance -= burned;
|
||||
*maybe_account = if account.balance < d.min_balance {
|
||||
burned += account.balance;
|
||||
Self::dead_account(&target, d, account.sufficient);
|
||||
None
|
||||
} else {
|
||||
Some(account)
|
||||
};
|
||||
Ok(burned)
|
||||
}
|
||||
)?;
|
||||
|
||||
d.supply = d.supply.saturating_sub(burned);
|
||||
|
||||
Self::deposit_event(Event::Burned(id, target, burned));
|
||||
Ok(burned)
|
||||
})
|
||||
}
|
||||
|
||||
fn do_transfer(
|
||||
id: T::AssetId,
|
||||
source: T::AccountId,
|
||||
dest: T::AccountId,
|
||||
amount: T::Balance,
|
||||
maybe_need_admin: Option<T::AccountId>,
|
||||
keep_alive: bool,
|
||||
) -> DispatchResult {
|
||||
let mut source_account = Account::<T>::get(id, &source);
|
||||
ensure!(!source_account.is_frozen, Error::<T>::Frozen);
|
||||
|
||||
source_account.balance = source_account.balance.checked_sub(&amount)
|
||||
.ok_or(Error::<T>::BalanceLow)?;
|
||||
|
||||
Asset::<T>::try_mutate(id, |maybe_details| {
|
||||
let details = maybe_details.as_mut().ok_or(Error::<T>::Unknown)?;
|
||||
ensure!(!details.is_frozen, Error::<T>::Frozen);
|
||||
|
||||
if let Some(need_admin) = maybe_need_admin {
|
||||
ensure!(&need_admin == &details.admin, Error::<T>::NoPermission);
|
||||
}
|
||||
|
||||
if dest != source && !amount.is_zero() {
|
||||
let mut amount = amount;
|
||||
if source_account.balance < details.min_balance {
|
||||
ensure!(!keep_alive, Error::<T>::WouldDie);
|
||||
amount += source_account.balance;
|
||||
source_account.balance = Zero::zero();
|
||||
}
|
||||
|
||||
Account::<T>::try_mutate(id, &dest, |a| -> DispatchResult {
|
||||
let new_balance = a.balance.saturating_add(amount);
|
||||
|
||||
ensure!(new_balance >= details.min_balance, Error::<T>::BalanceLow);
|
||||
|
||||
if a.balance.is_zero() {
|
||||
a.sufficient = Self::new_account(&dest, details)?;
|
||||
}
|
||||
a.balance = new_balance;
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
if source_account.balance.is_zero() {
|
||||
Self::dead_account(&source, details, source_account.sufficient);
|
||||
Account::<T>::remove(id, &source);
|
||||
} else {
|
||||
Account::<T>::insert(id, &source, &source_account)
|
||||
}
|
||||
}
|
||||
|
||||
Self::deposit_event(Event::Transferred(id, source, dest, amount));
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +100,42 @@ impl Config for Test {
|
||||
type MetadataDepositPerByte = MetadataDepositPerByte;
|
||||
type ApprovalDeposit = ApprovalDeposit;
|
||||
type StringLimit = StringLimit;
|
||||
type Freezer = TestFreezer;
|
||||
type WeightInfo = ();
|
||||
type Extra = ();
|
||||
}
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
pub(crate) enum Hook {
|
||||
Died(u32, u64),
|
||||
}
|
||||
thread_local! {
|
||||
static FROZEN: RefCell<HashMap<(u32, u64), u64>> = RefCell::new(Default::default());
|
||||
static HOOKS: RefCell<Vec<Hook>> = RefCell::new(Default::default());
|
||||
}
|
||||
|
||||
pub struct TestFreezer;
|
||||
impl FrozenBalance<u32, u64, u64> for TestFreezer {
|
||||
fn frozen_balance(asset: u32, who: &u64) -> Option<u64> {
|
||||
FROZEN.with(|f| f.borrow().get(&(asset, who.clone())).cloned())
|
||||
}
|
||||
|
||||
fn died(asset: u32, who: &u64) {
|
||||
HOOKS.with(|h| h.borrow_mut().push(Hook::Died(asset, who.clone())));
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn set_frozen_balance(asset: u32, who: u64, amount: u64) {
|
||||
FROZEN.with(|f| f.borrow_mut().insert((asset, who), amount));
|
||||
}
|
||||
pub(crate) fn clear_frozen_balance(asset: u32, who: u64) {
|
||||
FROZEN.with(|f| f.borrow_mut().remove(&(asset, who)));
|
||||
}
|
||||
pub(crate) fn hooks() -> Vec<Hook> {
|
||||
HOOKS.with(|h| h.borrow().clone())
|
||||
}
|
||||
|
||||
pub(crate) fn new_test_ext() -> sp_io::TestExternalities {
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
use super::*;
|
||||
use crate::{Error, mock::*};
|
||||
use sp_runtime::TokenError;
|
||||
use frame_support::{assert_ok, assert_noop, traits::Currency};
|
||||
use pallet_balances::Error as BalancesError;
|
||||
|
||||
@@ -198,11 +199,11 @@ fn non_providing_should_work() {
|
||||
assert_ok!(Assets::mint(Origin::signed(1), 0, 0, 100));
|
||||
|
||||
// Cannot mint into account 2 since it doesn't (yet) exist...
|
||||
assert_noop!(Assets::mint(Origin::signed(1), 0, 1, 100), Error::<Test>::NoProvider);
|
||||
assert_noop!(Assets::mint(Origin::signed(1), 0, 1, 100), TokenError::CannotCreate);
|
||||
// ...or transfer...
|
||||
assert_noop!(Assets::transfer(Origin::signed(0), 0, 1, 50), Error::<Test>::NoProvider);
|
||||
assert_noop!(Assets::transfer(Origin::signed(0), 0, 1, 50), TokenError::CannotCreate);
|
||||
// ...or force-transfer
|
||||
assert_noop!(Assets::force_transfer(Origin::signed(1), 0, 0, 1, 50), Error::<Test>::NoProvider);
|
||||
assert_noop!(Assets::force_transfer(Origin::signed(1), 0, 0, 1, 50), TokenError::CannotCreate);
|
||||
|
||||
Balances::make_free_balance_be(&1, 100);
|
||||
Balances::make_free_balance_be(&2, 100);
|
||||
@@ -219,12 +220,11 @@ fn min_balance_should_work() {
|
||||
assert_eq!(Asset::<Test>::get(0).unwrap().accounts, 1);
|
||||
|
||||
// Cannot create a new account with a balance that is below minimum...
|
||||
assert_noop!(Assets::mint(Origin::signed(1), 0, 2, 9), Error::<Test>::BalanceLow);
|
||||
assert_noop!(Assets::transfer(Origin::signed(1), 0, 2, 9), Error::<Test>::BalanceLow);
|
||||
assert_noop!(Assets::force_transfer(Origin::signed(1), 0, 1, 2, 9), Error::<Test>::BalanceLow);
|
||||
assert_noop!(Assets::mint(Origin::signed(1), 0, 2, 9), TokenError::BelowMinimum);
|
||||
assert_noop!(Assets::transfer(Origin::signed(1), 0, 2, 9), TokenError::BelowMinimum);
|
||||
assert_noop!(Assets::force_transfer(Origin::signed(1), 0, 1, 2, 9), TokenError::BelowMinimum);
|
||||
|
||||
// When deducting from an account to below minimum, it should be reaped.
|
||||
|
||||
assert_ok!(Assets::transfer(Origin::signed(1), 0, 2, 91));
|
||||
assert!(Assets::balance(0, 1).is_zero());
|
||||
assert_eq!(Assets::balance(0, 2), 100);
|
||||
@@ -277,7 +277,7 @@ fn transferring_enough_to_kill_source_when_keep_alive_should_fail() {
|
||||
assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 10));
|
||||
assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100));
|
||||
assert_eq!(Assets::balance(0, 1), 100);
|
||||
assert_noop!(Assets::transfer_keep_alive(Origin::signed(1), 0, 2, 91), Error::<Test>::WouldDie);
|
||||
assert_noop!(Assets::transfer_keep_alive(Origin::signed(1), 0, 2, 91), Error::<Test>::BalanceLow);
|
||||
assert_ok!(Assets::transfer_keep_alive(Origin::signed(1), 0, 2, 90));
|
||||
assert_eq!(Assets::balance(0, 1), 10);
|
||||
assert_eq!(Assets::balance(0, 2), 90);
|
||||
@@ -430,12 +430,14 @@ fn burning_asset_balance_with_positive_balance_should_work() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn burning_asset_balance_with_zero_balance_should_not_work() {
|
||||
fn burning_asset_balance_with_zero_balance_does_nothing() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1));
|
||||
assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100));
|
||||
assert_eq!(Assets::balance(0, 2), 0);
|
||||
assert_noop!(Assets::burn(Origin::signed(1), 0, 2, u64::max_value()), Error::<Test>::BalanceZero);
|
||||
assert_ok!(Assets::burn(Origin::signed(1), 0, 2, u64::max_value()));
|
||||
assert_eq!(Assets::balance(0, 2), 0);
|
||||
assert_eq!(Assets::total_supply(0), 100);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -491,3 +493,66 @@ fn set_metadata_should_work() {
|
||||
}
|
||||
|
||||
// TODO: tests for force_set_metadata, force_clear_metadata, force_asset_status
|
||||
// https://github.com/paritytech/substrate/issues/8470
|
||||
|
||||
#[test]
|
||||
fn freezer_should_work() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 10));
|
||||
assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100));
|
||||
assert_eq!(Assets::balance(0, 1), 100);
|
||||
|
||||
|
||||
// freeze 50 of it.
|
||||
set_frozen_balance(0, 1, 50);
|
||||
|
||||
assert_ok!(Assets::transfer(Origin::signed(1), 0, 2, 20));
|
||||
// cannot transfer another 21 away as this would take the non-frozen balance (30) to below
|
||||
// the minimum balance (10).
|
||||
assert_noop!(Assets::transfer(Origin::signed(1), 0, 2, 21), Error::<Test>::BalanceLow);
|
||||
|
||||
// create an approved transfer...
|
||||
Balances::make_free_balance_be(&1, 100);
|
||||
assert_ok!(Assets::approve_transfer(Origin::signed(1), 0, 2, 50));
|
||||
let e = Error::<Test>::BalanceLow;
|
||||
// ...but that wont work either:
|
||||
assert_noop!(Assets::transfer_approved(Origin::signed(2), 0, 1, 2, 21), e);
|
||||
// a force transfer won't work also.
|
||||
let e = Error::<Test>::BalanceLow;
|
||||
assert_noop!(Assets::force_transfer(Origin::signed(1), 0, 1, 2, 21), e);
|
||||
|
||||
// reduce it to only 49 frozen...
|
||||
set_frozen_balance(0, 1, 49);
|
||||
// ...and it's all good:
|
||||
assert_ok!(Assets::force_transfer(Origin::signed(1), 0, 1, 2, 21));
|
||||
|
||||
// and if we clear it, we can remove the account completely.
|
||||
clear_frozen_balance(0, 1);
|
||||
assert_ok!(Assets::transfer(Origin::signed(1), 0, 2, 50));
|
||||
assert_eq!(hooks(), vec![Hook::Died(0, 1)]);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn imbalances_should_work() {
|
||||
use frame_support::traits::tokens::fungibles::Balanced;
|
||||
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1));
|
||||
|
||||
let imb = Assets::issue(0, 100);
|
||||
assert_eq!(Assets::total_supply(0), 100);
|
||||
assert_eq!(imb.peek(), 100);
|
||||
|
||||
let (imb1, imb2) = imb.split(30);
|
||||
assert_eq!(imb1.peek(), 30);
|
||||
assert_eq!(imb2.peek(), 70);
|
||||
|
||||
drop(imb2);
|
||||
assert_eq!(Assets::total_supply(0), 30);
|
||||
|
||||
assert!(Assets::resolve(&1, imb1).is_ok());
|
||||
assert_eq!(Assets::balance(0, 1), 30);
|
||||
assert_eq!(Assets::total_supply(0), 30);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2017-2021 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.
|
||||
|
||||
//! Various basic tyoes for use in the assets pallet.
|
||||
|
||||
use super::*;
|
||||
|
||||
pub(super) type DepositBalanceOf<T> = <<T as Config>::Currency as Currency<<T as SystemConfig>::AccountId>>::Balance;
|
||||
|
||||
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug)]
|
||||
pub struct AssetDetails<
|
||||
Balance,
|
||||
AccountId,
|
||||
DepositBalance,
|
||||
> {
|
||||
/// Can change `owner`, `issuer`, `freezer` and `admin` accounts.
|
||||
pub(super) owner: AccountId,
|
||||
/// Can mint tokens.
|
||||
pub(super) issuer: AccountId,
|
||||
/// Can thaw tokens, force transfers and burn tokens from any account.
|
||||
pub(super) admin: AccountId,
|
||||
/// Can freeze tokens.
|
||||
pub(super) freezer: AccountId,
|
||||
/// The total supply across all accounts.
|
||||
pub(super) supply: Balance,
|
||||
/// The balance deposited for this asset. This pays for the data stored here.
|
||||
pub(super) deposit: DepositBalance,
|
||||
/// The ED for virtual accounts.
|
||||
pub(super) min_balance: Balance,
|
||||
/// If `true`, then any account with this asset is given a provider reference. Otherwise, it
|
||||
/// requires a consumer reference.
|
||||
pub(super) is_sufficient: bool,
|
||||
/// The total number of accounts.
|
||||
pub(super) accounts: u32,
|
||||
/// The total number of accounts for which we have placed a self-sufficient reference.
|
||||
pub(super) sufficients: u32,
|
||||
/// The total number of approvals.
|
||||
pub(super) approvals: u32,
|
||||
/// Whether the asset is frozen for non-admin transfers.
|
||||
pub(super) is_frozen: bool,
|
||||
}
|
||||
|
||||
impl<Balance, AccountId, DepositBalance> AssetDetails<Balance, AccountId, DepositBalance> {
|
||||
pub fn destroy_witness(&self) -> DestroyWitness {
|
||||
DestroyWitness {
|
||||
accounts: self.accounts,
|
||||
sufficients: self.sufficients,
|
||||
approvals: self.approvals,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A pair to act as a key for the approval storage map.
|
||||
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug)]
|
||||
pub struct ApprovalKey<AccountId> {
|
||||
/// The owner of the funds that are being approved.
|
||||
pub(super) owner: AccountId,
|
||||
/// The party to whom transfer of the funds is being delegated.
|
||||
pub(super) delegate: AccountId,
|
||||
}
|
||||
|
||||
/// Data concerning an approval.
|
||||
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default)]
|
||||
pub struct Approval<Balance, DepositBalance> {
|
||||
/// The amount of funds approved for the balance transfer from the owner to some delegated
|
||||
/// target.
|
||||
pub(super) amount: Balance,
|
||||
/// The amount reserved on the owner's account to hold this item in storage.
|
||||
pub(super) deposit: DepositBalance,
|
||||
}
|
||||
|
||||
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default)]
|
||||
pub struct AssetBalance<Balance, Extra> {
|
||||
/// The balance.
|
||||
pub(super) balance: Balance,
|
||||
/// Whether the account is frozen.
|
||||
pub(super) is_frozen: bool,
|
||||
/// `true` if this balance gave the account a self-sufficient reference.
|
||||
pub(super) sufficient: bool,
|
||||
/// Additional "sidecar" data, in case some other pallet wants to use this storage item.
|
||||
pub(super) extra: Extra,
|
||||
}
|
||||
|
||||
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default)]
|
||||
pub struct AssetMetadata<DepositBalance> {
|
||||
/// The balance deposited for this metadata.
|
||||
///
|
||||
/// This pays for the data stored in this struct.
|
||||
pub(super) deposit: DepositBalance,
|
||||
/// The user friendly name of this asset. Limited in length by `StringLimit`.
|
||||
pub(super) name: Vec<u8>,
|
||||
/// The ticker symbol for this asset. Limited in length by `StringLimit`.
|
||||
pub(super) symbol: Vec<u8>,
|
||||
/// The number of decimals this asset uses to represent one unit.
|
||||
pub(super) decimals: u8,
|
||||
/// Whether the asset metadata may be changed by a non Force origin.
|
||||
pub(super) is_frozen: bool,
|
||||
}
|
||||
|
||||
/// Witness data for the destroy transactions.
|
||||
#[derive(Copy, Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug)]
|
||||
pub struct DestroyWitness {
|
||||
/// The number of accounts holding the asset.
|
||||
#[codec(compact)]
|
||||
pub(super) accounts: u32,
|
||||
/// The number of accounts holding the asset with a self-sufficient reference.
|
||||
#[codec(compact)]
|
||||
pub(super) sufficients: u32,
|
||||
/// The number of transfer-approvals of the asset.
|
||||
#[codec(compact)]
|
||||
pub(super) approvals: u32,
|
||||
}
|
||||
|
||||
/// Trait for allowing a minimum balance on the account to be specified, beyond the
|
||||
/// `minimum_balance` of the asset. This is additive - the `minimum_balance` of the asset must be
|
||||
/// met *and then* anything here in addition.
|
||||
pub trait FrozenBalance<AssetId, AccountId, Balance> {
|
||||
/// Return the frozen balance. Under normal behaviour, this amount should always be
|
||||
/// withdrawable.
|
||||
///
|
||||
/// In reality, the balance of every account must be at least the sum of this (if `Some`) and
|
||||
/// the asset's minimum_balance, since there may be complications to destroying an asset's
|
||||
/// account completely.
|
||||
///
|
||||
/// If `None` is returned, then nothing special is enforced.
|
||||
///
|
||||
/// If any operation ever breaks this requirement (which will only happen through some sort of
|
||||
/// privileged intervention), then `melted` is called to do any cleanup.
|
||||
fn frozen_balance(asset: AssetId, who: &AccountId) -> Option<Balance>;
|
||||
|
||||
/// Called when an account has been removed.
|
||||
fn died(asset: AssetId, who: &AccountId);
|
||||
}
|
||||
|
||||
impl<AssetId, AccountId, Balance> FrozenBalance<AssetId, AccountId, Balance> for () {
|
||||
fn frozen_balance(_: AssetId, _: &AccountId) -> Option<Balance> { None }
|
||||
fn died(_: AssetId, _: &AccountId) {}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
pub(super) struct TransferFlags {
|
||||
/// The debited account must stay alive at the end of the operation; an error is returned if
|
||||
/// this cannot be achieved legally.
|
||||
pub(super) keep_alive: bool,
|
||||
/// Less than the amount specified needs be debited by the operation for it to be considered
|
||||
/// successful. If `false`, then the amount debited will always be at least the amount
|
||||
/// specified.
|
||||
pub(super) best_effort: bool,
|
||||
/// Any additional funds debited (due to minimum balance requirements) should be burned rather
|
||||
/// than credited to the destination account.
|
||||
pub(super) burn_dust: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
pub(super) struct DebitFlags {
|
||||
/// The debited account must stay alive at the end of the operation; an error is returned if
|
||||
/// this cannot be achieved legally.
|
||||
pub(super) keep_alive: bool,
|
||||
/// Less than the amount specified needs be debited by the operation for it to be considered
|
||||
/// successful. If `false`, then the amount debited will always be at least the amount
|
||||
/// specified.
|
||||
pub(super) best_effort: bool,
|
||||
}
|
||||
|
||||
impl From<TransferFlags> for DebitFlags {
|
||||
fn from(f: TransferFlags) -> Self {
|
||||
Self {
|
||||
keep_alive: f.keep_alive,
|
||||
best_effort: f.best_effort,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@
|
||||
//! ### Terminology
|
||||
//!
|
||||
//! - **Existential Deposit:** The minimum balance required to create or keep an account open. This prevents
|
||||
//! "dust accounts" from filling storage. When the free plus the reserved balance (i.e. the total balance)
|
||||
//! "dust accounts" from filling storage. When the free plus the reserved balance (i.e. the total balance)
|
||||
//! fall below this, then the account is said to be dead; and it loses its functionality as well as any
|
||||
//! prior history and all information on it is removed from the chain's state.
|
||||
//! No account should ever have a total balance that is strictly between 0 and the existential
|
||||
@@ -164,7 +164,8 @@ use frame_support::{
|
||||
Currency, OnUnbalanced, TryDrop, StoredMap,
|
||||
WithdrawReasons, LockIdentifier, LockableCurrency, ExistenceRequirement,
|
||||
Imbalance, SignedImbalance, ReservableCurrency, Get, ExistenceRequirement::KeepAlive,
|
||||
ExistenceRequirement::AllowDeath, BalanceStatus as Status,
|
||||
ExistenceRequirement::AllowDeath,
|
||||
tokens::{fungible, DepositConsequence, WithdrawConsequence, BalanceStatus as Status}
|
||||
}
|
||||
};
|
||||
#[cfg(feature = "std")]
|
||||
@@ -764,7 +765,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
/// the caller will do this.
|
||||
pub fn mutate_account<R>(
|
||||
who: &T::AccountId,
|
||||
f: impl FnOnce(&mut AccountData<T::Balance>) -> R
|
||||
f: impl FnOnce(&mut AccountData<T::Balance>) -> R,
|
||||
) -> Result<R, StoredMapError> {
|
||||
Self::try_mutate_account(who, |a, _| -> Result<R, StoredMapError> { Ok(f(a)) })
|
||||
}
|
||||
@@ -780,7 +781,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
/// the caller will do this.
|
||||
fn try_mutate_account<R, E: From<StoredMapError>>(
|
||||
who: &T::AccountId,
|
||||
f: impl FnOnce(&mut AccountData<T::Balance>, bool) -> Result<R, E>
|
||||
f: impl FnOnce(&mut AccountData<T::Balance>, bool) -> Result<R, E>,
|
||||
) -> Result<R, E> {
|
||||
Self::try_mutate_account_with_dust(who, f)
|
||||
.map(|(result, dust_cleaner)| {
|
||||
@@ -804,7 +805,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
/// the caller will do this.
|
||||
fn try_mutate_account_with_dust<R, E: From<StoredMapError>>(
|
||||
who: &T::AccountId,
|
||||
f: impl FnOnce(&mut AccountData<T::Balance>, bool) -> Result<R, E>
|
||||
f: impl FnOnce(&mut AccountData<T::Balance>, bool) -> Result<R, E>,
|
||||
) -> Result<(R, DustCleaner<T, I>), E> {
|
||||
let result = T::AccountStore::try_mutate_exists(who, |maybe_account| {
|
||||
let is_new = maybe_account.is_none();
|
||||
@@ -873,9 +874,57 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use frame_support::traits::tokens::{fungible, DepositConsequence, WithdrawConsequence};
|
||||
|
||||
/// Move the reserved balance of one account into the balance of another, according to `status`.
|
||||
///
|
||||
/// Is a no-op if:
|
||||
/// - the value to be moved is zero; or
|
||||
/// - the `slashed` id equal to `beneficiary` and the `status` is `Reserved`.
|
||||
fn do_transfer_reserved(
|
||||
slashed: &T::AccountId,
|
||||
beneficiary: &T::AccountId,
|
||||
value: T::Balance,
|
||||
best_effort: bool,
|
||||
status: Status,
|
||||
) -> Result<T::Balance, DispatchError> {
|
||||
if value.is_zero() { return Ok(Zero::zero()) }
|
||||
|
||||
if slashed == beneficiary {
|
||||
return match status {
|
||||
Status::Free => Ok(Self::unreserve(slashed, value)),
|
||||
Status::Reserved => Ok(value.saturating_sub(Self::reserved_balance(slashed))),
|
||||
};
|
||||
}
|
||||
|
||||
let ((actual, _maybe_one_dust), _maybe_other_dust) = Self::try_mutate_account_with_dust(
|
||||
beneficiary,
|
||||
|to_account, is_new| -> Result<(T::Balance, DustCleaner<T, I>), DispatchError> {
|
||||
ensure!(!is_new, Error::<T, I>::DeadAccount);
|
||||
Self::try_mutate_account_with_dust(
|
||||
slashed,
|
||||
|from_account, _| -> Result<T::Balance, DispatchError> {
|
||||
let actual = cmp::min(from_account.reserved, value);
|
||||
ensure!(best_effort || actual == value, Error::<T, I>::InsufficientBalance);
|
||||
match status {
|
||||
Status::Free => to_account.free = to_account.free
|
||||
.checked_add(&actual)
|
||||
.ok_or(Error::<T, I>::Overflow)?,
|
||||
Status::Reserved => to_account.reserved = to_account.reserved
|
||||
.checked_add(&actual)
|
||||
.ok_or(Error::<T, I>::Overflow)?,
|
||||
}
|
||||
from_account.reserved -= actual;
|
||||
Ok(actual)
|
||||
}
|
||||
)
|
||||
}
|
||||
)?;
|
||||
|
||||
Self::deposit_event(Event::ReserveRepatriated(slashed.clone(), beneficiary.clone(), actual, status));
|
||||
Ok(actual)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> fungible::Inspect<T::AccountId> for Pallet<T, I> {
|
||||
type Balance = T::Balance;
|
||||
@@ -889,6 +938,19 @@ impl<T: Config<I>, I: 'static> fungible::Inspect<T::AccountId> for Pallet<T, I>
|
||||
fn balance(who: &T::AccountId) -> Self::Balance {
|
||||
Self::account(who).total()
|
||||
}
|
||||
fn reducible_balance(who: &T::AccountId, keep_alive: bool) -> Self::Balance {
|
||||
let a = Self::account(who);
|
||||
// Liquid balance is what is neither reserved nor locked/frozen.
|
||||
let liquid = a.free.saturating_sub(a.fee_frozen.max(a.misc_frozen));
|
||||
if frame_system::Pallet::<T>::can_dec_provider(who) && !keep_alive {
|
||||
liquid
|
||||
} else {
|
||||
// `must_remain_to_exist` is the part of liquid balance which must remain to keep total over
|
||||
// ED.
|
||||
let must_remain_to_exist = T::ExistentialDeposit::get().saturating_sub(a.total() - liquid);
|
||||
liquid.saturating_sub(must_remain_to_exist)
|
||||
}
|
||||
}
|
||||
fn can_deposit(who: &T::AccountId, amount: Self::Balance) -> DepositConsequence {
|
||||
Self::deposit_consequence(who, amount, &Self::account(who))
|
||||
}
|
||||
@@ -898,7 +960,7 @@ impl<T: Config<I>, I: 'static> fungible::Inspect<T::AccountId> for Pallet<T, I>
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> fungible::Mutate<T::AccountId> for Pallet<T, I> {
|
||||
fn deposit(who: &T::AccountId, amount: Self::Balance) -> DispatchResult {
|
||||
fn mint_into(who: &T::AccountId, amount: Self::Balance) -> DispatchResult {
|
||||
if amount.is_zero() { return Ok(()) }
|
||||
Self::try_mutate_account(who, |account, _is_new| -> DispatchResult {
|
||||
Self::deposit_consequence(who, amount, &account).into_result()?;
|
||||
@@ -909,9 +971,8 @@ impl<T: Config<I>, I: 'static> fungible::Mutate<T::AccountId> for Pallet<T, I> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn withdraw(who: &T::AccountId, amount: Self::Balance) -> Result<Self::Balance, DispatchError> {
|
||||
fn burn_from(who: &T::AccountId, amount: Self::Balance) -> Result<Self::Balance, DispatchError> {
|
||||
if amount.is_zero() { return Ok(Self::Balance::zero()); }
|
||||
|
||||
let actual = Self::try_mutate_account(who, |account, _is_new| -> Result<T::Balance, DispatchError> {
|
||||
let extra = Self::withdraw_consequence(who, amount, &account).into_result()?;
|
||||
let actual = amount + extra;
|
||||
@@ -928,8 +989,11 @@ impl<T: Config<I>, I: 'static> fungible::Transfer<T::AccountId> for Pallet<T, I>
|
||||
source: &T::AccountId,
|
||||
dest: &T::AccountId,
|
||||
amount: T::Balance,
|
||||
keep_alive: bool,
|
||||
) -> Result<T::Balance, DispatchError> {
|
||||
<Self as fungible::Mutate::<T::AccountId>>::transfer(source, dest, amount)
|
||||
let er = if keep_alive { KeepAlive } else { AllowDeath };
|
||||
<Self as Currency::<T::AccountId>>::transfer(source, dest, amount, er)
|
||||
.map(|_| amount)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -944,6 +1008,60 @@ impl<T: Config<I>, I: 'static> fungible::Unbalanced<T::AccountId> for Pallet<T,
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> fungible::InspectHold<T::AccountId> for Pallet<T, I> {
|
||||
fn balance_on_hold(who: &T::AccountId) -> T::Balance {
|
||||
Self::account(who).reserved
|
||||
}
|
||||
fn can_hold(who: &T::AccountId, amount: T::Balance) -> bool {
|
||||
let a = Self::account(who);
|
||||
let min_balance = T::ExistentialDeposit::get().max(a.frozen(Reasons::All));
|
||||
if a.reserved.checked_add(&amount).is_none() { return false }
|
||||
// We require it to be min_balance + amount to ensure that the full reserved funds may be
|
||||
// slashed without compromising locked funds or destroying the account.
|
||||
let required_free = match min_balance.checked_add(&amount) {
|
||||
Some(x) => x,
|
||||
None => return false,
|
||||
};
|
||||
a.free >= required_free
|
||||
}
|
||||
}
|
||||
impl<T: Config<I>, I: 'static> fungible::MutateHold<T::AccountId> for Pallet<T, I> {
|
||||
fn hold(who: &T::AccountId, amount: Self::Balance) -> DispatchResult {
|
||||
if amount.is_zero() { return Ok(()) }
|
||||
ensure!(Self::can_reserve(who, amount), Error::<T, I>::InsufficientBalance);
|
||||
Self::mutate_account(who, |a| {
|
||||
a.free -= amount;
|
||||
a.reserved += amount;
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
fn release(who: &T::AccountId, amount: Self::Balance, best_effort: bool)
|
||||
-> Result<T::Balance, DispatchError>
|
||||
{
|
||||
if amount.is_zero() { return Ok(amount) }
|
||||
// Done on a best-effort basis.
|
||||
Self::try_mutate_account(who, |a, _| {
|
||||
let new_free = a.free.saturating_add(amount.min(a.reserved));
|
||||
let actual = new_free - a.free;
|
||||
ensure!(best_effort || actual == amount, Error::<T, I>::InsufficientBalance);
|
||||
// ^^^ Guaranteed to be <= amount and <= a.reserved
|
||||
a.free = new_free;
|
||||
a.reserved = a.reserved.saturating_sub(actual.clone());
|
||||
Ok(actual)
|
||||
})
|
||||
}
|
||||
fn transfer_held(
|
||||
source: &T::AccountId,
|
||||
dest: &T::AccountId,
|
||||
amount: Self::Balance,
|
||||
best_effort: bool,
|
||||
on_hold: bool,
|
||||
) -> Result<Self::Balance, DispatchError> {
|
||||
let status = if on_hold { Status::Reserved } else { Status::Free };
|
||||
Self::do_transfer_reserved(source, dest, amount, best_effort, status)
|
||||
}
|
||||
}
|
||||
|
||||
// wrapping these imbalances in a private module is necessary to ensure absolute privacy
|
||||
// of the inner member.
|
||||
mod imbalances {
|
||||
@@ -1521,40 +1639,8 @@ impl<T: Config<I>, I: 'static> ReservableCurrency<T::AccountId> for Pallet<T, I>
|
||||
value: Self::Balance,
|
||||
status: Status,
|
||||
) -> Result<Self::Balance, DispatchError> {
|
||||
if value.is_zero() { return Ok(Zero::zero()) }
|
||||
|
||||
if slashed == beneficiary {
|
||||
return match status {
|
||||
Status::Free => Ok(Self::unreserve(slashed, value)),
|
||||
Status::Reserved => Ok(value.saturating_sub(Self::reserved_balance(slashed))),
|
||||
};
|
||||
}
|
||||
|
||||
let ((actual, _maybe_one_dust), _maybe_other_dust) = Self::try_mutate_account_with_dust(
|
||||
beneficiary,
|
||||
|to_account, is_new| -> Result<(Self::Balance, DustCleaner<T, I>), DispatchError> {
|
||||
ensure!(!is_new, Error::<T, I>::DeadAccount);
|
||||
Self::try_mutate_account_with_dust(
|
||||
slashed,
|
||||
|from_account, _| -> Result<Self::Balance, DispatchError> {
|
||||
let actual = cmp::min(from_account.reserved, value);
|
||||
match status {
|
||||
Status::Free => to_account.free = to_account.free
|
||||
.checked_add(&actual)
|
||||
.ok_or(Error::<T, I>::Overflow)?,
|
||||
Status::Reserved => to_account.reserved = to_account.reserved
|
||||
.checked_add(&actual)
|
||||
.ok_or(Error::<T, I>::Overflow)?,
|
||||
}
|
||||
from_account.reserved -= actual;
|
||||
Ok(actual)
|
||||
}
|
||||
)
|
||||
}
|
||||
)?;
|
||||
|
||||
Self::deposit_event(Event::ReserveRepatriated(slashed.clone(), beneficiary.clone(), actual, status));
|
||||
Ok(value - actual)
|
||||
let actual = Self::do_transfer_reserved(slashed, beneficiary, value, true, status)?;
|
||||
Ok(value.saturating_sub(actual))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,15 +20,8 @@
|
||||
//! NOTE: If you're looking for `parameter_types`, it has moved in to the top-level module.
|
||||
|
||||
pub mod tokens;
|
||||
pub use tokens::fungible::{
|
||||
Inspect as InspectFungible, Mutate as MutateFungible, Transfer as TransferFungible,
|
||||
Reserve as ReserveFungible, Balanced as BalancedFungible, Unbalanced as UnbalancedFungible,
|
||||
ItemOf,
|
||||
};
|
||||
pub use tokens::fungibles::{
|
||||
Inspect as InspectFungibles, Mutate as MutateFungibles, Transfer as TransferFungibles,
|
||||
Reserve as ReserveFungibles, Balanced as BalancedFungibles, Unbalanced as UnbalancedFungibles,
|
||||
};
|
||||
pub use tokens::fungible;
|
||||
pub use tokens::fungibles;
|
||||
pub use tokens::currency::{
|
||||
Currency, LockIdentifier, LockableCurrency, ReservableCurrency, VestingSchedule,
|
||||
};
|
||||
|
||||
@@ -32,14 +32,22 @@ pub use imbalance::{Imbalance, HandleImbalanceDrop, DebtOf, CreditOf};
|
||||
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 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`.
|
||||
fn can_deposit(who: &AccountId, amount: Self::Balance) -> 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>;
|
||||
@@ -47,26 +55,42 @@ pub trait Inspect<AccountId> {
|
||||
|
||||
/// Trait for providing an ERC-20 style fungible asset.
|
||||
pub trait Mutate<AccountId>: Inspect<AccountId> {
|
||||
/// Increase the balance of `who` by `amount`.
|
||||
fn deposit(who: &AccountId, amount: Self::Balance) -> DispatchResult;
|
||||
/// Attempt to reduce the balance of `who` by `amount`.
|
||||
fn withdraw(who: &AccountId, amount: Self::Balance) -> Result<Self::Balance, DispatchError>;
|
||||
/// Transfer funds from one account into another.
|
||||
fn transfer(
|
||||
/// 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>;
|
||||
|
||||
/// 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 is 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()?;
|
||||
Self::can_deposit(&dest, amount.saturating_add(extra)).into_result()?;
|
||||
let actual = Self::withdraw(source, amount)?;
|
||||
let actual = Self::burn_from(source, amount)?;
|
||||
debug_assert!(actual == amount.saturating_add(extra), "can_withdraw must agree with withdraw; qed");
|
||||
match Self::deposit(dest, actual) {
|
||||
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::deposit(source, actual);
|
||||
let revert = Self::mint_into(source, actual);
|
||||
debug_assert!(revert.is_ok(), "withdrew funds previously; qed");
|
||||
Err(err)
|
||||
}
|
||||
@@ -81,31 +105,82 @@ pub trait Transfer<AccountId>: Inspect<AccountId> {
|
||||
source: &AccountId,
|
||||
dest: &AccountId,
|
||||
amount: Self::Balance,
|
||||
keep_alive: bool,
|
||||
) -> Result<Self::Balance, DispatchError>;
|
||||
}
|
||||
|
||||
/// Trait for providing a fungible asset which can be reserved.
|
||||
pub trait Reserve<AccountId>: Inspect<AccountId> {
|
||||
/// 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 reserved_balance(who: &AccountId) -> Self::Balance;
|
||||
/// Amount of funds held in total by `who`.
|
||||
fn total_balance(who: &AccountId) -> Self::Balance {
|
||||
Self::reserved_balance(who).saturating_add(Self::balance(who))
|
||||
}
|
||||
/// Check to see if some `amount` of funds may be reserved on the account of `who`.
|
||||
fn can_reserve(who: &AccountId, amount: Self::Balance) -> bool;
|
||||
/// Reserve some funds in an account.
|
||||
fn reserve(who: &AccountId, amount: Self::Balance) -> DispatchResult;
|
||||
/// Unreserve some funds in an account.
|
||||
fn unreserve(who: &AccountId, amount: Self::Balance) -> DispatchResult;
|
||||
/// Transfer reserved funds into another account.
|
||||
fn repatriate_reserved(
|
||||
who: &AccountId,
|
||||
amount: Self::Balance,
|
||||
status: BalanceStatus,
|
||||
) -> DispatchResult;
|
||||
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;
|
||||
}
|
||||
|
||||
/// 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>;
|
||||
|
||||
/// 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 {
|
||||
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>,
|
||||
@@ -129,6 +204,9 @@ impl<
|
||||
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) -> DepositConsequence {
|
||||
<F as fungibles::Inspect<AccountId>>::can_deposit(A::get(), who, amount)
|
||||
}
|
||||
@@ -142,11 +220,11 @@ impl<
|
||||
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
|
||||
AccountId,
|
||||
> Mutate<AccountId> for ItemOf<F, A, AccountId> {
|
||||
fn deposit(who: &AccountId, amount: Self::Balance) -> DispatchResult {
|
||||
<F as fungibles::Mutate<AccountId>>::deposit(A::get(), who, amount)
|
||||
fn mint_into(who: &AccountId, amount: Self::Balance) -> DispatchResult {
|
||||
<F as fungibles::Mutate<AccountId>>::mint_into(A::get(), who, amount)
|
||||
}
|
||||
fn withdraw(who: &AccountId, amount: Self::Balance) -> Result<Self::Balance, DispatchError> {
|
||||
<F as fungibles::Mutate<AccountId>>::withdraw(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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,39 +233,54 @@ impl<
|
||||
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)
|
||||
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)
|
||||
<F as fungibles::Transfer<AccountId>>::transfer(A::get(), source, dest, amount, keep_alive)
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
F: fungibles::Reserve<AccountId>,
|
||||
F: fungibles::InspectHold<AccountId>,
|
||||
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
|
||||
AccountId,
|
||||
> Reserve<AccountId> for ItemOf<F, A, AccountId> {
|
||||
fn reserved_balance(who: &AccountId) -> Self::Balance {
|
||||
<F as fungibles::Reserve<AccountId>>::reserved_balance(A::get(), who)
|
||||
> 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 total_balance(who: &AccountId) -> Self::Balance {
|
||||
<F as fungibles::Reserve<AccountId>>::total_balance(A::get(), who)
|
||||
fn can_hold(who: &AccountId, amount: Self::Balance) -> bool {
|
||||
<F as fungibles::InspectHold<AccountId>>::can_hold(A::get(), who, amount)
|
||||
}
|
||||
fn can_reserve(who: &AccountId, amount: Self::Balance) -> bool {
|
||||
<F as fungibles::Reserve<AccountId>>::can_reserve(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 reserve(who: &AccountId, amount: Self::Balance) -> DispatchResult {
|
||||
<F as fungibles::Reserve<AccountId>>::reserve(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 unreserve(who: &AccountId, amount: Self::Balance) -> DispatchResult {
|
||||
<F as fungibles::Reserve<AccountId>>::unreserve(A::get(), who, amount)
|
||||
}
|
||||
fn repatriate_reserved(
|
||||
who: &AccountId,
|
||||
fn transfer_held(
|
||||
source: &AccountId,
|
||||
dest: &AccountId,
|
||||
amount: Self::Balance,
|
||||
status: BalanceStatus,
|
||||
) -> DispatchResult {
|
||||
<F as fungibles::Reserve<AccountId>>::repatriate_reserved(A::get(), who, amount, status)
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,4 +308,3 @@ impl<
|
||||
<F as fungibles::Unbalanced<AccountId>>::increase_balance_at_most(A::get(), who, amount)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -55,14 +55,11 @@ pub trait Balanced<AccountId>: Inspect<AccountId> {
|
||||
///
|
||||
/// 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>)
|
||||
{
|
||||
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`, preferring to deduct from the
|
||||
/// free balance. This function cannot fail.
|
||||
/// 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.
|
||||
///
|
||||
|
||||
@@ -31,17 +31,26 @@ pub use imbalance::{Imbalance, HandleImbalanceDrop, DebtOf, CreditOf};
|
||||
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 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`.
|
||||
fn can_deposit(asset: Self::AssetId, who: &AccountId, amount: Self::Balance)
|
||||
-> DepositConsequence;
|
||||
|
||||
/// Returns `Failed` if the `asset` balance of `who` may not be decreased by `amount`, otherwise
|
||||
/// the consequence.
|
||||
fn can_withdraw(
|
||||
@@ -62,7 +71,7 @@ pub trait Mutate<AccountId>: Inspect<AccountId> {
|
||||
///
|
||||
/// 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 deposit(asset: Self::AssetId, who: &AccountId, amount: Self::Balance) -> DispatchResult;
|
||||
fn mint_into(asset: Self::AssetId, who: &AccountId, amount: Self::Balance) -> DispatchResult;
|
||||
|
||||
/// Attempt to reduce the `asset` balance of `who` by `amount`.
|
||||
///
|
||||
@@ -78,11 +87,25 @@ pub trait Mutate<AccountId>: Inspect<AccountId> {
|
||||
/// 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 withdraw(asset: Self::AssetId, who: &AccountId, amount: Self::Balance)
|
||||
fn burn_from(asset: Self::AssetId, who: &AccountId, amount: Self::Balance)
|
||||
-> Result<Self::Balance, DispatchError>;
|
||||
|
||||
/// Transfer funds from one account into another.
|
||||
fn transfer(
|
||||
/// 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,
|
||||
@@ -90,14 +113,14 @@ pub trait Mutate<AccountId>: Inspect<AccountId> {
|
||||
) -> Result<Self::Balance, DispatchError> {
|
||||
let extra = Self::can_withdraw(asset, &source, amount).into_result()?;
|
||||
Self::can_deposit(asset, &dest, amount.saturating_add(extra)).into_result()?;
|
||||
let actual = Self::withdraw(asset, source, amount)?;
|
||||
let actual = Self::burn_from(asset, source, amount)?;
|
||||
debug_assert!(actual == amount.saturating_add(extra), "can_withdraw must agree with withdraw; qed");
|
||||
match Self::deposit(asset, dest, actual) {
|
||||
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::deposit(asset, source, actual);
|
||||
let revert = Self::mint_into(asset, source, actual);
|
||||
debug_assert!(revert.is_ok(), "withdrew funds previously; qed");
|
||||
Err(err)
|
||||
}
|
||||
@@ -113,31 +136,75 @@ pub trait Transfer<AccountId>: Inspect<AccountId> {
|
||||
source: &AccountId,
|
||||
dest: &AccountId,
|
||||
amount: Self::Balance,
|
||||
keep_alive: bool,
|
||||
) -> Result<Self::Balance, DispatchError>;
|
||||
}
|
||||
|
||||
/// Trait for providing a set of named fungible assets which can be reserved.
|
||||
pub trait Reserve<AccountId>: Inspect<AccountId> {
|
||||
/// Amount of funds held in reserve.
|
||||
fn reserved_balance(asset: Self::AssetId, who: &AccountId) -> 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;
|
||||
|
||||
/// Amount of funds held in reserve.
|
||||
fn total_balance(asset: Self::AssetId, who: &AccountId) -> Self::Balance;
|
||||
|
||||
/// Check to see if some `amount` of `asset` may be reserved on the account of `who`.
|
||||
fn can_reserve(asset: Self::AssetId, who: &AccountId, amount: Self::Balance) -> bool;
|
||||
|
||||
/// Reserve some funds in an account.
|
||||
fn reserve(asset: Self::AssetId, who: &AccountId, amount: Self::Balance) -> DispatchResult;
|
||||
|
||||
/// Unreserve some funds in an account.
|
||||
fn unreserve(asset: Self::AssetId, who: &AccountId, amount: Self::Balance) -> DispatchResult;
|
||||
|
||||
/// Transfer reserved funds into another account.
|
||||
fn repatriate_reserved(
|
||||
asset: Self::AssetId,
|
||||
who: &AccountId,
|
||||
amount: Self::Balance,
|
||||
status: BalanceStatus,
|
||||
) -> DispatchResult;
|
||||
/// 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,10 @@ use crate::traits::misc::{SameOrOther, TryDrop};
|
||||
///
|
||||
/// 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
|
||||
|
||||
@@ -37,6 +37,9 @@ pub enum WithdrawConsequence<Balance> {
|
||||
/// There has been an underflow in the system. This is indicative of a corrupt state and
|
||||
/// likely unrecoverable.
|
||||
Underflow,
|
||||
/// There has been an overflow in the system. This is indicative of a corrupt state and
|
||||
/// likely unrecoverable.
|
||||
Overflow,
|
||||
/// Not enough of the funds in the account are unavailable for withdrawal.
|
||||
Frozen,
|
||||
/// Account balance would reduce to zero, potentially destroying it. The parameter is the
|
||||
@@ -56,6 +59,7 @@ impl<Balance: Zero> WithdrawConsequence<Balance> {
|
||||
WouldDie => Err(TokenError::WouldDie),
|
||||
UnknownAsset => Err(TokenError::UnknownAsset),
|
||||
Underflow => Err(TokenError::Underflow),
|
||||
Overflow => Err(TokenError::Overflow),
|
||||
Frozen => Err(TokenError::Frozen),
|
||||
ReducedToZero(result) => Ok(result),
|
||||
Success => Ok(Zero::zero()),
|
||||
|
||||
Reference in New Issue
Block a user