#![cfg_attr(not(feature = "std"), no_std)]
pub use impl_currency::*;
pub use impl_fungible::*;
pub use impl_fungibles::*;
pub use pallet::*;
pub use weights::*;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
mod impl_currency;
mod impl_fungible;
mod impl_fungibles;
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
pub mod weights;
use frame_support::{
dispatch::DispatchResult,
pallet_prelude::*,
traits::{fungible, fungibles, Currency, LockableCurrency, ReservableCurrency},
};
use scale_info::TypeInfo;
pub enum TokenType {
Native,
Other,
}
#[derive(Encode, Decode, Clone, PartialEq, Eq, Default, MaxEncodedLen, RuntimeDebug, TypeInfo)]
pub struct TransferDetails<AccountId, CurrencyId, Balance> {
pub send: AccountId,
pub recv: AccountId,
pub id: CurrencyId,
pub amount: Balance,
}
impl<AccountId, CurrencyId, Balance> TransferDetails<AccountId, CurrencyId, Balance> {
pub fn new(send: AccountId, recv: AccountId, id: CurrencyId, amount: Balance) -> Self {
TransferDetails {
send,
recv,
id,
amount,
}
}
}
#[frame_support::pallet]
pub mod pallet {
use cfg_traits::PreConditions;
use frame_support::{
pallet_prelude::TypeInfo,
sp_runtime::{
traits::{AtLeast32BitUnsigned, EnsureAdd, StaticLookup},
FixedPointOperand,
},
traits::tokens::{Fortitude, Precision, Preservation},
};
use frame_system::pallet_prelude::*;
use super::*;
use crate::{
impl_currency::{CurrencyEffects, ReservableCurrencyEffects},
impl_fungible::{
FungibleInspectEffects, FungibleInspectHoldEffects, FungibleMutateEffects,
FungibleMutateHoldEffects, FungibleTransferEffects,
},
impl_fungibles::{
FungiblesInspectEffects, FungiblesInspectHoldEffects, FungiblesMutateEffects,
FungiblesMutateHoldEffects, FungiblesTransferEffects,
},
};
#[pallet::composite_enum]
pub enum HoldReason {
NativeIndex,
}
#[pallet::config]
pub trait Config: frame_system::Config {
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
type RuntimeHoldReason: From<HoldReason> + Parameter;
type Balance: Parameter
+ Member
+ AtLeast32BitUnsigned
+ Default
+ Copy
+ MaybeSerializeDeserialize
+ MaxEncodedLen
+ FixedPointOperand;
type CurrencyId: Parameter + Member + Copy + Ord + TypeInfo + MaxEncodedLen;
type PreExtrTransfer: PreConditions<
TransferDetails<Self::AccountId, Self::CurrencyId, Self::Balance>,
Result = bool,
>;
type PreFungiblesInspect: PreConditions<
FungiblesInspectEffects<Self::CurrencyId, Self::AccountId, Self::Balance>,
Result = Self::Balance,
>;
type PreFungiblesInspectHold: PreConditions<
FungiblesInspectHoldEffects<Self::CurrencyId, Self::AccountId, Self::Balance>,
Result = bool,
>;
type PreFungiblesMutate: PreConditions<
FungiblesMutateEffects<Self::CurrencyId, Self::AccountId, Self::Balance>,
Result = bool,
>;
type PreFungiblesMutateHold: PreConditions<
FungiblesMutateHoldEffects<Self::CurrencyId, Self::AccountId, Self::Balance>,
Result = bool,
>;
type PreFungiblesTransfer: PreConditions<
FungiblesTransferEffects<Self::CurrencyId, Self::AccountId, Self::Balance>,
Result = bool,
>;
type PreFungiblesUnbalanced: PreConditions<
FungiblesUnbalancedEffects<Self::CurrencyId, Self::AccountId, Self::Balance>,
Result = bool,
>;
type Fungibles: fungibles::Inspect<Self::AccountId, AssetId = Self::CurrencyId, Balance = Self::Balance>
+ fungibles::InspectHold<Self::AccountId, Reason = ()>
+ fungibles::Mutate<Self::AccountId>
+ fungibles::MutateHold<Self::AccountId>;
type PreCurrency: PreConditions<
CurrencyEffects<Self::AccountId, Self::Balance>,
Result = bool,
>;
type PreReservableCurrency: PreConditions<
ReservableCurrencyEffects<Self::AccountId, Self::Balance>,
Result = bool,
>;
type PreFungibleInspect: PreConditions<
FungibleInspectEffects<Self::AccountId, Self::Balance>,
Result = Self::Balance,
>;
type PreFungibleInspectHold: PreConditions<
FungibleInspectHoldEffects<Self::AccountId, Self::Balance>,
Result = bool,
>;
type PreFungibleMutate: PreConditions<
FungibleMutateEffects<Self::AccountId, Self::Balance>,
Result = bool,
>;
type PreFungibleMutateHold: PreConditions<
FungibleMutateHoldEffects<Self::AccountId, Self::Balance>,
Result = bool,
>;
type PreFungibleTransfer: PreConditions<
FungibleTransferEffects<Self::AccountId, Self::Balance>,
Result = bool,
>;
type NativeFungible: Currency<Self::AccountId, Balance = Self::Balance>
+ LockableCurrency<Self::AccountId>
+ ReservableCurrency<Self::AccountId>
+ fungible::Inspect<Self::AccountId, Balance = Self::Balance>
+ fungible::InspectHold<Self::AccountId, Reason = Self::RuntimeHoldReason>
+ fungible::Mutate<Self::AccountId>
+ fungible::MutateHold<Self::AccountId>;
type NativeToken: Get<Self::CurrencyId>;
type WeightInfo: WeightInfo;
}
#[pallet::pallet]
pub struct Pallet<T>(_);
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
Transfer {
currency_id: T::CurrencyId,
from: T::AccountId,
to: T::AccountId,
amount: T::Balance,
},
BalanceSet {
currency_id: T::CurrencyId,
who: T::AccountId,
free: T::Balance,
reserved: T::Balance,
},
}
#[pallet::error]
pub enum Error<T> {
PreConditionsNotMet,
}
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::weight(T::WeightInfo::transfer_native().max(T::WeightInfo::transfer_other()))]
#[pallet::call_index(0)]
pub fn transfer(
origin: OriginFor<T>,
dest: <T::Lookup as StaticLookup>::Source,
currency_id: T::CurrencyId,
#[pallet::compact] amount: T::Balance,
) -> DispatchResultWithPostInfo {
let from = ensure_signed(origin)?;
let to = T::Lookup::lookup(dest)?;
ensure!(
T::PreExtrTransfer::check(TransferDetails::new(
from.clone(),
to.clone(),
currency_id,
amount
)),
Error::<T>::PreConditionsNotMet
);
let token = if T::NativeToken::get() == currency_id {
<T::NativeFungible as fungible::Mutate<T::AccountId>>::transfer(
&from,
&to,
amount,
Preservation::Expendable,
)?;
TokenType::Native
} else {
<T::Fungibles as fungibles::Mutate<T::AccountId>>::transfer(
currency_id,
&from,
&to,
amount,
Preservation::Expendable,
)?;
TokenType::Other
};
Self::deposit_event(Event::Transfer {
currency_id,
from,
to,
amount,
});
match token {
TokenType::Native => Ok(Some(T::WeightInfo::transfer_native()).into()),
TokenType::Other => Ok(Some(T::WeightInfo::transfer_other()).into()),
}
}
#[pallet::weight(
T::WeightInfo::transfer_all_native().max(
T::WeightInfo::transfer_all_other())
)]
#[pallet::call_index(1)]
pub fn transfer_all(
origin: OriginFor<T>,
dest: <T::Lookup as StaticLookup>::Source,
currency_id: T::CurrencyId,
keep_alive: bool,
) -> DispatchResultWithPostInfo {
let from = ensure_signed(origin)?;
let to = T::Lookup::lookup(dest)?;
let preservation = if keep_alive {
Preservation::Preserve
} else {
Preservation::Expendable
};
let reducible_balance = if T::NativeToken::get() == currency_id {
<T::NativeFungible as fungible::Inspect<T::AccountId>>::reducible_balance(
&from,
preservation,
Fortitude::Polite,
)
} else {
<T::Fungibles as fungibles::Inspect<T::AccountId>>::reducible_balance(
currency_id,
&from,
preservation,
Fortitude::Polite,
)
};
ensure!(
T::PreExtrTransfer::check(TransferDetails::new(
from.clone(),
to.clone(),
currency_id,
reducible_balance
)),
Error::<T>::PreConditionsNotMet
);
let token = if T::NativeToken::get() == currency_id {
<T::NativeFungible as fungible::Mutate<T::AccountId>>::transfer(
&from,
&to,
reducible_balance,
preservation,
)?;
TokenType::Native
} else {
<T::Fungibles as fungibles::Mutate<T::AccountId>>::transfer(
currency_id,
&from,
&to,
reducible_balance,
preservation,
)?;
TokenType::Other
};
Self::deposit_event(Event::Transfer {
currency_id,
from,
to,
amount: reducible_balance,
});
match token {
TokenType::Native => Ok(Some(T::WeightInfo::transfer_all_native()).into()),
TokenType::Other => Ok(Some(T::WeightInfo::transfer_all_other()).into()),
}
}
#[pallet::weight(
T::WeightInfo::transfer_keep_alive_native().max(
T::WeightInfo::transfer_keep_alive_other()
))]
#[pallet::call_index(2)]
pub fn transfer_keep_alive(
origin: OriginFor<T>,
dest: <T::Lookup as StaticLookup>::Source,
currency_id: T::CurrencyId,
#[pallet::compact] amount: T::Balance,
) -> DispatchResultWithPostInfo {
let from = ensure_signed(origin)?;
let to = T::Lookup::lookup(dest)?;
ensure!(
T::PreExtrTransfer::check(TransferDetails::new(
from.clone(),
to.clone(),
currency_id,
amount
)),
Error::<T>::PreConditionsNotMet
);
let token = if T::NativeToken::get() == currency_id {
<T::NativeFungible as fungible::Mutate<T::AccountId>>::transfer(
&from,
&to,
amount,
Preservation::Preserve,
)?;
TokenType::Native
} else {
<T::Fungibles as fungibles::Mutate<T::AccountId>>::transfer(
currency_id,
&from,
&to,
amount,
Preservation::Preserve,
)?;
TokenType::Other
};
Self::deposit_event(Event::Transfer {
currency_id,
from,
to,
amount,
});
match token {
TokenType::Native => Ok(Some(T::WeightInfo::transfer_keep_alive_native()).into()),
TokenType::Other => Ok(Some(T::WeightInfo::transfer_keep_alive_other()).into()),
}
}
#[pallet::weight(
T::WeightInfo::force_transfer_native().max(
T::WeightInfo::force_transfer_other())
)]
#[pallet::call_index(3)]
pub fn force_transfer(
origin: OriginFor<T>,
source: <T::Lookup as StaticLookup>::Source,
dest: <T::Lookup as StaticLookup>::Source,
currency_id: T::CurrencyId,
#[pallet::compact] amount: T::Balance,
) -> DispatchResultWithPostInfo {
ensure_root(origin)?;
let from = T::Lookup::lookup(source)?;
let to = T::Lookup::lookup(dest)?;
let token = if T::NativeToken::get() == currency_id {
<T::NativeFungible as fungible::Mutate<T::AccountId>>::transfer(
&from,
&to,
amount,
Preservation::Expendable,
)?;
TokenType::Native
} else {
<T::Fungibles as fungibles::Mutate<T::AccountId>>::transfer(
currency_id,
&from,
&to,
amount,
Preservation::Expendable,
)?;
TokenType::Other
};
Self::deposit_event(Event::Transfer {
currency_id,
from,
to,
amount,
});
match token {
TokenType::Native => Ok(Some(T::WeightInfo::force_transfer_native()).into()),
TokenType::Other => Ok(Some(T::WeightInfo::force_transfer_other()).into()),
}
}
#[pallet::weight(
T::WeightInfo::set_balance_native().max(
T::WeightInfo::set_balance_other())
)]
#[pallet::call_index(4)]
pub fn set_balance(
origin: OriginFor<T>,
who: <T::Lookup as StaticLookup>::Source,
currency_id: T::CurrencyId,
#[pallet::compact] new_free: T::Balance,
#[pallet::compact] new_reserved: T::Balance,
) -> DispatchResultWithPostInfo {
ensure_root(origin)?;
let who = T::Lookup::lookup(who)?;
let new_total = new_free.ensure_add(new_reserved)?;
let token = if T::NativeToken::get() == currency_id {
let old_reserved = <Self as fungible::InspectHold<T::AccountId>>::balance_on_hold(
&HoldReason::NativeIndex.into(),
&who,
);
<Self as fungible::MutateHold<T::AccountId>>::release(
&HoldReason::NativeIndex.into(),
&who,
old_reserved,
Precision::Exact,
)?;
let to_burn = <Self as fungible::Inspect<T::AccountId>>::balance(&who);
<Self as fungible::Mutate<T::AccountId>>::burn_from(
&who,
to_burn,
Precision::Exact,
Fortitude::Force,
)?;
<Self as fungible::Mutate<T::AccountId>>::mint_into(&who, new_total)?;
<Self as fungible::MutateHold<T::AccountId>>::hold(
&HoldReason::NativeIndex.into(),
&who,
new_reserved,
)?;
TokenType::Native
} else {
let old_reserved =
<T::Fungibles as fungibles::InspectHold<T::AccountId>>::balance_on_hold(
currency_id,
&(),
&who,
);
<T::Fungibles as fungibles::MutateHold<T::AccountId>>::release(
currency_id,
&(),
&who,
old_reserved,
Precision::Exact,
)?;
let to_burn =
<T::Fungibles as fungibles::Inspect<T::AccountId>>::balance(currency_id, &who);
<T::Fungibles as fungibles::Mutate<T::AccountId>>::burn_from(
currency_id,
&who,
to_burn,
Precision::Exact,
Fortitude::Force,
)?;
<T::Fungibles as fungibles::Mutate<T::AccountId>>::mint_into(
currency_id,
&who,
new_total,
)?;
<T::Fungibles as fungibles::MutateHold<T::AccountId>>::hold(
currency_id,
&(),
&who,
new_reserved,
)?;
TokenType::Other
};
Self::deposit_event(Event::BalanceSet {
currency_id,
who,
free: new_free,
reserved: new_reserved,
});
match token {
TokenType::Native => Ok(Some(T::WeightInfo::set_balance_native()).into()),
TokenType::Other => Ok(Some(T::WeightInfo::set_balance_other()).into()),
}
}
}
}