#![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::{
account::MergeAccount,
arithmetic::{Signed, SimpleArithmetic},
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 default_weight;
mod mock;
mod tests;
pub use module::*;
#[frame_support::pallet]
pub mod module {
use super::*;
pub trait WeightInfo {
fn transfer_non_native_currency() -> Weight;
fn transfer_native_currency() -> Weight;
fn update_balance_non_native_currency() -> Weight;
fn update_balance_native_currency_creating() -> Weight;
fn update_balance_native_currency_killing() -> Weight;
}
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: MergeAccount<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>>;
type WeightInfo: WeightInfo;
}
#[pallet::error]
pub enum Error<T> {
AmountIntoBalanceFailed,
BalanceTooLow,
}
#[pallet::event]
#[pallet::generate_deposit(pub(crate) fn deposit_event)]
pub enum Event<T: Config> {
Transferred(CurrencyIdOf<T>, T::AccountId, T::AccountId, BalanceOf<T>),
BalanceUpdated(CurrencyIdOf<T>, T::AccountId, AmountOf<T>),
Deposited(CurrencyIdOf<T>, T::AccountId, BalanceOf<T>),
Withdrawn(CurrencyIdOf<T>, T::AccountId, BalanceOf<T>),
}
#[pallet::pallet]
pub struct Pallet<T>(PhantomData<T>);
#[pallet::hooks]
impl<T: Config> Hooks<T::BlockNumber> for Pallet<T> {}
#[pallet::call]
impl<T: Config> Pallet<T> {
#[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())
}
#[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())
}
#[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>;
pub struct BasicCurrencyAdapter<T, Currency, Amount, Moment>(marker::PhantomData<(T, Currency, Amount, Moment)>);
type PalletBalanceOf<A, Currency> = <Currency as PalletCurrency<A>>::Balance;
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
}
}
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)
}
}
}
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(())
}
}
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> MergeAccount<T::AccountId> for Pallet<T> {
fn merge_account(source: &T::AccountId, dest: &T::AccountId) -> DispatchResult {
with_transaction_result(|| {
T::MultiCurrency::merge_account(source, dest)?;
T::NativeCurrency::unreserve(source, T::NativeCurrency::reserved_balance(source));
T::NativeCurrency::transfer(source, dest, T::NativeCurrency::free_balance(source))
})
}
}