//! # Currencies Module //! //! ## Overview //! //! The currencies module provides a mixed currencies system, by configuring a //! native currency which implements `BasicCurrencyExtended`, and a //! multi-currency which implements `MultiCurrency`. //! //! It also provides an adapter, to adapt `frame_support::traits::Currency` //! implementations into `BasicCurrencyExtended`. //! //! The currencies module provides functionality of both `MultiCurrencyExtended` //! and `BasicCurrencyExtended`, via unified interfaces, and all calls would be //! delegated to the underlying multi-currency and base currency system. //! A native currency ID could be set by `Config::GetNativeCurrencyId`, to //! identify the native currency. //! //! ### Implementations //! //! The currencies module provides implementations for following traits. //! //! - `MultiCurrency` - Abstraction over a fungible multi-currency system. //! - `MultiCurrencyExtended` - Extended `MultiCurrency` with additional helper //! types and methods, like updating balance //! by a given signed integer amount. //! //! ## Interface //! //! ### Dispatchable Functions //! //! - `transfer` - Transfer some balance to another account, in a given //! currency. //! - `transfer_native_currency` - Transfer some balance to another account, in //! native currency set in //! `Config::NativeCurrency`. //! - `update_balance` - Update balance by signed integer amount, in a given //! currency, root origin required. #![cfg_attr(not(feature = "std"), no_std)] #![allow(clippy::unused_unit)] use codec::Codec; use frame_support::{ pallet_prelude::*, traits::{ Currency as PalletCurrency, ExistenceRequirement, Get, LockableCurrency as PalletLockableCurrency, ReservableCurrency as PalletReservableCurrency, WithdrawReasons, }, }; use frame_system::{ensure_root, ensure_signed, pallet_prelude::*}; use orml_traits::{ arithmetic::{Signed, SimpleArithmetic}, currency::TransferAll, BalanceStatus, BasicCurrency, BasicCurrencyExtended, BasicLockableCurrency, BasicReservableCurrency, LockIdentifier, MultiCurrency, MultiCurrencyExtended, MultiLockableCurrency, MultiReservableCurrency, }; use orml_utilities::with_transaction_result; use sp_runtime::{ traits::{CheckedSub, MaybeSerializeDeserialize, StaticLookup, Zero}, DispatchError, DispatchResult, }; use sp_std::{ convert::{TryFrom, TryInto}, fmt::Debug, marker, result, }; mod mock; mod tests; mod weights; pub use module::*; pub use weights::WeightInfo; #[frame_support::pallet] pub mod module { use super::*; pub(crate) type BalanceOf<T> = <<T as Config>::MultiCurrency as MultiCurrency<<T as frame_system::Config>::AccountId>>::Balance; pub(crate) type CurrencyIdOf<T> = <<T as Config>::MultiCurrency as MultiCurrency<<T as frame_system::Config>::AccountId>>::CurrencyId; pub(crate) type AmountOf<T> = <<T as Config>::MultiCurrency as MultiCurrencyExtended<<T as frame_system::Config>::AccountId>>::Amount; #[pallet::config] pub trait Config: frame_system::Config { type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>; type MultiCurrency: TransferAll<Self::AccountId> + MultiCurrencyExtended<Self::AccountId> + MultiLockableCurrency<Self::AccountId> + MultiReservableCurrency<Self::AccountId>; type NativeCurrency: BasicCurrencyExtended<Self::AccountId, Balance = BalanceOf<Self>, Amount = AmountOf<Self>> + BasicLockableCurrency<Self::AccountId, Balance = BalanceOf<Self>> + BasicReservableCurrency<Self::AccountId, Balance = BalanceOf<Self>>; #[pallet::constant] type GetNativeCurrencyId: Get<CurrencyIdOf<Self>>; /// Weight information for extrinsics in this module. type WeightInfo: WeightInfo; } #[pallet::error] pub enum Error<T> { /// Unable to convert the Amount type into Balance. AmountIntoBalanceFailed, /// Balance is too low. BalanceTooLow, } #[pallet::event] #[pallet::generate_deposit(pub(crate) fn deposit_event)] #[pallet::metadata(CurrencyIdOf<T> = "Currency", T::AccountId = "AccountId", BalanceOf<T> = "Balance", AmountOf<T> = "Amount")] pub enum Event<T: Config> { /// Currency transfer success. \[currency_id, from, to, amount\] Transferred(CurrencyIdOf<T>, T::AccountId, T::AccountId, BalanceOf<T>), /// Update balance success. \[currency_id, who, amount\] BalanceUpdated(CurrencyIdOf<T>, T::AccountId, AmountOf<T>), /// Deposit success. \[currency_id, who, amount\] Deposited(CurrencyIdOf<T>, T::AccountId, BalanceOf<T>), /// Withdraw success. \[currency_id, who, amount\] Withdrawn(CurrencyIdOf<T>, T::AccountId, BalanceOf<T>), } #[pallet::pallet] pub struct Pallet<T>(_); #[pallet::hooks] impl<T: Config> Hooks<T::BlockNumber> for Pallet<T> {} #[pallet::call] impl<T: Config> Pallet<T> { /// Transfer some balance to another account under `currency_id`. /// /// The dispatch origin for this call must be `Signed` by the /// transactor. #[pallet::weight(T::WeightInfo::transfer_non_native_currency())] pub fn transfer( origin: OriginFor<T>, dest: <T::Lookup as StaticLookup>::Source, currency_id: CurrencyIdOf<T>, #[pallet::compact] amount: BalanceOf<T>, ) -> DispatchResultWithPostInfo { let from = ensure_signed(origin)?; let to = T::Lookup::lookup(dest)?; <Self as MultiCurrency<T::AccountId>>::transfer(currency_id, &from, &to, amount)?; Ok(().into()) } /// Transfer some native currency to another account. /// /// The dispatch origin for this call must be `Signed` by the /// transactor. #[pallet::weight(T::WeightInfo::transfer_native_currency())] pub fn transfer_native_currency( origin: OriginFor<T>, dest: <T::Lookup as StaticLookup>::Source, #[pallet::compact] amount: BalanceOf<T>, ) -> DispatchResultWithPostInfo { let from = ensure_signed(origin)?; let to = T::Lookup::lookup(dest)?; T::NativeCurrency::transfer(&from, &to, amount)?; Self::deposit_event(Event::Transferred(T::GetNativeCurrencyId::get(), from, to, amount)); Ok(().into()) } /// update amount of account `who` under `currency_id`. /// /// The dispatch origin of this call must be _Root_. #[pallet::weight(T::WeightInfo::update_balance_non_native_currency())] pub fn update_balance( origin: OriginFor<T>, who: <T::Lookup as StaticLookup>::Source, currency_id: CurrencyIdOf<T>, amount: AmountOf<T>, ) -> DispatchResultWithPostInfo { ensure_root(origin)?; let dest = T::Lookup::lookup(who)?; <Self as MultiCurrencyExtended<T::AccountId>>::update_balance(currency_id, &dest, amount)?; Ok(().into()) } } } impl<T: Config> MultiCurrency<T::AccountId> for Pallet<T> { type CurrencyId = CurrencyIdOf<T>; type Balance = BalanceOf<T>; fn minimum_balance(currency_id: Self::CurrencyId) -> Self::Balance { if currency_id == T::GetNativeCurrencyId::get() { T::NativeCurrency::minimum_balance() } else { T::MultiCurrency::minimum_balance(currency_id) } } fn total_issuance(currency_id: Self::CurrencyId) -> Self::Balance { if currency_id == T::GetNativeCurrencyId::get() { T::NativeCurrency::total_issuance() } else { T::MultiCurrency::total_issuance(currency_id) } } fn total_balance(currency_id: Self::CurrencyId, who: &T::AccountId) -> Self::Balance { if currency_id == T::GetNativeCurrencyId::get() { T::NativeCurrency::total_balance(who) } else { T::MultiCurrency::total_balance(currency_id, who) } } fn free_balance(currency_id: Self::CurrencyId, who: &T::AccountId) -> Self::Balance { if currency_id == T::GetNativeCurrencyId::get() { T::NativeCurrency::free_balance(who) } else { T::MultiCurrency::free_balance(currency_id, who) } } fn ensure_can_withdraw(currency_id: Self::CurrencyId, who: &T::AccountId, amount: Self::Balance) -> DispatchResult { if currency_id == T::GetNativeCurrencyId::get() { T::NativeCurrency::ensure_can_withdraw(who, amount) } else { T::MultiCurrency::ensure_can_withdraw(currency_id, who, amount) } } fn transfer( currency_id: Self::CurrencyId, from: &T::AccountId, to: &T::AccountId, amount: Self::Balance, ) -> DispatchResult { if amount.is_zero() || from == to { return Ok(()); } if currency_id == T::GetNativeCurrencyId::get() { T::NativeCurrency::transfer(from, to, amount)?; } else { T::MultiCurrency::transfer(currency_id, from, to, amount)?; } Self::deposit_event(Event::Transferred(currency_id, from.clone(), to.clone(), amount)); Ok(()) } fn deposit(currency_id: Self::CurrencyId, who: &T::AccountId, amount: Self::Balance) -> DispatchResult { if amount.is_zero() { return Ok(()); } if currency_id == T::GetNativeCurrencyId::get() { T::NativeCurrency::deposit(who, amount)?; } else { T::MultiCurrency::deposit(currency_id, who, amount)?; } Self::deposit_event(Event::Deposited(currency_id, who.clone(), amount)); Ok(()) } fn withdraw(currency_id: Self::CurrencyId, who: &T::AccountId, amount: Self::Balance) -> DispatchResult { if amount.is_zero() { return Ok(()); } if currency_id == T::GetNativeCurrencyId::get() { T::NativeCurrency::withdraw(who, amount)?; } else { T::MultiCurrency::withdraw(currency_id, who, amount)?; } Self::deposit_event(Event::Withdrawn(currency_id, who.clone(), amount)); Ok(()) } fn can_slash(currency_id: Self::CurrencyId, who: &T::AccountId, amount: Self::Balance) -> bool { if currency_id == T::GetNativeCurrencyId::get() { T::NativeCurrency::can_slash(who, amount) } else { T::MultiCurrency::can_slash(currency_id, who, amount) } } fn slash(currency_id: Self::CurrencyId, who: &T::AccountId, amount: Self::Balance) -> Self::Balance { if currency_id == T::GetNativeCurrencyId::get() { T::NativeCurrency::slash(who, amount) } else { T::MultiCurrency::slash(currency_id, who, amount) } } } impl<T: Config> MultiCurrencyExtended<T::AccountId> for Pallet<T> { type Amount = AmountOf<T>; fn update_balance(currency_id: Self::CurrencyId, who: &T::AccountId, by_amount: Self::Amount) -> DispatchResult { if currency_id == T::GetNativeCurrencyId::get() { T::NativeCurrency::update_balance(who, by_amount)?; } else { T::MultiCurrency::update_balance(currency_id, who, by_amount)?; } Self::deposit_event(Event::BalanceUpdated(currency_id, who.clone(), by_amount)); Ok(()) } } impl<T: Config> MultiLockableCurrency<T::AccountId> for Pallet<T> { type Moment = T::BlockNumber; fn set_lock( lock_id: LockIdentifier, currency_id: Self::CurrencyId, who: &T::AccountId, amount: Self::Balance, ) -> DispatchResult { if currency_id == T::GetNativeCurrencyId::get() { T::NativeCurrency::set_lock(lock_id, who, amount) } else { T::MultiCurrency::set_lock(lock_id, currency_id, who, amount) } } fn extend_lock( lock_id: LockIdentifier, currency_id: Self::CurrencyId, who: &T::AccountId, amount: Self::Balance, ) -> DispatchResult { if currency_id == T::GetNativeCurrencyId::get() { T::NativeCurrency::extend_lock(lock_id, who, amount) } else { T::MultiCurrency::extend_lock(lock_id, currency_id, who, amount) } } fn remove_lock(lock_id: LockIdentifier, currency_id: Self::CurrencyId, who: &T::AccountId) -> DispatchResult { if currency_id == T::GetNativeCurrencyId::get() { T::NativeCurrency::remove_lock(lock_id, who) } else { T::MultiCurrency::remove_lock(lock_id, currency_id, who) } } } impl<T: Config> MultiReservableCurrency<T::AccountId> for Pallet<T> { fn can_reserve(currency_id: Self::CurrencyId, who: &T::AccountId, value: Self::Balance) -> bool { if currency_id == T::GetNativeCurrencyId::get() { T::NativeCurrency::can_reserve(who, value) } else { T::MultiCurrency::can_reserve(currency_id, who, value) } } fn slash_reserved(currency_id: Self::CurrencyId, who: &T::AccountId, value: Self::Balance) -> Self::Balance { if currency_id == T::GetNativeCurrencyId::get() { T::NativeCurrency::slash_reserved(who, value) } else { T::MultiCurrency::slash_reserved(currency_id, who, value) } } fn reserved_balance(currency_id: Self::CurrencyId, who: &T::AccountId) -> Self::Balance { if currency_id == T::GetNativeCurrencyId::get() { T::NativeCurrency::reserved_balance(who) } else { T::MultiCurrency::reserved_balance(currency_id, who) } } fn reserve(currency_id: Self::CurrencyId, who: &T::AccountId, value: Self::Balance) -> DispatchResult { if currency_id == T::GetNativeCurrencyId::get() { T::NativeCurrency::reserve(who, value) } else { T::MultiCurrency::reserve(currency_id, who, value) } } fn unreserve(currency_id: Self::CurrencyId, who: &T::AccountId, value: Self::Balance) -> Self::Balance { if currency_id == T::GetNativeCurrencyId::get() { T::NativeCurrency::unreserve(who, value) } else { T::MultiCurrency::unreserve(currency_id, who, value) } } fn repatriate_reserved( currency_id: Self::CurrencyId, slashed: &T::AccountId, beneficiary: &T::AccountId, value: Self::Balance, status: BalanceStatus, ) -> result::Result<Self::Balance, DispatchError> { if currency_id == T::GetNativeCurrencyId::get() { T::NativeCurrency::repatriate_reserved(slashed, beneficiary, value, status) } else { T::MultiCurrency::repatriate_reserved(currency_id, slashed, beneficiary, value, status) } } } pub struct Currency<T, GetCurrencyId>(marker::PhantomData<T>, marker::PhantomData<GetCurrencyId>); impl<T, GetCurrencyId> BasicCurrency<T::AccountId> for Currency<T, GetCurrencyId> where T: Config, GetCurrencyId: Get<CurrencyIdOf<T>>, { type Balance = BalanceOf<T>; fn minimum_balance() -> Self::Balance { <Pallet<T>>::minimum_balance(GetCurrencyId::get()) } fn total_issuance() -> Self::Balance { <Pallet<T>>::total_issuance(GetCurrencyId::get()) } fn total_balance(who: &T::AccountId) -> Self::Balance { <Pallet<T>>::total_balance(GetCurrencyId::get(), who) } fn free_balance(who: &T::AccountId) -> Self::Balance { <Pallet<T>>::free_balance(GetCurrencyId::get(), who) } fn ensure_can_withdraw(who: &T::AccountId, amount: Self::Balance) -> DispatchResult { <Pallet<T>>::ensure_can_withdraw(GetCurrencyId::get(), who, amount) } fn transfer(from: &T::AccountId, to: &T::AccountId, amount: Self::Balance) -> DispatchResult { <Pallet<T> as MultiCurrency<T::AccountId>>::transfer(GetCurrencyId::get(), from, to, amount) } fn deposit(who: &T::AccountId, amount: Self::Balance) -> DispatchResult { <Pallet<T>>::deposit(GetCurrencyId::get(), who, amount) } fn withdraw(who: &T::AccountId, amount: Self::Balance) -> DispatchResult { <Pallet<T>>::withdraw(GetCurrencyId::get(), who, amount) } fn can_slash(who: &T::AccountId, amount: Self::Balance) -> bool { <Pallet<T>>::can_slash(GetCurrencyId::get(), who, amount) } fn slash(who: &T::AccountId, amount: Self::Balance) -> Self::Balance { <Pallet<T>>::slash(GetCurrencyId::get(), who, amount) } } impl<T, GetCurrencyId> BasicCurrencyExtended<T::AccountId> for Currency<T, GetCurrencyId> where T: Config, GetCurrencyId: Get<CurrencyIdOf<T>>, { type Amount = AmountOf<T>; fn update_balance(who: &T::AccountId, by_amount: Self::Amount) -> DispatchResult { <Pallet<T> as MultiCurrencyExtended<T::AccountId>>::update_balance(GetCurrencyId::get(), who, by_amount) } } impl<T, GetCurrencyId> BasicLockableCurrency<T::AccountId> for Currency<T, GetCurrencyId> where T: Config, GetCurrencyId: Get<CurrencyIdOf<T>>, { type Moment = T::BlockNumber; fn set_lock(lock_id: LockIdentifier, who: &T::AccountId, amount: Self::Balance) -> DispatchResult { <Pallet<T> as MultiLockableCurrency<T::AccountId>>::set_lock(lock_id, GetCurrencyId::get(), who, amount) } fn extend_lock(lock_id: LockIdentifier, who: &T::AccountId, amount: Self::Balance) -> DispatchResult { <Pallet<T> as MultiLockableCurrency<T::AccountId>>::extend_lock(lock_id, GetCurrencyId::get(), who, amount) } fn remove_lock(lock_id: LockIdentifier, who: &T::AccountId) -> DispatchResult { <Pallet<T> as MultiLockableCurrency<T::AccountId>>::remove_lock(lock_id, GetCurrencyId::get(), who) } } impl<T, GetCurrencyId> BasicReservableCurrency<T::AccountId> for Currency<T, GetCurrencyId> where T: Config, GetCurrencyId: Get<CurrencyIdOf<T>>, { fn can_reserve(who: &T::AccountId, value: Self::Balance) -> bool { <Pallet<T> as MultiReservableCurrency<T::AccountId>>::can_reserve(GetCurrencyId::get(), who, value) } fn slash_reserved(who: &T::AccountId, value: Self::Balance) -> Self::Balance { <Pallet<T> as MultiReservableCurrency<T::AccountId>>::slash_reserved(GetCurrencyId::get(), who, value) } fn reserved_balance(who: &T::AccountId) -> Self::Balance { <Pallet<T> as MultiReservableCurrency<T::AccountId>>::reserved_balance(GetCurrencyId::get(), who) } fn reserve(who: &T::AccountId, value: Self::Balance) -> DispatchResult { <Pallet<T> as MultiReservableCurrency<T::AccountId>>::reserve(GetCurrencyId::get(), who, value) } fn unreserve(who: &T::AccountId, value: Self::Balance) -> Self::Balance { <Pallet<T> as MultiReservableCurrency<T::AccountId>>::unreserve(GetCurrencyId::get(), who, value) } fn repatriate_reserved( slashed: &T::AccountId, beneficiary: &T::AccountId, value: Self::Balance, status: BalanceStatus, ) -> result::Result<Self::Balance, DispatchError> { <Pallet<T> as MultiReservableCurrency<T::AccountId>>::repatriate_reserved( GetCurrencyId::get(), slashed, beneficiary, value, status, ) } } pub type NativeCurrencyOf<T> = Currency<T, <T as Config>::GetNativeCurrencyId>; /// Adapt other currency traits implementation to `BasicCurrency`. pub struct BasicCurrencyAdapter<T, Currency, Amount, Moment>(marker::PhantomData<(T, Currency, Amount, Moment)>); type PalletBalanceOf<A, Currency> = <Currency as PalletCurrency<A>>::Balance; // Adapt `frame_support::traits::Currency` impl<T, AccountId, Currency, Amount, Moment> BasicCurrency<AccountId> for BasicCurrencyAdapter<T, Currency, Amount, Moment> where Currency: PalletCurrency<AccountId>, T: Config, { type Balance = PalletBalanceOf<AccountId, Currency>; fn minimum_balance() -> Self::Balance { Currency::minimum_balance() } fn total_issuance() -> Self::Balance { Currency::total_issuance() } fn total_balance(who: &AccountId) -> Self::Balance { Currency::total_balance(who) } fn free_balance(who: &AccountId) -> Self::Balance { Currency::free_balance(who) } fn ensure_can_withdraw(who: &AccountId, amount: Self::Balance) -> DispatchResult { let new_balance = Self::free_balance(who) .checked_sub(&amount) .ok_or(Error::<T>::BalanceTooLow)?; Currency::ensure_can_withdraw(who, amount, WithdrawReasons::all(), new_balance) } fn transfer(from: &AccountId, to: &AccountId, amount: Self::Balance) -> DispatchResult { Currency::transfer(from, to, amount, ExistenceRequirement::AllowDeath) } fn deposit(who: &AccountId, amount: Self::Balance) -> DispatchResult { let _ = Currency::deposit_creating(who, amount); Ok(()) } fn withdraw(who: &AccountId, amount: Self::Balance) -> DispatchResult { Currency::withdraw(who, amount, WithdrawReasons::all(), ExistenceRequirement::AllowDeath).map(|_| ()) } fn can_slash(who: &AccountId, amount: Self::Balance) -> bool { Currency::can_slash(who, amount) } fn slash(who: &AccountId, amount: Self::Balance) -> Self::Balance { let (_, gap) = Currency::slash(who, amount); gap } } // Adapt `frame_support::traits::Currency` impl<T, AccountId, Currency, Amount, Moment> BasicCurrencyExtended<AccountId> for BasicCurrencyAdapter<T, Currency, Amount, Moment> where Amount: Signed + TryInto<PalletBalanceOf<AccountId, Currency>> + TryFrom<PalletBalanceOf<AccountId, Currency>> + SimpleArithmetic + Codec + Copy + MaybeSerializeDeserialize + Debug + Default, Currency: PalletCurrency<AccountId>, T: Config, { type Amount = Amount; fn update_balance(who: &AccountId, by_amount: Self::Amount) -> DispatchResult { let by_balance = by_amount .abs() .try_into() .map_err(|_| Error::<T>::AmountIntoBalanceFailed)?; if by_amount.is_positive() { Self::deposit(who, by_balance) } else { Self::withdraw(who, by_balance) } } } // Adapt `frame_support::traits::LockableCurrency` impl<T, AccountId, Currency, Amount, Moment> BasicLockableCurrency<AccountId> for BasicCurrencyAdapter<T, Currency, Amount, Moment> where Currency: PalletLockableCurrency<AccountId>, T: Config, { type Moment = Moment; fn set_lock(lock_id: LockIdentifier, who: &AccountId, amount: Self::Balance) -> DispatchResult { Currency::set_lock(lock_id, who, amount, WithdrawReasons::all()); Ok(()) } fn extend_lock(lock_id: LockIdentifier, who: &AccountId, amount: Self::Balance) -> DispatchResult { Currency::extend_lock(lock_id, who, amount, WithdrawReasons::all()); Ok(()) } fn remove_lock(lock_id: LockIdentifier, who: &AccountId) -> DispatchResult { Currency::remove_lock(lock_id, who); Ok(()) } } // Adapt `frame_support::traits::ReservableCurrency` impl<T, AccountId, Currency, Amount, Moment> BasicReservableCurrency<AccountId> for BasicCurrencyAdapter<T, Currency, Amount, Moment> where Currency: PalletReservableCurrency<AccountId>, T: Config, { fn can_reserve(who: &AccountId, value: Self::Balance) -> bool { Currency::can_reserve(who, value) } fn slash_reserved(who: &AccountId, value: Self::Balance) -> Self::Balance { let (_, gap) = Currency::slash_reserved(who, value); gap } fn reserved_balance(who: &AccountId) -> Self::Balance { Currency::reserved_balance(who) } fn reserve(who: &AccountId, value: Self::Balance) -> DispatchResult { Currency::reserve(who, value) } fn unreserve(who: &AccountId, value: Self::Balance) -> Self::Balance { Currency::unreserve(who, value) } fn repatriate_reserved( slashed: &AccountId, beneficiary: &AccountId, value: Self::Balance, status: BalanceStatus, ) -> result::Result<Self::Balance, DispatchError> { Currency::repatriate_reserved(slashed, beneficiary, value, status) } } impl<T: Config> TransferAll<T::AccountId> for Pallet<T> { fn transfer_all(source: &T::AccountId, dest: &T::AccountId) -> DispatchResult { with_transaction_result(|| { // transfer non-native free to dest T::MultiCurrency::transfer_all(source, dest)?; // transfer all free to dest T::NativeCurrency::transfer(source, dest, T::NativeCurrency::free_balance(source)) }) } }