//! # Tokens Module //! //! ## Overview //! //! The tokens module provides fungible multi-currency functionality that //! implements `MultiCurrency` trait. //! //! The tokens module provides functions for: //! //! - Querying and setting the balance of a given account. //! - Getting and managing total issuance. //! - Balance transfer between accounts. //! - Depositing and withdrawing balance. //! - Slashing an account balance. //! //! ### Implementations //! //! The tokens module provides implementations for following traits. //! //! - `MultiCurrency` - Abstraction over a fungible multi-currency system. //! - `MultiCurrencyExtended` - Extended `MultiCurrency` with additional helper //! types and methods, like updating balance //! by a given signed integer amount. //! //! ## Interface //! //! ### Dispatchable Functions //! //! - `transfer` - Transfer some balance to another account. //! - `transfer_all` - Transfer all balance to another account. //! //! ### Genesis Config //! //! The tokens module depends on the `GenesisConfig`. Endowed accounts could be //! configured in genesis configs. #![cfg_attr(not(feature = "std"), no_std)] use codec::{Decode, Encode}; use frame_support::{ decl_error, decl_event, decl_module, decl_storage, ensure, traits::{ BalanceStatus as Status, Currency as PalletCurrency, ExistenceRequirement, Get, Imbalance, LockableCurrency as PalletLockableCurrency, ReservableCurrency as PalletReservableCurrency, SignedImbalance, WithdrawReasons, }, transactional, weights::Weight, Parameter, StorageMap, }; use frame_system::ensure_signed; use sp_runtime::{ traits::{ AccountIdConversion, AtLeast32BitUnsigned, Bounded, CheckedAdd, CheckedSub, MaybeSerializeDeserialize, Member, Saturating, StaticLookup, Zero, }, DispatchError, DispatchResult, ModuleId, RuntimeDebug, }; use sp_std::{ convert::{Infallible, TryFrom, TryInto}, marker, prelude::*, result, vec::Vec, }; #[cfg(feature = "std")] use sp_std::collections::btree_map::BTreeMap; pub use crate::imbalances::{NegativeImbalance, PositiveImbalance}; use orml_traits::{ account::MergeAccount, arithmetic::{self, Signed}, BalanceStatus, GetByKey, LockIdentifier, MultiCurrency, MultiCurrencyExtended, MultiLockableCurrency, MultiReservableCurrency, OnDust, OnReceived, }; mod default_weight; mod imbalances; mod mock; mod tests; pub trait WeightInfo { fn transfer() -> Weight; fn transfer_all() -> Weight; } pub trait Trait: frame_system::Trait { type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>; /// The balance type type Balance: Parameter + Member + AtLeast32BitUnsigned + Default + Copy + MaybeSerializeDeserialize; /// The amount type, should be signed version of `Balance` type Amount: Signed + TryInto<Self::Balance> + TryFrom<Self::Balance> + Parameter + Member + arithmetic::SimpleArithmetic + Default + Copy + MaybeSerializeDeserialize; /// The currency ID type type CurrencyId: Parameter + Member + Copy + MaybeSerializeDeserialize + Ord; /// Hook when some fund is deposited into an account type OnReceived: OnReceived<Self::AccountId, Self::CurrencyId, Self::Balance>; /// Weight information for extrinsics in this module. type WeightInfo: WeightInfo; /// The minimum amount required to keep an account. type ExistentialDeposits: GetByKey<Self::CurrencyId, Self::Balance>; /// Handler for the balance reduction when removing a dust account. type OnDust: OnDust<Self::CurrencyId, Self::Balance>; } /// A single lock on a balance. There can be many of these on an account and /// they "overlap", so the same balance is frozen by multiple locks. #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)] pub struct BalanceLock<Balance> { /// An identifier for this lock. Only one lock may be in existence for each /// identifier. pub id: LockIdentifier, /// The amount which the free balance may not drop below when this lock is /// in effect. pub amount: Balance, } /// balance information for an account. #[derive(Encode, Decode, Clone, PartialEq, Eq, Default, RuntimeDebug)] pub struct AccountData<Balance> { /// Non-reserved part of the balance. There may still be restrictions on /// this, but it is the total pool what may in principle be transferred, /// reserved. /// /// This is the only balance that matters in terms of most operations on /// tokens. pub free: Balance, /// Balance which is reserved and may not be used at all. /// /// This can still get slashed, but gets slashed last of all. /// /// This balance is a 'reserve' balance that other subsystems use in order /// to set aside tokens that are still 'owned' by the account holder, but /// which are suspendable. pub reserved: Balance, /// The amount that `free` may not drop below when withdrawing. pub frozen: Balance, } impl<Balance: Saturating + Copy + Ord> AccountData<Balance> { /// The amount that this account's free balance may not be reduced beyond. fn frozen(&self) -> Balance { self.frozen } /// The total balance in this account including any that is reserved and /// ignoring any frozen. fn total(&self) -> Balance { self.free.saturating_add(self.reserved) } } decl_storage! { trait Store for Module<T: Trait> as Tokens { /// The total issuance of a token type. pub TotalIssuance get(fn total_issuance) build(|config: &GenesisConfig<T>| { config .endowed_accounts .iter() .map(|(_, currency_id, initial_balance)| (currency_id, initial_balance)) .fold(BTreeMap::<T::CurrencyId, T::Balance>::new(), |mut acc, (currency_id, initial_balance)| { if let Some(issuance) = acc.get_mut(currency_id) { *issuance = issuance.checked_add(initial_balance).expect("total issuance cannot overflow when building genesis"); } else { acc.insert(*currency_id, *initial_balance); } acc }) .into_iter() .collect::<Vec<_>>() }): map hasher(twox_64_concat) T::CurrencyId => T::Balance; /// Any liquidity locks of a token type under an account. /// NOTE: Should only be accessed when setting, changing and freeing a lock. pub Locks get(fn locks): double_map hasher(blake2_128_concat) T::AccountId, hasher(twox_64_concat) T::CurrencyId => Vec<BalanceLock<T::Balance>>; /// The balance of a token type under an account. /// /// NOTE: If the total is ever zero, decrease account ref account. /// /// NOTE: This is only used in the case that this module is used to store balances. pub Accounts get(fn accounts): double_map hasher(blake2_128_concat) T::AccountId, hasher(twox_64_concat) T::CurrencyId => AccountData<T::Balance>; } add_extra_genesis { config(endowed_accounts): Vec<(T::AccountId, T::CurrencyId, T::Balance)>; build(|config: &GenesisConfig<T>| { config.endowed_accounts.iter().for_each(|(account_id, currency_id, initial_balance)| { assert!( *initial_balance >= T::ExistentialDeposits::get(¤cy_id), "the balance of any account should always be more than existential deposit.", ); Module::<T>::mutate_account(account_id, *currency_id, |account_data, _| account_data.free = *initial_balance); }) }) } } decl_event!( pub enum Event<T> where <T as frame_system::Trait>::AccountId, <T as Trait>::CurrencyId, <T as Trait>::Balance { /// Token transfer success. \[currency_id, from, to, amount\] Transferred(CurrencyId, AccountId, AccountId, Balance), /// An account was removed whose balance was non-zero but below ExistentialDeposit, /// resulting in an outright loss. \[account, currency_id, amount\] DustLost(AccountId, CurrencyId, Balance), } ); decl_module! { pub struct Module<T: Trait> for enum Call where origin: T::Origin { type Error = Error<T>; fn deposit_event() = default; /// Transfer some balance to another account. /// /// The dispatch origin for this call must be `Signed` by the transactor. /// /// # <weight> /// - Complexity: `O(1)` /// - Db reads: 4 /// - Db writes: 2 /// ------------------- /// Base Weight: 84.08 µs /// # </weight> #[weight = T::WeightInfo::transfer()] pub fn transfer( origin, dest: <T::Lookup as StaticLookup>::Source, currency_id: T::CurrencyId, #[compact] amount: T::Balance, ) { let from = ensure_signed(origin)?; let to = T::Lookup::lookup(dest)?; <Self as MultiCurrency<_>>::transfer(currency_id, &from, &to, amount)?; Self::deposit_event(RawEvent::Transferred(currency_id, from, to, amount)); } /// Transfer all remaining balance to the given account. /// /// The dispatch origin for this call must be `Signed` by the transactor. /// /// # <weight> /// - Complexity: `O(1)` /// - Db reads: 4 /// - Db writes: 2 /// ------------------- /// Base Weight: 87.71 µs /// # </weight> #[weight = T::WeightInfo::transfer_all()] pub fn transfer_all( origin, dest: <T::Lookup as StaticLookup>::Source, currency_id: T::CurrencyId, ) { let from = ensure_signed(origin)?; let to = T::Lookup::lookup(dest)?; let balance = <Self as MultiCurrency<T::AccountId>>::free_balance(currency_id, &from); <Self as MultiCurrency<T::AccountId>>::transfer(currency_id, &from, &to, balance)?; Self::deposit_event(RawEvent::Transferred(currency_id, from, to, balance)); } } } decl_error! { /// Error for token module. pub enum Error for Module<T: Trait> { /// The balance is too low BalanceTooLow, /// This operation will cause balance to overflow BalanceOverflow, /// This operation will cause total issuance to overflow TotalIssuanceOverflow, /// Cannot convert Amount into Balance type AmountIntoBalanceFailed, /// Failed because liquidity restrictions due to locking LiquidityRestrictions, /// Account still has active reserved StillHasActiveReserved, } } impl<T: Trait> Module<T> { /// Check whether account_id is a module account fn is_module_account_id(account_id: &T::AccountId) -> bool { ModuleId::try_from_account(account_id).is_some() } fn post_account_mutation( who: &T::AccountId, currency_id: T::CurrencyId, new: AccountData<T::Balance>, existed: bool, ) -> Option<AccountData<T::Balance>> { let total = new.total(); if total < T::ExistentialDeposits::get(¤cy_id) && !Self::is_module_account_id(who) { // remove dust if !total.is_zero() { // TotalIssuance deduct the dust amount here TotalIssuance::<T>::mutate(currency_id, |t| { *t = t.checked_sub(&total).expect("ensured non-underflow total amount; qed"); }); // NOTE: In any case, do not try to set/get the Accounts of `who` in the hook, // otherwise there may be some unexpected errors. // // It is recommended to deposit the amount of dust into a module account, or // do nothing which is equivalent to burn the dust. T::OnDust::on_dust(currency_id, total); Self::deposit_event(RawEvent::DustLost(who.clone(), currency_id, total)); } // if existed before, decrease account ref count if existed { frame_system::Module::<T>::dec_ref(who); } None } else { // if new, increase account ref count if !existed { frame_system::Module::<T>::inc_ref(who); } Some(new) } } fn try_mutate_account<R, E>( who: &T::AccountId, currency_id: T::CurrencyId, f: impl FnOnce(&mut AccountData<T::Balance>, bool) -> sp_std::result::Result<R, E>, ) -> sp_std::result::Result<R, E> { Accounts::<T>::try_mutate_exists(who, currency_id, |maybe_account| -> sp_std::result::Result<R, E> { let existed = maybe_account.is_some(); let mut account = maybe_account.take().unwrap_or_default(); f(&mut account, existed).map(move |result| { *maybe_account = Self::post_account_mutation(who, currency_id, account, existed); result }) }) } fn mutate_account<R>( who: &T::AccountId, currency_id: T::CurrencyId, f: impl FnOnce(&mut AccountData<T::Balance>, bool) -> R, ) -> R { Self::try_mutate_account(who, currency_id, |account, existed| -> Result<R, Infallible> { Ok(f(account, existed)) }) .expect("Error is infallible; qed") } /// Set free balance of `who` to a new value. /// /// Note this will not maintain total issuance, and the caller is expected /// to do it. fn set_free_balance(currency_id: T::CurrencyId, who: &T::AccountId, amount: T::Balance) { Self::mutate_account(who, currency_id, |account, _| { account.free = amount; }); } /// Set reserved balance of `who` to a new value. /// /// Note this will not maintain total issuance, and the caller is expected /// to do it. fn set_reserved_balance(currency_id: T::CurrencyId, who: &T::AccountId, amount: T::Balance) { Self::mutate_account(who, currency_id, |account, _| { account.reserved = amount; }); } /// Update the account entry for `who` under `currency_id`, given the locks. fn update_locks(currency_id: T::CurrencyId, who: &T::AccountId, locks: &[BalanceLock<T::Balance>]) { // update account data Self::mutate_account(who, currency_id, |account, _| { account.frozen = Zero::zero(); for lock in locks.iter() { account.frozen = account.frozen.max(lock.amount); } }); // update locks let existed = <Locks<T>>::contains_key(who, currency_id); if locks.is_empty() { <Locks<T>>::remove(who, currency_id); if existed { // decrease account ref count when destruct lock frame_system::Module::<T>::dec_ref(who); } } else { <Locks<T>>::insert(who, currency_id, locks); if !existed { // increase account ref count when initialize lock frame_system::Module::<T>::inc_ref(who); } } } } impl<T: Trait> MultiCurrency<T::AccountId> for Module<T> { type CurrencyId = T::CurrencyId; type Balance = T::Balance; fn minimum_balance(currency_id: Self::CurrencyId) -> Self::Balance { T::ExistentialDeposits::get(¤cy_id) } fn total_issuance(currency_id: Self::CurrencyId) -> Self::Balance { <TotalIssuance<T>>::get(currency_id) } fn total_balance(currency_id: Self::CurrencyId, who: &T::AccountId) -> Self::Balance { Self::accounts(who, currency_id).total() } fn free_balance(currency_id: Self::CurrencyId, who: &T::AccountId) -> Self::Balance { Self::accounts(who, currency_id).free } // Ensure that an account can withdraw from their free balance given any // existing withdrawal restrictions like locks and vesting balance. // Is a no-op if amount to be withdrawn is zero. fn ensure_can_withdraw(currency_id: Self::CurrencyId, who: &T::AccountId, amount: Self::Balance) -> DispatchResult { if amount.is_zero() { return Ok(()); } let new_balance = Self::free_balance(currency_id, who) .checked_sub(&amount) .ok_or(Error::<T>::BalanceTooLow)?; ensure!( new_balance >= Self::accounts(who, currency_id).frozen(), Error::<T>::LiquidityRestrictions ); Ok(()) } /// Transfer some free balance from `from` to `to`. /// Is a no-op if value to be transferred is zero or the `from` is the same /// as `to`. fn transfer( currency_id: Self::CurrencyId, from: &T::AccountId, to: &T::AccountId, amount: Self::Balance, ) -> DispatchResult { if amount.is_zero() || from == to { return Ok(()); } Self::ensure_can_withdraw(currency_id, from, amount)?; let from_balance = Self::free_balance(currency_id, from); let to_balance = Self::free_balance(currency_id, to) .checked_add(&amount) .ok_or(Error::<T>::BalanceOverflow)?; // Cannot underflow because ensure_can_withdraw check Self::set_free_balance(currency_id, from, from_balance - amount); Self::set_free_balance(currency_id, to, to_balance); T::OnReceived::on_received(to, currency_id, amount); Ok(()) } /// Deposit some `amount` into the free balance of account `who`. /// /// Is a no-op if the `amount` to be deposited is zero. fn deposit(currency_id: Self::CurrencyId, who: &T::AccountId, amount: Self::Balance) -> DispatchResult { if amount.is_zero() { return Ok(()); } TotalIssuance::<T>::try_mutate(currency_id, |total_issuance| -> DispatchResult { *total_issuance = total_issuance .checked_add(&amount) .ok_or(Error::<T>::TotalIssuanceOverflow)?; Self::set_free_balance(currency_id, who, Self::free_balance(currency_id, who) + amount); T::OnReceived::on_received(who, currency_id, amount); Ok(()) }) } fn withdraw(currency_id: Self::CurrencyId, who: &T::AccountId, amount: Self::Balance) -> DispatchResult { if amount.is_zero() { return Ok(()); } Self::ensure_can_withdraw(currency_id, who, amount)?; // Cannot underflow because ensure_can_withdraw check <TotalIssuance<T>>::mutate(currency_id, |v| *v -= amount); Self::set_free_balance(currency_id, who, Self::free_balance(currency_id, who) - amount); Ok(()) } // Check if `value` amount of free balance can be slashed from `who`. fn can_slash(currency_id: Self::CurrencyId, who: &T::AccountId, value: Self::Balance) -> bool { if value.is_zero() { return true; } Self::free_balance(currency_id, who) >= value } /// Is a no-op if `value` to be slashed is zero. /// /// NOTE: `slash()` prefers free balance, but assumes that reserve balance /// can be drawn from in extreme circumstances. `can_slash()` should be used /// prior to `slash()` to avoid having to draw from reserved funds, however /// we err on the side of punishment if things are inconsistent /// or `can_slash` wasn't used appropriately. fn slash(currency_id: Self::CurrencyId, who: &T::AccountId, amount: Self::Balance) -> Self::Balance { if amount.is_zero() { return amount; } let account = Self::accounts(who, currency_id); let free_slashed_amount = account.free.min(amount); // Cannot underflow becuase free_slashed_amount can never be greater than amount let mut remaining_slash = amount - free_slashed_amount; // slash free balance if !free_slashed_amount.is_zero() { // Cannot underflow becuase free_slashed_amount can never be greater than // account.free Self::set_free_balance(currency_id, who, account.free - free_slashed_amount); } // slash reserved balance if !remaining_slash.is_zero() { let reserved_slashed_amount = account.reserved.min(remaining_slash); // Cannot underflow due to above line remaining_slash -= reserved_slashed_amount; Self::set_reserved_balance(currency_id, who, account.reserved - reserved_slashed_amount); } // Cannot underflow because the slashed value cannot be greater than total // issuance <TotalIssuance<T>>::mutate(currency_id, |v| *v -= amount - remaining_slash); remaining_slash } } impl<T: Trait> MultiCurrencyExtended<T::AccountId> for Module<T> { type Amount = T::Amount; fn update_balance(currency_id: Self::CurrencyId, who: &T::AccountId, by_amount: Self::Amount) -> DispatchResult { if by_amount.is_zero() { return Ok(()); } // Ensure this doesn't overflow. There isn't any traits that exposes // `saturating_abs` so we need to do it manually. let by_amount_abs = if by_amount == Self::Amount::min_value() { Self::Amount::max_value() } else { by_amount.abs() }; let by_balance = TryInto::<Self::Balance>::try_into(by_amount_abs).map_err(|_| Error::<T>::AmountIntoBalanceFailed)?; if by_amount.is_positive() { Self::deposit(currency_id, who, by_balance) } else { Self::withdraw(currency_id, who, by_balance).map(|_| ()) } } } impl<T: Trait> MultiLockableCurrency<T::AccountId> for Module<T> { type Moment = T::BlockNumber; // Set a lock on the balance of `who` under `currency_id`. // Is a no-op if lock amount is zero. fn set_lock(lock_id: LockIdentifier, currency_id: Self::CurrencyId, who: &T::AccountId, amount: Self::Balance) { if amount.is_zero() { return; } let mut new_lock = Some(BalanceLock { id: lock_id, amount }); let mut locks = Self::locks(who, currency_id) .into_iter() .filter_map(|lock| { if lock.id == lock_id { new_lock.take() } else { Some(lock) } }) .collect::<Vec<_>>(); if let Some(lock) = new_lock { locks.push(lock) } Self::update_locks(currency_id, who, &locks[..]); } // Extend a lock on the balance of `who` under `currency_id`. // Is a no-op if lock amount is zero fn extend_lock(lock_id: LockIdentifier, currency_id: Self::CurrencyId, who: &T::AccountId, amount: Self::Balance) { if amount.is_zero() { return; } let mut new_lock = Some(BalanceLock { id: lock_id, amount }); let mut locks = Self::locks(who, currency_id) .into_iter() .filter_map(|lock| { if lock.id == lock_id { new_lock.take().map(|nl| BalanceLock { id: lock.id, amount: lock.amount.max(nl.amount), }) } else { Some(lock) } }) .collect::<Vec<_>>(); if let Some(lock) = new_lock { locks.push(lock) } Self::update_locks(currency_id, who, &locks[..]); } fn remove_lock(lock_id: LockIdentifier, currency_id: Self::CurrencyId, who: &T::AccountId) { let mut locks = Self::locks(who, currency_id); locks.retain(|lock| lock.id != lock_id); Self::update_locks(currency_id, who, &locks[..]); } } impl<T: Trait> MultiReservableCurrency<T::AccountId> for Module<T> { /// Check if `who` can reserve `value` from their free balance. /// /// Always `true` if value to be reserved is zero. fn can_reserve(currency_id: Self::CurrencyId, who: &T::AccountId, value: Self::Balance) -> bool { if value.is_zero() { return true; } Self::ensure_can_withdraw(currency_id, who, value).is_ok() } /// Slash from reserved balance, returning any amount that was unable to be /// slashed. /// /// Is a no-op if the value to be slashed is zero. fn slash_reserved(currency_id: Self::CurrencyId, who: &T::AccountId, value: Self::Balance) -> Self::Balance { if value.is_zero() { return value; } let reserved_balance = Self::reserved_balance(currency_id, who); let actual = reserved_balance.min(value); Self::set_reserved_balance(currency_id, who, reserved_balance - actual); <TotalIssuance<T>>::mutate(currency_id, |v| *v -= actual); value - actual } fn reserved_balance(currency_id: Self::CurrencyId, who: &T::AccountId) -> Self::Balance { Self::accounts(who, currency_id).reserved } /// Move `value` from the free balance from `who` to their reserved balance. /// /// Is a no-op if value to be reserved is zero. fn reserve(currency_id: Self::CurrencyId, who: &T::AccountId, value: Self::Balance) -> DispatchResult { if value.is_zero() { return Ok(()); } Self::ensure_can_withdraw(currency_id, who, value)?; let account = Self::accounts(who, currency_id); Self::set_free_balance(currency_id, who, account.free - value); // Cannot overflow becuase total issuance is using the same balance type and // this doesn't increase total issuance Self::set_reserved_balance(currency_id, who, account.reserved + value); Ok(()) } /// Unreserve some funds, returning any amount that was unable to be /// unreserved. /// /// Is a no-op if the value to be unreserved is zero. fn unreserve(currency_id: Self::CurrencyId, who: &T::AccountId, value: Self::Balance) -> Self::Balance { if value.is_zero() { return value; } let account = Self::accounts(who, currency_id); let actual = account.reserved.min(value); Self::set_reserved_balance(currency_id, who, account.reserved - actual); Self::set_free_balance(currency_id, who, account.free + actual); T::OnReceived::on_received(who, currency_id, actual); value - actual } /// Move the reserved balance of one account into the balance of another, /// according to `status`. /// /// Is a no-op if: /// - the value to be moved is zero; or /// - the `slashed` id equal to `beneficiary` and the `status` is /// `Reserved`. fn repatriate_reserved( currency_id: Self::CurrencyId, slashed: &T::AccountId, beneficiary: &T::AccountId, value: Self::Balance, status: BalanceStatus, ) -> result::Result<Self::Balance, DispatchError> { if value.is_zero() { return Ok(value); } if slashed == beneficiary { return match status { BalanceStatus::Free => Ok(Self::unreserve(currency_id, slashed, value)), BalanceStatus::Reserved => Ok(value.saturating_sub(Self::reserved_balance(currency_id, slashed))), }; } let from_account = Self::accounts(slashed, currency_id); let to_account = Self::accounts(beneficiary, currency_id); let actual = from_account.reserved.min(value); match status { BalanceStatus::Free => { Self::set_free_balance(currency_id, beneficiary, to_account.free + actual); T::OnReceived::on_received(beneficiary, currency_id, actual); } BalanceStatus::Reserved => { Self::set_reserved_balance(currency_id, beneficiary, to_account.reserved + actual); } } Self::set_reserved_balance(currency_id, slashed, from_account.reserved - actual); Ok(value - actual) } } pub struct CurrencyAdapter<T, GetCurrencyId>(marker::PhantomData<(T, GetCurrencyId)>); impl<T, GetCurrencyId> PalletCurrency<T::AccountId> for CurrencyAdapter<T, GetCurrencyId> where T: Trait, GetCurrencyId: Get<T::CurrencyId>, { type Balance = T::Balance; type PositiveImbalance = PositiveImbalance<T, GetCurrencyId>; type NegativeImbalance = NegativeImbalance<T, GetCurrencyId>; fn total_balance(who: &T::AccountId) -> Self::Balance { Module::<T>::total_balance(GetCurrencyId::get(), who) } fn can_slash(who: &T::AccountId, value: Self::Balance) -> bool { Module::<T>::can_slash(GetCurrencyId::get(), who, value) } fn total_issuance() -> Self::Balance { Module::<T>::total_issuance(GetCurrencyId::get()) } fn minimum_balance() -> Self::Balance { Module::<T>::minimum_balance(GetCurrencyId::get()) } fn burn(mut amount: Self::Balance) -> Self::PositiveImbalance { if amount.is_zero() { return PositiveImbalance::zero(); } <TotalIssuance<T>>::mutate(GetCurrencyId::get(), |issued| { *issued = issued.checked_sub(&amount).unwrap_or_else(|| { amount = *issued; Zero::zero() }); }); PositiveImbalance::new(amount) } fn issue(mut amount: Self::Balance) -> Self::NegativeImbalance { if amount.is_zero() { return NegativeImbalance::zero(); } <TotalIssuance<T>>::mutate(GetCurrencyId::get(), |issued| { *issued = issued.checked_add(&amount).unwrap_or_else(|| { amount = Self::Balance::max_value() - *issued; Self::Balance::max_value() }) }); NegativeImbalance::new(amount) } fn free_balance(who: &T::AccountId) -> Self::Balance { Module::<T>::free_balance(GetCurrencyId::get(), who) } fn ensure_can_withdraw( who: &T::AccountId, amount: Self::Balance, _reasons: WithdrawReasons, _new_balance: Self::Balance, ) -> DispatchResult { Module::<T>::ensure_can_withdraw(GetCurrencyId::get(), who, amount) } fn transfer( source: &T::AccountId, dest: &T::AccountId, value: Self::Balance, _existence_requirement: ExistenceRequirement, ) -> DispatchResult { <Module<T> as MultiCurrency<T::AccountId>>::transfer(GetCurrencyId::get(), &source, &dest, value) } fn slash(who: &T::AccountId, value: Self::Balance) -> (Self::NegativeImbalance, Self::Balance) { if value.is_zero() { return (Self::NegativeImbalance::zero(), value); } let currency_id = GetCurrencyId::get(); let account = Module::<T>::accounts(who, currency_id); let free_slashed_amount = account.free.min(value); let mut remaining_slash = value - free_slashed_amount; // slash free balance if !free_slashed_amount.is_zero() { Module::<T>::set_free_balance(currency_id, who, account.free - free_slashed_amount); } // slash reserved balance if !remaining_slash.is_zero() { let reserved_slashed_amount = account.reserved.min(remaining_slash); remaining_slash -= reserved_slashed_amount; Module::<T>::set_reserved_balance(currency_id, who, account.reserved - reserved_slashed_amount); ( Self::NegativeImbalance::new(free_slashed_amount + reserved_slashed_amount), remaining_slash, ) } else { (Self::NegativeImbalance::new(value), remaining_slash) } } fn deposit_into_existing( who: &T::AccountId, value: Self::Balance, ) -> result::Result<Self::PositiveImbalance, DispatchError> { if value.is_zero() { return Ok(Self::PositiveImbalance::zero()); } let currency_id = GetCurrencyId::get(); let new_total = Module::<T>::free_balance(currency_id, who) .checked_add(&value) .ok_or(Error::<T>::TotalIssuanceOverflow)?; Module::<T>::set_free_balance(currency_id, who, new_total); Ok(Self::PositiveImbalance::new(value)) } fn deposit_creating(who: &T::AccountId, value: Self::Balance) -> Self::PositiveImbalance { Self::deposit_into_existing(who, value).unwrap_or_else(|_| Self::PositiveImbalance::zero()) } fn withdraw( who: &T::AccountId, value: Self::Balance, _reasons: WithdrawReasons, _liveness: ExistenceRequirement, ) -> result::Result<Self::NegativeImbalance, DispatchError> { if value.is_zero() { return Ok(Self::NegativeImbalance::zero()); } let currency_id = GetCurrencyId::get(); Module::<T>::ensure_can_withdraw(currency_id, who, value)?; Module::<T>::set_free_balance(currency_id, who, Module::<T>::free_balance(currency_id, who) - value); Ok(Self::NegativeImbalance::new(value)) } fn make_free_balance_be( who: &T::AccountId, value: Self::Balance, ) -> SignedImbalance<Self::Balance, Self::PositiveImbalance> { let currency_id = GetCurrencyId::get(); Module::<T>::try_mutate_account( who, currency_id, |account, existed| -> Result<SignedImbalance<Self::Balance, Self::PositiveImbalance>, ()> { // If we're attempting to set an existing account to less than ED, then // bypass the entire operation. It's a no-op if you follow it through, but // since this is an instance where we might account for a negative imbalance // (in the dust cleaner of set_account) before we account for its actual // equal and opposite cause (returned as an Imbalance), then in the // instance that there's no other accounts on the system at all, we might // underflow the issuance and our arithmetic will be off. let ed = T::ExistentialDeposits::get(¤cy_id); ensure!(value.saturating_add(account.reserved) >= ed || existed, ()); let imbalance = if account.free <= value { SignedImbalance::Positive(PositiveImbalance::new(value - account.free)) } else { SignedImbalance::Negative(NegativeImbalance::new(account.free - value)) }; account.free = value; Ok(imbalance) }, ) .unwrap_or_else(|_| SignedImbalance::Positive(Self::PositiveImbalance::zero())) } } impl<T, GetCurrencyId> PalletReservableCurrency<T::AccountId> for CurrencyAdapter<T, GetCurrencyId> where T: Trait, GetCurrencyId: Get<T::CurrencyId>, { fn can_reserve(who: &T::AccountId, value: Self::Balance) -> bool { Module::<T>::can_reserve(GetCurrencyId::get(), who, value) } fn slash_reserved(who: &T::AccountId, value: Self::Balance) -> (Self::NegativeImbalance, Self::Balance) { let actual = Module::<T>::slash_reserved(GetCurrencyId::get(), who, value); (Self::NegativeImbalance::zero(), actual) } fn reserved_balance(who: &T::AccountId) -> Self::Balance { Module::<T>::reserved_balance(GetCurrencyId::get(), who) } fn reserve(who: &T::AccountId, value: Self::Balance) -> DispatchResult { Module::<T>::reserve(GetCurrencyId::get(), who, value) } fn unreserve(who: &T::AccountId, value: Self::Balance) -> Self::Balance { Module::<T>::unreserve(GetCurrencyId::get(), who, value) } fn repatriate_reserved( slashed: &T::AccountId, beneficiary: &T::AccountId, value: Self::Balance, status: Status, ) -> result::Result<Self::Balance, DispatchError> { Module::<T>::repatriate_reserved(GetCurrencyId::get(), slashed, beneficiary, value, status) } } impl<T, GetCurrencyId> PalletLockableCurrency<T::AccountId> for CurrencyAdapter<T, GetCurrencyId> where T: Trait, GetCurrencyId: Get<T::CurrencyId>, { type Moment = T::BlockNumber; type MaxLocks = (); fn set_lock(id: LockIdentifier, who: &T::AccountId, amount: Self::Balance, _reasons: WithdrawReasons) { Module::<T>::set_lock(id, GetCurrencyId::get(), who, amount) } fn extend_lock(id: LockIdentifier, who: &T::AccountId, amount: Self::Balance, _reasons: WithdrawReasons) { Module::<T>::extend_lock(id, GetCurrencyId::get(), who, amount) } fn remove_lock(id: LockIdentifier, who: &T::AccountId) { Module::<T>::remove_lock(id, GetCurrencyId::get(), who) } } impl<T: Trait> MergeAccount<T::AccountId> for Module<T> { #[transactional] fn merge_account(source: &T::AccountId, dest: &T::AccountId) -> DispatchResult { Accounts::<T>::iter_prefix(source).try_for_each(|(currency_id, account_data)| -> DispatchResult { // ensure the account has no active reserved of non-native token ensure!(account_data.reserved.is_zero(), Error::<T>::StillHasActiveReserved); // transfer all free to recipient <Self as MultiCurrency<T::AccountId>>::transfer(currency_id, source, dest, account_data.free)?; Ok(()) }) } }