#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(test)]
mod mock;
#[cfg(test)]
pub(crate) mod tests;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
pub mod weights;
pub use cfg_traits::swaps::TokenSwaps;
pub use pallet::*;
pub use weights::WeightInfo;
#[frame_support::pallet]
pub mod pallet {
use cfg_traits::swaps::{OrderRatio, TokenSwaps};
use cfg_types::tokens::CustomMetadata;
use frame_support::{
pallet_prelude::{DispatchResult, *},
traits::{
fungibles,
fungibles::Mutate,
tokens::{Fortitude, Precision, Preservation},
},
PalletId,
};
use frame_system::pallet_prelude::{OriginFor, *};
use orml_traits::asset_registry::{self, Inspect as _};
use sp_arithmetic::{traits::AtLeast32BitUnsigned, FixedPointOperand};
use sp_runtime::traits::{AccountIdConversion, EnsureFixedPointNumber, One};
use super::*;
pub type BalanceOf<T> = <<T as Config>::Tokens as fungibles::Inspect<
<T as frame_system::Config>::AccountId,
>>::Balance;
const STORAGE_VERSION: StorageVersion = StorageVersion::new(0);
#[pallet::pallet]
#[pallet::storage_version(STORAGE_VERSION)]
pub struct Pallet<T>(_);
#[pallet::config]
pub trait Config: frame_system::Config {
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
#[pallet::constant]
type PalletId: Get<PalletId>;
type AssetRegistry: asset_registry::Inspect<
CustomMetadata = CustomMetadata,
AssetId = Self::CurrencyId,
>;
type BalanceIn: Member
+ Parameter
+ FixedPointOperand
+ AtLeast32BitUnsigned
+ MaxEncodedLen
+ Into<BalanceOf<Self>>
+ From<BalanceOf<Self>>;
type BalanceOut: Member
+ Parameter
+ FixedPointOperand
+ AtLeast32BitUnsigned
+ MaxEncodedLen
+ Into<BalanceOf<Self>>
+ From<BalanceOf<Self>>;
type BalanceRatio: Parameter + Member + sp_runtime::FixedPointNumber + MaxEncodedLen;
type OrderId: Parameter + Member + Copy + Ord + MaxEncodedLen;
type CurrencyId: Parameter
+ Member
+ Copy
+ MaxEncodedLen
+ From<Self::LocalAssetId>
+ TryInto<Self::LocalAssetId>;
type LocalAssetId: From<cfg_types::tokens::LocalAssetId>;
type Tokens: fungibles::Inspect<Self::AccountId, AssetId = Self::CurrencyId>
+ Mutate<Self::AccountId, AssetId = Self::CurrencyId>;
type OrderBook: TokenSwaps<
Self::AccountId,
CurrencyId = Self::CurrencyId,
BalanceIn = Self::BalanceIn,
BalanceOut = Self::BalanceOut,
OrderId = Self::OrderId,
Ratio = Self::BalanceRatio,
>;
type WeightInfo: WeightInfo;
}
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
Deposited {
who: T::AccountId,
currency_out: T::CurrencyId,
currency_in: T::CurrencyId,
amount: T::BalanceOut,
},
Burned {
who: T::AccountId,
currency_out: T::CurrencyId,
currency_in: T::CurrencyId,
amount: T::BalanceOut,
},
SwapMatched {
id: T::OrderId,
amount: T::BalanceOut,
},
}
#[pallet::error]
pub enum Error<T> {
MetadataNotFound,
NoLocalRepresentation,
SwapNotFound,
NotIdenticalSwap,
InvalidSwapCurrencies,
DecimalMismatch,
}
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::call_index(0)]
#[pallet::weight(T::WeightInfo::deposit())]
pub fn deposit(
origin: OriginFor<T>,
currency_out: T::CurrencyId,
amount_out: T::BalanceOut,
) -> DispatchResult {
let who = ensure_signed(origin)?;
let local = Self::try_local(¤cy_out)?;
Self::mint_route(&who, local, currency_out, amount_out)?;
Self::deposit_event(Event::<T>::Deposited {
who,
currency_out,
currency_in: local,
amount: amount_out,
});
Ok(())
}
#[pallet::call_index(1)]
#[pallet::weight(T::WeightInfo::burn())]
pub fn burn(
origin: OriginFor<T>,
currency_out: T::CurrencyId,
amount_out: T::BalanceOut,
) -> DispatchResult {
let who = ensure_signed(origin)?;
let local = Self::try_local(¤cy_out)?;
Self::burn_route(&who, local, currency_out, amount_out)?;
Self::deposit_event(Event::<T>::Burned {
who,
currency_out: local,
currency_in: currency_out,
amount: amount_out,
});
Ok(())
}
#[pallet::call_index(2)]
#[pallet::weight(T::WeightInfo::match_swap())]
pub fn match_swap(
origin: OriginFor<T>,
order_id: T::OrderId,
amount: T::BalanceOut,
) -> DispatchResult {
let _ = ensure_signed(origin)?;
let order =
T::OrderBook::get_order_details(order_id).ok_or(Error::<T>::SwapNotFound)?;
let ratio = match order.ratio {
OrderRatio::Market => T::BalanceRatio::ensure_from_rational(
amount,
T::OrderBook::convert_by_market(
order.swap.currency_in,
order.swap.currency_out,
amount,
)?,
)?,
OrderRatio::Custom(ratio) => ratio,
};
ensure!(ratio == One::one(), Error::<T>::NotIdenticalSwap);
match (
Self::try_local(&order.swap.currency_out),
Self::try_local(&order.swap.currency_in),
) {
(Ok(_), Ok(_)) | (Err(_), Err(_)) => {
return Err(Error::<T>::InvalidSwapCurrencies.into())
}
(Ok(local), Err(_)) => {
ensure!(
order.swap.currency_in == local,
Error::<T>::InvalidSwapCurrencies
);
T::Tokens::mint_into(local, &Self::account(), amount.into())?;
T::OrderBook::fill_order(Self::account(), order_id, amount)?;
}
(Err(_), Ok(local)) => {
ensure!(
order.swap.currency_out == local,
Error::<T>::InvalidSwapCurrencies
);
T::OrderBook::fill_order(Self::account(), order_id, amount)?;
T::Tokens::burn_from(
local,
&Self::account(),
amount.into(),
Precision::Exact,
Fortitude::Polite,
)?;
}
}
Self::deposit_event(Event::<T>::SwapMatched {
id: order_id,
amount,
});
Ok(())
}
}
impl<T: Config> Pallet<T> {
pub(crate) fn account() -> T::AccountId {
T::PalletId::get().into_account_truncating()
}
fn mint_route(
who: &T::AccountId,
local: T::CurrencyId,
variant: T::CurrencyId,
amount: T::BalanceOut,
) -> DispatchResult {
T::Tokens::transfer(
variant,
who,
&Self::account(),
amount.into(),
Preservation::Expendable,
)?;
T::Tokens::mint_into(local, who, amount.into()).map(|_| ())
}
fn burn_route(
who: &T::AccountId,
local: T::CurrencyId,
variant: T::CurrencyId,
amount: T::BalanceOut,
) -> DispatchResult {
T::Tokens::burn_from(
local,
who,
amount.into(),
Precision::Exact,
Fortitude::Polite,
)?;
T::Tokens::transfer(
variant,
&Self::account(),
who,
amount.into(),
Preservation::Expendable,
)
.map(|_| ())
}
pub(crate) fn try_local(currency: &T::CurrencyId) -> Result<T::CurrencyId, DispatchError> {
let meta_variant =
T::AssetRegistry::metadata(currency).ok_or(Error::<T>::MetadataNotFound)?;
let local: T::CurrencyId = T::LocalAssetId::from(
meta_variant
.additional
.local_representation
.ok_or(Error::<T>::NoLocalRepresentation)?,
)
.into();
let meta_local =
T::AssetRegistry::metadata(&local).ok_or(Error::<T>::MetadataNotFound)?;
ensure!(
meta_local.decimals == meta_variant.decimals,
Error::<T>::DecimalMismatch
);
Ok(local)
}
}
}