Skip to content
Snippets Groups Projects
lib.rs 28.5 KiB
Newer Older
  • Learn to ignore specific revisions
  • 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::Get,
    	traits::{
    		BalanceStatus as Status, Currency as PalletCurrency, ExistenceRequirement, Imbalance,
    		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::{
    
    zjb0807's avatar
    zjb0807 committed
    		AtLeast32BitUnsigned, Bounded, CheckedAdd, CheckedSub, MaybeSerializeDeserialize, Member, Saturating,
    		StaticLookup, Zero,
    
    	DispatchError, DispatchResult, RuntimeDebug,
    
    Shaopeng Wang's avatar
    Shaopeng Wang committed
    use sp_std::{
    	convert::{TryFrom, TryInto},
    
    zjb0807's avatar
    zjb0807 committed
    	marker,
    
    Shaopeng Wang's avatar
    Shaopeng Wang committed
    	prelude::*,
    	result,
    };
    
    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},
    
    	BalanceStatus, LockIdentifier, MultiCurrency, MultiCurrencyExtended, MultiLockableCurrency,
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    	MultiReservableCurrency, OnReceived,
    
    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
    
    	/// Hook when some fund is deposited into an account
    
    	type OnReceived: OnReceived<Self::AccountId, Self::CurrencyId, Self::Balance>;
    
    
    	/// Weight information for extrinsics in this module.
    	type WeightInfo: WeightInfo;
    
    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
    	}
    	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)| {
    
    				<Accounts<T>>::mutate(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
    	{
    
    		/// Token transfer success. [currency_id, from, to, amount]
    
    Shaopeng Wang's avatar
    Shaopeng Wang committed
    		Transferred(CurrencyId, AccountId, AccountId, 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> {
    
    	/// Set free balance of `who` to a new value.
    
    	/// Note this will not maintain total issuance.
    
    	fn set_free_balance(currency_id: T::CurrencyId, who: &T::AccountId, balance: T::Balance) {
    
    		<Accounts<T>>::mutate(who, currency_id, |account_data| account_data.free = balance);
    
    	/// Set reserved balance of `who` to a new value, meanwhile enforce
    	/// existential rule.
    
    	/// Note this will not maintain total issuance, and the caller is expected
    	/// to do it.
    
    	fn set_reserved_balance(currency_id: T::CurrencyId, who: &T::AccountId, balance: T::Balance) {
    
    		<Accounts<T>>::mutate(who, currency_id, |account_data| account_data.reserved = balance);
    
    	}
    
    	/// 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
    
    		<Accounts<T>>::mutate(who, currency_id, |account_data| {
    
    			account_data.frozen = Zero::zero();
    			for lock in locks.iter() {
    				account_data.frozen = account_data.frozen.max(lock.amount);
    			}
    		});
    
    		// update locks
    
    		let existed = <Locks<T>>::contains_key(who, currency_id);
    
    		if locks.is_empty() {
    
    			<Locks<T>>::remove(who, currency_id);
    
    			if existed {
    				// decrease account ref count when destruct lock
    
    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;
    
    	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);
    
    		T::OnReceived::on_received(to, currency_id, amount);
    
    	/// 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(());
    		}
    
    
    		let new_total = Self::total_issuance(currency_id)
    			.checked_add(&amount)
    			.ok_or(Error::<T>::TotalIssuanceOverflow)?;
    		<TotalIssuance<T>>::insert(currency_id, new_total);
    		Self::set_free_balance(currency_id, who, Self::free_balance(currency_id, who) + amount);
    
    		T::OnReceived::on_received(who, currency_id, amount);
    
    	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);
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    		T::OnReceived::on_received(who, currency_id, 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);
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    				T::OnReceived::on_received(beneficiary, currency_id, actual);
    
    			}
    			BalanceStatus::Reserved => {
    				Self::set_reserved_balance(currency_id, beneficiary, to_account.reserved + actual);
    			}
    		}
    		Self::set_reserved_balance(currency_id, slashed, from_account.reserved - actual);
    		Ok(value - actual)
    	}
    }
    
    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 {
    		Zero::zero()
    	}
    
    	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> {
    		<Accounts<T>>::mutate(
    			who,
    			GetCurrencyId::get(),
    			|account| -> Result<SignedImbalance<Self::Balance, Self::PositiveImbalance>, ()> {
    				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 {
    
    zjb0807's avatar
    zjb0807 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)?;
    			<Accounts<T>>::remove(source, currency_id);
    			Ok(())