mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-13 08:11:04 +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:
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user