#![cfg_attr(not(feature = "std"), no_std)]
#![allow(clippy::unused_unit)]
#![allow(clippy::comparison_chain)]
pub use crate::imbalances::{NegativeImbalance, PositiveImbalance};
use codec::MaxEncodedLen;
use frame_support::{
ensure,
pallet_prelude::*,
traits::{
tokens::currency::{MultiTokenCurrency, MultiTokenLockableCurrency},
tokens::{
fungible, fungibles, DepositConsequence, Fortitude, Precision, Preservation, Provenance, Restriction,
WithdrawConsequence,
},
BalanceStatus as Status, Contains, Currency as PalletCurrency, DefensiveSaturating, ExistenceRequirement, Get,
Imbalance, LockableCurrency as PalletLockableCurrency,
NamedReservableCurrency as PalletNamedReservableCurrency, ReservableCurrency as PalletReservableCurrency,
SignedImbalance, WithdrawReasons,
},
transactional, BoundedVec,
};
use frame_system::{ensure_root, ensure_signed, pallet_prelude::*};
use scale_info::TypeInfo;
use sp_core::U256;
use sp_runtime::{
traits::{
AtLeast32BitUnsigned, Bounded, CheckedAdd, CheckedSub, MaybeSerializeDeserialize, Member, One, Saturating,
StaticLookup, Zero,
},
ArithmeticError, DispatchError, DispatchResult, FixedPointOperand, RuntimeDebug, TokenError,
};
use sp_std::{cmp, convert::Infallible, marker, prelude::*, vec::Vec};
use orml_traits::{
arithmetic::{self, Signed},
currency::{MutationHooks, OnDeposit, OnDust, OnSlash, OnTransfer, TransferAll},
BalanceStatus, GetByKey, Happened, LockIdentifier, MultiCurrency, MultiCurrencyExtended, MultiLockableCurrency,
MultiReservableCurrency, NamedMultiReservableCurrency,
};
use codec::{Decode, Encode, FullCodec};
pub use multi_token_currency::{MultiTokenCurrencyExtended, MultiTokenReservableCurrency};
pub use multi_token_imbalances::{
NegativeImbalance as MultiTokenNegativeImbalance, PositiveImbalance as MultiTokenPositiveImbalance,
};
mod imbalances;
mod impls;
mod mock;
mod multi_token_currency;
mod multi_token_imbalances;
mod tests;
mod tests_currency_adapter;
mod tests_events;
mod tests_fungibles;
mod tests_multicurrency;
mod weights;
mod benchmarking;
pub use impls::*;
pub use weights::WeightInfo;
pub struct TransferDust<T, GetAccountId>(marker::PhantomData<(T, GetAccountId)>);
impl<T, GetAccountId> OnDust<T::AccountId, T::CurrencyId, T::Balance> for TransferDust<T, GetAccountId>
where
T: Config,
GetAccountId: Get<T::AccountId>,
{
fn on_dust(who: &T::AccountId, currency_id: T::CurrencyId, amount: T::Balance) {
let _ = Pallet::<T>::do_transfer(
currency_id,
who,
&GetAccountId::get(),
amount,
ExistenceRequirement::AllowDeath,
);
}
}
pub struct BurnDust<T>(marker::PhantomData<T>);
impl<T: Config> OnDust<T::AccountId, T::CurrencyId, T::Balance> for BurnDust<T> {
fn on_dust(who: &T::AccountId, currency_id: T::CurrencyId, amount: T::Balance) {
let _ = Pallet::<T>::do_withdraw(currency_id, who, amount, ExistenceRequirement::AllowDeath, true);
}
}
#[derive(Encode, Decode, Clone, PartialEq, Eq, MaxEncodedLen, RuntimeDebug, TypeInfo)]
pub struct BalanceLock<Balance> {
pub id: LockIdentifier,
pub amount: Balance,
}
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, MaxEncodedLen, TypeInfo)]
pub struct ReserveData<ReserveIdentifier, Balance> {
pub id: ReserveIdentifier,
pub amount: Balance,
}
#[derive(Encode, Decode, Clone, PartialEq, Eq, Default, MaxEncodedLen, RuntimeDebug, TypeInfo)]
pub struct AccountData<Balance> {
pub free: Balance,
pub reserved: Balance,
pub frozen: Balance,
}
impl<Balance: Saturating + Copy + Ord> AccountData<Balance> {
pub(crate) fn frozen(&self) -> Balance {
self.frozen
}
fn total(&self) -> Balance {
self.free.saturating_add(self.reserved)
}
}
pub use module::*;
#[frame_support::pallet]
pub mod module {
use orml_traits::currency::MutationHooks;
use super::*;
#[pallet::config]
pub trait Config: frame_system::Config {
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
type Balance: Parameter
+ Member
+ AtLeast32BitUnsigned
+ Default
+ Copy
+ MaybeSerializeDeserialize
+ MaxEncodedLen
+ TypeInfo
+ FixedPointOperand
+ Into<u128>
+ TryFrom<U256>;
type Amount: Signed
+ TryInto<Self::Balance>
+ TryFrom<Self::Balance>
+ Parameter
+ Member
+ arithmetic::SimpleArithmetic
+ Default
+ Copy
+ MaybeSerializeDeserialize
+ MaxEncodedLen
+ TypeInfo;
type CurrencyId: Parameter
+ Member
+ Copy
+ MaybeSerializeDeserialize
+ Ord
+ TypeInfo
+ MaxEncodedLen
+ Default
+ AtLeast32BitUnsigned
+ FullCodec;
type WeightInfo: WeightInfo;
type ExistentialDeposits: GetByKey<Self::CurrencyId, Self::Balance>;
type CurrencyHooks: MutationHooks<Self::AccountId, Self::CurrencyId, Self::Balance>;
#[pallet::constant]
type MaxLocks: Get<u32>;
#[pallet::constant]
type MaxReserves: Get<u32>;
type ReserveIdentifier: Parameter + Member + MaxEncodedLen + Ord + Copy;
type DustRemovalWhitelist: Contains<Self::AccountId>;
}
#[pallet::error]
pub enum Error<T> {
BalanceTooLow,
AmountIntoBalanceFailed,
LiquidityRestrictions,
MaxLocksExceeded,
KeepAlive,
ExistentialDeposit,
DeadAccount,
TokenIdNotExists,
TooManyReserves,
}
#[pallet::event]
#[pallet::generate_deposit(pub(crate) fn deposit_event)]
pub enum Event<T: Config> {
Endowed {
currency_id: T::CurrencyId,
who: T::AccountId,
amount: T::Balance,
},
DustLost {
currency_id: T::CurrencyId,
who: T::AccountId,
amount: T::Balance,
},
Transfer {
currency_id: T::CurrencyId,
from: T::AccountId,
to: T::AccountId,
amount: T::Balance,
},
Reserved {
currency_id: T::CurrencyId,
who: T::AccountId,
amount: T::Balance,
},
Unreserved {
currency_id: T::CurrencyId,
who: T::AccountId,
amount: T::Balance,
},
ReserveRepatriated {
currency_id: T::CurrencyId,
from: T::AccountId,
to: T::AccountId,
amount: T::Balance,
status: BalanceStatus,
},
BalanceSet {
currency_id: T::CurrencyId,
who: T::AccountId,
free: T::Balance,
reserved: T::Balance,
},
TotalIssuanceSet {
currency_id: T::CurrencyId,
amount: T::Balance,
},
Withdrawn {
currency_id: T::CurrencyId,
who: T::AccountId,
amount: T::Balance,
},
Slashed {
currency_id: T::CurrencyId,
who: T::AccountId,
free_amount: T::Balance,
reserved_amount: T::Balance,
},
Deposited {
currency_id: T::CurrencyId,
who: T::AccountId,
amount: T::Balance,
},
LockSet {
lock_id: LockIdentifier,
currency_id: T::CurrencyId,
who: T::AccountId,
amount: T::Balance,
},
LockRemoved {
lock_id: LockIdentifier,
currency_id: T::CurrencyId,
who: T::AccountId,
},
Created(T::CurrencyId, T::AccountId, T::Balance),
Minted(T::CurrencyId, T::AccountId, T::Balance),
Locked {
currency_id: T::CurrencyId,
who: T::AccountId,
amount: T::Balance,
},
Unlocked {
currency_id: T::CurrencyId,
who: T::AccountId,
amount: T::Balance,
},
Issued {
currency_id: T::CurrencyId,
amount: T::Balance,
},
Rescinded {
currency_id: T::CurrencyId,
amount: T::Balance,
},
}
#[pallet::storage]
#[pallet::getter(fn total_issuance)]
pub type TotalIssuance<T: Config> = StorageMap<_, Twox64Concat, T::CurrencyId, T::Balance, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn next_asset_id)]
pub type NextCurrencyId<T: Config> = StorageValue<_, T::CurrencyId, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn locks)]
pub type Locks<T: Config> = StorageDoubleMap<
_,
Blake2_128Concat,
T::AccountId,
Twox64Concat,
T::CurrencyId,
BoundedVec<BalanceLock<T::Balance>, T::MaxLocks>,
ValueQuery,
>;
#[pallet::storage]
#[pallet::getter(fn accounts)]
pub type Accounts<T: Config> = StorageDoubleMap<
_,
Blake2_128Concat,
T::AccountId,
Twox64Concat,
T::CurrencyId,
AccountData<T::Balance>,
ValueQuery,
>;
#[pallet::storage]
#[pallet::getter(fn reserves)]
pub type Reserves<T: Config> = StorageDoubleMap<
_,
Blake2_128Concat,
T::AccountId,
Twox64Concat,
T::CurrencyId,
BoundedVec<ReserveData<T::ReserveIdentifier, T::Balance>, T::MaxReserves>,
ValueQuery,
>;
#[pallet::genesis_config]
pub struct GenesisConfig<T: Config> {
pub tokens_endowment: Vec<(T::AccountId, T::CurrencyId, T::Balance)>,
pub created_tokens_for_staking: Vec<(T::AccountId, T::CurrencyId, T::Balance)>,
}
impl<T: Config> Default for GenesisConfig<T> {
fn default() -> Self {
GenesisConfig {
tokens_endowment: vec![],
created_tokens_for_staking: vec![],
}
}
}
#[pallet::genesis_build]
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
fn build(&self) {
self.tokens_endowment
.iter()
.for_each(|(account_id, token_id, initial_balance)| {
if MultiTokenCurrencyAdapter::<T>::exists(*token_id) {
assert!(
MultiTokenCurrencyAdapter::<T>::mint(*token_id, account_id, *initial_balance).is_ok(),
"Tokens mint failed"
);
} else {
let created_token_id = MultiTokenCurrencyAdapter::<T>::create(account_id, *initial_balance)
.expect("Token creation failed");
assert!(
created_token_id == *token_id,
"Assets not initialized in the expected sequence"
);
}
});
self.created_tokens_for_staking
.iter()
.for_each(|(account_id, token_id, initial_balance)| {
if MultiTokenCurrencyAdapter::<T>::exists(*token_id) {
assert!(
MultiTokenCurrencyAdapter::<T>::mint(*token_id, account_id, *initial_balance).is_ok(),
"Tokens mint failed"
);
} else {
let created_token_id = MultiTokenCurrencyAdapter::<T>::create(account_id, *initial_balance)
.expect("Token creation failed");
assert!(
created_token_id == *token_id,
"Assets not initialized in the expected sequence"
);
}
})
}
}
#[pallet::pallet]
pub struct Pallet<T>(_);
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {}
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::call_index(0)]
#[pallet::weight(T::WeightInfo::transfer())]
pub fn transfer(
origin: OriginFor<T>,
dest: <T::Lookup as StaticLookup>::Source,
currency_id: T::CurrencyId,
#[pallet::compact] amount: T::Balance,
) -> DispatchResult {
let from = ensure_signed(origin)?;
let to = T::Lookup::lookup(dest)?;
Self::do_transfer(currency_id, &from, &to, amount, ExistenceRequirement::AllowDeath)
}
#[pallet::call_index(1)]
#[pallet::weight(T::WeightInfo::transfer_all())]
pub fn transfer_all(
origin: OriginFor<T>,
dest: <T::Lookup as StaticLookup>::Source,
currency_id: T::CurrencyId,
keep_alive: bool,
) -> DispatchResult {
let from = ensure_signed(origin)?;
let to = T::Lookup::lookup(dest)?;
let preservation = if keep_alive {
Preservation::Protect
} else {
Preservation::Expendable
};
let reducible_balance = <Self as fungibles::Inspect<T::AccountId>>::reducible_balance(
currency_id,
&from,
preservation,
Fortitude::Polite,
);
<Self as fungibles::Mutate<_>>::transfer(currency_id, &from, &to, reducible_balance, preservation)
.map(|_| ())
}
#[pallet::call_index(2)]
#[pallet::weight(T::WeightInfo::transfer_keep_alive())]
pub fn transfer_keep_alive(
origin: OriginFor<T>,
dest: <T::Lookup as StaticLookup>::Source,
currency_id: T::CurrencyId,
#[pallet::compact] amount: T::Balance,
) -> DispatchResultWithPostInfo {
let from = ensure_signed(origin)?;
let to = T::Lookup::lookup(dest)?;
Self::do_transfer(currency_id, &from, &to, amount, ExistenceRequirement::KeepAlive)?;
Ok(().into())
}
#[pallet::call_index(3)]
#[pallet::weight(T::WeightInfo::force_transfer())]
pub fn force_transfer(
origin: OriginFor<T>,
source: <T::Lookup as StaticLookup>::Source,
dest: <T::Lookup as StaticLookup>::Source,
currency_id: T::CurrencyId,
#[pallet::compact] amount: T::Balance,
) -> DispatchResult {
ensure_root(origin)?;
let from = T::Lookup::lookup(source)?;
let to = T::Lookup::lookup(dest)?;
Self::do_transfer(currency_id, &from, &to, amount, ExistenceRequirement::AllowDeath)
}
#[pallet::call_index(4)]
#[pallet::weight(T::WeightInfo::set_balance())]
pub fn set_balance(
origin: OriginFor<T>,
who: <T::Lookup as StaticLookup>::Source,
currency_id: T::CurrencyId,
#[pallet::compact] new_free: T::Balance,
#[pallet::compact] new_reserved: T::Balance,
) -> DispatchResult {
ensure_root(origin)?;
let who = T::Lookup::lookup(who)?;
Self::try_mutate_account(&who, currency_id, |account, _| -> DispatchResult {
let mut new_total = new_free.checked_add(&new_reserved).ok_or(ArithmeticError::Overflow)?;
let (new_free, new_reserved) = if new_total < T::ExistentialDeposits::get(¤cy_id) {
new_total = Zero::zero();
(Zero::zero(), Zero::zero())
} else {
(new_free, new_reserved)
};
let old_total = account.total();
account.free = new_free;
account.reserved = new_reserved;
if new_total > old_total {
TotalIssuance::<T>::try_mutate(currency_id, |t| -> DispatchResult {
*t = t
.checked_add(&(new_total.defensive_saturating_sub(old_total)))
.ok_or(ArithmeticError::Overflow)?;
Ok(())
})?;
} else if new_total < old_total {
TotalIssuance::<T>::try_mutate(currency_id, |t| -> DispatchResult {
*t = t
.checked_sub(&(old_total.defensive_saturating_sub(new_total)))
.ok_or(ArithmeticError::Underflow)?;
Ok(())
})?;
}
Self::deposit_event(Event::BalanceSet {
currency_id,
who: who.clone(),
free: new_free,
reserved: new_reserved,
});
Ok(())
})?;
Ok(())
}
#[pallet::call_index(5)]
#[pallet::weight(T::WeightInfo::create())]
pub fn create(
origin: OriginFor<T>,
who: <T::Lookup as StaticLookup>::Source,
#[pallet::compact] amount: T::Balance,
) -> DispatchResultWithPostInfo {
ensure_root(origin)?;
let who = T::Lookup::lookup(who)?;
let currency_id = MultiTokenCurrencyAdapter::<T>::create(&who, amount)?;
Self::deposit_event(Event::Created(currency_id, who, amount));
Ok(().into())
}
#[pallet::call_index(6)]
#[pallet::weight(T::WeightInfo::mint())]
pub fn mint(
origin: OriginFor<T>,
currency_id: T::CurrencyId,
who: <T::Lookup as StaticLookup>::Source,
#[pallet::compact] amount: T::Balance,
) -> DispatchResultWithPostInfo {
ensure_root(origin)?;
let who = T::Lookup::lookup(who)?;
MultiTokenCurrencyAdapter::<T>::mint(currency_id, &who, amount)?;
Self::deposit_event(Event::Minted(currency_id, who, amount));
Ok(().into())
}
}
}
impl<T: Config> Pallet<T> {
pub(crate) fn deposit_consequence(
_who: &T::AccountId,
currency_id: T::CurrencyId,
amount: T::Balance,
account: &AccountData<T::Balance>,
) -> DepositConsequence {
if amount.is_zero() {
return DepositConsequence::Success;
}
if TotalIssuance::<T>::get(currency_id).checked_add(&amount).is_none() {
return DepositConsequence::Overflow;
}
let new_total_balance = match account.total().checked_add(&amount) {
Some(x) => x,
None => return DepositConsequence::Overflow,
};
if new_total_balance < T::ExistentialDeposits::get(¤cy_id) {
return DepositConsequence::BelowMinimum;
}
DepositConsequence::Success
}
pub(crate) fn withdraw_consequence(
who: &T::AccountId,
currency_id: T::CurrencyId,
amount: T::Balance,
account: &AccountData<T::Balance>,
) -> WithdrawConsequence<T::Balance> {
if amount.is_zero() {
return WithdrawConsequence::Success;
}
if TotalIssuance::<T>::get(currency_id).checked_sub(&amount).is_none() {
return WithdrawConsequence::Underflow;
}
let new_total_balance = match account.total().checked_sub(&amount) {
Some(x) => x,
None => return WithdrawConsequence::BalanceLow,
};
let ed = T::ExistentialDeposits::get(¤cy_id);
let success = if new_total_balance < ed {
if frame_system::Pallet::<T>::can_dec_provider(who) {
WithdrawConsequence::ReducedToZero(new_total_balance)
} else {
return WithdrawConsequence::WouldDie;
}
} else {
WithdrawConsequence::Success
};
let new_free_balance = match account.free.checked_sub(&amount) {
Some(b) => b,
None => return WithdrawConsequence::BalanceLow,
};
if new_free_balance < account.frozen() {
return WithdrawConsequence::Frozen;
}
success
}
pub(crate) fn ensure_can_withdraw(
currency_id: T::CurrencyId,
who: &T::AccountId,
amount: T::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(())
}
pub(crate) 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, Option<T::Balance>), E> {
Accounts::<T>::try_mutate_exists(who, currency_id, |maybe_account| {
let existed = maybe_account.is_some();
let mut account = maybe_account.take().unwrap_or_default();
f(&mut account, existed).map(move |result| {
let maybe_endowed = if !existed { Some(account.free) } else { None };
let mut maybe_dust: Option<T::Balance> = None;
let total = account.total();
*maybe_account = if total < T::ExistentialDeposits::get(¤cy_id) {
if total.is_zero() {
None
} else {
if !T::DustRemovalWhitelist::contains(who) {
maybe_dust = Some(total);
}
Some(account)
}
} else {
Some(account)
};
(maybe_endowed, existed, maybe_account.is_some(), maybe_dust, result)
})
})
.map(|(maybe_endowed, existed, exists, maybe_dust, result)| {
if existed && !exists {
let _ = frame_system::Pallet::<T>::dec_providers(who);
<T::CurrencyHooks as MutationHooks<T::AccountId, T::CurrencyId, T::Balance>>::OnKilledTokenAccount::happened(&(who.clone(), currency_id));
} else if !existed && exists {
frame_system::Pallet::<T>::inc_providers(who);
<T::CurrencyHooks as MutationHooks<T::AccountId, T::CurrencyId, T::Balance>>::OnNewTokenAccount::happened(&(who.clone(), currency_id));
}
if let Some(endowed) = maybe_endowed {
Self::deposit_event(Event::Endowed {
currency_id,
who: who.clone(),
amount: endowed,
});
}
if let Some(dust_amount) = maybe_dust {
<T::CurrencyHooks as MutationHooks<T::AccountId, T::CurrencyId, T::Balance>>::OnDust::on_dust(who, currency_id, dust_amount);
Self::deposit_event(Event::DustLost {
currency_id,
who: who.clone(),
amount: dust_amount,
});
}
(result, maybe_dust)
})
}
pub(crate) fn mutate_account<R>(
who: &T::AccountId,
currency_id: T::CurrencyId,
f: impl FnOnce(&mut AccountData<T::Balance>, bool) -> R,
) -> (R, Option<T::Balance>) {
Self::try_mutate_account(who, currency_id, |account, existed| -> Result<R, Infallible> {
Ok(f(account, existed))
})
.expect("Error is infallible; qed")
}
pub(crate) fn set_free_balance(currency_id: T::CurrencyId, who: &T::AccountId, amount: T::Balance) {
Self::mutate_account(who, currency_id, |account, _| {
account.free = amount;
Self::deposit_event(Event::BalanceSet {
currency_id,
who: who.clone(),
free: account.free,
reserved: account.reserved,
});
});
}
pub(crate) fn set_reserved_balance(currency_id: T::CurrencyId, who: &T::AccountId, amount: T::Balance) {
Self::mutate_account(who, currency_id, |account, _| {
account.reserved = amount;
Self::deposit_event(Event::BalanceSet {
currency_id,
who: who.clone(),
free: account.free,
reserved: account.reserved,
});
});
}
pub(crate) fn update_locks(
currency_id: T::CurrencyId,
who: &T::AccountId,
locks: &[BalanceLock<T::Balance>],
) -> DispatchResult {
let mut total_frozen_prev = Zero::zero();
let mut total_frozen_after = Zero::zero();
Self::mutate_account(who, currency_id, |account, _| {
total_frozen_prev = account.frozen;
account.frozen = Zero::zero();
for lock in locks.iter() {
account.frozen = account.frozen.max(lock.amount);
}
total_frozen_after = account.frozen;
});
let existed = Locks::<T>::contains_key(who, currency_id);
if locks.is_empty() {
Locks::<T>::remove(who, currency_id);
if existed {
frame_system::Pallet::<T>::dec_consumers(who);
}
} else {
let bounded_locks: BoundedVec<BalanceLock<T::Balance>, T::MaxLocks> =
locks.to_vec().try_into().map_err(|_| Error::<T>::MaxLocksExceeded)?;
Locks::<T>::insert(who, currency_id, bounded_locks);
if !existed {
if frame_system::Pallet::<T>::inc_consumers(who).is_err() {
log::warn!(
"Warning: Attempt to introduce lock consumer reference, yet no providers. \
This is unexpected but should be safe."
);
}
}
}
if total_frozen_prev < total_frozen_after {
let amount = total_frozen_after.saturating_sub(total_frozen_prev);
Self::deposit_event(Event::Locked {
currency_id,
who: who.clone(),
amount,
});
} else if total_frozen_prev > total_frozen_after {
let amount = total_frozen_prev.saturating_sub(total_frozen_after);
Self::deposit_event(Event::Unlocked {
currency_id,
who: who.clone(),
amount,
});
}
Ok(())
}
pub(crate) fn do_transfer(
currency_id: T::CurrencyId,
from: &T::AccountId,
to: &T::AccountId,
amount: T::Balance,
existence_requirement: ExistenceRequirement,
) -> DispatchResult {
if amount.is_zero() || from == to {
return Ok(());
}
<T::CurrencyHooks as MutationHooks<T::AccountId, T::CurrencyId, T::Balance>>::PreTransfer::on_transfer(
currency_id,
from,
to,
amount,
)?;
Self::try_mutate_account(to, currency_id, |to_account, _existed| -> DispatchResult {
Self::try_mutate_account(from, currency_id, |from_account, _existed| -> DispatchResult {
from_account.free = from_account
.free
.checked_sub(&amount)
.ok_or(Error::<T>::BalanceTooLow)?;
to_account.free = to_account.free.checked_add(&amount).ok_or(ArithmeticError::Overflow)?;
let ed = T::ExistentialDeposits::get(¤cy_id);
ensure!(
to_account.total() >= ed || T::DustRemovalWhitelist::contains(to),
Error::<T>::ExistentialDeposit
);
Self::ensure_can_withdraw(currency_id, from, amount)?;
let allow_death = existence_requirement == ExistenceRequirement::AllowDeath;
let allow_death = allow_death && frame_system::Pallet::<T>::can_dec_provider(from);
let would_be_dead = if from_account.total() < ed {
if from_account.total().is_zero() {
true
} else {
!T::DustRemovalWhitelist::contains(from)
}
} else {
false
};
ensure!(allow_death || !would_be_dead, Error::<T>::KeepAlive);
Ok(())
})?;
Ok(())
})?;
<T::CurrencyHooks as MutationHooks<T::AccountId, T::CurrencyId, T::Balance>>::PostTransfer::on_transfer(
currency_id,
from,
to,
amount,
)?;
Self::deposit_event(Event::Transfer {
currency_id,
from: from.clone(),
to: to.clone(),
amount,
});
Ok(())
}
pub(crate) fn do_withdraw(
currency_id: T::CurrencyId,
who: &T::AccountId,
amount: T::Balance,
existence_requirement: ExistenceRequirement,
change_total_issuance: bool,
) -> DispatchResult {
if amount.is_zero() {
return Ok(());
}
Self::try_mutate_account(who, currency_id, |account, _existed| -> DispatchResult {
Self::ensure_can_withdraw(currency_id, who, amount)?;
let previous_total = account.total();
account.free = account.free.defensive_saturating_sub(amount);
let ed = T::ExistentialDeposits::get(¤cy_id);
let would_be_dead = if account.total() < ed {
if account.total().is_zero() {
true
} else {
!T::DustRemovalWhitelist::contains(who)
}
} else {
false
};
let would_kill = would_be_dead && (previous_total >= ed || !previous_total.is_zero());
ensure!(
existence_requirement == ExistenceRequirement::AllowDeath || !would_kill,
Error::<T>::KeepAlive
);
if change_total_issuance {
TotalIssuance::<T>::mutate(currency_id, |v| *v = v.defensive_saturating_sub(amount));
}
Self::deposit_event(Event::Withdrawn {
currency_id,
who: who.clone(),
amount,
});
Ok(())
})?;
Ok(())
}
pub(crate) fn do_deposit(
currency_id: T::CurrencyId,
who: &T::AccountId,
amount: T::Balance,
require_existed: bool,
change_total_issuance: bool,
) -> Result<T::Balance, DispatchError> {
if amount.is_zero() {
return Ok(amount);
}
<T::CurrencyHooks as MutationHooks<T::AccountId, T::CurrencyId, T::Balance>>::PreDeposit::on_deposit(
currency_id,
who,
amount,
)?;
Self::try_mutate_account(who, currency_id, |account, existed| -> DispatchResult {
if require_existed {
ensure!(existed, Error::<T>::DeadAccount);
} else {
let ed = T::ExistentialDeposits::get(¤cy_id);
ensure!(
amount >= ed || existed || T::DustRemovalWhitelist::contains(who),
Error::<T>::ExistentialDeposit
);
}
let new_total_issuance = Self::total_issuance(currency_id)
.checked_add(&amount)
.ok_or(ArithmeticError::Overflow)?;
if change_total_issuance {
TotalIssuance::<T>::mutate(currency_id, |v| *v = new_total_issuance);
}
account.free = account.free.defensive_saturating_add(amount);
Ok(())
})?;
<T::CurrencyHooks as MutationHooks<T::AccountId, T::CurrencyId, T::Balance>>::PostDeposit::on_deposit(
currency_id,
who,
amount,
)?;
Self::deposit_event(Event::Deposited {
currency_id,
who: who.clone(),
amount,
});
Ok(amount)
}
}
impl<T: Config> MultiCurrency<T::AccountId> for Pallet<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 {
Self::total_issuance(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
}
fn ensure_can_withdraw(currency_id: Self::CurrencyId, who: &T::AccountId, amount: Self::Balance) -> DispatchResult {
Self::ensure_can_withdraw(currency_id, who, amount)
}
fn transfer(
currency_id: Self::CurrencyId,
from: &T::AccountId,
to: &T::AccountId,
amount: Self::Balance,
) -> DispatchResult {
Self::do_transfer(currency_id, from, to, amount, ExistenceRequirement::AllowDeath)
}
fn deposit(currency_id: Self::CurrencyId, who: &T::AccountId, amount: Self::Balance) -> DispatchResult {
Self::do_deposit(currency_id, who, amount, false, true)?;
Ok(())
}
fn withdraw(currency_id: Self::CurrencyId, who: &T::AccountId, amount: Self::Balance) -> DispatchResult {
Self::do_withdraw(currency_id, who, amount, ExistenceRequirement::AllowDeath, true)
}
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
}
fn slash(currency_id: Self::CurrencyId, who: &T::AccountId, amount: Self::Balance) -> Self::Balance {
if amount.is_zero() {
return amount;
}
<T::CurrencyHooks as MutationHooks<T::AccountId, T::CurrencyId, T::Balance>>::OnSlash::on_slash(
currency_id,
who,
amount,
);
let account = Self::accounts(who, currency_id);
let free_slashed_amount = account.free.min(amount);
let mut remaining_slash = amount.defensive_saturating_sub(free_slashed_amount);
if !free_slashed_amount.is_zero() {
Self::set_free_balance(
currency_id,
who,
account.free.defensive_saturating_sub(free_slashed_amount),
);
}
let reserved_slashed_amount = account.reserved.min(remaining_slash);
if !reserved_slashed_amount.is_zero() {
remaining_slash = remaining_slash.defensive_saturating_sub(reserved_slashed_amount);
Self::set_reserved_balance(
currency_id,
who,
account.reserved.defensive_saturating_sub(reserved_slashed_amount),
);
}
TotalIssuance::<T>::mutate(currency_id, |v| {
*v = v.defensive_saturating_sub(amount.defensive_saturating_sub(remaining_slash))
});
Self::deposit_event(Event::Slashed {
currency_id,
who: who.clone(),
free_amount: free_slashed_amount,
reserved_amount: reserved_slashed_amount,
});
remaining_slash
}
}
impl<T: Config> MultiCurrencyExtended<T::AccountId> for Pallet<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(());
}
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: Config> MultiLockableCurrency<T::AccountId> for Pallet<T> {
type Moment = BlockNumberFor<T>;
fn set_lock(
lock_id: LockIdentifier,
currency_id: Self::CurrencyId,
who: &T::AccountId,
amount: Self::Balance,
) -> DispatchResult {
if amount.is_zero() {
return Ok(());
}
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[..])?;
Self::deposit_event(Event::LockSet {
lock_id,
currency_id,
who: who.clone(),
amount,
});
Ok(())
}
fn extend_lock(
lock_id: LockIdentifier,
currency_id: Self::CurrencyId,
who: &T::AccountId,
amount: Self::Balance,
) -> DispatchResult {
if amount.is_zero() {
return Ok(());
}
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| {
let new_amount = lock.amount.max(nl.amount);
Self::deposit_event(Event::LockSet {
lock_id,
currency_id,
who: who.clone(),
amount: new_amount,
});
BalanceLock {
id: lock.id,
amount: new_amount,
}
})
} else {
Some(lock)
}
})
.collect::<Vec<_>>();
if let Some(lock) = new_lock {
Self::deposit_event(Event::LockSet {
lock_id,
currency_id,
who: who.clone(),
amount: lock.amount,
});
locks.push(lock)
}
Self::update_locks(currency_id, who, &locks[..])
}
fn remove_lock(lock_id: LockIdentifier, currency_id: Self::CurrencyId, who: &T::AccountId) -> DispatchResult {
let mut locks = Self::locks(who, currency_id);
locks.retain(|lock| lock.id != lock_id);
let locks_vec = locks.to_vec();
Self::update_locks(currency_id, who, &locks_vec[..])?;
Self::deposit_event(Event::LockRemoved {
lock_id,
currency_id,
who: who.clone(),
});
Ok(())
}
}
impl<T: Config> MultiReservableCurrency<T::AccountId> for Pallet<T> {
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()
}
fn slash_reserved(currency_id: Self::CurrencyId, who: &T::AccountId, value: Self::Balance) -> Self::Balance {
if value.is_zero() {
return value;
}
<T::CurrencyHooks as MutationHooks<T::AccountId, T::CurrencyId, T::Balance>>::OnSlash::on_slash(
currency_id,
who,
value,
);
let reserved_balance = Self::reserved_balance(currency_id, who);
let actual = reserved_balance.min(value);
Self::mutate_account(who, currency_id, |account, _| {
account.reserved = reserved_balance.defensive_saturating_sub(actual);
});
TotalIssuance::<T>::mutate(currency_id, |v| *v = v.defensive_saturating_sub(actual));
Self::deposit_event(Event::Slashed {
currency_id,
who: who.clone(),
free_amount: Zero::zero(),
reserved_amount: actual,
});
value.defensive_saturating_sub(actual)
}
fn reserved_balance(currency_id: Self::CurrencyId, who: &T::AccountId) -> Self::Balance {
Self::accounts(who, currency_id).reserved
}
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)?;
Self::mutate_account(who, currency_id, |account, _| {
account.free = account.free.defensive_saturating_sub(value);
account.reserved = account.reserved.defensive_saturating_add(value);
Self::deposit_event(Event::Reserved {
currency_id,
who: who.clone(),
amount: value,
});
});
Ok(())
}
fn unreserve(currency_id: Self::CurrencyId, who: &T::AccountId, value: Self::Balance) -> Self::Balance {
if value.is_zero() {
return value;
}
let (remaining, _) = Self::mutate_account(who, currency_id, |account, _| {
let actual = account.reserved.min(value);
account.reserved = account.reserved.defensive_saturating_sub(actual);
account.free = account.free.defensive_saturating_add(actual);
Self::deposit_event(Event::Unreserved {
currency_id,
who: who.clone(),
amount: actual,
});
value.defensive_saturating_sub(actual)
});
remaining
}
fn repatriate_reserved(
currency_id: Self::CurrencyId,
slashed: &T::AccountId,
beneficiary: &T::AccountId,
value: Self::Balance,
status: BalanceStatus,
) -> sp_std::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.defensive_saturating_add(actual),
);
}
BalanceStatus::Reserved => {
Self::set_reserved_balance(
currency_id,
beneficiary,
to_account.reserved.defensive_saturating_add(actual),
);
}
}
Self::set_reserved_balance(
currency_id,
slashed,
from_account.reserved.defensive_saturating_sub(actual),
);
Self::deposit_event(Event::<T>::ReserveRepatriated {
currency_id,
from: slashed.clone(),
to: beneficiary.clone(),
amount: actual,
status,
});
Ok(value.defensive_saturating_sub(actual))
}
}
impl<T: Config> NamedMultiReservableCurrency<T::AccountId> for Pallet<T> {
type ReserveIdentifier = T::ReserveIdentifier;
fn reserved_balance_named(
id: &Self::ReserveIdentifier,
currency_id: Self::CurrencyId,
who: &T::AccountId,
) -> Self::Balance {
let reserves = Self::reserves(who, currency_id);
reserves
.binary_search_by_key(id, |data| data.id)
.map(|index| reserves[index].amount)
.unwrap_or_default()
}
fn reserve_named(
id: &Self::ReserveIdentifier,
currency_id: Self::CurrencyId,
who: &T::AccountId,
value: Self::Balance,
) -> DispatchResult {
if value.is_zero() {
return Ok(());
}
Reserves::<T>::try_mutate(who, currency_id, |reserves| -> DispatchResult {
match reserves.binary_search_by_key(id, |data| data.id) {
Ok(index) => {
reserves[index].amount = reserves[index].amount.defensive_saturating_add(value);
}
Err(index) => {
reserves
.try_insert(index, ReserveData { id: *id, amount: value })
.map_err(|_| Error::<T>::TooManyReserves)?;
}
};
<Self as MultiReservableCurrency<_>>::reserve(currency_id, who, value)
})
}
fn unreserve_named(
id: &Self::ReserveIdentifier,
currency_id: Self::CurrencyId,
who: &T::AccountId,
value: Self::Balance,
) -> Self::Balance {
if value.is_zero() {
return Zero::zero();
}
Reserves::<T>::mutate_exists(who, currency_id, |maybe_reserves| -> Self::Balance {
if let Some(reserves) = maybe_reserves.as_mut() {
match reserves.binary_search_by_key(id, |data| data.id) {
Ok(index) => {
let to_change = cmp::min(reserves[index].amount, value);
let remain = <Self as MultiReservableCurrency<_>>::unreserve(currency_id, who, to_change);
let actual = to_change.defensive_saturating_sub(remain);
reserves[index].amount = reserves[index].amount.defensive_saturating_sub(actual);
if reserves[index].amount.is_zero() {
if reserves.len() == 1 {
*maybe_reserves = None;
} else {
reserves.remove(index);
}
}
value.defensive_saturating_sub(actual)
}
Err(_) => value,
}
} else {
value
}
})
}
fn slash_reserved_named(
id: &Self::ReserveIdentifier,
currency_id: Self::CurrencyId,
who: &T::AccountId,
value: Self::Balance,
) -> Self::Balance {
if value.is_zero() {
return Zero::zero();
}
Reserves::<T>::mutate(who, currency_id, |reserves| -> Self::Balance {
match reserves.binary_search_by_key(id, |data| data.id) {
Ok(index) => {
let to_change = cmp::min(reserves[index].amount, value);
let remain = <Self as MultiReservableCurrency<_>>::slash_reserved(currency_id, who, to_change);
let actual = to_change.defensive_saturating_sub(remain);
reserves[index].amount = reserves[index].amount.defensive_saturating_sub(actual);
Self::deposit_event(Event::Slashed {
who: who.clone(),
currency_id,
free_amount: Zero::zero(),
reserved_amount: actual,
});
value.defensive_saturating_sub(actual)
}
Err(_) => value,
}
})
}
fn repatriate_reserved_named(
id: &Self::ReserveIdentifier,
currency_id: Self::CurrencyId,
slashed: &T::AccountId,
beneficiary: &T::AccountId,
value: Self::Balance,
status: Status,
) -> Result<Self::Balance, DispatchError> {
if value.is_zero() {
return Ok(Zero::zero());
}
if slashed == beneficiary {
return match status {
Status::Free => Ok(Self::unreserve_named(id, currency_id, slashed, value)),
Status::Reserved => Ok(value.saturating_sub(Self::reserved_balance_named(id, currency_id, slashed))),
};
}
Reserves::<T>::try_mutate(
slashed,
currency_id,
|reserves| -> Result<Self::Balance, DispatchError> {
match reserves.binary_search_by_key(id, |data| data.id) {
Ok(index) => {
let to_change = cmp::min(reserves[index].amount, value);
let actual = if status == Status::Reserved {
Reserves::<T>::try_mutate(
beneficiary,
currency_id,
|reserves| -> Result<T::Balance, DispatchError> {
match reserves.binary_search_by_key(id, |data| data.id) {
Ok(index) => {
let remain = <Self as MultiReservableCurrency<_>>::repatriate_reserved(
currency_id,
slashed,
beneficiary,
to_change,
status,
)?;
let actual = to_change.defensive_saturating_sub(remain);
reserves[index].amount =
reserves[index].amount.defensive_saturating_add(actual);
Ok(actual)
}
Err(index) => {
let remain = <Self as MultiReservableCurrency<_>>::repatriate_reserved(
currency_id,
slashed,
beneficiary,
to_change,
status,
)?;
let actual = to_change.defensive_saturating_sub(remain);
reserves
.try_insert(
index,
ReserveData {
id: *id,
amount: actual,
},
)
.map_err(|_| Error::<T>::TooManyReserves)?;
Ok(actual)
}
}
},
)?
} else {
let remain = <Self as MultiReservableCurrency<_>>::repatriate_reserved(
currency_id,
slashed,
beneficiary,
to_change,
status,
)?;
to_change.defensive_saturating_sub(remain)
};
reserves[index].amount = reserves[index].amount.defensive_saturating_sub(actual);
Ok(value.defensive_saturating_sub(actual))
}
Err(_) => Ok(value),
}
},
)
}
}
impl<T: Config> fungibles::Inspect<T::AccountId> for Pallet<T> {
type AssetId = T::CurrencyId;
type Balance = T::Balance;
fn total_issuance(asset_id: Self::AssetId) -> Self::Balance {
Self::total_issuance(asset_id)
}
fn minimum_balance(asset_id: Self::AssetId) -> Self::Balance {
T::ExistentialDeposits::get(&asset_id)
}
fn balance(asset_id: Self::AssetId, who: &T::AccountId) -> Self::Balance {
Self::accounts(who, asset_id).free
}
fn total_balance(asset_id: Self::AssetId, who: &T::AccountId) -> Self::Balance {
Self::accounts(who, asset_id).total()
}
fn reducible_balance(
asset_id: Self::AssetId,
who: &T::AccountId,
preservation: Preservation,
_force: Fortitude,
) -> Self::Balance {
let a = Self::accounts(who, asset_id);
let liquid = a.free.saturating_sub(a.frozen);
if frame_system::Pallet::<T>::can_dec_provider(who) && !matches!(preservation, Preservation::Protect) {
liquid
} else {
let must_remain_to_exist =
T::ExistentialDeposits::get(&asset_id).saturating_sub(a.total().saturating_sub(liquid));
liquid.saturating_sub(must_remain_to_exist)
}
}
fn can_deposit(
asset_id: Self::AssetId,
who: &T::AccountId,
amount: Self::Balance,
_provenance: Provenance,
) -> DepositConsequence {
Self::deposit_consequence(who, asset_id, amount, &Self::accounts(who, asset_id))
}
fn can_withdraw(
asset_id: Self::AssetId,
who: &T::AccountId,
amount: Self::Balance,
) -> WithdrawConsequence<Self::Balance> {
Self::withdraw_consequence(who, asset_id, amount, &Self::accounts(who, asset_id))
}
fn asset_exists(asset: Self::AssetId) -> bool {
TotalIssuance::<T>::contains_key(asset)
}
}
impl<T: Config> fungibles::Mutate<T::AccountId> for Pallet<T> {
fn mint_into(
asset_id: Self::AssetId,
who: &T::AccountId,
amount: Self::Balance,
) -> Result<Self::Balance, DispatchError> {
Self::deposit_consequence(who, asset_id, amount, &Self::accounts(who, asset_id)).into_result()?;
Self::do_deposit(asset_id, who, amount, false, true)
}
fn burn_from(
asset_id: Self::AssetId,
who: &T::AccountId,
amount: Self::Balance,
_precision: Precision,
_fortitude: Fortitude,
) -> Result<Self::Balance, DispatchError> {
let extra =
Self::withdraw_consequence(who, asset_id, amount, &Self::accounts(who, asset_id)).into_result(false)?;
let actual = amount.defensive_saturating_add(extra);
Self::do_withdraw(asset_id, who, actual, ExistenceRequirement::AllowDeath, true).map(|_| actual)
}
fn transfer(
asset_id: Self::AssetId,
source: &T::AccountId,
dest: &T::AccountId,
amount: T::Balance,
preservation: Preservation,
) -> Result<T::Balance, DispatchError> {
let existence_requirement = match preservation {
Preservation::Expendable => ExistenceRequirement::AllowDeath,
Preservation::Protect | Preservation::Preserve => ExistenceRequirement::KeepAlive,
};
Self::do_transfer(asset_id, source, dest, amount, existence_requirement).map(|_| amount)
}
}
impl<T: Config> fungibles::Unbalanced<T::AccountId> for Pallet<T> {
fn handle_dust(_dust: fungibles::Dust<T::AccountId, Self>) {
}
fn write_balance(
asset_id: Self::AssetId,
who: &T::AccountId,
amount: Self::Balance,
) -> Result<Option<Self::Balance>, DispatchError> {
let max_reduction = <Self as fungibles::Inspect<_>>::reducible_balance(
asset_id,
who,
Preservation::Expendable,
Fortitude::Force,
);
let (_, dust_amount) = Self::try_mutate_account(who, asset_id, |account, _| -> Result<(), DispatchError> {
let reduction = account.free.saturating_sub(amount);
ensure!(reduction <= max_reduction, Error::<T>::BalanceTooLow);
account.free = amount;
Self::deposit_event(Event::BalanceSet {
currency_id: asset_id,
who: who.clone(),
free: account.free,
reserved: account.reserved,
});
Ok(())
})?;
Ok(dust_amount)
}
fn set_total_issuance(asset_id: Self::AssetId, amount: Self::Balance) {
TotalIssuance::<T>::mutate(asset_id, |t| *t = amount);
Self::deposit_event(Event::TotalIssuanceSet {
currency_id: asset_id,
amount,
});
}
fn decrease_balance(
asset: Self::AssetId,
who: &T::AccountId,
mut amount: Self::Balance,
precision: Precision,
preservation: Preservation,
force: Fortitude,
) -> Result<Self::Balance, DispatchError> {
let old_balance = <Pallet<T> as fungibles::Inspect<T::AccountId>>::balance(asset, who);
let free = <Pallet<T> as fungibles::Inspect<T::AccountId>>::reducible_balance(asset, who, preservation, force);
if let Precision::BestEffort = precision {
amount = amount.min(free);
}
let new_balance = old_balance.checked_sub(&amount).ok_or(TokenError::FundsUnavailable)?;
let _dust_amount = Self::write_balance(asset, who, new_balance)?.unwrap_or_default();
Ok(old_balance.saturating_sub(new_balance))
}
}
impl<T: Config> fungibles::Balanced<T::AccountId> for Pallet<T> {
type OnDropDebt = fungibles::IncreaseIssuance<T::AccountId, Self>;
type OnDropCredit = fungibles::DecreaseIssuance<T::AccountId, Self>;
fn done_deposit(currency_id: Self::AssetId, who: &T::AccountId, amount: Self::Balance) {
Self::deposit_event(Event::Deposited {
currency_id,
who: who.clone(),
amount,
});
}
fn done_withdraw(currency_id: Self::AssetId, who: &T::AccountId, amount: Self::Balance) {
Self::deposit_event(Event::Withdrawn {
currency_id,
who: who.clone(),
amount,
});
}
fn done_issue(currency_id: Self::AssetId, amount: Self::Balance) {
Self::deposit_event(Event::Issued { currency_id, amount });
}
fn done_rescind(currency_id: Self::AssetId, amount: Self::Balance) {
Self::deposit_event(Event::Rescinded { currency_id, amount });
}
}
type ReasonOf<P, T> = <P as fungibles::InspectHold<<T as frame_system::Config>::AccountId>>::Reason;
impl<T: Config> fungibles::InspectHold<T::AccountId> for Pallet<T> {
type Reason = ();
fn balance_on_hold(asset_id: Self::AssetId, _reason: &Self::Reason, who: &T::AccountId) -> T::Balance {
Self::accounts(who, asset_id).reserved
}
fn total_balance_on_hold(asset: Self::AssetId, who: &T::AccountId) -> Self::Balance {
Self::accounts(who, asset).reserved
}
fn reducible_total_balance_on_hold(_asset: Self::AssetId, _who: &T::AccountId, _force: Fortitude) -> Self::Balance {
0u32.into()
}
fn hold_available(_asset: Self::AssetId, _reason: &Self::Reason, _who: &T::AccountId) -> bool {
true
}
fn can_hold(asset_id: Self::AssetId, _reason: &Self::Reason, who: &T::AccountId, amount: T::Balance) -> bool {
let a = Self::accounts(who, asset_id);
let min_balance = T::ExistentialDeposits::get(&asset_id).max(a.frozen);
if a.reserved.checked_add(&amount).is_none() {
return false;
}
let required_free = match min_balance.checked_add(&amount) {
Some(x) => x,
None => return false,
};
a.free >= required_free
}
}
impl<T: Config> fungibles::MutateHold<T::AccountId> for Pallet<T> {
fn hold(
asset_id: Self::AssetId,
_reason: &ReasonOf<Self, T>,
who: &T::AccountId,
amount: Self::Balance,
) -> DispatchResult {
<Pallet<T> as MultiReservableCurrency<_>>::reserve(asset_id, who, amount)
}
fn release(
asset_id: Self::AssetId,
_reason: &ReasonOf<Self, T>,
who: &T::AccountId,
amount: Self::Balance,
precision: Precision,
) -> Result<T::Balance, DispatchError> {
if amount.is_zero() {
return Ok(amount);
}
let (released, _) =
Self::try_mutate_account(who, asset_id, |a, _existed| -> Result<T::Balance, DispatchError> {
let new_free = a.free.saturating_add(amount.min(a.reserved));
let actual = new_free.defensive_saturating_sub(a.free);
ensure!(
matches!(precision, Precision::BestEffort) || actual == amount,
Error::<T>::BalanceTooLow
);
a.free = new_free;
a.reserved = a.reserved.saturating_sub(actual);
Self::deposit_event(Event::Unreserved {
currency_id: asset_id,
who: who.clone(),
amount,
});
Ok(actual)
})?;
Ok(released)
}
fn transfer_on_hold(
asset_id: Self::AssetId,
reason: &ReasonOf<Self, T>,
source: &T::AccountId,
dest: &T::AccountId,
amount: Self::Balance,
precision: Precision,
restriction: Restriction,
_fortitude: Fortitude,
) -> Result<Self::Balance, DispatchError> {
let status = if restriction == Restriction::OnHold {
Status::Reserved
} else {
Status::Free
};
ensure!(
amount <= <Self as fungibles::InspectHold<T::AccountId>>::balance_on_hold(asset_id, reason, source)
|| precision == Precision::BestEffort,
Error::<T>::BalanceTooLow
);
let gap = Self::repatriate_reserved(asset_id, source, dest, amount, status)?;
Ok(amount.saturating_sub(gap))
}
}
impl<T: Config> fungibles::UnbalancedHold<T::AccountId> for Pallet<T> {
fn set_balance_on_hold(
asset: Self::AssetId,
_reason: &Self::Reason,
who: &T::AccountId,
amount: Self::Balance,
) -> DispatchResult {
Self::try_mutate_account(who, asset, |account, _| -> Result<(), DispatchError> {
let old_reserved = account.reserved;
account.reserved = amount;
account.free = account
.free
.checked_add(&old_reserved)
.ok_or(ArithmeticError::Overflow)?
.checked_sub(&account.reserved)
.ok_or(TokenError::BelowMinimum)?;
Self::deposit_event(Event::BalanceSet {
currency_id: asset,
who: who.clone(),
free: account.free,
reserved: account.reserved,
});
Ok(())
})
.map(|_| ())
}
}
pub struct CurrencyAdapter<T, GetCurrencyId>(marker::PhantomData<(T, GetCurrencyId)>);
impl<T, GetCurrencyId> PalletCurrency<T::AccountId> for CurrencyAdapter<T, GetCurrencyId>
where
T: Config,
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 {
<Pallet<T> as MultiCurrency<_>>::total_balance(GetCurrencyId::get(), who)
}
fn can_slash(who: &T::AccountId, value: Self::Balance) -> bool {
<Pallet<T> as MultiCurrency<_>>::can_slash(GetCurrencyId::get(), who, value)
}
fn total_issuance() -> Self::Balance {
<Pallet<T> as MultiCurrency<_>>::total_issuance(GetCurrencyId::get())
}
fn minimum_balance() -> Self::Balance {
<Pallet<T> as MultiCurrency<_>>::minimum_balance(GetCurrencyId::get())
}
fn burn(mut amount: Self::Balance) -> Self::PositiveImbalance {
if amount.is_zero() {
return PositiveImbalance::zero();
}
let currency_id = GetCurrencyId::get();
TotalIssuance::<T>::mutate(currency_id, |issued| {
*issued = issued.checked_sub(&amount).unwrap_or_else(|| {
amount = *issued;
Zero::zero()
})
});
Pallet::<T>::deposit_event(Event::TotalIssuanceSet {
currency_id,
amount: Self::total_issuance(),
});
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().defensive_saturating_sub(*issued);
Self::Balance::max_value()
})
});
Pallet::<T>::deposit_event(Event::TotalIssuanceSet {
currency_id: GetCurrencyId::get(),
amount: Self::total_issuance(),
});
NegativeImbalance::new(amount)
}
fn free_balance(who: &T::AccountId) -> Self::Balance {
<Pallet<T> as MultiCurrency<_>>::free_balance(GetCurrencyId::get(), who)
}
fn ensure_can_withdraw(
who: &T::AccountId,
amount: Self::Balance,
_reasons: WithdrawReasons,
_new_balance: Self::Balance,
) -> DispatchResult {
<Pallet<T> as MultiCurrency<_>>::ensure_can_withdraw(GetCurrencyId::get(), who, amount)
}
fn transfer(
source: &T::AccountId,
dest: &T::AccountId,
value: Self::Balance,
existence_requirement: ExistenceRequirement,
) -> DispatchResult {
Pallet::<T>::do_transfer(GetCurrencyId::get(), source, dest, value, existence_requirement)
}
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 = Pallet::<T>::accounts(who, currency_id);
let free_slashed_amount = account.free.min(value);
let mut remaining_slash = value.defensive_saturating_sub(free_slashed_amount);
if !free_slashed_amount.is_zero() {
Pallet::<T>::set_free_balance(
currency_id,
who,
account.free.defensive_saturating_sub(free_slashed_amount),
);
}
if !remaining_slash.is_zero() {
let reserved_slashed_amount = account.reserved.min(remaining_slash);
remaining_slash = remaining_slash.defensive_saturating_sub(reserved_slashed_amount);
Pallet::<T>::set_reserved_balance(
currency_id,
who,
account.reserved.defensive_saturating_sub(reserved_slashed_amount),
);
Pallet::<T>::deposit_event(Event::Slashed {
currency_id,
who: who.clone(),
free_amount: free_slashed_amount,
reserved_amount: reserved_slashed_amount,
});
(
Self::NegativeImbalance::new(free_slashed_amount.saturating_add(reserved_slashed_amount)),
remaining_slash,
)
} else {
Pallet::<T>::deposit_event(Event::Slashed {
currency_id,
who: who.clone(),
free_amount: value,
reserved_amount: Zero::zero(),
});
(Self::NegativeImbalance::new(value), remaining_slash)
}
}
fn deposit_into_existing(
who: &T::AccountId,
value: Self::Balance,
) -> sp_std::result::Result<Self::PositiveImbalance, DispatchError> {
Pallet::<T>::do_deposit(GetCurrencyId::get(), who, value, true, false).map(|_| PositiveImbalance::new(value))
}
fn deposit_creating(who: &T::AccountId, value: Self::Balance) -> Self::PositiveImbalance {
Pallet::<T>::do_deposit(GetCurrencyId::get(), who, value, false, false)
.map_or_else(|_| Self::PositiveImbalance::zero(), |_| PositiveImbalance::new(value))
}
fn withdraw(
who: &T::AccountId,
value: Self::Balance,
_reasons: WithdrawReasons,
liveness: ExistenceRequirement,
) -> sp_std::result::Result<Self::NegativeImbalance, DispatchError> {
Pallet::<T>::do_withdraw(GetCurrencyId::get(), who, value, liveness, false)
.map(|_| 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();
Pallet::<T>::try_mutate_account(
who,
currency_id,
|account, existed| -> Result<SignedImbalance<Self::Balance, Self::PositiveImbalance>, ()> {
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.saturating_sub(account.free)))
} else {
SignedImbalance::Negative(NegativeImbalance::new(account.free.saturating_sub(value)))
};
account.free = value;
Pallet::<T>::deposit_event(Event::BalanceSet {
currency_id,
who: who.clone(),
free: value,
reserved: account.reserved,
});
Ok(imbalance)
},
)
.map(|(imbalance, _)| imbalance)
.unwrap_or_else(|_| SignedImbalance::Positive(Self::PositiveImbalance::zero()))
}
}
impl<T, GetCurrencyId> PalletReservableCurrency<T::AccountId> for CurrencyAdapter<T, GetCurrencyId>
where
T: Config,
GetCurrencyId: Get<T::CurrencyId>,
{
fn can_reserve(who: &T::AccountId, value: Self::Balance) -> bool {
<Pallet<T> as MultiReservableCurrency<_>>::can_reserve(GetCurrencyId::get(), who, value)
}
fn slash_reserved(who: &T::AccountId, value: Self::Balance) -> (Self::NegativeImbalance, Self::Balance) {
let actual = <Pallet<T> as MultiReservableCurrency<_>>::slash_reserved(GetCurrencyId::get(), who, value);
(Self::NegativeImbalance::zero(), actual)
}
fn reserved_balance(who: &T::AccountId) -> Self::Balance {
<Pallet<T> as MultiReservableCurrency<_>>::reserved_balance(GetCurrencyId::get(), who)
}
fn reserve(who: &T::AccountId, value: Self::Balance) -> DispatchResult {
<Pallet<T> as MultiReservableCurrency<_>>::reserve(GetCurrencyId::get(), who, value)
}
fn unreserve(who: &T::AccountId, value: Self::Balance) -> Self::Balance {
<Pallet<T> as MultiReservableCurrency<_>>::unreserve(GetCurrencyId::get(), who, value)
}
fn repatriate_reserved(
slashed: &T::AccountId,
beneficiary: &T::AccountId,
value: Self::Balance,
status: Status,
) -> sp_std::result::Result<Self::Balance, DispatchError> {
<Pallet<T> as MultiReservableCurrency<_>>::repatriate_reserved(
GetCurrencyId::get(),
slashed,
beneficiary,
value,
status,
)
}
}
impl<T, GetCurrencyId> PalletNamedReservableCurrency<T::AccountId> for CurrencyAdapter<T, GetCurrencyId>
where
T: Config,
GetCurrencyId: Get<T::CurrencyId>,
{
type ReserveIdentifier = T::ReserveIdentifier;
fn reserved_balance_named(id: &Self::ReserveIdentifier, who: &T::AccountId) -> Self::Balance {
<Pallet<T> as NamedMultiReservableCurrency<_>>::reserved_balance_named(id, GetCurrencyId::get(), who)
}
fn reserve_named(id: &Self::ReserveIdentifier, who: &T::AccountId, value: Self::Balance) -> DispatchResult {
<Pallet<T> as NamedMultiReservableCurrency<_>>::reserve_named(id, GetCurrencyId::get(), who, value)
}
fn unreserve_named(id: &Self::ReserveIdentifier, who: &T::AccountId, value: Self::Balance) -> Self::Balance {
<Pallet<T> as NamedMultiReservableCurrency<_>>::unreserve_named(id, GetCurrencyId::get(), who, value)
}
fn slash_reserved_named(
id: &Self::ReserveIdentifier,
who: &T::AccountId,
value: Self::Balance,
) -> (Self::NegativeImbalance, Self::Balance) {
let actual =
<Pallet<T> as NamedMultiReservableCurrency<_>>::slash_reserved_named(id, GetCurrencyId::get(), who, value);
(Self::NegativeImbalance::zero(), actual)
}
fn repatriate_reserved_named(
id: &Self::ReserveIdentifier,
slashed: &T::AccountId,
beneficiary: &T::AccountId,
value: Self::Balance,
status: Status,
) -> sp_std::result::Result<Self::Balance, DispatchError> {
<Pallet<T> as NamedMultiReservableCurrency<_>>::repatriate_reserved_named(
id,
GetCurrencyId::get(),
slashed,
beneficiary,
value,
status,
)
}
}
impl<T, GetCurrencyId> PalletLockableCurrency<T::AccountId> for CurrencyAdapter<T, GetCurrencyId>
where
T: Config,
GetCurrencyId: Get<T::CurrencyId>,
{
type Moment = BlockNumberFor<T>;
type MaxLocks = ();
fn set_lock(id: LockIdentifier, who: &T::AccountId, amount: Self::Balance, _reasons: WithdrawReasons) {
let _ = <Pallet<T> as MultiLockableCurrency<_>>::set_lock(id, GetCurrencyId::get(), who, amount);
}
fn extend_lock(id: LockIdentifier, who: &T::AccountId, amount: Self::Balance, _reasons: WithdrawReasons) {
let _ = <Pallet<T> as MultiLockableCurrency<_>>::extend_lock(id, GetCurrencyId::get(), who, amount);
}
fn remove_lock(id: LockIdentifier, who: &T::AccountId) {
let _ = <Pallet<T> as MultiLockableCurrency<_>>::remove_lock(id, GetCurrencyId::get(), who);
}
}
impl<T: Config> TransferAll<T::AccountId> for Pallet<T> {
#[transactional]
fn transfer_all(source: &T::AccountId, dest: &T::AccountId) -> DispatchResult {
Accounts::<T>::iter_prefix(source).try_for_each(|(currency_id, account_data)| -> DispatchResult {
Self::do_transfer(
currency_id,
source,
dest,
account_data.free,
ExistenceRequirement::AllowDeath,
)
})
}
}
impl<T, GetCurrencyId> fungible::Inspect<T::AccountId> for CurrencyAdapter<T, GetCurrencyId>
where
T: Config,
GetCurrencyId: Get<T::CurrencyId>,
{
type Balance = T::Balance;
fn total_issuance() -> Self::Balance {
<Pallet<T> as fungibles::Inspect<_>>::total_issuance(GetCurrencyId::get())
}
fn minimum_balance() -> Self::Balance {
<Pallet<T> as fungibles::Inspect<_>>::minimum_balance(GetCurrencyId::get())
}
fn balance(who: &T::AccountId) -> Self::Balance {
<Pallet<T> as fungibles::Inspect<_>>::balance(GetCurrencyId::get(), who)
}
fn total_balance(who: &T::AccountId) -> Self::Balance {
<Pallet<T> as fungibles::Inspect<_>>::total_balance(GetCurrencyId::get(), who)
}
fn reducible_balance(who: &T::AccountId, preservation: Preservation, fortitude: Fortitude) -> Self::Balance {
<Pallet<T> as fungibles::Inspect<_>>::reducible_balance(GetCurrencyId::get(), who, preservation, fortitude)
}
fn can_deposit(who: &T::AccountId, amount: Self::Balance, provenance: Provenance) -> DepositConsequence {
<Pallet<T> as fungibles::Inspect<_>>::can_deposit(GetCurrencyId::get(), who, amount, provenance)
}
fn can_withdraw(who: &T::AccountId, amount: Self::Balance) -> WithdrawConsequence<Self::Balance> {
<Pallet<T> as fungibles::Inspect<_>>::can_withdraw(GetCurrencyId::get(), who, amount)
}
}
impl<T, GetCurrencyId> fungible::Mutate<T::AccountId> for CurrencyAdapter<T, GetCurrencyId>
where
T: Config,
GetCurrencyId: Get<T::CurrencyId>,
{
fn mint_into(who: &T::AccountId, amount: Self::Balance) -> Result<Self::Balance, DispatchError> {
<Pallet<T> as fungibles::Mutate<_>>::mint_into(GetCurrencyId::get(), who, amount)
}
fn burn_from(
who: &T::AccountId,
amount: Self::Balance,
precision: Precision,
fortitude: Fortitude,
) -> Result<Self::Balance, DispatchError> {
<Pallet<T> as fungibles::Mutate<_>>::burn_from(GetCurrencyId::get(), who, amount, precision, fortitude)
}
fn transfer(
source: &T::AccountId,
dest: &T::AccountId,
amount: T::Balance,
preservation: Preservation,
) -> Result<T::Balance, DispatchError> {
<Pallet<T> as fungibles::Mutate<_>>::transfer(GetCurrencyId::get(), source, dest, amount, preservation)
}
}
impl<T, GetCurrencyId> fungible::Unbalanced<T::AccountId> for CurrencyAdapter<T, GetCurrencyId>
where
T: Config,
GetCurrencyId: Get<T::CurrencyId>,
{
fn handle_dust(_dust: fungible::Dust<T::AccountId, Self>) {
}
fn write_balance(who: &T::AccountId, amount: Self::Balance) -> Result<Option<Self::Balance>, DispatchError> {
<Pallet<T> as fungibles::Unbalanced<_>>::write_balance(GetCurrencyId::get(), who, amount)
}
fn set_total_issuance(amount: Self::Balance) {
<Pallet<T> as fungibles::Unbalanced<_>>::set_total_issuance(GetCurrencyId::get(), amount)
}
}
type ReasonOfFungible<P, T> = <P as fungible::InspectHold<<T as frame_system::Config>::AccountId>>::Reason;
impl<T, GetCurrencyId> fungible::InspectHold<T::AccountId> for CurrencyAdapter<T, GetCurrencyId>
where
T: Config,
GetCurrencyId: Get<T::CurrencyId>,
{
type Reason = <Pallet<T> as fungibles::InspectHold<T::AccountId>>::Reason;
fn balance_on_hold(reason: &Self::Reason, who: &T::AccountId) -> T::Balance {
<Pallet<T> as fungibles::InspectHold<_>>::balance_on_hold(GetCurrencyId::get(), reason, who)
}
fn total_balance_on_hold(who: &T::AccountId) -> Self::Balance {
<Pallet<T> as fungibles::InspectHold<_>>::total_balance_on_hold(GetCurrencyId::get(), who)
}
fn reducible_total_balance_on_hold(who: &T::AccountId, force: Fortitude) -> Self::Balance {
<Pallet<T> as fungibles::InspectHold<_>>::reducible_total_balance_on_hold(GetCurrencyId::get(), who, force)
}
fn hold_available(reason: &Self::Reason, who: &T::AccountId) -> bool {
<Pallet<T> as fungibles::InspectHold<_>>::hold_available(GetCurrencyId::get(), reason, who)
}
fn can_hold(reason: &Self::Reason, who: &T::AccountId, amount: T::Balance) -> bool {
<Pallet<T> as fungibles::InspectHold<_>>::can_hold(GetCurrencyId::get(), reason, who, amount)
}
}
impl<T, GetCurrencyId> fungible::MutateHold<T::AccountId> for CurrencyAdapter<T, GetCurrencyId>
where
T: Config,
GetCurrencyId: Get<T::CurrencyId>,
{
fn hold(reason: &ReasonOfFungible<Self, T>, who: &T::AccountId, amount: Self::Balance) -> DispatchResult {
<Pallet<T> as fungibles::MutateHold<_>>::hold(GetCurrencyId::get(), reason, who, amount)
}
fn release(
reason: &ReasonOfFungible<Self, T>,
who: &T::AccountId,
amount: Self::Balance,
precision: Precision,
) -> Result<T::Balance, DispatchError> {
<Pallet<T> as fungibles::MutateHold<_>>::release(GetCurrencyId::get(), reason, who, amount, precision)
}
fn transfer_on_hold(
reason: &ReasonOfFungible<Self, T>,
source: &T::AccountId,
dest: &T::AccountId,
amount: Self::Balance,
precision: Precision,
restriction: Restriction,
fortitude: Fortitude,
) -> Result<Self::Balance, DispatchError> {
<Pallet<T> as fungibles::MutateHold<_>>::transfer_on_hold(
GetCurrencyId::get(),
reason,
source,
dest,
amount,
precision,
restriction,
fortitude,
)
}
}
impl<T, GetCurrencyId> fungible::UnbalancedHold<T::AccountId> for CurrencyAdapter<T, GetCurrencyId>
where
T: Config,
GetCurrencyId: Get<T::CurrencyId>,
{
fn set_balance_on_hold(reason: &Self::Reason, who: &T::AccountId, amount: Self::Balance) -> DispatchResult {
<Pallet<T> as fungibles::UnbalancedHold<_>>::set_balance_on_hold(GetCurrencyId::get(), reason, who, amount)
}
}
pub struct MultiTokenCurrencyAdapter<T>(marker::PhantomData<T>);
impl<T> MultiTokenCurrency<T::AccountId> for MultiTokenCurrencyAdapter<T>
where
T: Config,
{
type Balance = T::Balance;
type CurrencyId = T::CurrencyId;
type PositiveImbalance = MultiTokenPositiveImbalance<T>;
type NegativeImbalance = MultiTokenNegativeImbalance<T>;
fn total_balance(currency_id: T::CurrencyId, who: &T::AccountId) -> Self::Balance {
<Pallet<T> as MultiCurrency<_>>::total_balance(currency_id, who)
}
fn can_slash(currency_id: T::CurrencyId, who: &T::AccountId, value: Self::Balance) -> bool {
<Pallet<T> as MultiCurrency<_>>::can_slash(currency_id, who, value)
}
fn total_issuance(currency_id: T::CurrencyId) -> Self::Balance {
<Pallet<T> as MultiCurrency<_>>::total_issuance(currency_id)
}
fn minimum_balance(currency_id: T::CurrencyId) -> Self::Balance {
<Pallet<T> as MultiCurrency<_>>::minimum_balance(currency_id)
}
fn burn(currency_id: T::CurrencyId, mut amount: Self::Balance) -> Self::PositiveImbalance {
if amount.is_zero() {
return Self::PositiveImbalance::zero(currency_id);
}
<TotalIssuance<T>>::mutate(currency_id, |issued| {
*issued = issued.checked_sub(&amount).unwrap_or_else(|| {
amount = *issued;
Zero::zero()
});
});
Pallet::<T>::deposit_event(Event::TotalIssuanceSet {
currency_id,
amount: Self::total_issuance(currency_id),
});
Self::PositiveImbalance::new(currency_id, amount)
}
fn issue(currency_id: T::CurrencyId, mut amount: Self::Balance) -> Self::NegativeImbalance {
if amount.is_zero() {
return Self::NegativeImbalance::zero(currency_id);
}
<TotalIssuance<T>>::mutate(currency_id, |issued| {
*issued = issued.checked_add(&amount).unwrap_or_else(|| {
amount = Self::Balance::max_value().defensive_saturating_sub(*issued);
Self::Balance::max_value()
})
});
Pallet::<T>::deposit_event(Event::TotalIssuanceSet {
currency_id,
amount: Self::total_issuance(currency_id),
});
Self::NegativeImbalance::new(currency_id, amount)
}
fn free_balance(currency_id: T::CurrencyId, who: &T::AccountId) -> Self::Balance {
<Pallet<T> as MultiCurrency<_>>::free_balance(currency_id, who)
}
fn ensure_can_withdraw(
currency_id: T::CurrencyId,
who: &T::AccountId,
amount: Self::Balance,
_reasons: WithdrawReasons,
_new_balance: Self::Balance,
) -> DispatchResult {
<Pallet<T> as MultiCurrency<_>>::ensure_can_withdraw(currency_id, who, amount)
}
fn transfer(
currency_id: T::CurrencyId,
source: &T::AccountId,
dest: &T::AccountId,
value: Self::Balance,
existence_requirement: ExistenceRequirement,
) -> DispatchResult {
Pallet::<T>::do_transfer(currency_id, source, dest, value, existence_requirement)
}
fn slash(
currency_id: T::CurrencyId,
who: &T::AccountId,
value: Self::Balance,
) -> (Self::NegativeImbalance, Self::Balance) {
if value.is_zero() {
return (Self::NegativeImbalance::zero(currency_id), value);
}
let account = Pallet::<T>::accounts(who, currency_id);
let free_slashed_amount = account.free.min(value);
let mut remaining_slash = value.defensive_saturating_sub(free_slashed_amount);
if !free_slashed_amount.is_zero() {
Pallet::<T>::set_free_balance(
currency_id,
who,
account.free.defensive_saturating_sub(free_slashed_amount),
);
}
if !remaining_slash.is_zero() {
let reserved_slashed_amount = account.reserved.min(remaining_slash);
remaining_slash = remaining_slash.defensive_saturating_sub(reserved_slashed_amount);
Pallet::<T>::set_reserved_balance(
currency_id,
who,
account.reserved.defensive_saturating_sub(reserved_slashed_amount),
);
Pallet::<T>::deposit_event(Event::Slashed {
currency_id,
who: who.clone(),
free_amount: free_slashed_amount,
reserved_amount: reserved_slashed_amount,
});
(
Self::NegativeImbalance::new(currency_id, free_slashed_amount.saturating_add(reserved_slashed_amount)),
remaining_slash,
)
} else {
Pallet::<T>::deposit_event(Event::Slashed {
currency_id,
who: who.clone(),
free_amount: value,
reserved_amount: Zero::zero(),
});
(Self::NegativeImbalance::new(currency_id, value), remaining_slash)
}
}
fn deposit_into_existing(
currency_id: T::CurrencyId,
who: &T::AccountId,
value: Self::Balance,
) -> sp_std::result::Result<Self::PositiveImbalance, DispatchError> {
Pallet::<T>::do_deposit(currency_id, who, value, true, false)
.map(|_| Self::PositiveImbalance::new(currency_id, value))
}
fn deposit_creating(
currency_id: T::CurrencyId,
who: &T::AccountId,
value: Self::Balance,
) -> Self::PositiveImbalance {
Pallet::<T>::do_deposit(currency_id, who, value, false, false).map_or_else(
|_| Self::PositiveImbalance::zero(currency_id),
|_| Self::PositiveImbalance::new(currency_id, value),
)
}
fn withdraw(
currency_id: T::CurrencyId,
who: &T::AccountId,
value: Self::Balance,
_reasons: WithdrawReasons,
liveness: ExistenceRequirement,
) -> sp_std::result::Result<Self::NegativeImbalance, DispatchError> {
Pallet::<T>::do_withdraw(currency_id, who, value, liveness, false)
.map(|_| Self::NegativeImbalance::new(currency_id, value))
}
fn make_free_balance_be(
currency_id: T::CurrencyId,
who: &T::AccountId,
value: Self::Balance,
) -> SignedImbalance<Self::Balance, Self::PositiveImbalance> {
Pallet::<T>::try_mutate_account(
who,
currency_id,
|account, existed| -> Result<SignedImbalance<Self::Balance, Self::PositiveImbalance>, ()> {
let ed = T::ExistentialDeposits::get(¤cy_id);
ensure!(value.saturating_add(account.reserved) >= ed || existed, ());
let imbalance = if account.free <= value {
SignedImbalance::Positive(Self::PositiveImbalance::new(
currency_id,
value.saturating_sub(account.free),
))
} else {
SignedImbalance::Negative(Self::NegativeImbalance::new(
currency_id,
account.free.saturating_sub(value),
))
};
account.free = value;
Ok(imbalance)
},
)
.map(|(imbalance, _)| imbalance)
.unwrap_or_else(|_| SignedImbalance::Positive(Self::PositiveImbalance::zero(currency_id)))
}
}
impl<T> MultiTokenReservableCurrency<T::AccountId> for MultiTokenCurrencyAdapter<T>
where
T: Config,
{
fn can_reserve(currency_id: T::CurrencyId, who: &T::AccountId, value: Self::Balance) -> bool {
<Pallet<T> as MultiReservableCurrency<_>>::can_reserve(currency_id, who, value)
}
fn slash_reserved(
currency_id: T::CurrencyId,
who: &T::AccountId,
value: Self::Balance,
) -> (Self::NegativeImbalance, Self::Balance) {
let actual = <Pallet<T> as MultiReservableCurrency<_>>::slash_reserved(currency_id, who, value);
(Self::NegativeImbalance::zero(currency_id), actual)
}
fn reserved_balance(currency_id: T::CurrencyId, who: &T::AccountId) -> Self::Balance {
<Pallet<T> as MultiReservableCurrency<_>>::reserved_balance(currency_id, who)
}
fn reserve(currency_id: T::CurrencyId, who: &T::AccountId, value: Self::Balance) -> DispatchResult {
<Pallet<T> as MultiReservableCurrency<_>>::reserve(currency_id, who, value)
}
fn unreserve(currency_id: T::CurrencyId, who: &T::AccountId, value: Self::Balance) -> Self::Balance {
<Pallet<T> as MultiReservableCurrency<_>>::unreserve(currency_id, who, value)
}
fn repatriate_reserved(
currency_id: T::CurrencyId,
slashed: &T::AccountId,
beneficiary: &T::AccountId,
value: Self::Balance,
status: BalanceStatus,
) -> sp_std::result::Result<Self::Balance, DispatchError> {
<Pallet<T> as MultiReservableCurrency<_>>::repatriate_reserved(currency_id, slashed, beneficiary, value, status)
}
}
impl<T> MultiTokenLockableCurrency<T::AccountId> for MultiTokenCurrencyAdapter<T>
where
T: Config,
{
type Moment = BlockNumberFor<T>;
type MaxLocks = ();
fn set_lock(
currency_id: T::CurrencyId,
id: LockIdentifier,
who: &T::AccountId,
amount: Self::Balance,
_reasons: WithdrawReasons,
) {
let _ = <Pallet<T> as MultiLockableCurrency<_>>::set_lock(id, currency_id, who, amount);
}
fn extend_lock(
currency_id: T::CurrencyId,
id: LockIdentifier,
who: &T::AccountId,
amount: Self::Balance,
_reasons: WithdrawReasons,
) {
let _ = <Pallet<T> as MultiLockableCurrency<_>>::extend_lock(id, currency_id, who, amount);
}
fn remove_lock(currency_id: T::CurrencyId, id: LockIdentifier, who: &T::AccountId) {
let _ = <Pallet<T> as MultiLockableCurrency<_>>::remove_lock(id, currency_id, who);
}
}
impl<T> MultiTokenCurrencyExtended<T::AccountId> for MultiTokenCurrencyAdapter<T>
where
T: Config,
{
fn create(who: &T::AccountId, amount: T::Balance) -> sp_std::result::Result<T::CurrencyId, DispatchError> {
let token_id = <NextCurrencyId<T>>::get();
<Pallet<T> as fungibles::Inspect<_>>::can_deposit(token_id, who, amount, Provenance::Extant).into_result()?;
NextCurrencyId::<T>::mutate(|id| *id += One::one());
Pallet::<T>::do_deposit(token_id, who, amount, false, true)?;
Ok(token_id)
}
fn mint(currency_id: T::CurrencyId, who: &T::AccountId, amount: T::Balance) -> DispatchResult {
if !Self::exists(currency_id) {
return Err(DispatchError::from(Error::<T>::TokenIdNotExists));
}
<Pallet<T> as fungibles::Mutate<_>>::mint_into(currency_id, who, amount)?;
Ok(())
}
fn get_next_currency_id() -> Self::CurrencyId {
Pallet::<T>::next_asset_id()
}
fn exists(currency_id: Self::CurrencyId) -> bool {
currency_id < <NextCurrencyId<T>>::get()
}
fn burn_and_settle(currency_id: T::CurrencyId, who: &T::AccountId, amount: T::Balance) -> DispatchResult {
Pallet::<T>::do_withdraw(currency_id, who, amount, ExistenceRequirement::AllowDeath, true)
}
fn locked_balance(currency_id: T::CurrencyId, who: &T::AccountId) -> T::Balance {
Pallet::<T>::accounts(who, currency_id).frozen()
}
fn available_balance(currency_id: T::CurrencyId, who: &T::AccountId) -> T::Balance {
let account_balance_info = Pallet::<T>::accounts(who, currency_id);
account_balance_info
.free
.defensive_saturating_sub(account_balance_info.frozen())
}
}