From 080dcf01b3d9268023c0eb138cee15600b5211ca Mon Sep 17 00:00:00 2001 From: Daniel Olano Date: Mon, 29 Mar 2021 15:42:33 +0200 Subject: [PATCH] Make pallet Assets instantiable --- frame/assets/src/extra_mutator.rs | 137 +- frame/assets/src/functions.rs | 915 +++++------ frame/assets/src/impl_fungibles.rs | 266 ++-- frame/assets/src/impl_stored_map.rs | 70 +- frame/assets/src/lib.rs | 2235 ++++++++++++++------------- frame/assets/src/types.rs | 233 ++- 6 files changed, 1977 insertions(+), 1879 deletions(-) diff --git a/frame/assets/src/extra_mutator.rs b/frame/assets/src/extra_mutator.rs index 26a9a3f357c52..81dbbc5bed5c5 100644 --- a/frame/assets/src/extra_mutator.rs +++ b/frame/assets/src/extra_mutator.rs @@ -25,81 +25,84 @@ use super::*; /// 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 { - id: T::AssetId, - who: T::AccountId, - original: T::Extra, - pending: Option, +pub struct ExtraMutator, I: 'static = ()> { + id: T::AssetId, + who: T::AccountId, + original: T::Extra, + pending: Option, } -impl Drop for ExtraMutator { - fn drop(&mut self) { - debug_assert!(self.commit().is_ok(), "attempt to write to non-existent asset account"); - } +impl, I: 'static> Drop for ExtraMutator { + fn drop(&mut self) { + debug_assert!( + self.commit().is_ok(), + "attempt to write to non-existent asset account" + ); + } } -impl sp_std::ops::Deref for ExtraMutator { - type Target = T::Extra; - fn deref(&self) -> &T::Extra { - match self.pending { - Some(ref value) => value, - None => &self.original, - } - } +impl, I: 'static> sp_std::ops::Deref for ExtraMutator { + type Target = T::Extra; + fn deref(&self) -> &T::Extra { + match self.pending { + Some(ref value) => value, + None => &self.original, + } + } } -impl sp_std::ops::DerefMut for ExtraMutator { - 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, I: 'static> sp_std::ops::DerefMut for ExtraMutator { + 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 ExtraMutator { - pub(super) fn maybe_new(id: T::AssetId, who: impl sp_std::borrow::Borrow) - -> Option> - { - if Account::::contains_key(id, who.borrow()) { - Some(ExtraMutator:: { - id, - who: who.borrow().clone(), - original: Account::::get(id, who.borrow()).extra, - pending: None, - }) - } else { - None - } - } +impl, I: 'static> ExtraMutator { + pub(super) fn maybe_new( + id: T::AssetId, + who: impl sp_std::borrow::Borrow, + ) -> Option> { + if Account::::contains_key(id, who.borrow()) { + Some(ExtraMutator:: { + id, + who: who.borrow().clone(), + original: Account::::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::::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(()) + } + } - /// Commit any changes to storage. - pub fn commit(&mut self) -> Result<(), ()> { - if let Some(extra) = self.pending.take() { - Account::::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::::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(()) - } - ) - } + /// Revert any changes, even those already committed by `self` and drop self. + pub fn revert(mut self) -> Result<(), ()> { + self.pending = None; + Account::::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(()) + } + }) + } } diff --git a/frame/assets/src/functions.rs b/frame/assets/src/functions.rs index 197b010b6eb87..43b7208c52199 100644 --- a/frame/assets/src/functions.rs +++ b/frame/assets/src/functions.rs @@ -20,450 +20,473 @@ use super::*; // The main implementation block for the module. -impl Pallet { - // 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) - -> Option> - { - 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::Balance { - Account::::get(id, who.borrow()).balance - } - - /// Get the total supply of an asset `id`. - pub fn total_supply(id: T::AssetId) -> T::Balance { - Asset::::get(id).map(|x| x.supply).unwrap_or_else(Zero::zero) - } - - pub(super) fn new_account( - who: &T::AccountId, - d: &mut AssetDetails>, - ) -> Result { - let accounts = d.accounts.checked_add(1).ok_or(Error::::Overflow)?; - let is_sufficient = if d.is_sufficient { - frame_system::Pallet::::inc_sufficients(who); - d.sufficients += 1; - true - } else { - frame_system::Pallet::::inc_consumers(who).map_err(|_| Error::::NoProvider)?; - false - }; - d.accounts = accounts; - Ok(is_sufficient) - } - - pub(super) fn dead_account( - what: T::AssetId, - who: &T::AccountId, - d: &mut AssetDetails>, - sufficient: bool, - ) { - if sufficient { - d.sufficients = d.sufficients.saturating_sub(1); - frame_system::Pallet::::dec_sufficients(who); - } else { - frame_system::Pallet::::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::::get(id) { - Some(details) => details, - None => return DepositConsequence::UnknownAsset, - }; - if details.supply.checked_add(&amount).is_none() { - return DepositConsequence::Overflow - } - let account = Account::::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::::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 { - use WithdrawConsequence::*; - let details = match Asset::::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::::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::::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> { - let details = match Asset::::get(id) { - Some(details) => details, - None => return Err(Error::::Unknown), - }; - ensure!(!details.is_frozen, Error::::Frozen); - - let account = Account::::get(id, who); - ensure!(!account.is_frozen, Error::::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::::Overflow)?; - account.balance.saturating_sub(required) - } else { - let is_provider = false; - let is_required = is_provider && !frame_system::Pallet::::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 { - let actual = Self::reducible_balance(id, target, f.keep_alive)? - .min(amount); - ensure!(f.best_effort || actual >= amount, Error::::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), 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, - ) -> DispatchResult { - Self::increase_balance(id, beneficiary, amount, |details| -> DispatchResult { - if let Some(check_issuer) = maybe_check_issuer { - ensure!(&check_issuer == &details.issuer, Error::::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>) -> DispatchResult, - ) -> DispatchResult { - if amount.is_zero() { return Ok(()) } - - Self::can_increase(id, beneficiary, amount).into_result()?; - Asset::::try_mutate(id, |maybe_details| -> DispatchResult { - let details = maybe_details.as_mut().ok_or(Error::::Unknown)?; - - check(details)?; - - Account::::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, - f: DebitFlags, - ) -> Result { - 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::::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>, - ) -> DispatchResult, - ) -> Result { - if amount.is_zero() { return Ok(amount) } - - let actual = Self::prep_debit(id, target, amount, f)?; - - Asset::::try_mutate(id, |maybe_details| -> DispatchResult { - let details = maybe_details.as_mut().ok_or(Error::::Unknown)?; - - check(actual, details)?; - - Account::::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, - f: TransferFlags, - ) -> Result { - // 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::::get(id, &source); - - Asset::::try_mutate(id, |maybe_details| -> DispatchResult { - let details = maybe_details.as_mut().ok_or(Error::::Unknown)?; - - // Check admin rights. - if let Some(need_admin) = maybe_need_admin { - ensure!(&need_admin == &details.admin, Error::::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::::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::::remove(id, &source); - } else { - Account::::insert(id, &source, &source_account) - } - - Ok(()) - })?; - - Self::deposit_event(Event::Transferred(id, source.clone(), dest.clone(), credit)); - Ok(credit) - } +impl, I: 'static> Pallet { + // 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, + ) -> Option> { + 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::Balance { + Account::::get(id, who.borrow()).balance + } + + /// Get the total supply of an asset `id`. + pub fn total_supply(id: T::AssetId) -> T::Balance { + Asset::::get(id) + .map(|x| x.supply) + .unwrap_or_else(Zero::zero) + } + + pub(super) fn new_account( + who: &T::AccountId, + d: &mut AssetDetails>, + ) -> Result { + let accounts = d.accounts.checked_add(1).ok_or(Error::::Overflow)?; + let is_sufficient = if d.is_sufficient { + frame_system::Pallet::::inc_sufficients(who); + d.sufficients += 1; + true + } else { + frame_system::Pallet::::inc_consumers(who).map_err(|_| Error::::NoProvider)?; + false + }; + d.accounts = accounts; + Ok(is_sufficient) + } + + pub(super) fn dead_account( + what: T::AssetId, + who: &T::AccountId, + d: &mut AssetDetails>, + sufficient: bool, + ) { + if sufficient { + d.sufficients = d.sufficients.saturating_sub(1); + frame_system::Pallet::::dec_sufficients(who); + } else { + frame_system::Pallet::::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::::get(id) { + Some(details) => details, + None => return DepositConsequence::UnknownAsset, + }; + if details.supply.checked_add(&amount).is_none() { + return DepositConsequence::Overflow; + } + let account = Account::::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::::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 { + use WithdrawConsequence::*; + let details = match Asset::::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::::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::::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> { + let details = match Asset::::get(id) { + Some(details) => details, + None => return Err(Error::::Unknown), + }; + ensure!(!details.is_frozen, Error::::Frozen); + + let account = Account::::get(id, who); + ensure!(!account.is_frozen, Error::::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::::Overflow)?; + account.balance.saturating_sub(required) + } else { + let is_provider = false; + let is_required = is_provider && !frame_system::Pallet::::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 { + let actual = Self::reducible_balance(id, target, f.keep_alive)?.min(amount); + ensure!(f.best_effort || actual >= amount, Error::::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), 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, + ) -> DispatchResult { + Self::increase_balance(id, beneficiary, amount, |details| -> DispatchResult { + if let Some(check_issuer) = maybe_check_issuer { + ensure!( + &check_issuer == &details.issuer, + Error::::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>, + ) -> DispatchResult, + ) -> DispatchResult { + if amount.is_zero() { + return Ok(()); + } + + Self::can_increase(id, beneficiary, amount).into_result()?; + Asset::::try_mutate(id, |maybe_details| -> DispatchResult { + let details = maybe_details.as_mut().ok_or(Error::::Unknown)?; + + check(details)?; + + Account::::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, + f: DebitFlags, + ) -> Result { + 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::::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>, + ) -> DispatchResult, + ) -> Result { + if amount.is_zero() { + return Ok(amount); + } + + let actual = Self::prep_debit(id, target, amount, f)?; + + Asset::::try_mutate(id, |maybe_details| -> DispatchResult { + let details = maybe_details.as_mut().ok_or(Error::::Unknown)?; + + check(actual, details)?; + + Account::::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, + f: TransferFlags, + ) -> Result { + // 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::::get(id, &source); + + Asset::::try_mutate(id, |maybe_details| -> DispatchResult { + let details = maybe_details.as_mut().ok_or(Error::::Unknown)?; + + // Check admin rights. + if let Some(need_admin) = maybe_need_admin { + ensure!(&need_admin == &details.admin, Error::::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::::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::::remove(id, &source); + } else { + Account::::insert(id, &source, &source_account) + } + + Ok(()) + })?; + + Self::deposit_event(Event::Transferred(id, source.clone(), dest.clone(), credit)); + Ok(credit) + } } diff --git a/frame/assets/src/impl_fungibles.rs b/frame/assets/src/impl_fungibles.rs index a4cff9b7e9a62..c4d330ed47f28 100644 --- a/frame/assets/src/impl_fungibles.rs +++ b/frame/assets/src/impl_fungibles.rs @@ -19,135 +19,151 @@ use super::*; -impl fungibles::Inspect<::AccountId> for Pallet { - type AssetId = T::AssetId; - type Balance = T::Balance; - - fn total_issuance(asset: Self::AssetId) -> Self::Balance { - Asset::::get(asset).map(|x| x.supply).unwrap_or_else(Zero::zero) - } - - fn minimum_balance(asset: Self::AssetId) -> Self::Balance { - Asset::::get(asset).map(|x| x.min_balance).unwrap_or_else(Zero::zero) - } - - fn balance( - asset: Self::AssetId, - who: &::AccountId, - ) -> Self::Balance { - Pallet::::balance(asset, who) - } - - fn reducible_balance( - asset: Self::AssetId, - who: &::AccountId, - keep_alive: bool, - ) -> Self::Balance { - Pallet::::reducible_balance(asset, who, keep_alive).unwrap_or(Zero::zero()) - } - - fn can_deposit( - asset: Self::AssetId, - who: &::AccountId, - amount: Self::Balance, - ) -> DepositConsequence { - Pallet::::can_increase(asset, who, amount) - } - - fn can_withdraw( - asset: Self::AssetId, - who: &::AccountId, - amount: Self::Balance, - ) -> WithdrawConsequence { - Pallet::::can_decrease(asset, who, amount, false) - } +impl, I: 'static> fungibles::Inspect<::AccountId> for Pallet { + type AssetId = T::AssetId; + type Balance = T::Balance; + + fn total_issuance(asset: Self::AssetId) -> Self::Balance { + Asset::::get(asset) + .map(|x| x.supply) + .unwrap_or_else(Zero::zero) + } + + fn minimum_balance(asset: Self::AssetId) -> Self::Balance { + Asset::::get(asset) + .map(|x| x.min_balance) + .unwrap_or_else(Zero::zero) + } + + fn balance(asset: Self::AssetId, who: &::AccountId) -> Self::Balance { + Pallet::::balance(asset, who) + } + + fn reducible_balance( + asset: Self::AssetId, + who: &::AccountId, + keep_alive: bool, + ) -> Self::Balance { + Pallet::::reducible_balance(asset, who, keep_alive).unwrap_or(Zero::zero()) + } + + fn can_deposit( + asset: Self::AssetId, + who: &::AccountId, + amount: Self::Balance, + ) -> DepositConsequence { + Pallet::::can_increase(asset, who, amount) + } + + fn can_withdraw( + asset: Self::AssetId, + who: &::AccountId, + amount: Self::Balance, + ) -> WithdrawConsequence { + Pallet::::can_decrease(asset, who, amount, false) + } } -impl fungibles::Mutate<::AccountId> for Pallet { - fn mint_into( - asset: Self::AssetId, - who: &::AccountId, - amount: Self::Balance, - ) -> DispatchResult { - Self::do_mint(asset, who, amount, None) - } - - fn burn_from( - asset: Self::AssetId, - who: &::AccountId, - amount: Self::Balance, - ) -> Result { - let f = DebitFlags { - keep_alive: false, - best_effort: false, - }; - Self::do_burn(asset, who, amount, None, f) - } - - fn slash( - asset: Self::AssetId, - who: &::AccountId, - amount: Self::Balance, - ) -> Result { - let f = DebitFlags { - keep_alive: false, - best_effort: true, - }; - Self::do_burn(asset, who, amount, None, f) - } +impl, I: 'static> fungibles::Mutate<::AccountId> for Pallet { + fn mint_into( + asset: Self::AssetId, + who: &::AccountId, + amount: Self::Balance, + ) -> DispatchResult { + Self::do_mint(asset, who, amount, None) + } + + fn burn_from( + asset: Self::AssetId, + who: &::AccountId, + amount: Self::Balance, + ) -> Result { + let f = DebitFlags { + keep_alive: false, + best_effort: false, + }; + Self::do_burn(asset, who, amount, None, f) + } + + fn slash( + asset: Self::AssetId, + who: &::AccountId, + amount: Self::Balance, + ) -> Result { + let f = DebitFlags { + keep_alive: false, + best_effort: true, + }; + Self::do_burn(asset, who, amount, None, f) + } } -impl fungibles::Transfer for Pallet { - fn transfer( - asset: Self::AssetId, - source: &T::AccountId, - dest: &T::AccountId, - amount: T::Balance, - keep_alive: bool, - ) -> Result { - let f = TransferFlags { - keep_alive, - best_effort: false, - burn_dust: false - }; - Self::do_transfer(asset, source, dest, amount, None, f) - } +impl, I: 'static> fungibles::Transfer for Pallet { + fn transfer( + asset: Self::AssetId, + source: &T::AccountId, + dest: &T::AccountId, + amount: T::Balance, + keep_alive: bool, + ) -> Result { + let f = TransferFlags { + keep_alive, + best_effort: false, + burn_dust: false, + }; + Self::do_transfer(asset, source, dest, amount, None, f) + } } -impl fungibles::Unbalanced for Pallet { - 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::::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 - { - 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::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(), - } - } +impl, I: 'static> fungibles::Unbalanced for Pallet { + 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::::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 { + 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::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(), + } + } } diff --git a/frame/assets/src/impl_stored_map.rs b/frame/assets/src/impl_stored_map.rs index a8a6f95557dfb..28ce8eebc8486 100644 --- a/frame/assets/src/impl_stored_map.rs +++ b/frame/assets/src/impl_stored_map.rs @@ -19,40 +19,40 @@ use super::*; -impl StoredMap<(T::AssetId, T::AccountId), T::Extra> for Pallet { - fn get(id_who: &(T::AssetId, T::AccountId)) -> T::Extra { - let &(id, ref who) = id_who; - if Account::::contains_key(id, who) { - Account::::get(id, who).extra - } else { - Default::default() - } - } +impl, I: 'static> StoredMap<(T::AssetId, T::AccountId), T::Extra> for Pallet { + fn get(id_who: &(T::AssetId, T::AccountId)) -> T::Extra { + let &(id, ref who) = id_who; + if Account::::contains_key(id, who) { + Account::::get(id, who).extra + } else { + Default::default() + } + } - fn try_mutate_exists>( - id_who: &(T::AssetId, T::AccountId), - f: impl FnOnce(&mut Option) -> Result, - ) -> Result { - let &(id, ref who) = id_who; - let mut maybe_extra = Some(Account::::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::::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) - }) - } + fn try_mutate_exists>( + id_who: &(T::AssetId, T::AccountId), + f: impl FnOnce(&mut Option) -> Result, + ) -> Result { + let &(id, ref who) = id_who; + let mut maybe_extra = Some(Account::::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::::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) + }) + } } diff --git a/frame/assets/src/lib.rs b/frame/assets/src/lib.rs index 2a162c2c936b1..79cd59e8ab448 100644 --- a/frame/assets/src/lib.rs +++ b/frame/assets/src/lib.rs @@ -122,1117 +122,1174 @@ // Ensure we're `no_std` when compiling for Wasm. #![cfg_attr(not(feature = "std"), no_std)] -pub mod weights; #[cfg(feature = "runtime-benchmarks")] mod benchmarking; #[cfg(test)] pub mod mock; #[cfg(test)] mod tests; +pub mod weights; mod extra_mutator; pub use extra_mutator::*; -mod impl_stored_map; -mod impl_fungibles; mod functions; +mod impl_fungibles; +mod impl_stored_map; mod types; pub use types::*; -use sp_std::{prelude::*, borrow::Borrow}; -use sp_runtime::{ - RuntimeDebug, TokenError, traits::{ - AtLeast32BitUnsigned, Zero, StaticLookup, Saturating, CheckedSub, CheckedAdd, Bounded, - StoredMapError, - } +use codec::{Decode, Encode, HasCompact}; +use frame_support::traits::tokens::{fungibles, DepositConsequence, WithdrawConsequence}; +use frame_support::traits::{BalanceStatus::Reserved, Currency, ReservableCurrency, StoredMap}; +use frame_support::{ + dispatch::{DispatchError, DispatchResult}, + ensure, }; -use codec::{Encode, Decode, HasCompact}; -use frame_support::{ensure, dispatch::{DispatchError, DispatchResult}}; -use frame_support::traits::{Currency, ReservableCurrency, BalanceStatus::Reserved, StoredMap}; -use frame_support::traits::tokens::{WithdrawConsequence, DepositConsequence, fungibles}; use frame_system::Config as SystemConfig; +use sp_runtime::{ + traits::{ + AtLeast32BitUnsigned, Bounded, CheckedAdd, CheckedSub, Saturating, StaticLookup, + StoredMapError, Zero, + }, + RuntimeDebug, TokenError, +}; +use sp_std::{borrow::Borrow, prelude::*}; -pub use weights::WeightInfo; pub use pallet::*; +pub use weights::WeightInfo; #[frame_support::pallet] pub mod pallet { - use frame_support::{ - dispatch::DispatchResult, - pallet_prelude::*, - }; - use frame_system::pallet_prelude::*; - use super::*; - - #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] - pub struct Pallet(_); - - #[pallet::config] - /// The module configuration trait. - pub trait Config: frame_system::Config { - /// The overarching event type. - type Event: From> + IsType<::Event>; - - /// The units in which we record balances. - type Balance: Member + Parameter + AtLeast32BitUnsigned + Default + Copy; - - /// Identifier for the class of asset. - type AssetId: Member + Parameter + Default + Copy + HasCompact; - - /// The currency mechanism. - type Currency: ReservableCurrency; - - /// The origin which may forcibly create or destroy an asset or otherwise alter privileged - /// attributes. - type ForceOrigin: EnsureOrigin; - - /// The basic amount of funds that must be reserved for an asset. - type AssetDeposit: Get>; - - /// The basic amount of funds that must be reserved when adding metadata to your asset. - type MetadataDepositBase: Get>; - - /// The additional funds that must be reserved for the number of bytes you store in your - /// metadata. - type MetadataDepositPerByte: Get>; - - /// The amount of funds that must be reserved when creating a new approval. - type ApprovalDeposit: Get>; - - /// The maximum length of a name or symbol stored on-chain. - type StringLimit: Get; - - /// 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; - - /// 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; - } - - #[pallet::storage] - /// Details of an asset. - pub(super) type Asset = StorageMap< - _, - Blake2_128Concat, - T::AssetId, - AssetDetails>, - >; - - #[pallet::storage] - /// The number of units of assets held by any given account. - pub(super) type Account = StorageDoubleMap< - _, - Blake2_128Concat, - T::AssetId, - Blake2_128Concat, - T::AccountId, - AssetBalance, - ValueQuery, - >; - - #[pallet::storage] - /// Approved balance transfers. First balance is the amount approved for transfer. Second - /// is the amount of `T::Currency` reserved for storing this. - pub(super) type Approvals = StorageDoubleMap< - _, - Blake2_128Concat, - T::AssetId, - Blake2_128Concat, - ApprovalKey, - Approval>, - OptionQuery, - >; - - #[pallet::storage] - /// Metadata of an asset. - pub(super) type Metadata = StorageMap< - _, - Blake2_128Concat, - T::AssetId, - AssetMetadata>, - ValueQuery, - >; - - #[pallet::event] - #[pallet::generate_deposit(pub(super) fn deposit_event)] - #[pallet::metadata(T::AccountId = "AccountId", T::Balance = "Balance", T::AssetId = "AssetId")] - pub enum Event { - /// Some asset class was created. \[asset_id, creator, owner\] - Created(T::AssetId, T::AccountId, T::AccountId), - /// Some assets were issued. \[asset_id, owner, total_supply\] - Issued(T::AssetId, T::AccountId, T::Balance), - /// Some assets were transferred. \[asset_id, from, to, amount\] - Transferred(T::AssetId, T::AccountId, T::AccountId, T::Balance), - /// Some assets were destroyed. \[asset_id, owner, balance\] - Burned(T::AssetId, T::AccountId, T::Balance), - /// The management team changed \[asset_id, issuer, admin, freezer\] - TeamChanged(T::AssetId, T::AccountId, T::AccountId, T::AccountId), - /// The owner changed \[asset_id, owner\] - OwnerChanged(T::AssetId, T::AccountId), - /// Some account `who` was frozen. \[asset_id, who\] - Frozen(T::AssetId, T::AccountId), - /// Some account `who` was thawed. \[asset_id, who\] - Thawed(T::AssetId, T::AccountId), - /// Some asset `asset_id` was frozen. \[asset_id\] - AssetFrozen(T::AssetId), - /// Some asset `asset_id` was thawed. \[asset_id\] - AssetThawed(T::AssetId), - /// An asset class was destroyed. - Destroyed(T::AssetId), - /// Some asset class was force-created. \[asset_id, owner\] - ForceCreated(T::AssetId, T::AccountId), - /// New metadata has been set for an asset. \[asset_id, name, symbol, decimals, is_frozen\] - MetadataSet(T::AssetId, Vec, Vec, u8, bool), - /// Metadata has been cleared for an asset. \[asset_id\] - MetadataCleared(T::AssetId), - /// (Additional) funds have been approved for transfer to a destination account. - /// \[asset_id, source, delegate, amount\] - ApprovedTransfer(T::AssetId, T::AccountId, T::AccountId, T::Balance), - /// An approval for account `delegate` was cancelled by `owner`. - /// \[id, owner, delegate\] - ApprovalCancelled(T::AssetId, T::AccountId, T::AccountId), - /// An `amount` was transferred in its entirety from `owner` to `destination` by - /// the approved `delegate`. - /// \[id, owner, delegate, destination\] - TransferredApproved(T::AssetId, T::AccountId, T::AccountId, T::AccountId, T::Balance), - /// An asset has had its attributes changed by the `Force` origin. - /// \[id\] - AssetStatusChanged(T::AssetId), - } - - #[pallet::error] - pub enum Error { - /// Account balance must be greater than or equal to the transfer amount. - BalanceLow, - /// Balance should be non-zero. - BalanceZero, - /// The signing account has no permission to do the operation. - NoPermission, - /// The given asset ID is unknown. - Unknown, - /// The origin account is frozen. - Frozen, - /// The asset ID is already taken. - InUse, - /// Invalid witness data given. - BadWitness, - /// Minimum balance should be non-zero. - MinBalanceZero, - /// A mint operation lead to an overflow. - Overflow, - /// No provider reference exists to allow a non-zero balance of a non-self-sufficient asset. - NoProvider, - /// Invalid metadata given. - BadMetadata, - /// No approval exists that would allow the transfer. - Unapproved, - /// The source account would not survive the transfer and it needs to stay alive. - WouldDie, - } - - #[pallet::hooks] - impl Hooks> for Pallet {} - - #[pallet::call] - impl Pallet { - /// Issue a new class of fungible assets from a public origin. - /// - /// This new asset class has no assets initially and its owner is the origin. - /// - /// The origin must be Signed and the sender must have sufficient funds free. - /// - /// Funds of sender are reserved by `AssetDeposit`. - /// - /// Parameters: - /// - `id`: The identifier of the new asset. This must not be currently in use to identify - /// an existing asset. - /// - `admin`: The admin of this class of assets. The admin is the initial address of each - /// member of the asset class's admin team. - /// - `min_balance`: The minimum balance of this new asset that any single account must - /// have. If an account's balance is reduced below this, then it collapses to zero. - /// - /// Emits `Created` event when successful. - /// - /// Weight: `O(1)` - #[pallet::weight(T::WeightInfo::create())] - pub(super) fn create( - origin: OriginFor, - #[pallet::compact] id: T::AssetId, - admin: ::Source, - min_balance: T::Balance, - ) -> DispatchResult { - let owner = ensure_signed(origin)?; - let admin = T::Lookup::lookup(admin)?; - - ensure!(!Asset::::contains_key(id), Error::::InUse); - ensure!(!min_balance.is_zero(), Error::::MinBalanceZero); - - let deposit = T::AssetDeposit::get(); - T::Currency::reserve(&owner, deposit)?; - - Asset::::insert(id, AssetDetails { - owner: owner.clone(), - issuer: admin.clone(), - admin: admin.clone(), - freezer: admin.clone(), - supply: Zero::zero(), - deposit, - min_balance, - is_sufficient: false, - accounts: 0, - sufficients: 0, - approvals: 0, - is_frozen: false, - }); - Self::deposit_event(Event::Created(id, owner, admin)); - Ok(()) - } - - /// Issue a new class of fungible assets from a privileged origin. - /// - /// This new asset class has no assets initially. - /// - /// The origin must conform to `ForceOrigin`. - /// - /// Unlike `create`, no funds are reserved. - /// - /// - `id`: The identifier of the new asset. This must not be currently in use to identify - /// an existing asset. - /// - `owner`: The owner of this class of assets. The owner has full superuser permissions - /// over this asset, but may later change and configure the permissions using `transfer_ownership` - /// and `set_team`. - /// - `max_zombies`: The total number of accounts which may hold assets in this class yet - /// have no existential deposit. - /// - `min_balance`: The minimum balance of this new asset that any single account must - /// have. If an account's balance is reduced below this, then it collapses to zero. - /// - /// Emits `ForceCreated` event when successful. - /// - /// Weight: `O(1)` - #[pallet::weight(T::WeightInfo::force_create())] - pub(super) fn force_create( - origin: OriginFor, - #[pallet::compact] id: T::AssetId, - owner: ::Source, - is_sufficient: bool, - #[pallet::compact] min_balance: T::Balance, - ) -> DispatchResult { - T::ForceOrigin::ensure_origin(origin)?; - let owner = T::Lookup::lookup(owner)?; - - ensure!(!Asset::::contains_key(id), Error::::InUse); - ensure!(!min_balance.is_zero(), Error::::MinBalanceZero); - - Asset::::insert(id, AssetDetails { - owner: owner.clone(), - issuer: owner.clone(), - admin: owner.clone(), - freezer: owner.clone(), - supply: Zero::zero(), - deposit: Zero::zero(), - min_balance, - is_sufficient, - accounts: 0, - sufficients: 0, - approvals: 0, - is_frozen: false, - }); - Self::deposit_event(Event::ForceCreated(id, owner)); - Ok(()) - } - - /// Destroy a class of fungible assets. - /// - /// The origin must conform to `ForceOrigin` or must be Signed and the sender must be the - /// owner of the asset `id`. - /// - /// - `id`: The identifier of the asset to be destroyed. This must identify an existing - /// asset. - /// - /// Emits `Destroyed` event when successful. - /// - /// Weight: `O(c + p + a)` where: - /// - `c = (witness.accounts - witness.sufficients)` - /// - `s = witness.sufficients` - /// - `a = witness.approvals` - #[pallet::weight(T::WeightInfo::destroy( + use super::*; + use frame_support::{dispatch::DispatchResult, pallet_prelude::*}; + use frame_system::pallet_prelude::*; + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(_); + + #[pallet::config] + /// The module configuration trait. + pub trait Config: frame_system::Config { + /// The overarching event type. + type Event: From> + IsType<::Event>; + + /// The units in which we record balances. + type Balance: Member + Parameter + AtLeast32BitUnsigned + Default + Copy; + + /// Identifier for the class of asset. + type AssetId: Member + Parameter + Default + Copy + HasCompact; + + /// The currency mechanism. + type Currency: ReservableCurrency; + + /// The origin which may forcibly create or destroy an asset or otherwise alter privileged + /// attributes. + type ForceOrigin: EnsureOrigin; + + /// The basic amount of funds that must be reserved for an asset. + type AssetDeposit: Get>; + + /// The basic amount of funds that must be reserved when adding metadata to your asset. + type MetadataDepositBase: Get>; + + /// The additional funds that must be reserved for the number of bytes you store in your + /// metadata. + type MetadataDepositPerByte: Get>; + + /// The amount of funds that must be reserved when creating a new approval. + type ApprovalDeposit: Get>; + + /// The maximum length of a name or symbol stored on-chain. + type StringLimit: Get; + + /// 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; + + /// 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; + } + + #[pallet::storage] + /// Details of an asset. + pub(super) type Asset, I: 'static = ()> = StorageMap< + _, + Blake2_128Concat, + T::AssetId, + AssetDetails>, + >; + + #[pallet::storage] + /// The number of units of assets held by any given account. + pub(super) type Account, I: 'static = ()> = StorageDoubleMap< + _, + Blake2_128Concat, + T::AssetId, + Blake2_128Concat, + T::AccountId, + AssetBalance, + ValueQuery, + >; + + #[pallet::storage] + /// Approved balance transfers. First balance is the amount approved for transfer. Second + /// is the amount of `T::Currency` reserved for storing this. + pub(super) type Approvals, I: 'static = ()> = StorageDoubleMap< + _, + Blake2_128Concat, + T::AssetId, + Blake2_128Concat, + ApprovalKey, + Approval>, + OptionQuery, + >; + + #[pallet::storage] + /// Metadata of an asset. + pub(super) type Metadata, I: 'static = ()> = StorageMap< + _, + Blake2_128Concat, + T::AssetId, + AssetMetadata>, + ValueQuery, + >; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + #[pallet::metadata( + T::AccountId = "AccountId", + T::Balance = "Balance", + T::AssetId = "AssetId" + )] + pub enum Event, I: 'static = ()> { + /// Some asset class was created. \[asset_id, creator, owner\] + Created(T::AssetId, T::AccountId, T::AccountId), + /// Some assets were issued. \[asset_id, owner, total_supply\] + Issued(T::AssetId, T::AccountId, T::Balance), + /// Some assets were transferred. \[asset_id, from, to, amount\] + Transferred(T::AssetId, T::AccountId, T::AccountId, T::Balance), + /// Some assets were destroyed. \[asset_id, owner, balance\] + Burned(T::AssetId, T::AccountId, T::Balance), + /// The management team changed \[asset_id, issuer, admin, freezer\] + TeamChanged(T::AssetId, T::AccountId, T::AccountId, T::AccountId), + /// The owner changed \[asset_id, owner\] + OwnerChanged(T::AssetId, T::AccountId), + /// Some account `who` was frozen. \[asset_id, who\] + Frozen(T::AssetId, T::AccountId), + /// Some account `who` was thawed. \[asset_id, who\] + Thawed(T::AssetId, T::AccountId), + /// Some asset `asset_id` was frozen. \[asset_id\] + AssetFrozen(T::AssetId), + /// Some asset `asset_id` was thawed. \[asset_id\] + AssetThawed(T::AssetId), + /// An asset class was destroyed. + Destroyed(T::AssetId), + /// Some asset class was force-created. \[asset_id, owner\] + ForceCreated(T::AssetId, T::AccountId), + /// New metadata has been set for an asset. \[asset_id, name, symbol, decimals, is_frozen\] + MetadataSet(T::AssetId, Vec, Vec, u8, bool), + /// Metadata has been cleared for an asset. \[asset_id\] + MetadataCleared(T::AssetId), + /// (Additional) funds have been approved for transfer to a destination account. + /// \[asset_id, source, delegate, amount\] + ApprovedTransfer(T::AssetId, T::AccountId, T::AccountId, T::Balance), + /// An approval for account `delegate` was cancelled by `owner`. + /// \[id, owner, delegate\] + ApprovalCancelled(T::AssetId, T::AccountId, T::AccountId), + /// An `amount` was transferred in its entirety from `owner` to `destination` by + /// the approved `delegate`. + /// \[id, owner, delegate, destination\] + TransferredApproved( + T::AssetId, + T::AccountId, + T::AccountId, + T::AccountId, + T::Balance, + ), + /// An asset has had its attributes changed by the `Force` origin. + /// \[id\] + AssetStatusChanged(T::AssetId), + } + + #[pallet::error] + pub enum Error { + /// Account balance must be greater than or equal to the transfer amount. + BalanceLow, + /// Balance should be non-zero. + BalanceZero, + /// The signing account has no permission to do the operation. + NoPermission, + /// The given asset ID is unknown. + Unknown, + /// The origin account is frozen. + Frozen, + /// The asset ID is already taken. + InUse, + /// Invalid witness data given. + BadWitness, + /// Minimum balance should be non-zero. + MinBalanceZero, + /// A mint operation lead to an overflow. + Overflow, + /// No provider reference exists to allow a non-zero balance of a non-self-sufficient asset. + NoProvider, + /// Invalid metadata given. + BadMetadata, + /// No approval exists that would allow the transfer. + Unapproved, + /// The source account would not survive the transfer and it needs to stay alive. + WouldDie, + } + + #[pallet::hooks] + impl, I: 'static> Hooks> for Pallet {} + + #[pallet::call] + impl, I: 'static> Pallet { + /// Issue a new class of fungible assets from a public origin. + /// + /// This new asset class has no assets initially and its owner is the origin. + /// + /// The origin must be Signed and the sender must have sufficient funds free. + /// + /// Funds of sender are reserved by `AssetDeposit`. + /// + /// Parameters: + /// - `id`: The identifier of the new asset. This must not be currently in use to identify + /// an existing asset. + /// - `admin`: The admin of this class of assets. The admin is the initial address of each + /// member of the asset class's admin team. + /// - `min_balance`: The minimum balance of this new asset that any single account must + /// have. If an account's balance is reduced below this, then it collapses to zero. + /// + /// Emits `Created` event when successful. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::create())] + pub(super) fn create( + origin: OriginFor, + #[pallet::compact] id: T::AssetId, + admin: ::Source, + min_balance: T::Balance, + ) -> DispatchResult { + let owner = ensure_signed(origin)?; + let admin = T::Lookup::lookup(admin)?; + + ensure!(!Asset::::contains_key(id), Error::::InUse); + ensure!(!min_balance.is_zero(), Error::::MinBalanceZero); + + let deposit = T::AssetDeposit::get(); + T::Currency::reserve(&owner, deposit)?; + + Asset::::insert( + id, + AssetDetails { + owner: owner.clone(), + issuer: admin.clone(), + admin: admin.clone(), + freezer: admin.clone(), + supply: Zero::zero(), + deposit, + min_balance, + is_sufficient: false, + accounts: 0, + sufficients: 0, + approvals: 0, + is_frozen: false, + }, + ); + Self::deposit_event(Event::Created(id, owner, admin)); + Ok(()) + } + + /// Issue a new class of fungible assets from a privileged origin. + /// + /// This new asset class has no assets initially. + /// + /// The origin must conform to `ForceOrigin`. + /// + /// Unlike `create`, no funds are reserved. + /// + /// - `id`: The identifier of the new asset. This must not be currently in use to identify + /// an existing asset. + /// - `owner`: The owner of this class of assets. The owner has full superuser permissions + /// over this asset, but may later change and configure the permissions using `transfer_ownership` + /// and `set_team`. + /// - `max_zombies`: The total number of accounts which may hold assets in this class yet + /// have no existential deposit. + /// - `min_balance`: The minimum balance of this new asset that any single account must + /// have. If an account's balance is reduced below this, then it collapses to zero. + /// + /// Emits `ForceCreated` event when successful. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::force_create())] + pub(super) fn force_create( + origin: OriginFor, + #[pallet::compact] id: T::AssetId, + owner: ::Source, + is_sufficient: bool, + #[pallet::compact] min_balance: T::Balance, + ) -> DispatchResult { + T::ForceOrigin::ensure_origin(origin)?; + let owner = T::Lookup::lookup(owner)?; + + ensure!(!Asset::::contains_key(id), Error::::InUse); + ensure!(!min_balance.is_zero(), Error::::MinBalanceZero); + + Asset::::insert( + id, + AssetDetails { + owner: owner.clone(), + issuer: owner.clone(), + admin: owner.clone(), + freezer: owner.clone(), + supply: Zero::zero(), + deposit: Zero::zero(), + min_balance, + is_sufficient, + accounts: 0, + sufficients: 0, + approvals: 0, + is_frozen: false, + }, + ); + Self::deposit_event(Event::ForceCreated(id, owner)); + Ok(()) + } + + /// Destroy a class of fungible assets. + /// + /// The origin must conform to `ForceOrigin` or must be Signed and the sender must be the + /// owner of the asset `id`. + /// + /// - `id`: The identifier of the asset to be destroyed. This must identify an existing + /// asset. + /// + /// Emits `Destroyed` event when successful. + /// + /// Weight: `O(c + p + a)` where: + /// - `c = (witness.accounts - witness.sufficients)` + /// - `s = witness.sufficients` + /// - `a = witness.approvals` + #[pallet::weight(T::WeightInfo::destroy( witness.accounts.saturating_sub(witness.sufficients), witness.sufficients, witness.approvals, ))] - pub(super) fn destroy( - origin: OriginFor, - #[pallet::compact] id: T::AssetId, - witness: DestroyWitness, - ) -> DispatchResult { - let maybe_check_owner = match T::ForceOrigin::try_origin(origin) { - Ok(_) => None, - Err(origin) => Some(ensure_signed(origin)?), - }; - Asset::::try_mutate_exists(id, |maybe_details| { - let mut details = maybe_details.take().ok_or(Error::::Unknown)?; - if let Some(check_owner) = maybe_check_owner { - ensure!(details.owner == check_owner, Error::::NoPermission); - } - ensure!(details.accounts == witness.accounts, Error::::BadWitness); - ensure!(details.sufficients == witness.sufficients, Error::::BadWitness); - ensure!(details.approvals == witness.approvals, Error::::BadWitness); - - for (who, v) in Account::::drain_prefix(id) { - Self::dead_account(id, &who, &mut details, v.sufficient); - } - debug_assert_eq!(details.accounts, 0); - debug_assert_eq!(details.sufficients, 0); - - let metadata = Metadata::::take(&id); - T::Currency::unreserve(&details.owner, details.deposit.saturating_add(metadata.deposit)); - - Approvals::::remove_prefix(&id); - Self::deposit_event(Event::Destroyed(id)); - - // NOTE: could use postinfo to reflect the actual number of accounts/sufficient/approvals - Ok(()) - }) - } - - /// Mint assets of a particular class. - /// - /// The origin must be Signed and the sender must be the Issuer of the asset `id`. - /// - /// - `id`: The identifier of the asset to have some amount minted. - /// - `beneficiary`: The account to be credited with the minted assets. - /// - `amount`: The amount of the asset to be minted. - /// - /// Emits `Destroyed` event when successful. - /// - /// Weight: `O(1)` - /// Modes: Pre-existing balance of `beneficiary`; Account pre-existence of `beneficiary`. - #[pallet::weight(T::WeightInfo::mint())] - pub(super) fn mint( - origin: OriginFor, - #[pallet::compact] id: T::AssetId, - beneficiary: ::Source, - #[pallet::compact] amount: T::Balance - ) -> DispatchResult { - let origin = ensure_signed(origin)?; - let beneficiary = T::Lookup::lookup(beneficiary)?; - 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`. - /// - /// Origin must be Signed and the sender should be the Manager of the asset `id`. - /// - /// Bails with `BalanceZero` if the `who` is already dead. - /// - /// - `id`: The identifier of the asset to have some amount burned. - /// - `who`: The account to be debited from. - /// - `amount`: The maximum amount by which `who`'s balance should be reduced. - /// - /// Emits `Burned` with the actual amount burned. If this takes the balance to below the - /// minimum for the asset, then the amount burned is increased to take it to zero. - /// - /// Weight: `O(1)` - /// Modes: Post-existence of `who`; Pre & post Zombie-status of `who`. - #[pallet::weight(T::WeightInfo::burn())] - pub(super) fn burn( - origin: OriginFor, - #[pallet::compact] id: T::AssetId, - who: ::Source, - #[pallet::compact] amount: T::Balance - ) -> DispatchResult { - let origin = ensure_signed(origin)?; - let who = T::Lookup::lookup(who)?; - - 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. - /// - /// Origin must be Signed. - /// - /// - `id`: The identifier of the asset to have some amount transferred. - /// - `target`: The account to be credited. - /// - `amount`: The amount by which the sender's balance of assets should be reduced and - /// `target`'s balance increased. The amount actually transferred may be slightly greater in - /// the case that the transfer would otherwise take the sender balance above zero but below - /// the minimum balance. Must be greater than zero. - /// - /// Emits `Transferred` with the actual amount transferred. If this takes the source balance - /// to below the minimum for the asset, then the amount transferred is increased to take it - /// to zero. - /// - /// Weight: `O(1)` - /// Modes: Pre-existence of `target`; Post-existence of sender; Prior & post zombie-status - /// of sender; Account pre-existence of `target`. - #[pallet::weight(T::WeightInfo::transfer())] - pub(super) fn transfer( - origin: OriginFor, - #[pallet::compact] id: T::AssetId, - target: ::Source, - #[pallet::compact] amount: T::Balance - ) -> DispatchResult { - let origin = ensure_signed(origin)?; - let dest = T::Lookup::lookup(target)?; - - 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. - /// - /// Origin must be Signed. - /// - /// - `id`: The identifier of the asset to have some amount transferred. - /// - `target`: The account to be credited. - /// - `amount`: The amount by which the sender's balance of assets should be reduced and - /// `target`'s balance increased. The amount actually transferred may be slightly greater in - /// the case that the transfer would otherwise take the sender balance above zero but below - /// the minimum balance. Must be greater than zero. - /// - /// Emits `Transferred` with the actual amount transferred. If this takes the source balance - /// to below the minimum for the asset, then the amount transferred is increased to take it - /// to zero. - /// - /// Weight: `O(1)` - /// Modes: Pre-existence of `target`; Post-existence of sender; Prior & post zombie-status - /// of sender; Account pre-existence of `target`. - #[pallet::weight(T::WeightInfo::transfer_keep_alive())] - pub(super) fn transfer_keep_alive( - origin: OriginFor, - #[pallet::compact] id: T::AssetId, - target: ::Source, - #[pallet::compact] amount: T::Balance - ) -> DispatchResult { - let source = ensure_signed(origin)?; - let dest = T::Lookup::lookup(target)?; - - 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. - /// - /// Origin must be Signed and the sender should be the Admin of the asset `id`. - /// - /// - `id`: The identifier of the asset to have some amount transferred. - /// - `source`: The account to be debited. - /// - `dest`: The account to be credited. - /// - `amount`: The amount by which the `source`'s balance of assets should be reduced and - /// `dest`'s balance increased. The amount actually transferred may be slightly greater in - /// the case that the transfer would otherwise take the `source` balance above zero but - /// below the minimum balance. Must be greater than zero. - /// - /// Emits `Transferred` with the actual amount transferred. If this takes the source balance - /// to below the minimum for the asset, then the amount transferred is increased to take it - /// to zero. - /// - /// Weight: `O(1)` - /// Modes: Pre-existence of `dest`; Post-existence of `source`; Prior & post zombie-status - /// of `source`; Account pre-existence of `dest`. - #[pallet::weight(T::WeightInfo::force_transfer())] - pub(super) fn force_transfer( - origin: OriginFor, - #[pallet::compact] id: T::AssetId, - source: ::Source, - dest: ::Source, - #[pallet::compact] amount: T::Balance, - ) -> DispatchResult { - let origin = ensure_signed(origin)?; - let source = T::Lookup::lookup(source)?; - let dest = T::Lookup::lookup(dest)?; - - 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. - /// - /// Origin must be Signed and the sender should be the Freezer of the asset `id`. - /// - /// - `id`: The identifier of the asset to be frozen. - /// - `who`: The account to be frozen. - /// - /// Emits `Frozen`. - /// - /// Weight: `O(1)` - #[pallet::weight(T::WeightInfo::freeze())] - pub(super) fn freeze( - origin: OriginFor, - #[pallet::compact] id: T::AssetId, - who: ::Source - ) -> DispatchResult { - let origin = ensure_signed(origin)?; - - let d = Asset::::get(id).ok_or(Error::::Unknown)?; - ensure!(&origin == &d.freezer, Error::::NoPermission); - let who = T::Lookup::lookup(who)?; - ensure!(Account::::contains_key(id, &who), Error::::BalanceZero); - - Account::::mutate(id, &who, |a| a.is_frozen = true); - - Self::deposit_event(Event::::Frozen(id, who)); - Ok(()) - } - - /// Allow unprivileged transfers from an account again. - /// - /// Origin must be Signed and the sender should be the Admin of the asset `id`. - /// - /// - `id`: The identifier of the asset to be frozen. - /// - `who`: The account to be unfrozen. - /// - /// Emits `Thawed`. - /// - /// Weight: `O(1)` - #[pallet::weight(T::WeightInfo::thaw())] - pub(super) fn thaw( - origin: OriginFor, - #[pallet::compact] - id: T::AssetId, - who: ::Source - ) -> DispatchResult { - let origin = ensure_signed(origin)?; - - let details = Asset::::get(id).ok_or(Error::::Unknown)?; - ensure!(&origin == &details.admin, Error::::NoPermission); - let who = T::Lookup::lookup(who)?; - ensure!(Account::::contains_key(id, &who), Error::::BalanceZero); - - Account::::mutate(id, &who, |a| a.is_frozen = false); - - Self::deposit_event(Event::::Thawed(id, who)); - Ok(()) - } - - /// Disallow further unprivileged transfers for the asset class. - /// - /// Origin must be Signed and the sender should be the Freezer of the asset `id`. - /// - /// - `id`: The identifier of the asset to be frozen. - /// - /// Emits `Frozen`. - /// - /// Weight: `O(1)` - #[pallet::weight(T::WeightInfo::freeze_asset())] - pub(super) fn freeze_asset( - origin: OriginFor, - #[pallet::compact] id: T::AssetId - ) -> DispatchResult { - let origin = ensure_signed(origin)?; - - Asset::::try_mutate(id, |maybe_details| { - let d = maybe_details.as_mut().ok_or(Error::::Unknown)?; - ensure!(&origin == &d.freezer, Error::::NoPermission); - - d.is_frozen = true; - - Self::deposit_event(Event::::AssetFrozen(id)); - Ok(()) - }) - } - - /// Allow unprivileged transfers for the asset again. - /// - /// Origin must be Signed and the sender should be the Admin of the asset `id`. - /// - /// - `id`: The identifier of the asset to be frozen. - /// - /// Emits `Thawed`. - /// - /// Weight: `O(1)` - #[pallet::weight(T::WeightInfo::thaw_asset())] - pub(super) fn thaw_asset( - origin: OriginFor, - #[pallet::compact] id: T::AssetId - ) -> DispatchResult { - let origin = ensure_signed(origin)?; - - Asset::::try_mutate(id, |maybe_details| { - let d = maybe_details.as_mut().ok_or(Error::::Unknown)?; - ensure!(&origin == &d.admin, Error::::NoPermission); - - d.is_frozen = false; - - Self::deposit_event(Event::::AssetThawed(id)); - Ok(()) - }) - } - - /// Change the Owner of an asset. - /// - /// Origin must be Signed and the sender should be the Owner of the asset `id`. - /// - /// - `id`: The identifier of the asset. - /// - `owner`: The new Owner of this asset. - /// - /// Emits `OwnerChanged`. - /// - /// Weight: `O(1)` - #[pallet::weight(T::WeightInfo::transfer_ownership())] - pub(super) fn transfer_ownership( - origin: OriginFor, - #[pallet::compact] id: T::AssetId, - owner: ::Source, - ) -> DispatchResult { - let origin = ensure_signed(origin)?; - let owner = T::Lookup::lookup(owner)?; - - Asset::::try_mutate(id, |maybe_details| { - let details = maybe_details.as_mut().ok_or(Error::::Unknown)?; - ensure!(&origin == &details.owner, Error::::NoPermission); - if details.owner == owner { return Ok(()) } - - let metadata_deposit = Metadata::::get(id).deposit; - let deposit = details.deposit + metadata_deposit; - - // Move the deposit to the new owner. - T::Currency::repatriate_reserved(&details.owner, &owner, deposit, Reserved)?; - - details.owner = owner.clone(); - - Self::deposit_event(Event::OwnerChanged(id, owner)); - Ok(()) - }) - } - - /// Change the Issuer, Admin and Freezer of an asset. - /// - /// Origin must be Signed and the sender should be the Owner of the asset `id`. - /// - /// - `id`: The identifier of the asset to be frozen. - /// - `issuer`: The new Issuer of this asset. - /// - `admin`: The new Admin of this asset. - /// - `freezer`: The new Freezer of this asset. - /// - /// Emits `TeamChanged`. - /// - /// Weight: `O(1)` - #[pallet::weight(T::WeightInfo::set_team())] - pub(super) fn set_team( - origin: OriginFor, - #[pallet::compact] id: T::AssetId, - issuer: ::Source, - admin: ::Source, - freezer: ::Source, - ) -> DispatchResult { - let origin = ensure_signed(origin)?; - let issuer = T::Lookup::lookup(issuer)?; - let admin = T::Lookup::lookup(admin)?; - let freezer = T::Lookup::lookup(freezer)?; - - Asset::::try_mutate(id, |maybe_details| { - let details = maybe_details.as_mut().ok_or(Error::::Unknown)?; - ensure!(&origin == &details.owner, Error::::NoPermission); - - details.issuer = issuer.clone(); - details.admin = admin.clone(); - details.freezer = freezer.clone(); - - Self::deposit_event(Event::TeamChanged(id, issuer, admin, freezer)); - Ok(()) - }) - } - - /// Set the metadata for an asset. - /// - /// Origin must be Signed and the sender should be the Owner of the asset `id`. - /// - /// Funds of sender are reserved according to the formula: - /// `MetadataDepositBase + MetadataDepositPerByte * (name.len + symbol.len)` taking into - /// account any already reserved funds. - /// - /// - `id`: The identifier of the asset to update. - /// - `name`: The user friendly name of this asset. Limited in length by `StringLimit`. - /// - `symbol`: The exchange symbol for this asset. Limited in length by `StringLimit`. - /// - `decimals`: The number of decimals this asset uses to represent one unit. - /// - /// Emits `MetadataSet`. - /// - /// Weight: `O(1)` - #[pallet::weight(T::WeightInfo::set_metadata(name.len() as u32, symbol.len() as u32))] - pub(super) fn set_metadata( - origin: OriginFor, - #[pallet::compact] id: T::AssetId, - name: Vec, - symbol: Vec, - decimals: u8, - ) -> DispatchResult { - let origin = ensure_signed(origin)?; - - ensure!(name.len() <= T::StringLimit::get() as usize, Error::::BadMetadata); - ensure!(symbol.len() <= T::StringLimit::get() as usize, Error::::BadMetadata); - - let d = Asset::::get(id).ok_or(Error::::Unknown)?; - ensure!(&origin == &d.owner, Error::::NoPermission); - - Metadata::::try_mutate_exists(id, |metadata| { - ensure!(metadata.as_ref().map_or(true, |m| !m.is_frozen), Error::::NoPermission); - - let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit); - let new_deposit = T::MetadataDepositPerByte::get() - .saturating_mul(((name.len() + symbol.len()) as u32).into()) - .saturating_add(T::MetadataDepositBase::get()); - - if new_deposit > old_deposit { - T::Currency::reserve(&origin, new_deposit - old_deposit)?; - } else { - T::Currency::unreserve(&origin, old_deposit - new_deposit); - } - - *metadata = Some(AssetMetadata { - deposit: new_deposit, - name: name.clone(), - symbol: symbol.clone(), - decimals, - is_frozen: false, - }); - - Self::deposit_event(Event::MetadataSet(id, name, symbol, decimals, false)); - Ok(()) - }) - } - - /// Clear the metadata for an asset. - /// - /// Origin must be Signed and the sender should be the Owner of the asset `id`. - /// - /// Any deposit is freed for the asset owner. - /// - /// - `id`: The identifier of the asset to clear. - /// - /// Emits `MetadataCleared`. - /// - /// Weight: `O(1)` - #[pallet::weight(T::WeightInfo::clear_metadata())] - pub(super) fn clear_metadata( - origin: OriginFor, - #[pallet::compact] id: T::AssetId, - ) -> DispatchResult { - let origin = ensure_signed(origin)?; - - let d = Asset::::get(id).ok_or(Error::::Unknown)?; - ensure!(&origin == &d.owner, Error::::NoPermission); - - Metadata::::try_mutate_exists(id, |metadata| { - let deposit = metadata.take().ok_or(Error::::Unknown)?.deposit; - T::Currency::unreserve(&d.owner, deposit); - Self::deposit_event(Event::MetadataCleared(id)); - Ok(()) - }) - } - - /// Force the metadata for an asset to some value. - /// - /// Origin must be ForceOrigin. - /// - /// Any deposit is left alone. - /// - /// - `id`: The identifier of the asset to update. - /// - `name`: The user friendly name of this asset. Limited in length by `StringLimit`. - /// - `symbol`: The exchange symbol for this asset. Limited in length by `StringLimit`. - /// - `decimals`: The number of decimals this asset uses to represent one unit. - /// - /// Emits `MetadataSet`. - /// - /// Weight: `O(N + S)` where N and S are the length of the name and symbol respectively. - #[pallet::weight(T::WeightInfo::force_set_metadata(name.len() as u32, symbol.len() as u32))] - pub(super) fn force_set_metadata( - origin: OriginFor, - #[pallet::compact] id: T::AssetId, - name: Vec, - symbol: Vec, - decimals: u8, - is_frozen: bool, - ) -> DispatchResult { - T::ForceOrigin::ensure_origin(origin)?; - - ensure!(name.len() <= T::StringLimit::get() as usize, Error::::BadMetadata); - ensure!(symbol.len() <= T::StringLimit::get() as usize, Error::::BadMetadata); - - ensure!(Asset::::contains_key(id), Error::::Unknown); - Metadata::::try_mutate_exists(id, |metadata| { - let deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit); - *metadata = Some(AssetMetadata { - deposit, - name: name.clone(), - symbol: symbol.clone(), - decimals, - is_frozen, - }); - - Self::deposit_event(Event::MetadataSet(id, name, symbol, decimals, is_frozen)); - Ok(()) - }) - } - - /// Clear the metadata for an asset. - /// - /// Origin must be ForceOrigin. - /// - /// Any deposit is returned. - /// - /// - `id`: The identifier of the asset to clear. - /// - /// Emits `MetadataCleared`. - /// - /// Weight: `O(1)` - #[pallet::weight(T::WeightInfo::force_clear_metadata())] - pub(super) fn force_clear_metadata( - origin: OriginFor, - #[pallet::compact] id: T::AssetId, - ) -> DispatchResult { - T::ForceOrigin::ensure_origin(origin)?; - - let d = Asset::::get(id).ok_or(Error::::Unknown)?; - Metadata::::try_mutate_exists(id, |metadata| { - let deposit = metadata.take().ok_or(Error::::Unknown)?.deposit; - T::Currency::unreserve(&d.owner, deposit); - Self::deposit_event(Event::MetadataCleared(id)); - Ok(()) - }) - } - - /// Alter the attributes of a given asset. - /// - /// Origin must be `ForceOrigin`. - /// - /// - `id`: The identifier of the asset. - /// - `owner`: The new Owner of this asset. - /// - `issuer`: The new Issuer of this asset. - /// - `admin`: The new Admin of this asset. - /// - `freezer`: The new Freezer of this asset. - /// - `min_balance`: The minimum balance of this new asset that any single account must - /// have. If an account's balance is reduced below this, then it collapses to zero. - /// - `is_sufficient`: Whether a non-zero balance of this asset is deposit of sufficient - /// value to account for the state bloat associated with its balance storage. If set to - /// `true`, then non-zero balances may be stored without a `consumer` reference (and thus - /// an ED in the Balances pallet or whatever else is used to control user-account state - /// growth). - /// - `is_frozen`: Whether this asset class is frozen except for permissioned/admin - /// instructions. - /// - /// Emits `AssetStatusChanged` with the identity of the asset. - /// - /// Weight: `O(1)` - #[pallet::weight(T::WeightInfo::force_asset_status())] - pub(super) fn force_asset_status( - origin: OriginFor, - #[pallet::compact] id: T::AssetId, - owner: ::Source, - issuer: ::Source, - admin: ::Source, - freezer: ::Source, - #[pallet::compact] min_balance: T::Balance, - is_sufficient: bool, - is_frozen: bool, - ) -> DispatchResult { - T::ForceOrigin::ensure_origin(origin)?; - - Asset::::try_mutate(id, |maybe_asset| { - let mut asset = maybe_asset.take().ok_or(Error::::Unknown)?; - asset.owner = T::Lookup::lookup(owner)?; - asset.issuer = T::Lookup::lookup(issuer)?; - asset.admin = T::Lookup::lookup(admin)?; - asset.freezer = T::Lookup::lookup(freezer)?; - asset.min_balance = min_balance; - asset.is_sufficient = is_sufficient; - asset.is_frozen = is_frozen; - *maybe_asset = Some(asset); - - Self::deposit_event(Event::AssetStatusChanged(id)); - Ok(()) - }) - } - - /// Approve an amount of asset for transfer by a delegated third-party account. - /// - /// Origin must be Signed. - /// - /// Ensures that `ApprovalDeposit` worth of `Currency` is reserved from signing account - /// for the purpose of holding the approval. If some non-zero amount of assets is already - /// approved from signing account to `delegate`, then it is topped up or unreserved to - /// meet the right value. - /// - /// NOTE: The signing account does not need to own `amount` of assets at the point of - /// making this call. - /// - /// - `id`: The identifier of the asset. - /// - `delegate`: The account to delegate permission to transfer asset. - /// - `amount`: The amount of asset that may be transferred by `delegate`. If there is - /// already an approval in place, then this acts additively. - /// - /// Emits `ApprovedTransfer` on success. - /// - /// Weight: `O(1)` - #[pallet::weight(T::WeightInfo::approve_transfer())] - pub(super) fn approve_transfer( - origin: OriginFor, - #[pallet::compact] id: T::AssetId, - delegate: ::Source, - #[pallet::compact] amount: T::Balance, - ) -> DispatchResult { - let owner = ensure_signed(origin)?; - let delegate = T::Lookup::lookup(delegate)?; - - let key = ApprovalKey { owner, delegate }; - Approvals::::try_mutate(id, &key, |maybe_approved| -> DispatchResult { - let mut approved = maybe_approved.take().unwrap_or_default(); - let deposit_required = T::ApprovalDeposit::get(); - if approved.deposit < deposit_required { - T::Currency::reserve(&key.owner, deposit_required - approved.deposit)?; - approved.deposit = deposit_required; - } - approved.amount = approved.amount.saturating_add(amount); - *maybe_approved = Some(approved); - Ok(()) - })?; - Self::deposit_event(Event::ApprovedTransfer(id, key.owner, key.delegate, amount)); - - Ok(()) - } - - /// Cancel all of some asset approved for delegated transfer by a third-party account. - /// - /// Origin must be Signed and there must be an approval in place between signer and - /// `delegate`. - /// - /// Unreserves any deposit previously reserved by `approve_transfer` for the approval. - /// - /// - `id`: The identifier of the asset. - /// - `delegate`: The account delegated permission to transfer asset. - /// - /// Emits `ApprovalCancelled` on success. - /// - /// Weight: `O(1)` - #[pallet::weight(T::WeightInfo::cancel_approval())] - pub(super) fn cancel_approval( - origin: OriginFor, - #[pallet::compact] id: T::AssetId, - delegate: ::Source, - ) -> DispatchResult { - let owner = ensure_signed(origin)?; - let delegate = T::Lookup::lookup(delegate)?; - let key = ApprovalKey { owner, delegate }; - let approval = Approvals::::take(id, &key).ok_or(Error::::Unknown)?; - T::Currency::unreserve(&key.owner, approval.deposit); - - Self::deposit_event(Event::ApprovalCancelled(id, key.owner, key.delegate)); - Ok(()) - } - - /// Cancel all of some asset approved for delegated transfer by a third-party account. - /// - /// Origin must be either ForceOrigin or Signed origin with the signer being the Admin - /// account of the asset `id`. - /// - /// Unreserves any deposit previously reserved by `approve_transfer` for the approval. - /// - /// - `id`: The identifier of the asset. - /// - `delegate`: The account delegated permission to transfer asset. - /// - /// Emits `ApprovalCancelled` on success. - /// - /// Weight: `O(1)` - #[pallet::weight(T::WeightInfo::force_cancel_approval())] - pub(super) fn force_cancel_approval( - origin: OriginFor, - #[pallet::compact] id: T::AssetId, - owner: ::Source, - delegate: ::Source, - ) -> DispatchResult { - T::ForceOrigin::try_origin(origin) - .map(|_| ()) - .or_else(|origin| -> DispatchResult { - let origin = ensure_signed(origin)?; - let d = Asset::::get(id).ok_or(Error::::Unknown)?; - ensure!(&origin == &d.admin, Error::::NoPermission); - Ok(()) - })?; - - let owner = T::Lookup::lookup(owner)?; - let delegate = T::Lookup::lookup(delegate)?; - - let key = ApprovalKey { owner, delegate }; - let approval = Approvals::::take(id, &key).ok_or(Error::::Unknown)?; - T::Currency::unreserve(&key.owner, approval.deposit); - - Self::deposit_event(Event::ApprovalCancelled(id, key.owner, key.delegate)); - Ok(()) - } - - /// Transfer some asset balance from a previously delegated account to some third-party - /// account. - /// - /// Origin must be Signed and there must be an approval in place by the `owner` to the - /// signer. - /// - /// If the entire amount approved for transfer is transferred, then any deposit previously - /// reserved by `approve_transfer` is unreserved. - /// - /// - `id`: The identifier of the asset. - /// - `owner`: The account which previously approved for a transfer of at least `amount` and - /// from which the asset balance will be withdrawn. - /// - `destination`: The account to which the asset balance of `amount` will be transferred. - /// - `amount`: The amount of assets to transfer. - /// - /// Emits `TransferredApproved` on success. - /// - /// Weight: `O(1)` - #[pallet::weight(T::WeightInfo::transfer_approved())] - pub(super) fn transfer_approved( - origin: OriginFor, - #[pallet::compact] id: T::AssetId, - owner: ::Source, - destination: ::Source, - #[pallet::compact] amount: T::Balance, - ) -> DispatchResult { - let delegate = ensure_signed(origin)?; - let owner = T::Lookup::lookup(owner)?; - let destination = T::Lookup::lookup(destination)?; - - let key = ApprovalKey { owner, delegate }; - Approvals::::try_mutate_exists(id, &key, |maybe_approved| -> DispatchResult { - let mut approved = maybe_approved.take().ok_or(Error::::Unapproved)?; - let remaining = approved.amount.checked_sub(&amount).ok_or(Error::::Unapproved)?; - - 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); - } else { - approved.amount = remaining; - *maybe_approved = Some(approved); - } - Ok(()) - })?; - Ok(()) - } - } + pub(super) fn destroy( + origin: OriginFor, + #[pallet::compact] id: T::AssetId, + witness: DestroyWitness, + ) -> DispatchResult { + let maybe_check_owner = match T::ForceOrigin::try_origin(origin) { + Ok(_) => None, + Err(origin) => Some(ensure_signed(origin)?), + }; + Asset::::try_mutate_exists(id, |maybe_details| { + let mut details = maybe_details.take().ok_or(Error::::Unknown)?; + if let Some(check_owner) = maybe_check_owner { + ensure!(details.owner == check_owner, Error::::NoPermission); + } + ensure!( + details.accounts == witness.accounts, + Error::::BadWitness + ); + ensure!( + details.sufficients == witness.sufficients, + Error::::BadWitness + ); + ensure!( + details.approvals == witness.approvals, + Error::::BadWitness + ); + + for (who, v) in Account::::drain_prefix(id) { + Self::dead_account(id, &who, &mut details, v.sufficient); + } + debug_assert_eq!(details.accounts, 0); + debug_assert_eq!(details.sufficients, 0); + + let metadata = Metadata::::take(&id); + T::Currency::unreserve( + &details.owner, + details.deposit.saturating_add(metadata.deposit), + ); + + Approvals::::remove_prefix(&id); + Self::deposit_event(Event::Destroyed(id)); + + // NOTE: could use postinfo to reflect the actual number of accounts/sufficient/approvals + Ok(()) + }) + } + + /// Mint assets of a particular class. + /// + /// The origin must be Signed and the sender must be the Issuer of the asset `id`. + /// + /// - `id`: The identifier of the asset to have some amount minted. + /// - `beneficiary`: The account to be credited with the minted assets. + /// - `amount`: The amount of the asset to be minted. + /// + /// Emits `Destroyed` event when successful. + /// + /// Weight: `O(1)` + /// Modes: Pre-existing balance of `beneficiary`; Account pre-existence of `beneficiary`. + #[pallet::weight(T::WeightInfo::mint())] + pub(super) fn mint( + origin: OriginFor, + #[pallet::compact] id: T::AssetId, + beneficiary: ::Source, + #[pallet::compact] amount: T::Balance, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + let beneficiary = T::Lookup::lookup(beneficiary)?; + 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`. + /// + /// Origin must be Signed and the sender should be the Manager of the asset `id`. + /// + /// Bails with `BalanceZero` if the `who` is already dead. + /// + /// - `id`: The identifier of the asset to have some amount burned. + /// - `who`: The account to be debited from. + /// - `amount`: The maximum amount by which `who`'s balance should be reduced. + /// + /// Emits `Burned` with the actual amount burned. If this takes the balance to below the + /// minimum for the asset, then the amount burned is increased to take it to zero. + /// + /// Weight: `O(1)` + /// Modes: Post-existence of `who`; Pre & post Zombie-status of `who`. + #[pallet::weight(T::WeightInfo::burn())] + pub(super) fn burn( + origin: OriginFor, + #[pallet::compact] id: T::AssetId, + who: ::Source, + #[pallet::compact] amount: T::Balance, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + let who = T::Lookup::lookup(who)?; + + 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. + /// + /// Origin must be Signed. + /// + /// - `id`: The identifier of the asset to have some amount transferred. + /// - `target`: The account to be credited. + /// - `amount`: The amount by which the sender's balance of assets should be reduced and + /// `target`'s balance increased. The amount actually transferred may be slightly greater in + /// the case that the transfer would otherwise take the sender balance above zero but below + /// the minimum balance. Must be greater than zero. + /// + /// Emits `Transferred` with the actual amount transferred. If this takes the source balance + /// to below the minimum for the asset, then the amount transferred is increased to take it + /// to zero. + /// + /// Weight: `O(1)` + /// Modes: Pre-existence of `target`; Post-existence of sender; Prior & post zombie-status + /// of sender; Account pre-existence of `target`. + #[pallet::weight(T::WeightInfo::transfer())] + pub(super) fn transfer( + origin: OriginFor, + #[pallet::compact] id: T::AssetId, + target: ::Source, + #[pallet::compact] amount: T::Balance, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + let dest = T::Lookup::lookup(target)?; + + 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. + /// + /// Origin must be Signed. + /// + /// - `id`: The identifier of the asset to have some amount transferred. + /// - `target`: The account to be credited. + /// - `amount`: The amount by which the sender's balance of assets should be reduced and + /// `target`'s balance increased. The amount actually transferred may be slightly greater in + /// the case that the transfer would otherwise take the sender balance above zero but below + /// the minimum balance. Must be greater than zero. + /// + /// Emits `Transferred` with the actual amount transferred. If this takes the source balance + /// to below the minimum for the asset, then the amount transferred is increased to take it + /// to zero. + /// + /// Weight: `O(1)` + /// Modes: Pre-existence of `target`; Post-existence of sender; Prior & post zombie-status + /// of sender; Account pre-existence of `target`. + #[pallet::weight(T::WeightInfo::transfer_keep_alive())] + pub(super) fn transfer_keep_alive( + origin: OriginFor, + #[pallet::compact] id: T::AssetId, + target: ::Source, + #[pallet::compact] amount: T::Balance, + ) -> DispatchResult { + let source = ensure_signed(origin)?; + let dest = T::Lookup::lookup(target)?; + + 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. + /// + /// Origin must be Signed and the sender should be the Admin of the asset `id`. + /// + /// - `id`: The identifier of the asset to have some amount transferred. + /// - `source`: The account to be debited. + /// - `dest`: The account to be credited. + /// - `amount`: The amount by which the `source`'s balance of assets should be reduced and + /// `dest`'s balance increased. The amount actually transferred may be slightly greater in + /// the case that the transfer would otherwise take the `source` balance above zero but + /// below the minimum balance. Must be greater than zero. + /// + /// Emits `Transferred` with the actual amount transferred. If this takes the source balance + /// to below the minimum for the asset, then the amount transferred is increased to take it + /// to zero. + /// + /// Weight: `O(1)` + /// Modes: Pre-existence of `dest`; Post-existence of `source`; Prior & post zombie-status + /// of `source`; Account pre-existence of `dest`. + #[pallet::weight(T::WeightInfo::force_transfer())] + pub(super) fn force_transfer( + origin: OriginFor, + #[pallet::compact] id: T::AssetId, + source: ::Source, + dest: ::Source, + #[pallet::compact] amount: T::Balance, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + let source = T::Lookup::lookup(source)?; + let dest = T::Lookup::lookup(dest)?; + + 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. + /// + /// Origin must be Signed and the sender should be the Freezer of the asset `id`. + /// + /// - `id`: The identifier of the asset to be frozen. + /// - `who`: The account to be frozen. + /// + /// Emits `Frozen`. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::freeze())] + pub(super) fn freeze( + origin: OriginFor, + #[pallet::compact] id: T::AssetId, + who: ::Source, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + + let d = Asset::::get(id).ok_or(Error::::Unknown)?; + ensure!(&origin == &d.freezer, Error::::NoPermission); + let who = T::Lookup::lookup(who)?; + ensure!( + Account::::contains_key(id, &who), + Error::::BalanceZero + ); + + Account::::mutate(id, &who, |a| a.is_frozen = true); + + Self::deposit_event(Event::::Frozen(id, who)); + Ok(()) + } + + /// Allow unprivileged transfers from an account again. + /// + /// Origin must be Signed and the sender should be the Admin of the asset `id`. + /// + /// - `id`: The identifier of the asset to be frozen. + /// - `who`: The account to be unfrozen. + /// + /// Emits `Thawed`. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::thaw())] + pub(super) fn thaw( + origin: OriginFor, + #[pallet::compact] id: T::AssetId, + who: ::Source, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + + let details = Asset::::get(id).ok_or(Error::::Unknown)?; + ensure!(&origin == &details.admin, Error::::NoPermission); + let who = T::Lookup::lookup(who)?; + ensure!( + Account::::contains_key(id, &who), + Error::::BalanceZero + ); + + Account::::mutate(id, &who, |a| a.is_frozen = false); + + Self::deposit_event(Event::::Thawed(id, who)); + Ok(()) + } + + /// Disallow further unprivileged transfers for the asset class. + /// + /// Origin must be Signed and the sender should be the Freezer of the asset `id`. + /// + /// - `id`: The identifier of the asset to be frozen. + /// + /// Emits `Frozen`. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::freeze_asset())] + pub(super) fn freeze_asset( + origin: OriginFor, + #[pallet::compact] id: T::AssetId, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + + Asset::::try_mutate(id, |maybe_details| { + let d = maybe_details.as_mut().ok_or(Error::::Unknown)?; + ensure!(&origin == &d.freezer, Error::::NoPermission); + + d.is_frozen = true; + + Self::deposit_event(Event::::AssetFrozen(id)); + Ok(()) + }) + } + + /// Allow unprivileged transfers for the asset again. + /// + /// Origin must be Signed and the sender should be the Admin of the asset `id`. + /// + /// - `id`: The identifier of the asset to be frozen. + /// + /// Emits `Thawed`. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::thaw_asset())] + pub(super) fn thaw_asset( + origin: OriginFor, + #[pallet::compact] id: T::AssetId, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + + Asset::::try_mutate(id, |maybe_details| { + let d = maybe_details.as_mut().ok_or(Error::::Unknown)?; + ensure!(&origin == &d.admin, Error::::NoPermission); + + d.is_frozen = false; + + Self::deposit_event(Event::::AssetThawed(id)); + Ok(()) + }) + } + + /// Change the Owner of an asset. + /// + /// Origin must be Signed and the sender should be the Owner of the asset `id`. + /// + /// - `id`: The identifier of the asset. + /// - `owner`: The new Owner of this asset. + /// + /// Emits `OwnerChanged`. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::transfer_ownership())] + pub(super) fn transfer_ownership( + origin: OriginFor, + #[pallet::compact] id: T::AssetId, + owner: ::Source, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + let owner = T::Lookup::lookup(owner)?; + + Asset::::try_mutate(id, |maybe_details| { + let details = maybe_details.as_mut().ok_or(Error::::Unknown)?; + ensure!(&origin == &details.owner, Error::::NoPermission); + if details.owner == owner { + return Ok(()); + } + + let metadata_deposit = Metadata::::get(id).deposit; + let deposit = details.deposit + metadata_deposit; + + // Move the deposit to the new owner. + T::Currency::repatriate_reserved(&details.owner, &owner, deposit, Reserved)?; + + details.owner = owner.clone(); + + Self::deposit_event(Event::OwnerChanged(id, owner)); + Ok(()) + }) + } + + /// Change the Issuer, Admin and Freezer of an asset. + /// + /// Origin must be Signed and the sender should be the Owner of the asset `id`. + /// + /// - `id`: The identifier of the asset to be frozen. + /// - `issuer`: The new Issuer of this asset. + /// - `admin`: The new Admin of this asset. + /// - `freezer`: The new Freezer of this asset. + /// + /// Emits `TeamChanged`. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::set_team())] + pub(super) fn set_team( + origin: OriginFor, + #[pallet::compact] id: T::AssetId, + issuer: ::Source, + admin: ::Source, + freezer: ::Source, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + let issuer = T::Lookup::lookup(issuer)?; + let admin = T::Lookup::lookup(admin)?; + let freezer = T::Lookup::lookup(freezer)?; + + Asset::::try_mutate(id, |maybe_details| { + let details = maybe_details.as_mut().ok_or(Error::::Unknown)?; + ensure!(&origin == &details.owner, Error::::NoPermission); + + details.issuer = issuer.clone(); + details.admin = admin.clone(); + details.freezer = freezer.clone(); + + Self::deposit_event(Event::TeamChanged(id, issuer, admin, freezer)); + Ok(()) + }) + } + + /// Set the metadata for an asset. + /// + /// Origin must be Signed and the sender should be the Owner of the asset `id`. + /// + /// Funds of sender are reserved according to the formula: + /// `MetadataDepositBase + MetadataDepositPerByte * (name.len + symbol.len)` taking into + /// account any already reserved funds. + /// + /// - `id`: The identifier of the asset to update. + /// - `name`: The user friendly name of this asset. Limited in length by `StringLimit`. + /// - `symbol`: The exchange symbol for this asset. Limited in length by `StringLimit`. + /// - `decimals`: The number of decimals this asset uses to represent one unit. + /// + /// Emits `MetadataSet`. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::set_metadata(name.len() as u32, symbol.len() as u32))] + pub(super) fn set_metadata( + origin: OriginFor, + #[pallet::compact] id: T::AssetId, + name: Vec, + symbol: Vec, + decimals: u8, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + + ensure!( + name.len() <= T::StringLimit::get() as usize, + Error::::BadMetadata + ); + ensure!( + symbol.len() <= T::StringLimit::get() as usize, + Error::::BadMetadata + ); + + let d = Asset::::get(id).ok_or(Error::::Unknown)?; + ensure!(&origin == &d.owner, Error::::NoPermission); + + Metadata::::try_mutate_exists(id, |metadata| { + ensure!( + metadata.as_ref().map_or(true, |m| !m.is_frozen), + Error::::NoPermission + ); + + let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit); + let new_deposit = T::MetadataDepositPerByte::get() + .saturating_mul(((name.len() + symbol.len()) as u32).into()) + .saturating_add(T::MetadataDepositBase::get()); + + if new_deposit > old_deposit { + T::Currency::reserve(&origin, new_deposit - old_deposit)?; + } else { + T::Currency::unreserve(&origin, old_deposit - new_deposit); + } + + *metadata = Some(AssetMetadata { + deposit: new_deposit, + name: name.clone(), + symbol: symbol.clone(), + decimals, + is_frozen: false, + }); + + Self::deposit_event(Event::MetadataSet(id, name, symbol, decimals, false)); + Ok(()) + }) + } + + /// Clear the metadata for an asset. + /// + /// Origin must be Signed and the sender should be the Owner of the asset `id`. + /// + /// Any deposit is freed for the asset owner. + /// + /// - `id`: The identifier of the asset to clear. + /// + /// Emits `MetadataCleared`. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::clear_metadata())] + pub(super) fn clear_metadata( + origin: OriginFor, + #[pallet::compact] id: T::AssetId, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + + let d = Asset::::get(id).ok_or(Error::::Unknown)?; + ensure!(&origin == &d.owner, Error::::NoPermission); + + Metadata::::try_mutate_exists(id, |metadata| { + let deposit = metadata.take().ok_or(Error::::Unknown)?.deposit; + T::Currency::unreserve(&d.owner, deposit); + Self::deposit_event(Event::MetadataCleared(id)); + Ok(()) + }) + } + + /// Force the metadata for an asset to some value. + /// + /// Origin must be ForceOrigin. + /// + /// Any deposit is left alone. + /// + /// - `id`: The identifier of the asset to update. + /// - `name`: The user friendly name of this asset. Limited in length by `StringLimit`. + /// - `symbol`: The exchange symbol for this asset. Limited in length by `StringLimit`. + /// - `decimals`: The number of decimals this asset uses to represent one unit. + /// + /// Emits `MetadataSet`. + /// + /// Weight: `O(N + S)` where N and S are the length of the name and symbol respectively. + #[pallet::weight(T::WeightInfo::force_set_metadata(name.len() as u32, symbol.len() as u32))] + pub(super) fn force_set_metadata( + origin: OriginFor, + #[pallet::compact] id: T::AssetId, + name: Vec, + symbol: Vec, + decimals: u8, + is_frozen: bool, + ) -> DispatchResult { + T::ForceOrigin::ensure_origin(origin)?; + + ensure!( + name.len() <= T::StringLimit::get() as usize, + Error::::BadMetadata + ); + ensure!( + symbol.len() <= T::StringLimit::get() as usize, + Error::::BadMetadata + ); + + ensure!(Asset::::contains_key(id), Error::::Unknown); + Metadata::::try_mutate_exists(id, |metadata| { + let deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit); + *metadata = Some(AssetMetadata { + deposit, + name: name.clone(), + symbol: symbol.clone(), + decimals, + is_frozen, + }); + + Self::deposit_event(Event::MetadataSet(id, name, symbol, decimals, is_frozen)); + Ok(()) + }) + } + + /// Clear the metadata for an asset. + /// + /// Origin must be ForceOrigin. + /// + /// Any deposit is returned. + /// + /// - `id`: The identifier of the asset to clear. + /// + /// Emits `MetadataCleared`. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::force_clear_metadata())] + pub(super) fn force_clear_metadata( + origin: OriginFor, + #[pallet::compact] id: T::AssetId, + ) -> DispatchResult { + T::ForceOrigin::ensure_origin(origin)?; + + let d = Asset::::get(id).ok_or(Error::::Unknown)?; + Metadata::::try_mutate_exists(id, |metadata| { + let deposit = metadata.take().ok_or(Error::::Unknown)?.deposit; + T::Currency::unreserve(&d.owner, deposit); + Self::deposit_event(Event::MetadataCleared(id)); + Ok(()) + }) + } + + /// Alter the attributes of a given asset. + /// + /// Origin must be `ForceOrigin`. + /// + /// - `id`: The identifier of the asset. + /// - `owner`: The new Owner of this asset. + /// - `issuer`: The new Issuer of this asset. + /// - `admin`: The new Admin of this asset. + /// - `freezer`: The new Freezer of this asset. + /// - `min_balance`: The minimum balance of this new asset that any single account must + /// have. If an account's balance is reduced below this, then it collapses to zero. + /// - `is_sufficient`: Whether a non-zero balance of this asset is deposit of sufficient + /// value to account for the state bloat associated with its balance storage. If set to + /// `true`, then non-zero balances may be stored without a `consumer` reference (and thus + /// an ED in the Balances pallet or whatever else is used to control user-account state + /// growth). + /// - `is_frozen`: Whether this asset class is frozen except for permissioned/admin + /// instructions. + /// + /// Emits `AssetStatusChanged` with the identity of the asset. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::force_asset_status())] + pub(super) fn force_asset_status( + origin: OriginFor, + #[pallet::compact] id: T::AssetId, + owner: ::Source, + issuer: ::Source, + admin: ::Source, + freezer: ::Source, + #[pallet::compact] min_balance: T::Balance, + is_sufficient: bool, + is_frozen: bool, + ) -> DispatchResult { + T::ForceOrigin::ensure_origin(origin)?; + + Asset::::try_mutate(id, |maybe_asset| { + let mut asset = maybe_asset.take().ok_or(Error::::Unknown)?; + asset.owner = T::Lookup::lookup(owner)?; + asset.issuer = T::Lookup::lookup(issuer)?; + asset.admin = T::Lookup::lookup(admin)?; + asset.freezer = T::Lookup::lookup(freezer)?; + asset.min_balance = min_balance; + asset.is_sufficient = is_sufficient; + asset.is_frozen = is_frozen; + *maybe_asset = Some(asset); + + Self::deposit_event(Event::AssetStatusChanged(id)); + Ok(()) + }) + } + + /// Approve an amount of asset for transfer by a delegated third-party account. + /// + /// Origin must be Signed. + /// + /// Ensures that `ApprovalDeposit` worth of `Currency` is reserved from signing account + /// for the purpose of holding the approval. If some non-zero amount of assets is already + /// approved from signing account to `delegate`, then it is topped up or unreserved to + /// meet the right value. + /// + /// NOTE: The signing account does not need to own `amount` of assets at the point of + /// making this call. + /// + /// - `id`: The identifier of the asset. + /// - `delegate`: The account to delegate permission to transfer asset. + /// - `amount`: The amount of asset that may be transferred by `delegate`. If there is + /// already an approval in place, then this acts additively. + /// + /// Emits `ApprovedTransfer` on success. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::approve_transfer())] + pub(super) fn approve_transfer( + origin: OriginFor, + #[pallet::compact] id: T::AssetId, + delegate: ::Source, + #[pallet::compact] amount: T::Balance, + ) -> DispatchResult { + let owner = ensure_signed(origin)?; + let delegate = T::Lookup::lookup(delegate)?; + + let key = ApprovalKey { owner, delegate }; + Approvals::::try_mutate(id, &key, |maybe_approved| -> DispatchResult { + let mut approved = maybe_approved.take().unwrap_or_default(); + let deposit_required = T::ApprovalDeposit::get(); + if approved.deposit < deposit_required { + T::Currency::reserve(&key.owner, deposit_required - approved.deposit)?; + approved.deposit = deposit_required; + } + approved.amount = approved.amount.saturating_add(amount); + *maybe_approved = Some(approved); + Ok(()) + })?; + Self::deposit_event(Event::ApprovedTransfer(id, key.owner, key.delegate, amount)); + + Ok(()) + } + + /// Cancel all of some asset approved for delegated transfer by a third-party account. + /// + /// Origin must be Signed and there must be an approval in place between signer and + /// `delegate`. + /// + /// Unreserves any deposit previously reserved by `approve_transfer` for the approval. + /// + /// - `id`: The identifier of the asset. + /// - `delegate`: The account delegated permission to transfer asset. + /// + /// Emits `ApprovalCancelled` on success. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::cancel_approval())] + pub(super) fn cancel_approval( + origin: OriginFor, + #[pallet::compact] id: T::AssetId, + delegate: ::Source, + ) -> DispatchResult { + let owner = ensure_signed(origin)?; + let delegate = T::Lookup::lookup(delegate)?; + let key = ApprovalKey { owner, delegate }; + let approval = Approvals::::take(id, &key).ok_or(Error::::Unknown)?; + T::Currency::unreserve(&key.owner, approval.deposit); + + Self::deposit_event(Event::ApprovalCancelled(id, key.owner, key.delegate)); + Ok(()) + } + + /// Cancel all of some asset approved for delegated transfer by a third-party account. + /// + /// Origin must be either ForceOrigin or Signed origin with the signer being the Admin + /// account of the asset `id`. + /// + /// Unreserves any deposit previously reserved by `approve_transfer` for the approval. + /// + /// - `id`: The identifier of the asset. + /// - `delegate`: The account delegated permission to transfer asset. + /// + /// Emits `ApprovalCancelled` on success. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::force_cancel_approval())] + pub(super) fn force_cancel_approval( + origin: OriginFor, + #[pallet::compact] id: T::AssetId, + owner: ::Source, + delegate: ::Source, + ) -> DispatchResult { + T::ForceOrigin::try_origin(origin) + .map(|_| ()) + .or_else(|origin| -> DispatchResult { + let origin = ensure_signed(origin)?; + let d = Asset::::get(id).ok_or(Error::::Unknown)?; + ensure!(&origin == &d.admin, Error::::NoPermission); + Ok(()) + })?; + + let owner = T::Lookup::lookup(owner)?; + let delegate = T::Lookup::lookup(delegate)?; + + let key = ApprovalKey { owner, delegate }; + let approval = Approvals::::take(id, &key).ok_or(Error::::Unknown)?; + T::Currency::unreserve(&key.owner, approval.deposit); + + Self::deposit_event(Event::ApprovalCancelled(id, key.owner, key.delegate)); + Ok(()) + } + + /// Transfer some asset balance from a previously delegated account to some third-party + /// account. + /// + /// Origin must be Signed and there must be an approval in place by the `owner` to the + /// signer. + /// + /// If the entire amount approved for transfer is transferred, then any deposit previously + /// reserved by `approve_transfer` is unreserved. + /// + /// - `id`: The identifier of the asset. + /// - `owner`: The account which previously approved for a transfer of at least `amount` and + /// from which the asset balance will be withdrawn. + /// - `destination`: The account to which the asset balance of `amount` will be transferred. + /// - `amount`: The amount of assets to transfer. + /// + /// Emits `TransferredApproved` on success. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::transfer_approved())] + pub(super) fn transfer_approved( + origin: OriginFor, + #[pallet::compact] id: T::AssetId, + owner: ::Source, + destination: ::Source, + #[pallet::compact] amount: T::Balance, + ) -> DispatchResult { + let delegate = ensure_signed(origin)?; + let owner = T::Lookup::lookup(owner)?; + let destination = T::Lookup::lookup(destination)?; + + let key = ApprovalKey { owner, delegate }; + Approvals::::try_mutate_exists(id, &key, |maybe_approved| -> DispatchResult { + let mut approved = maybe_approved.take().ok_or(Error::::Unapproved)?; + let remaining = approved + .amount + .checked_sub(&amount) + .ok_or(Error::::Unapproved)?; + + 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); + } else { + approved.amount = remaining; + *maybe_approved = Some(approved); + } + Ok(()) + })?; + Ok(()) + } + } } diff --git a/frame/assets/src/types.rs b/frame/assets/src/types.rs index 7e0e235b1b7e6..22ac31013feb4 100644 --- a/frame/assets/src/types.rs +++ b/frame/assets/src/types.rs @@ -15,172 +15,171 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Various basic tyoes for use in the assets pallet. +//! Various basic types for use in the assets pallet. use super::*; -pub(super) type DepositBalanceOf = <::Currency as Currency<::AccountId>>::Balance; +pub(super) type DepositBalanceOf = + <>::Currency as Currency<::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, +pub struct AssetDetails { + /// 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 AssetDetails { - pub fn destroy_witness(&self) -> DestroyWitness { - DestroyWitness { - accounts: self.accounts, - sufficients: self.sufficients, - approvals: self.approvals, - } - } + 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 { - /// 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, + /// 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 { - /// 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, + /// 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 { - /// 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, + /// 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 { - /// 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, - /// The ticker symbol for this asset. Limited in length by `StringLimit`. - pub(super) symbol: Vec, - /// 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, + /// 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, + /// The ticker symbol for this asset. Limited in length by `StringLimit`. + pub(super) symbol: Vec, + /// 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, + /// 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 { - /// 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; - - /// Called when an account has been removed. - fn died(asset: AssetId, who: &AccountId); + /// 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; + + /// Called when an account has been removed. + fn died(asset: AssetId, who: &AccountId); } impl FrozenBalance for () { - fn frozen_balance(_: AssetId, _: &AccountId) -> Option { None } - fn died(_: AssetId, _: &AccountId) {} + fn frozen_balance(_: AssetId, _: &AccountId) -> Option { + 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, + /// 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, + /// 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 for DebitFlags { - fn from(f: TransferFlags) -> Self { - Self { - keep_alive: f.keep_alive, - best_effort: f.best_effort, - } - } + fn from(f: TransferFlags) -> Self { + Self { + keep_alive: f.keep_alive, + best_effort: f.best_effort, + } + } }