Skip to content
Snippets Groups Projects
lib.rs 31.6 KiB
Newer Older
Xiliang Chen's avatar
Xiliang Chen committed
//! # Tokens Module
//!
//! ## Overview
//!
//! The tokens module provides fungible multi-currency functionality that
//! implements `MultiCurrency` trait.
Xiliang Chen's avatar
Xiliang Chen committed
//!
//! 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
Xiliang Chen's avatar
Xiliang Chen committed
//! 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.
Bryan Chen's avatar
Bryan Chen committed
#![cfg_attr(not(feature = "std"), no_std)]

use codec::{Decode, Encode};
zjb0807's avatar
zjb0807 committed
use frame_support::{
	decl_error, decl_event, decl_module, decl_storage, ensure,
	traits::{
wangjj9219's avatar
wangjj9219 committed
		BalanceStatus as Status, Currency as PalletCurrency, ExistenceRequirement, Get, Imbalance,
zjb0807's avatar
zjb0807 committed
		LockableCurrency as PalletLockableCurrency, ReservableCurrency as PalletReservableCurrency, SignedImbalance,
		WithdrawReasons,
	},
zjb0807's avatar
zjb0807 committed
	transactional,
zjb0807's avatar
zjb0807 committed
	weights::Weight,
	Parameter, StorageMap,
};
Xiliang Chen's avatar
Xiliang Chen committed
use frame_system::ensure_signed;
use sp_runtime::{
wangjj9219's avatar
wangjj9219 committed
		AccountIdConversion, AtLeast32BitUnsigned, Bounded, CheckedAdd, CheckedSub, MaybeSerializeDeserialize, Member,
		Saturating, StaticLookup, Zero,
wangjj9219's avatar
wangjj9219 committed
	DispatchError, DispatchResult, ModuleId, RuntimeDebug,
Shaopeng Wang's avatar
Shaopeng Wang committed
use sp_std::{
wangjj9219's avatar
wangjj9219 committed
	convert::{Infallible, TryFrom, TryInto},
zjb0807's avatar
zjb0807 committed
	marker,
Shaopeng Wang's avatar
Shaopeng Wang committed
	prelude::*,
	result,
wangjj9219's avatar
wangjj9219 committed
	vec::Vec,
Shaopeng Wang's avatar
Shaopeng Wang committed
};
Shaopeng Wang's avatar
Shaopeng Wang committed

Xiliang Chen's avatar
Xiliang Chen committed
#[cfg(feature = "std")]
Shaopeng Wang's avatar
Shaopeng Wang committed
use sp_std::collections::btree_map::BTreeMap;
zjb0807's avatar
zjb0807 committed
pub use crate::imbalances::{NegativeImbalance, PositiveImbalance};
use orml_traits::{
zjb0807's avatar
zjb0807 committed
	account::MergeAccount,
	arithmetic::{self, Signed},
wangjj9219's avatar
wangjj9219 committed
	BalanceStatus, GetByKey, LockIdentifier, MultiCurrency, MultiCurrencyExtended, MultiLockableCurrency,
	MultiReservableCurrency, OnDust,
Bryan Chen's avatar
Bryan Chen committed

mod default_weight;
zjb0807's avatar
zjb0807 committed
mod imbalances;
mod mock;
mod tests;

pub trait WeightInfo {
	fn transfer() -> Weight;
	fn transfer_all() -> Weight;
}

Xiliang Chen's avatar
Xiliang Chen committed
pub trait Trait: frame_system::Trait {
	type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>;
Xiliang Chen's avatar
Xiliang Chen committed

	/// The balance type
	type Balance: Parameter + Member + AtLeast32BitUnsigned + Default + Copy + MaybeSerializeDeserialize;
Xiliang Chen's avatar
Xiliang Chen committed

	/// 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;
Xiliang Chen's avatar
Xiliang Chen committed

	/// The currency ID type
	type CurrencyId: Parameter + Member + Copy + MaybeSerializeDeserialize + Ord;
Xiliang Chen's avatar
Xiliang Chen committed

	/// Weight information for extrinsics in this module.
	type WeightInfo: WeightInfo;
wangjj9219's avatar
wangjj9219 committed

	/// The minimum amount required to keep an account.
	type ExistentialDeposits: GetByKey<Self::CurrencyId, Self::Balance>;
	/// Handler to burn or transfer account's dust
	type OnDust: OnDust<Self::AccountId, Self::CurrencyId, Self::Balance>;
Bryan Chen's avatar
Bryan Chen committed
}

/// 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)
	}
}

Bryan Chen's avatar
Bryan Chen committed
decl_storage! {
	trait Store for Module<T: Trait> as Tokens {
		/// The total issuance of a token type.
Shaopeng Wang's avatar
Shaopeng Wang committed
		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<_>>()
Xiliang Chen's avatar
Xiliang Chen committed
		}): map hasher(twox_64_concat) T::CurrencyId => T::Balance;
Bryan Chen's avatar
Bryan Chen committed

		/// Any liquidity locks of a token type under an account.
		/// NOTE: Should only be accessed when setting, changing and freeing a lock.
Xiliang Chen's avatar
Xiliang Chen committed
		pub Locks get(fn locks): double_map hasher(blake2_128_concat) T::AccountId, hasher(twox_64_concat) T::CurrencyId => Vec<BalanceLock<T::Balance>>;
Shaopeng Wang's avatar
Shaopeng Wang committed
		/// 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.
Xiliang Chen's avatar
Xiliang Chen committed
		pub Accounts get(fn accounts): double_map hasher(blake2_128_concat) T::AccountId, hasher(twox_64_concat) T::CurrencyId => AccountData<T::Balance>;
Shaopeng Wang's avatar
Shaopeng Wang committed
	}
Shaopeng Wang's avatar
Shaopeng Wang committed
	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)| {
wangjj9219's avatar
wangjj9219 committed
				assert!(
					*initial_balance >= T::ExistentialDeposits::get(&currency_id),
wangjj9219's avatar
wangjj9219 committed
					"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);
Bryan Chen's avatar
Bryan Chen committed
	}
}

decl_event!(
	pub enum Event<T> where
Xiliang Chen's avatar
Xiliang Chen committed
		<T as frame_system::Trait>::AccountId,
Shaopeng Wang's avatar
Shaopeng Wang committed
		<T as Trait>::CurrencyId,
		<T as Trait>::Balance
Bryan Chen's avatar
Bryan Chen committed
	{
wangjj9219's avatar
wangjj9219 committed
		/// Token transfer success. \[currency_id, from, to, amount\]
Shaopeng Wang's avatar
Shaopeng Wang committed
		Transferred(CurrencyId, AccountId, AccountId, Balance),
wangjj9219's avatar
wangjj9219 committed
		/// 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),
Bryan Chen's avatar
Bryan Chen committed
	}
);

decl_module! {
	pub struct Module<T: Trait> for enum Call where origin: T::Origin {
		type Error = Error<T>;

Bryan Chen's avatar
Bryan Chen committed
		fn deposit_event() = default;

Shaopeng Wang's avatar
Shaopeng Wang committed
		/// 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 = T::WeightInfo::transfer()]
Shaopeng Wang's avatar
Shaopeng Wang committed
		pub fn transfer(
			origin,
			dest: <T::Lookup as StaticLookup>::Source,
			currency_id: T::CurrencyId,
Shaopeng Wang's avatar
Shaopeng Wang committed
			#[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 = 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));
		}
Bryan Chen's avatar
Bryan Chen committed
	}
}

decl_error! {
	/// Error for token module.
	pub enum Error for Module<T: Trait> {
Xiliang Chen's avatar
Xiliang Chen committed
		/// The balance is too low
zjb0807's avatar
zjb0807 committed
		/// This operation will cause balance to overflow
		BalanceOverflow,
Xiliang Chen's avatar
Xiliang Chen committed
		/// This operation will cause total issuance to overflow
Xiliang Chen's avatar
Xiliang Chen committed
		/// Cannot convert Amount into Balance type
		AmountIntoBalanceFailed,
Xiliang Chen's avatar
Xiliang Chen committed
		/// Failed because liquidity restrictions due to locking
		LiquidityRestrictions,
zjb0807's avatar
zjb0807 committed
		/// Account still has active reserved
		StillHasActiveReserved,
impl<T: Trait> Module<T> {
wangjj9219's avatar
wangjj9219 committed
	/// 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 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| {
wangjj9219's avatar
wangjj9219 committed
			let existed = maybe_account.is_some();
			let mut account = maybe_account.take().unwrap_or_default();
			f(&mut account, existed).map(move |result| {
				let mut handle_dust: Option<T::Balance> = None;
				let total = account.total();
				*maybe_account = if total.is_zero() {
					None
				} else {
					// if non_zero total is below existential deposit and the account is not a
					// module account, should handle the dust.
					if total < T::ExistentialDeposits::get(&currency_id) && !Self::is_module_account_id(who) {
						handle_dust = Some(total);
					}
					Some(account)
				};

				(existed, maybe_account.is_some(), handle_dust, result)
wangjj9219's avatar
wangjj9219 committed
			})
		})
		.map(|(existed, exists, handle_dust, result)| {
			if existed && !exists {
				// if existed before, decrease account ref count
				frame_system::Module::<T>::dec_ref(who);
			} else if !existed && exists {
				// if new, increase account ref count
				frame_system::Module::<T>::inc_ref(who);
			}

			if let Some(dust_amount) = handle_dust {
				// `OnDust` maybe get/set storage `Accounts` of `who`, trigger handler here
				// to avoid some unexpected errors.
				T::OnDust::on_dust(who, currency_id, dust_amount);
				Self::deposit_event(RawEvent::DustLost(who.clone(), currency_id, dust_amount));
			}

			result
		})
wangjj9219's avatar
wangjj9219 committed
	}

	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.
wangjj9219's avatar
wangjj9219 committed
	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.
wangjj9219's avatar
wangjj9219 committed
	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
wangjj9219's avatar
wangjj9219 committed
		Self::mutate_account(who, currency_id, |account, _| {
			account.frozen = Zero::zero();
			for lock in locks.iter() {
wangjj9219's avatar
wangjj9219 committed
				account.frozen = account.frozen.max(lock.amount);
		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
Xiliang Chen's avatar
Xiliang Chen committed
				frame_system::Module::<T>::dec_ref(who);
			<Locks<T>>::insert(who, currency_id, locks);
			if !existed {
				// increase account ref count when initialize lock
Xiliang Chen's avatar
Xiliang Chen committed
				frame_system::Module::<T>::inc_ref(who);
Bryan Chen's avatar
Bryan Chen committed

Shaopeng Wang's avatar
Shaopeng Wang committed
impl<T: Trait> MultiCurrency<T::AccountId> for Module<T> {
	type CurrencyId = T::CurrencyId;
wangjj9219's avatar
wangjj9219 committed
	fn minimum_balance(currency_id: Self::CurrencyId) -> Self::Balance {
		T::ExistentialDeposits::get(&currency_id)
	fn total_issuance(currency_id: Self::CurrencyId) -> Self::Balance {
Shaopeng Wang's avatar
Shaopeng Wang committed
		<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 {
Ermal Kaleci's avatar
Ermal Kaleci committed
		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`.
Shaopeng Wang's avatar
Shaopeng Wang committed
	fn transfer(
		currency_id: Self::CurrencyId,
		from: &T::AccountId,
		to: &T::AccountId,
		amount: Self::Balance,
	) -> DispatchResult {
Ermal Kaleci's avatar
Ermal Kaleci committed
		if amount.is_zero() || from == to {
			return Ok(());
		}
		Self::ensure_can_withdraw(currency_id, from, amount)?;
Ermal Kaleci's avatar
Ermal Kaleci committed

		let from_balance = Self::free_balance(currency_id, from);
zjb0807's avatar
zjb0807 committed
		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);
zjb0807's avatar
zjb0807 committed
		Self::set_free_balance(currency_id, to, to_balance);
	/// 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 {
Ermal Kaleci's avatar
Ermal Kaleci committed
		if amount.is_zero() {
			return Ok(());
		}

wangjj9219's avatar
wangjj9219 committed
		TotalIssuance::<T>::try_mutate(currency_id, |total_issuance| -> DispatchResult {
			*total_issuance = total_issuance
				.checked_add(&amount)
				.ok_or(Error::<T>::TotalIssuanceOverflow)?;
wangjj9219's avatar
wangjj9219 committed
			Self::set_free_balance(currency_id, who, Self::free_balance(currency_id, who) + amount);

			Ok(())
		})
	fn withdraw(currency_id: Self::CurrencyId, who: &T::AccountId, amount: Self::Balance) -> DispatchResult {
Ermal Kaleci's avatar
Ermal Kaleci committed
		if amount.is_zero() {
			return Ok(());
		}
		Self::ensure_can_withdraw(currency_id, who, amount)?;
		// Cannot underflow because ensure_can_withdraw check
Shaopeng Wang's avatar
Shaopeng Wang committed
		<TotalIssuance<T>>::mutate(currency_id, |v| *v -= amount);
		Self::set_free_balance(currency_id, who, Self::free_balance(currency_id, who) - amount);
	// 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.
Shaopeng Wang's avatar
Shaopeng Wang committed
	fn slash(currency_id: Self::CurrencyId, who: &T::AccountId, amount: Self::Balance) -> Self::Balance {
Ermal Kaleci's avatar
Ermal Kaleci committed
		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 {
Ermal Kaleci's avatar
Ermal Kaleci committed
		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()
		};

			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 {
zjb0807's avatar
zjb0807 committed
			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;
		}
Keith Yeung's avatar
Keith Yeung committed
		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;
		}
Keith Yeung's avatar
Keith Yeung committed
		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() {
zjb0807's avatar
zjb0807 committed
			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() {
zjb0807's avatar
zjb0807 committed
			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);
	/// 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,
Shaopeng Wang's avatar
Shaopeng Wang committed
	) -> result::Result<Self::Balance, DispatchError> {
		if value.is_zero() {
zjb0807's avatar
zjb0807 committed
			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);
			}
			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)
	}
}
zjb0807's avatar
zjb0807 committed

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 {
wangjj9219's avatar
wangjj9219 committed
		Module::<T>::minimum_balance(GetCurrencyId::get())
zjb0807's avatar
zjb0807 committed
	}

	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> {
wangjj9219's avatar
wangjj9219 committed
		let currency_id = GetCurrencyId::get();
		Module::<T>::try_mutate_account(
zjb0807's avatar
zjb0807 committed
			who,
wangjj9219's avatar
wangjj9219 committed
			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(&currency_id);
wangjj9219's avatar
wangjj9219 committed
				ensure!(value.saturating_add(account.reserved) >= ed || existed, ());

zjb0807's avatar
zjb0807 committed
				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 = ();
zjb0807's avatar
zjb0807 committed

	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)
	}
}
zjb0807's avatar
zjb0807 committed

impl<T: Trait> MergeAccount<T::AccountId> for Module<T> {
	#[transactional]
	fn merge_account(source: &T::AccountId, dest: &T::AccountId) -> DispatchResult {
wangjj9219's avatar
wangjj9219 committed
		Accounts::<T>::iter_prefix(source).try_for_each(|(currency_id, account_data)| -> DispatchResult {
zjb0807's avatar
zjb0807 committed
			// 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
zjb0807's avatar
zjb0807 committed
			<Self as MultiCurrency<T::AccountId>>::transfer(currency_id, source, dest, account_data.free)?;
			Ok(())