#![cfg_attr(not(feature = "std"), no_std)]
use frame_support::{
assert_ok,
dispatch::{DispatchErrorWithPostInfo, DispatchResult, PostDispatchInfo},
ensure,
traits::Contains,
PalletId,
};
use frame_system::ensure_signed;
use sp_core::U256;
use frame_support::{
pallet_prelude::*,
traits::{
tokens::currency::{MultiTokenCurrency, MultiTokenVestingLocks},
ExistenceRequirement, Get, WithdrawReasons,
},
transactional,
};
use frame_system::pallet_prelude::*;
use mangata_support::traits::{
ActivationReservesProviderTrait, GetMaintenanceStatusTrait, PoolCreateApi, PreValidateSwaps,
ProofOfStakeRewardsApi, Valuate, XykFunctionsTrait,
};
use mangata_types::multipurpose_liquidity::ActivateKind;
use orml_tokens::{MultiTokenCurrencyExtended, MultiTokenReservableCurrency};
use sp_arithmetic::{helpers_128bit::multiply_by_rational_with_rounding, per_things::Rounding};
use sp_runtime::{
traits::{
AccountIdConversion, Bounded, CheckedAdd, CheckedDiv, CheckedSub, One, Saturating, Zero,
},
DispatchError, ModuleError, Permill, SaturatedConversion,
};
use sp_std::{
collections::btree_set::BTreeSet,
convert::{TryFrom, TryInto},
ops::Div,
prelude::*,
vec,
};
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
pub(crate) const LOG_TARGET: &str = "xyk";
#[macro_export]
macro_rules! log {
($level:tt, $patter:expr $(, $values:expr)* $(,)?) => {
log::$level!(
target: $crate::LOG_TARGET,
concat!("[{:?}] 💸 ", $patter), <frame_system::Pallet<T>>::block_number() $(, $values)*
)
};
}
const PALLET_ID: PalletId = PalletId(*b"79b14c96");
const LIQUIDITY_TOKEN_IDENTIFIER: &[u8] = b"LiquidityPoolToken";
const HEX_INDICATOR: &[u8] = b"0x";
const TOKEN_SYMBOL: &[u8] = b"TKN";
const TOKEN_SYMBOL_SEPARATOR: &[u8] = b"-";
const DEFAULT_DECIMALS: u32 = 18u32;
pub use pallet::*;
mod benchmarking;
pub mod weights;
pub use weights::WeightInfo;
type AccountIdOf<T> = <T as frame_system::Config>::AccountId;
pub type BalanceOf<T> = <<T as pallet::Config>::Currency as MultiTokenCurrency<
<T as frame_system::Config>::AccountId,
>>::Balance;
pub type CurrencyIdOf<T> = <<T as pallet::Config>::Currency as MultiTokenCurrency<
<T as frame_system::Config>::AccountId,
>>::CurrencyId;
#[derive(Eq, PartialEq, Encode, Decode)]
pub enum SwapKind {
Sell,
Buy,
}
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::dispatch::DispatchClass;
#[pallet::pallet]
pub struct Pallet<T>(PhantomData<T>);
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {}
#[cfg(feature = "runtime-benchmarks")]
pub trait XykBenchmarkingConfig:
pallet_issuance::Config + pallet_proof_of_stake::Config
{
}
#[cfg(not(feature = "runtime-benchmarks"))]
pub trait XykBenchmarkingConfig {}
#[pallet::config]
pub trait Config: frame_system::Config + XykBenchmarkingConfig {
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
type MaintenanceStatusProvider: GetMaintenanceStatusTrait;
type ActivationReservesProvider: ActivationReservesProviderTrait<
Self::AccountId,
BalanceOf<Self>,
CurrencyIdOf<Self>,
>;
type Currency: MultiTokenCurrencyExtended<Self::AccountId>
+ MultiTokenReservableCurrency<Self::AccountId>;
type NativeCurrencyId: Get<CurrencyIdOf<Self>>;
type TreasuryPalletId: Get<PalletId>;
type BnbTreasurySubAccDerive: Get<[u8; 4]>;
type LiquidityMiningRewards: ProofOfStakeRewardsApi<
Self::AccountId,
BalanceOf<Self>,
CurrencyIdOf<Self>,
>;
#[pallet::constant]
type PoolFeePercentage: Get<u128>;
#[pallet::constant]
type TreasuryFeePercentage: Get<u128>;
#[pallet::constant]
type BuyAndBurnFeePercentage: Get<u128>;
type DisallowedPools: Contains<(CurrencyIdOf<Self>, CurrencyIdOf<Self>)>;
type DisabledTokens: Contains<CurrencyIdOf<Self>>;
type VestingProvider: MultiTokenVestingLocks<
Self::AccountId,
Currency = <Self as pallet::Config>::Currency,
Moment = BlockNumberFor<Self>,
>;
type AssetMetadataMutation: AssetMetadataMutationTrait<CurrencyIdOf<Self>>;
type WeightInfo: WeightInfo;
}
#[pallet::error]
pub enum Error<T> {
PoolAlreadyExists,
NotEnoughAssets,
NoSuchPool,
NoSuchLiquidityAsset,
NotEnoughReserve,
ZeroAmount,
InsufficientInputAmount,
InsufficientOutputAmount,
SameAsset,
AssetAlreadyExists,
AssetDoesNotExists,
DivisionByZero,
UnexpectedFailure,
NotMangataLiquidityAsset,
SecondAssetAmountExceededExpectations,
MathOverflow,
LiquidityTokenCreationFailed,
NotEnoughRewardsEarned,
NotAPromotedPool,
PastTimeCalculation,
PoolAlreadyPromoted,
SoldAmountTooLow,
FunctionNotAvailableForThisToken,
DisallowedPool,
LiquidityCheckpointMathError,
CalculateRewardsMathError,
CalculateCumulativeWorkMaxRatioMathError,
CalculateRewardsAllMathError,
NoRights,
MultiswapShouldBeAtleastTwoHops,
MultiBuyAssetCantHaveSamePoolAtomicSwaps,
MultiSwapCantHaveSameTokenConsequetively,
TradingBlockedByMaintenanceMode,
PoolIsEmpty,
}
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
PoolCreated(T::AccountId, CurrencyIdOf<T>, BalanceOf<T>, CurrencyIdOf<T>, BalanceOf<T>),
AssetsSwapped(T::AccountId, Vec<CurrencyIdOf<T>>, BalanceOf<T>, BalanceOf<T>),
SellAssetFailedDueToSlippage(
T::AccountId,
CurrencyIdOf<T>,
BalanceOf<T>,
CurrencyIdOf<T>,
BalanceOf<T>,
BalanceOf<T>,
),
BuyAssetFailedDueToSlippage(
T::AccountId,
CurrencyIdOf<T>,
BalanceOf<T>,
CurrencyIdOf<T>,
BalanceOf<T>,
BalanceOf<T>,
),
LiquidityMinted(
T::AccountId,
CurrencyIdOf<T>,
BalanceOf<T>,
CurrencyIdOf<T>,
BalanceOf<T>,
CurrencyIdOf<T>,
BalanceOf<T>,
),
LiquidityBurned(
T::AccountId,
CurrencyIdOf<T>,
BalanceOf<T>,
CurrencyIdOf<T>,
BalanceOf<T>,
CurrencyIdOf<T>,
BalanceOf<T>,
),
PoolPromotionUpdated(CurrencyIdOf<T>, Option<u8>),
LiquidityActivated(T::AccountId, CurrencyIdOf<T>, BalanceOf<T>),
LiquidityDeactivated(T::AccountId, CurrencyIdOf<T>, BalanceOf<T>),
RewardsClaimed(T::AccountId, CurrencyIdOf<T>, BalanceOf<T>),
MultiSwapAssetFailedOnAtomicSwap(
T::AccountId,
Vec<CurrencyIdOf<T>>,
BalanceOf<T>,
ModuleError,
),
}
#[pallet::storage]
#[pallet::getter(fn asset_pool)]
pub type Pools<T: Config> = StorageMap<
_,
Blake2_256,
(CurrencyIdOf<T>, CurrencyIdOf<T>),
(BalanceOf<T>, BalanceOf<T>),
ValueQuery,
>;
#[pallet::storage]
#[pallet::getter(fn liquidity_asset)]
pub type LiquidityAssets<T: Config> = StorageMap<
_,
Blake2_256,
(CurrencyIdOf<T>, CurrencyIdOf<T>),
Option<CurrencyIdOf<T>>,
ValueQuery,
>;
#[pallet::storage]
#[pallet::getter(fn liquidity_pool)]
pub type LiquidityPools<T: Config> = StorageMap<
_,
Blake2_256,
CurrencyIdOf<T>,
Option<(CurrencyIdOf<T>, CurrencyIdOf<T>)>,
ValueQuery,
>;
#[pallet::genesis_config]
pub struct GenesisConfig<T: Config> {
pub created_pools_for_staking: Vec<(
T::AccountId,
CurrencyIdOf<T>,
BalanceOf<T>,
CurrencyIdOf<T>,
BalanceOf<T>,
CurrencyIdOf<T>,
)>,
}
impl<T: Config> Default for GenesisConfig<T> {
fn default() -> Self {
GenesisConfig { created_pools_for_staking: vec![] }
}
}
#[pallet::genesis_build]
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
fn build(&self) {
self.created_pools_for_staking.iter().for_each(
|(
account_id,
native_token_id,
native_token_amount,
pooled_token_id,
pooled_token_amount,
liquidity_token_id,
)| {
if <T as Config>::Currency::exists({ *liquidity_token_id }.into()) {
assert!(
<Pallet<T> as XykFunctionsTrait<
T::AccountId,
BalanceOf<T>,
CurrencyIdOf<T>,
>>::mint_liquidity(
account_id.clone(),
*native_token_id,
*pooled_token_id,
*native_token_amount,
*pooled_token_amount,
true,
)
.is_ok(),
"Pool mint failed"
);
} else {
let created_liquidity_token_id: CurrencyIdOf<T> =
<T as Config>::Currency::get_next_currency_id().into();
assert_eq!(
created_liquidity_token_id, *liquidity_token_id,
"Assets not initialized in the expected sequence",
);
assert_ok!(<Pallet<T> as XykFunctionsTrait<
T::AccountId,
BalanceOf<T>,
CurrencyIdOf<T>,
>>::create_pool(
account_id.clone(),
*native_token_id,
*native_token_amount,
*pooled_token_id,
*pooled_token_amount
));
}
},
)
}
}
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::call_index(0)]
#[pallet::weight(<<T as Config>::WeightInfo>::create_pool())]
pub fn create_pool(
origin: OriginFor<T>,
first_asset_id: CurrencyIdOf<T>,
first_asset_amount: BalanceOf<T>,
second_asset_id: CurrencyIdOf<T>,
second_asset_amount: BalanceOf<T>,
) -> DispatchResultWithPostInfo {
let sender = ensure_signed(origin)?;
ensure!(
!T::DisabledTokens::contains(&first_asset_id) &&
!T::DisabledTokens::contains(&second_asset_id),
Error::<T>::FunctionNotAvailableForThisToken
);
ensure!(
!T::DisallowedPools::contains(&(first_asset_id, second_asset_id)),
Error::<T>::DisallowedPool,
);
<Self as XykFunctionsTrait<T::AccountId, BalanceOf<T>, CurrencyIdOf<T>>>::create_pool(
sender,
first_asset_id,
first_asset_amount,
second_asset_id,
second_asset_amount,
)?;
Ok(().into())
}
#[pallet::call_index(1)]
#[pallet::weight((<<T as Config>::WeightInfo>::sell_asset(), DispatchClass::Operational, Pays::No))]
#[deprecated(note = "multiswap_sell_asset should be used instead")]
pub fn sell_asset(
origin: OriginFor<T>,
sold_asset_id: CurrencyIdOf<T>,
bought_asset_id: CurrencyIdOf<T>,
sold_asset_amount: BalanceOf<T>,
min_amount_out: BalanceOf<T>,
) -> DispatchResultWithPostInfo {
let sender = ensure_signed(origin)?;
<Self as XykFunctionsTrait<T::AccountId, BalanceOf<T>, CurrencyIdOf<T>>>::sell_asset(
sender,
sold_asset_id,
bought_asset_id,
sold_asset_amount,
min_amount_out,
false,
)
.map_err(|err| DispatchErrorWithPostInfo {
post_info: PostDispatchInfo {
actual_weight: Some(<<T as Config>::WeightInfo>::sell_asset()),
pays_fee: Pays::Yes,
},
error: err,
})?;
Ok(Pays::No.into())
}
#[pallet::call_index(2)]
#[pallet::weight((<<T as Config>::WeightInfo>::multiswap_sell_asset(swap_token_list.len() as u32), DispatchClass::Operational, Pays::No))]
pub fn multiswap_sell_asset(
origin: OriginFor<T>,
swap_token_list: Vec<CurrencyIdOf<T>>,
sold_asset_amount: BalanceOf<T>,
min_amount_out: BalanceOf<T>,
) -> DispatchResultWithPostInfo {
let sender = ensure_signed(origin)?;
if let (Some(sold_asset_id), Some(bought_asset_id), 2) =
(swap_token_list.get(0), swap_token_list.get(1), swap_token_list.len())
{
<Self as XykFunctionsTrait<T::AccountId, BalanceOf<T>, CurrencyIdOf<T>>>::sell_asset(
sender,
*sold_asset_id,
*bought_asset_id,
sold_asset_amount,
min_amount_out,
false,
)
} else {
<Self as XykFunctionsTrait<T::AccountId, BalanceOf<T>, CurrencyIdOf<T>>>::multiswap_sell_asset(
sender,
swap_token_list.clone(),
sold_asset_amount,
min_amount_out,
false,
false,
)
}
.map_err(|err| DispatchErrorWithPostInfo {
post_info: PostDispatchInfo {
actual_weight: Some(<<T as Config>::WeightInfo>::multiswap_sell_asset(
swap_token_list.len() as u32,
)),
pays_fee: Pays::Yes,
},
error: err,
})?;
Ok(Pays::No.into())
}
#[pallet::call_index(3)]
#[pallet::weight((<<T as Config>::WeightInfo>::buy_asset(), DispatchClass::Operational, Pays::No))]
#[deprecated(note = "multiswap_buy_asset should be used instead")]
pub fn buy_asset(
origin: OriginFor<T>,
sold_asset_id: CurrencyIdOf<T>,
bought_asset_id: CurrencyIdOf<T>,
bought_asset_amount: BalanceOf<T>,
max_amount_in: BalanceOf<T>,
) -> DispatchResultWithPostInfo {
let sender = ensure_signed(origin)?;
<Self as XykFunctionsTrait<T::AccountId, BalanceOf<T>, CurrencyIdOf<T>>>::buy_asset(
sender,
sold_asset_id,
bought_asset_id,
bought_asset_amount,
max_amount_in,
false,
)
.map_err(|err| DispatchErrorWithPostInfo {
post_info: PostDispatchInfo {
actual_weight: Some(<<T as Config>::WeightInfo>::buy_asset()),
pays_fee: Pays::Yes,
},
error: err,
})?;
Ok(Pays::No.into())
}
#[pallet::call_index(4)]
#[pallet::weight((<<T as Config>::WeightInfo>::multiswap_buy_asset(swap_token_list.len() as u32), DispatchClass::Operational, Pays::No))]
pub fn multiswap_buy_asset(
origin: OriginFor<T>,
swap_token_list: Vec<CurrencyIdOf<T>>,
bought_asset_amount: BalanceOf<T>,
max_amount_in: BalanceOf<T>,
) -> DispatchResultWithPostInfo {
let sender = ensure_signed(origin)?;
if let (Some(sold_asset_id), Some(bought_asset_id), 2) =
(swap_token_list.get(0), swap_token_list.get(1), swap_token_list.len())
{
<Self as XykFunctionsTrait<T::AccountId, BalanceOf<T>, CurrencyIdOf<T>>>::buy_asset(
sender,
*sold_asset_id,
*bought_asset_id,
bought_asset_amount,
max_amount_in,
false,
)
} else {
<Self as XykFunctionsTrait<T::AccountId, BalanceOf<T>, CurrencyIdOf<T>>>::multiswap_buy_asset(
sender,
swap_token_list.clone(),
bought_asset_amount,
max_amount_in,
false,
false,
)
}
.map_err(|err| DispatchErrorWithPostInfo {
post_info: PostDispatchInfo {
actual_weight: Some(<<T as Config>::WeightInfo>::multiswap_buy_asset(
swap_token_list.len() as u32,
)),
pays_fee: Pays::Yes,
},
error: err,
})?;
Ok(Pays::No.into())
}
#[pallet::call_index(5)]
#[pallet::weight(<<T as Config>::WeightInfo>::mint_liquidity_using_vesting_native_tokens())]
#[transactional]
pub fn mint_liquidity_using_vesting_native_tokens_by_vesting_index(
origin: OriginFor<T>,
native_asset_vesting_index: u32,
vesting_native_asset_unlock_some_amount_or_all: Option<BalanceOf<T>>,
second_asset_id: CurrencyIdOf<T>,
expected_second_asset_amount: BalanceOf<T>,
) -> DispatchResultWithPostInfo {
let sender = ensure_signed(origin)?;
let liquidity_asset_id =
Pallet::<T>::get_liquidity_asset(Self::native_token_id(), second_asset_id)?;
ensure!(
<T::LiquidityMiningRewards as ProofOfStakeRewardsApi<
T::AccountId,
BalanceOf<T>,
CurrencyIdOf<T>,
>>::is_enabled(liquidity_asset_id),
Error::<T>::NotAPromotedPool
);
let (unlocked_amount, vesting_starting_block, vesting_ending_block_as_balance): (
BalanceOf<T>,
BlockNumberFor<T>,
BalanceOf<T>,
) = <<T as Config>::VestingProvider>::unlock_tokens_by_vesting_index(
&sender,
Self::native_token_id().into(),
native_asset_vesting_index,
vesting_native_asset_unlock_some_amount_or_all,
)
.map(|x| (x.0, x.1, x.2))?;
let (liquidity_token_id, liquidity_assets_minted) = <Self as XykFunctionsTrait<
T::AccountId,
BalanceOf<T>,
CurrencyIdOf<T>,
>>::mint_liquidity(
sender.clone(),
Self::native_token_id(),
second_asset_id,
unlocked_amount,
expected_second_asset_amount,
false,
)?;
<<T as Config>::VestingProvider>::lock_tokens(
&sender,
liquidity_token_id.into(),
liquidity_assets_minted,
Some(vesting_starting_block),
vesting_ending_block_as_balance,
)?;
Ok(().into())
}
#[pallet::call_index(6)]
#[pallet::weight(<<T as Config>::WeightInfo>::mint_liquidity_using_vesting_native_tokens())]
#[transactional]
pub fn mint_liquidity_using_vesting_native_tokens(
origin: OriginFor<T>,
vesting_native_asset_amount: BalanceOf<T>,
second_asset_id: CurrencyIdOf<T>,
expected_second_asset_amount: BalanceOf<T>,
) -> DispatchResultWithPostInfo {
let sender = ensure_signed(origin)?;
let liquidity_asset_id =
Pallet::<T>::get_liquidity_asset(Self::native_token_id(), second_asset_id)?;
ensure!(
<T::LiquidityMiningRewards as ProofOfStakeRewardsApi<
T::AccountId,
BalanceOf<T>,
CurrencyIdOf<T>,
>>::is_enabled(liquidity_asset_id),
Error::<T>::NotAPromotedPool
);
let (vesting_starting_block, vesting_ending_block_as_balance): (
BlockNumberFor<T>,
BalanceOf<T>,
) = <<T as Config>::VestingProvider>::unlock_tokens(
&sender,
Self::native_token_id().into(),
vesting_native_asset_amount,
)
.map(|x| (x.0, x.1))?;
let (liquidity_token_id, liquidity_assets_minted) = <Self as XykFunctionsTrait<
T::AccountId,
BalanceOf<T>,
CurrencyIdOf<T>,
>>::mint_liquidity(
sender.clone(),
Self::native_token_id(),
second_asset_id,
vesting_native_asset_amount,
expected_second_asset_amount,
false,
)?;
<<T as Config>::VestingProvider>::lock_tokens(
&sender,
liquidity_token_id.into(),
liquidity_assets_minted,
Some(vesting_starting_block),
vesting_ending_block_as_balance,
)?;
Ok(().into())
}
#[pallet::call_index(7)]
#[pallet::weight(<<T as Config>::WeightInfo>::mint_liquidity())]
pub fn mint_liquidity(
origin: OriginFor<T>,
first_asset_id: CurrencyIdOf<T>,
second_asset_id: CurrencyIdOf<T>,
first_asset_amount: BalanceOf<T>,
expected_second_asset_amount: BalanceOf<T>,
) -> DispatchResultWithPostInfo {
let sender = ensure_signed(origin)?;
ensure!(
!T::DisabledTokens::contains(&first_asset_id) &&
!T::DisabledTokens::contains(&second_asset_id),
Error::<T>::FunctionNotAvailableForThisToken
);
<Self as XykFunctionsTrait<T::AccountId, BalanceOf<T>, CurrencyIdOf<T>>>::mint_liquidity(
sender,
first_asset_id,
second_asset_id,
first_asset_amount,
expected_second_asset_amount,
true,
)?;
Ok(().into())
}
#[pallet::call_index(8)]
#[pallet::weight(<<T as Config>::WeightInfo>::compound_rewards())]
#[transactional]
pub fn compound_rewards(
origin: OriginFor<T>,
liquidity_asset_id: CurrencyIdOf<T>,
amount_permille: Permill,
) -> DispatchResultWithPostInfo {
let sender = ensure_signed(origin)?;
<Self as XykFunctionsTrait<T::AccountId, BalanceOf<T>, CurrencyIdOf<T>>>::do_compound_rewards(
sender,
liquidity_asset_id,
amount_permille,
)?;
Ok(().into())
}
#[pallet::call_index(9)]
#[pallet::weight(<<T as Config>::WeightInfo>::provide_liquidity_with_conversion())]
#[transactional]
pub fn provide_liquidity_with_conversion(
origin: OriginFor<T>,
liquidity_asset_id: CurrencyIdOf<T>,
provided_asset_id: CurrencyIdOf<T>,
provided_asset_amount: BalanceOf<T>,
) -> DispatchResultWithPostInfo {
let sender = ensure_signed(origin)?;
let (first_asset_id, second_asset_id) = LiquidityPools::<T>::get(liquidity_asset_id)
.ok_or(Error::<T>::NoSuchLiquidityAsset)?;
ensure!(
!T::DisabledTokens::contains(&first_asset_id) &&
!T::DisabledTokens::contains(&second_asset_id),
Error::<T>::FunctionNotAvailableForThisToken
);
<Self as XykFunctionsTrait<T::AccountId, BalanceOf<T>, CurrencyIdOf<T>>>::provide_liquidity_with_conversion(
sender,
first_asset_id,
second_asset_id,
provided_asset_id,
provided_asset_amount,
true,
)?;
Ok(().into())
}
#[pallet::call_index(10)]
#[pallet::weight(<<T as Config>::WeightInfo>::burn_liquidity())]
pub fn burn_liquidity(
origin: OriginFor<T>,
first_asset_id: CurrencyIdOf<T>,
second_asset_id: CurrencyIdOf<T>,
liquidity_asset_amount: BalanceOf<T>,
) -> DispatchResultWithPostInfo {
let sender = ensure_signed(origin)?;
<Self as XykFunctionsTrait<T::AccountId, BalanceOf<T>, CurrencyIdOf<T>>>::burn_liquidity(
sender,
first_asset_id,
second_asset_id,
liquidity_asset_amount,
)?;
Ok(().into())
}
}
}
impl<T: Config> Pallet<T> {
fn total_fee() -> u128 {
T::PoolFeePercentage::get() +
T::TreasuryFeePercentage::get() +
T::BuyAndBurnFeePercentage::get()
}
pub fn get_max_instant_burn_amount(
user: &AccountIdOf<T>,
liquidity_asset_id: CurrencyIdOf<T>,
) -> BalanceOf<T> {
Self::get_max_instant_unreserve_amount(user, liquidity_asset_id).saturating_add(
<T as Config>::Currency::available_balance(liquidity_asset_id.into(), user),
)
}
pub fn get_max_instant_unreserve_amount(
user: &AccountIdOf<T>,
liquidity_asset_id: CurrencyIdOf<T>,
) -> BalanceOf<T> {
<T as pallet::Config>::ActivationReservesProvider::get_max_instant_unreserve_amount(
liquidity_asset_id,
user,
)
}
pub fn set_liquidity_asset_info(
liquidity_asset_id: CurrencyIdOf<T>,
first_asset_id: CurrencyIdOf<T>,
second_asset_id: CurrencyIdOf<T>,
) -> DispatchResult {
let mut name: Vec<u8> = Vec::<u8>::new();
name.extend_from_slice(LIQUIDITY_TOKEN_IDENTIFIER);
name.extend_from_slice(HEX_INDICATOR);
for bytes in liquidity_asset_id.saturated_into::<u32>().to_be_bytes().iter() {
match (bytes >> 4) as u8 {
x @ 0u8..=9u8 => name.push(x.saturating_add(48u8)),
x => name.push(x.saturating_add(55u8)),
}
match (bytes & 0b0000_1111) as u8 {
x @ 0u8..=9u8 => name.push(x.saturating_add(48u8)),
x => name.push(x.saturating_add(55u8)),
}
}
let mut symbol: Vec<u8> = Vec::<u8>::new();
symbol.extend_from_slice(TOKEN_SYMBOL);
symbol.extend_from_slice(HEX_INDICATOR);
for bytes in first_asset_id.saturated_into::<u32>().to_be_bytes().iter() {
match (bytes >> 4) as u8 {
x @ 0u8..=9u8 => symbol.push(x.saturating_add(48u8)),
x => symbol.push(x.saturating_add(55u8)),
}
match (bytes & 0b0000_1111) as u8 {
x @ 0u8..=9u8 => symbol.push(x.saturating_add(48u8)),
x => symbol.push(x.saturating_add(55u8)),
}
}
symbol.extend_from_slice(TOKEN_SYMBOL_SEPARATOR);
symbol.extend_from_slice(TOKEN_SYMBOL);
symbol.extend_from_slice(HEX_INDICATOR);
for bytes in second_asset_id.saturated_into::<u32>().to_be_bytes().iter() {
match (bytes >> 4) as u8 {
x @ 0u8..=9u8 => symbol.push(x.saturating_add(48u8)),
x => symbol.push(x.saturating_add(55u8)),
}
match (bytes & 0b0000_1111) as u8 {
x @ 0u8..=9u8 => symbol.push(x.saturating_add(48u8)),
x => symbol.push(x.saturating_add(55u8)),
}
}
T::AssetMetadataMutation::set_asset_info(
liquidity_asset_id,
name,
symbol,
DEFAULT_DECIMALS,
)?;
Ok(())
}
pub fn calculate_sell_price(
input_reserve: BalanceOf<T>,
output_reserve: BalanceOf<T>,
sell_amount: BalanceOf<T>,
) -> Result<BalanceOf<T>, DispatchError> {
let after_fee_percentage: u128 = 10000_u128
.checked_sub(Self::total_fee())
.ok_or_else(|| DispatchError::from(Error::<T>::MathOverflow))?;
let input_reserve_saturated: U256 = input_reserve.into().into();
let output_reserve_saturated: U256 = output_reserve.into().into();
let sell_amount_saturated: U256 = sell_amount.into().into();
let input_amount_with_fee: U256 =
sell_amount_saturated.saturating_mul(after_fee_percentage.into());
let numerator: U256 = input_amount_with_fee
.checked_mul(output_reserve_saturated)
.ok_or_else(|| DispatchError::from(Error::<T>::MathOverflow))?;
let denominator: U256 = input_reserve_saturated
.saturating_mul(10000.into())
.checked_add(input_amount_with_fee)
.ok_or_else(|| DispatchError::from(Error::<T>::MathOverflow))?;
let result_u256 = numerator
.checked_div(denominator)
.ok_or_else(|| DispatchError::from(Error::<T>::DivisionByZero))?;
let result_u128 = u128::try_from(result_u256)
.map_err(|_| DispatchError::from(Error::<T>::MathOverflow))?;
let result = BalanceOf::<T>::try_from(result_u128)
.map_err(|_| DispatchError::from(Error::<T>::MathOverflow))?;
log!(
info,
"calculate_sell_price: ({:?}, {:?}, {:?}) -> {:?}",
input_reserve,
output_reserve,
sell_amount,
result
);
Ok(result)
}
pub fn calculate_sell_price_no_fee(
input_reserve: BalanceOf<T>,
output_reserve: BalanceOf<T>,
sell_amount: BalanceOf<T>,
) -> Result<BalanceOf<T>, DispatchError> {
let input_reserve_saturated: U256 = input_reserve.into().into();
let output_reserve_saturated: U256 = output_reserve.into().into();
let sell_amount_saturated: U256 = sell_amount.into().into();
let numerator: U256 = sell_amount_saturated.saturating_mul(output_reserve_saturated);
let denominator: U256 = input_reserve_saturated.saturating_add(sell_amount_saturated);
let result_u256 = numerator
.checked_div(denominator)
.ok_or_else(|| DispatchError::from(Error::<T>::DivisionByZero))?;
let result_u128 = u128::try_from(result_u256)
.map_err(|_| DispatchError::from(Error::<T>::MathOverflow))?;
let result = BalanceOf::<T>::try_from(result_u128)
.map_err(|_| DispatchError::from(Error::<T>::MathOverflow))?;
log!(
info,
"calculate_sell_price_no_fee: ({:?}, {:?}, {:?}) -> {:?}",
input_reserve,
output_reserve,
sell_amount,
result
);
Ok(result)
}
pub fn calculate_buy_price(
input_reserve: BalanceOf<T>,
output_reserve: BalanceOf<T>,
buy_amount: BalanceOf<T>,
) -> Result<BalanceOf<T>, DispatchError> {
let after_fee_percentage: u128 = 10000_u128
.checked_sub(Self::total_fee())
.ok_or_else(|| DispatchError::from(Error::<T>::MathOverflow))?;
let input_reserve_saturated: U256 = input_reserve.into().into();
let output_reserve_saturated: U256 = output_reserve.into().into();
let buy_amount_saturated: U256 = buy_amount.into().into();
let numerator: U256 = input_reserve_saturated
.saturating_mul(buy_amount_saturated)
.checked_mul(10000.into())
.ok_or_else(|| DispatchError::from(Error::<T>::MathOverflow))?;
let denominator: U256 = output_reserve_saturated
.checked_sub(buy_amount_saturated)
.ok_or_else(|| DispatchError::from(Error::<T>::NotEnoughReserve))?
.checked_mul(after_fee_percentage.into())
.ok_or_else(|| DispatchError::from(Error::<T>::MathOverflow))?;
let result_u256 = numerator
.checked_div(denominator)
.ok_or_else(|| DispatchError::from(Error::<T>::DivisionByZero))?
.checked_add(1.into())
.ok_or_else(|| DispatchError::from(Error::<T>::MathOverflow))?;
let result_u128 = u128::try_from(result_u256)
.map_err(|_| DispatchError::from(Error::<T>::MathOverflow))?;
let result = BalanceOf::<T>::try_from(result_u128)
.map_err(|_| DispatchError::from(Error::<T>::MathOverflow))?;
log!(
info,
"calculate_buy_price: ({:?}, {:?}, {:?}) -> {:?}",
input_reserve,
output_reserve,
buy_amount,
result
);
Ok(result)
}
pub fn calculate_balanced_sell_amount(
total_amount: BalanceOf<T>,
reserve_amount: BalanceOf<T>,
) -> Result<BalanceOf<T>, DispatchError> {
let multiplier: U256 = 10_000.into();
let multiplier_sq: U256 = multiplier.pow(2.into());
let non_pool_fees: U256 = Self::total_fee()
.checked_sub(T::PoolFeePercentage::get())
.ok_or_else(|| DispatchError::from(Error::<T>::MathOverflow))?
.into(); let total_fee: U256 = Self::total_fee().into(); let total_amount_saturated: U256 = total_amount.into().into(); let reserve_amount_saturated: U256 = reserve_amount.into().into(); let fee_rate = multiplier_sq
.saturating_mul(2.into())
.checked_sub(total_fee.saturating_mul(multiplier))
.ok_or_else(|| DispatchError::from(Error::<T>::MathOverflow))?;
let denominator_negative = multiplier_sq
.checked_add(non_pool_fees.saturating_mul(total_fee))
.and_then(|v| v.checked_sub(total_fee.saturating_mul(multiplier)))
.and_then(|v| v.checked_sub(non_pool_fees.saturating_mul(multiplier)))
.ok_or_else(|| DispatchError::from(Error::<T>::MathOverflow))?
.saturating_mul(2.into());
let sqrt_arg = reserve_amount_saturated
.checked_pow(2.into())
.and_then(|v| v.checked_mul(fee_rate.pow(2.into())))
.and_then(|v| {
v.checked_add(
total_amount_saturated
.saturating_mul(reserve_amount_saturated)
.saturating_mul(denominator_negative)
.saturating_mul(multiplier_sq)
.saturating_mul(2.into()),
)
})
.ok_or_else(|| DispatchError::from(Error::<T>::MathOverflow))?;
let numerator_negative = sqrt_arg
.integer_sqrt()
.checked_sub(reserve_amount_saturated.saturating_mul(fee_rate))
.ok_or_else(|| DispatchError::from(Error::<T>::MathOverflow))?;
let result_u256 = numerator_negative
.checked_div(denominator_negative)
.ok_or_else(|| DispatchError::from(Error::<T>::DivisionByZero))?;
let result_u128 = u128::try_from(result_u256)
.map_err(|_| DispatchError::from(Error::<T>::MathOverflow))?;
let result = BalanceOf::<T>::try_from(result_u128)
.map_err(|_| DispatchError::from(Error::<T>::MathOverflow))?;
Ok(result)
}
pub fn get_liq_tokens_for_trading() -> Result<Vec<CurrencyIdOf<T>>, DispatchError> {
let result = LiquidityAssets::<T>::iter_values()
.filter_map(|v| v)
.filter(|v| !<T as Config>::Currency::total_issuance((*v).into()).is_zero())
.collect();
Ok(result)
}
pub fn get_liquidity_asset(
first_asset_id: CurrencyIdOf<T>,
second_asset_id: CurrencyIdOf<T>,
) -> Result<CurrencyIdOf<T>, DispatchError> {
if LiquidityAssets::<T>::contains_key((first_asset_id, second_asset_id)) {
LiquidityAssets::<T>::get((first_asset_id, second_asset_id))
.ok_or_else(|| Error::<T>::UnexpectedFailure.into())
} else {
LiquidityAssets::<T>::get((second_asset_id, first_asset_id))
.ok_or_else(|| Error::<T>::NoSuchPool.into())
}
}
pub fn calculate_sell_price_id(
sold_token_id: CurrencyIdOf<T>,
bought_token_id: CurrencyIdOf<T>,
sell_amount: BalanceOf<T>,
) -> Result<BalanceOf<T>, DispatchError> {
let (input_reserve, output_reserve) =
Pallet::<T>::get_reserves(sold_token_id, bought_token_id)?;
ensure!(!(Self::is_pool_empty(sold_token_id, bought_token_id)?), Error::<T>::PoolIsEmpty);
Self::calculate_sell_price(input_reserve, output_reserve, sell_amount)
}
pub fn calculate_buy_price_id(
sold_token_id: CurrencyIdOf<T>,
bought_token_id: CurrencyIdOf<T>,
buy_amount: BalanceOf<T>,
) -> Result<BalanceOf<T>, DispatchError> {
let (input_reserve, output_reserve) =
Pallet::<T>::get_reserves(sold_token_id, bought_token_id)?;
ensure!(!(Self::is_pool_empty(sold_token_id, bought_token_id)?), Error::<T>::PoolIsEmpty);
Self::calculate_buy_price(input_reserve, output_reserve, buy_amount)
}
pub fn get_reserves(
first_asset_id: CurrencyIdOf<T>,
second_asset_id: CurrencyIdOf<T>,
) -> Result<(BalanceOf<T>, BalanceOf<T>), DispatchError> {
let mut reserves = Pools::<T>::get((first_asset_id, second_asset_id));
if Pools::<T>::contains_key((first_asset_id, second_asset_id)) {
Ok((reserves.0, reserves.1))
} else if Pools::<T>::contains_key((second_asset_id, first_asset_id)) {
reserves = Pools::<T>::get((second_asset_id, first_asset_id));
Ok((reserves.1, reserves.0))
} else {
Err(DispatchError::from(Error::<T>::NoSuchPool))
}
}
pub fn set_reserves(
first_asset_id: CurrencyIdOf<T>,
first_asset_amount: BalanceOf<T>,
second_asset_id: CurrencyIdOf<T>,
second_asset_amount: BalanceOf<T>,
) -> DispatchResult {
if Pools::<T>::contains_key((first_asset_id, second_asset_id)) {
Pools::<T>::insert(
(first_asset_id, second_asset_id),
(first_asset_amount, second_asset_amount),
);
} else if Pools::<T>::contains_key((second_asset_id, first_asset_id)) {
Pools::<T>::insert(
(second_asset_id, first_asset_id),
(second_asset_amount, first_asset_amount),
);
} else {
return Err(DispatchError::from(Error::<T>::NoSuchPool))
}
Ok(())
}
pub fn get_burn_amount(
first_asset_id: CurrencyIdOf<T>,
second_asset_id: CurrencyIdOf<T>,
liquidity_asset_amount: BalanceOf<T>,
) -> Result<(BalanceOf<T>, BalanceOf<T>), DispatchError> {
let liquidity_asset_id = Self::get_liquidity_asset(first_asset_id, second_asset_id)?;
let (first_asset_reserve, second_asset_reserve) =
Pallet::<T>::get_reserves(first_asset_id, second_asset_id)?;
ensure!(!(Self::is_pool_empty(first_asset_id, second_asset_id)?), Error::<T>::PoolIsEmpty);
let (first_asset_amount, second_asset_amount) = Self::get_burn_amount_reserves(
first_asset_reserve,
second_asset_reserve,
liquidity_asset_id,
liquidity_asset_amount,
)?;
log!(
info,
"get_burn_amount: ({:?}, {:?}, {:?}) -> ({:?}, {:?})",
first_asset_id,
second_asset_id,
liquidity_asset_amount,
first_asset_amount,
second_asset_amount
);
Ok((first_asset_amount, second_asset_amount))
}
pub fn get_burn_amount_reserves(
first_asset_reserve: BalanceOf<T>,
second_asset_reserve: BalanceOf<T>,
liquidity_asset_id: CurrencyIdOf<T>,
liquidity_asset_amount: BalanceOf<T>,
) -> Result<(BalanceOf<T>, BalanceOf<T>), DispatchError> {
let total_liquidity_assets: BalanceOf<T> =
<T as Config>::Currency::total_issuance(liquidity_asset_id.into());
ensure!(!total_liquidity_assets.is_zero(), Error::<T>::DivisionByZero);
let first_asset_amount = multiply_by_rational_with_rounding(
first_asset_reserve.into(),
liquidity_asset_amount.into(),
total_liquidity_assets.into(),
Rounding::Down,
)
.map(SaturatedConversion::saturated_into)
.ok_or(Error::<T>::UnexpectedFailure)?;
let second_asset_amount = multiply_by_rational_with_rounding(
second_asset_reserve.into(),
liquidity_asset_amount.into(),
total_liquidity_assets.into(),
Rounding::Down,
)
.map(SaturatedConversion::saturated_into)
.ok_or(Error::<T>::UnexpectedFailure)?;
Ok((first_asset_amount, second_asset_amount))
}
fn settle_treasury_and_burn(
sold_asset_id: CurrencyIdOf<T>,
burn_amount: BalanceOf<T>,
treasury_amount: BalanceOf<T>,
) -> DispatchResult {
let vault = Self::account_id();
let mangata_id: CurrencyIdOf<T> = Self::native_token_id();
let treasury_account: T::AccountId = Self::treasury_account_id();
let bnb_treasury_account: T::AccountId = Self::bnb_treasury_account_id();
if sold_asset_id == mangata_id {
<T as Config>::Currency::burn_and_settle(
sold_asset_id.into(),
&bnb_treasury_account,
burn_amount,
)?;
}
else if Pools::<T>::contains_key((sold_asset_id, mangata_id)) ||
Pools::<T>::contains_key((mangata_id, sold_asset_id))
{
let (input_reserve, output_reserve) =
Pallet::<T>::get_reserves(sold_asset_id, mangata_id)?;
let settle_amount_in_mangata = Self::calculate_sell_price_no_fee(
input_reserve,
output_reserve,
treasury_amount
.checked_add(&burn_amount)
.ok_or_else(|| DispatchError::from(Error::<T>::MathOverflow))?,
)?;
let treasury_amount_in_mangata: BalanceOf<T> = settle_amount_in_mangata
.into()
.checked_mul(T::TreasuryFeePercentage::get())
.ok_or_else(|| DispatchError::from(Error::<T>::MathOverflow))?
.checked_div(
T::TreasuryFeePercentage::get()
.checked_add(T::BuyAndBurnFeePercentage::get())
.ok_or_else(|| DispatchError::from(Error::<T>::MathOverflow))?,
)
.ok_or_else(|| DispatchError::from(Error::<T>::MathOverflow))?
.try_into()
.map_err(|_| DispatchError::from(Error::<T>::MathOverflow))?;
let burn_amount_in_mangata: BalanceOf<T> = settle_amount_in_mangata
.into()
.checked_sub(treasury_amount_in_mangata.into())
.ok_or_else(|| DispatchError::from(Error::<T>::MathOverflow))?
.try_into()
.map_err(|_| DispatchError::from(Error::<T>::MathOverflow))?;
Pallet::<T>::set_reserves(
sold_asset_id,
input_reserve.saturating_add(treasury_amount).saturating_add(burn_amount),
mangata_id,
output_reserve
.saturating_sub(treasury_amount_in_mangata)
.saturating_sub(burn_amount_in_mangata),
)?;
<T as Config>::Currency::transfer(
sold_asset_id.into(),
&treasury_account,
&vault,
treasury_amount,
ExistenceRequirement::KeepAlive,
)?;
<T as Config>::Currency::transfer(
mangata_id.into(),
&vault,
&treasury_account,
treasury_amount_in_mangata,
ExistenceRequirement::KeepAlive,
)?;
<T as Config>::Currency::transfer(
sold_asset_id.into(),
&bnb_treasury_account,
&vault,
burn_amount,
ExistenceRequirement::KeepAlive,
)?;
<T as Config>::Currency::burn_and_settle(
mangata_id.into(),
&vault,
burn_amount_in_mangata,
)?;
}
else {
}
Ok(())
}
fn account_id() -> T::AccountId {
PALLET_ID.into_account_truncating()
}
fn treasury_account_id() -> T::AccountId {
T::TreasuryPalletId::get().into_account_truncating()
}
fn bnb_treasury_account_id() -> T::AccountId {
T::TreasuryPalletId::get().into_sub_account_truncating(T::BnbTreasurySubAccDerive::get())
}
fn native_token_id() -> CurrencyIdOf<T> {
<T as Config>::NativeCurrencyId::get()
}
fn calculate_initial_liquidity(
first_asset_amount: BalanceOf<T>,
second_asset_amount: BalanceOf<T>,
) -> Result<BalanceOf<T>, DispatchError> {
let initial_liquidity = first_asset_amount
.checked_div(&2_u32.into())
.ok_or_else(|| DispatchError::from(Error::<T>::MathOverflow))?
.checked_add(
&second_asset_amount
.checked_div(&2_u32.into())
.ok_or_else(|| DispatchError::from(Error::<T>::MathOverflow))?,
)
.ok_or_else(|| DispatchError::from(Error::<T>::MathOverflow))?;
return Ok(if initial_liquidity == BalanceOf::<T>::zero() {
BalanceOf::<T>::one()
} else {
initial_liquidity
})
}
fn is_pool_empty(
first_asset_id: CurrencyIdOf<T>,
second_asset_id: CurrencyIdOf<T>,
) -> Result<bool, DispatchError> {
let liquidity_asset_id = Pallet::<T>::get_liquidity_asset(first_asset_id, second_asset_id)?;
let total_liquidity_assets: BalanceOf<T> =
<T as Config>::Currency::total_issuance(liquidity_asset_id.into());
return Ok(total_liquidity_assets.is_zero())
}
}
impl<T: Config> PreValidateSwaps<T::AccountId, BalanceOf<T>, CurrencyIdOf<T>> for Pallet<T> {
fn pre_validate_sell_asset(
sender: &T::AccountId,
sold_asset_id: CurrencyIdOf<T>,
bought_asset_id: CurrencyIdOf<T>,
sold_asset_amount: BalanceOf<T>,
_min_amount_out: BalanceOf<T>,
) -> Result<
(BalanceOf<T>, BalanceOf<T>, BalanceOf<T>, BalanceOf<T>, BalanceOf<T>, BalanceOf<T>),
DispatchError,
> {
ensure!(
!T::MaintenanceStatusProvider::is_maintenance(),
Error::<T>::TradingBlockedByMaintenanceMode
);
ensure!(!sold_asset_amount.is_zero(), Error::<T>::ZeroAmount,);
ensure!(
!T::DisabledTokens::contains(&sold_asset_id) &&
!T::DisabledTokens::contains(&bought_asset_id),
Error::<T>::FunctionNotAvailableForThisToken
);
ensure!(!(Self::is_pool_empty(sold_asset_id, bought_asset_id)?), Error::<T>::PoolIsEmpty);
let buy_and_burn_amount: BalanceOf<T> = multiply_by_rational_with_rounding(
sold_asset_amount.into(),
T::BuyAndBurnFeePercentage::get(),
10000,
Rounding::Down,
)
.ok_or(Error::<T>::UnexpectedFailure)?
.checked_add(1)
.ok_or(Error::<T>::MathOverflow)?
.try_into()
.map_err(|_| Error::<T>::MathOverflow)?;
let treasury_amount: BalanceOf<T> = multiply_by_rational_with_rounding(
sold_asset_amount.into(),
T::TreasuryFeePercentage::get(),
10000,
Rounding::Down,
)
.ok_or(Error::<T>::UnexpectedFailure)?
.checked_add(1)
.ok_or(Error::<T>::MathOverflow)?
.try_into()
.map_err(|_| Error::<T>::MathOverflow)?;
let pool_fee_amount: BalanceOf<T> = multiply_by_rational_with_rounding(
sold_asset_amount.into(),
T::PoolFeePercentage::get(),
10000,
Rounding::Down,
)
.ok_or(Error::<T>::UnexpectedFailure)?
.checked_add(1)
.ok_or(Error::<T>::MathOverflow)?
.try_into()
.map_err(|_| Error::<T>::MathOverflow)?;
let total_fees: BalanceOf<T> = buy_and_burn_amount
.checked_add(&treasury_amount)
.and_then(|v| v.checked_add(&pool_fee_amount))
.ok_or(Error::<T>::MathOverflow)?;
let (input_reserve, output_reserve) =
Pallet::<T>::get_reserves(sold_asset_id, bought_asset_id)?;
ensure!(input_reserve.checked_add(&sold_asset_amount).is_some(), Error::<T>::MathOverflow);
let bought_asset_amount =
Pallet::<T>::calculate_sell_price(input_reserve, output_reserve, sold_asset_amount)?;
<T as Config>::Currency::ensure_can_withdraw(
sold_asset_id.into(),
sender,
total_fees,
WithdrawReasons::all(),
Default::default(),
)
.or(Err(Error::<T>::NotEnoughAssets))?;
Ok((
buy_and_burn_amount,
treasury_amount,
pool_fee_amount,
input_reserve,
output_reserve,
bought_asset_amount,
))
}
fn pre_validate_multiswap_sell_asset(
sender: &T::AccountId,
swap_token_list: Vec<CurrencyIdOf<T>>,
sold_asset_amount: BalanceOf<T>,
_min_amount_out: BalanceOf<T>,
) -> Result<
(
BalanceOf<T>,
BalanceOf<T>,
BalanceOf<T>,
BalanceOf<T>,
BalanceOf<T>,
CurrencyIdOf<T>,
CurrencyIdOf<T>,
),
DispatchError,
> {
ensure!(
!T::MaintenanceStatusProvider::is_maintenance(),
Error::<T>::TradingBlockedByMaintenanceMode
);
ensure!(swap_token_list.len() > 2_usize, Error::<T>::MultiswapShouldBeAtleastTwoHops);
let sold_asset_id =
*swap_token_list.get(0).ok_or(Error::<T>::MultiswapShouldBeAtleastTwoHops)?;
let bought_asset_id =
*swap_token_list.get(1).ok_or(Error::<T>::MultiswapShouldBeAtleastTwoHops)?;
ensure!(!sold_asset_amount.is_zero(), Error::<T>::ZeroAmount,);
let atomic_pairs: Vec<(CurrencyIdOf<T>, CurrencyIdOf<T>)> = swap_token_list
.clone()
.into_iter()
.zip(swap_token_list.clone().into_iter().skip(1))
.collect();
for (x, y) in atomic_pairs.iter() {
ensure!(!(Self::is_pool_empty(*x, *y)?), Error::<T>::PoolIsEmpty);
if x == y {
return Err(Error::<T>::MultiSwapCantHaveSameTokenConsequetively.into())
}
}
ensure!(
!T::DisabledTokens::contains(&sold_asset_id) &&
!T::DisabledTokens::contains(&bought_asset_id),
Error::<T>::FunctionNotAvailableForThisToken
);
let buy_and_burn_amount: BalanceOf<T> = multiply_by_rational_with_rounding(
sold_asset_amount.into(),
T::BuyAndBurnFeePercentage::get(),
10000,
Rounding::Down,
)
.ok_or(Error::<T>::UnexpectedFailure)?
.checked_add(1)
.ok_or(Error::<T>::MathOverflow)?
.try_into()
.map_err(|_| Error::<T>::MathOverflow)?;
let treasury_amount: BalanceOf<T> = multiply_by_rational_with_rounding(
sold_asset_amount.into(),
T::TreasuryFeePercentage::get(),
10000,
Rounding::Down,
)
.ok_or(Error::<T>::UnexpectedFailure)?
.checked_add(1)
.ok_or(Error::<T>::MathOverflow)?
.try_into()
.map_err(|_| Error::<T>::MathOverflow)?;
let pool_fee_amount: BalanceOf<T> = multiply_by_rational_with_rounding(
sold_asset_amount.into(),
T::PoolFeePercentage::get(),
10000,
Rounding::Down,
)
.ok_or(Error::<T>::UnexpectedFailure)?
.checked_add(1)
.ok_or(Error::<T>::MathOverflow)?
.try_into()
.map_err(|_| Error::<T>::MathOverflow)?;
let total_fees = buy_and_burn_amount
.checked_add(&treasury_amount)
.and_then(|v| v.checked_add(&pool_fee_amount))
.ok_or(Error::<T>::MathOverflow)?;
let (input_reserve, output_reserve) =
Pallet::<T>::get_reserves(sold_asset_id, bought_asset_id)?;
ensure!(input_reserve.checked_add(&pool_fee_amount).is_some(), Error::<T>::MathOverflow);
<T as Config>::Currency::ensure_can_withdraw(
sold_asset_id.into(),
sender,
total_fees,
WithdrawReasons::all(),
Default::default(),
)
.or(Err(Error::<T>::NotEnoughAssets))?;
Ok((
buy_and_burn_amount,
treasury_amount,
pool_fee_amount,
input_reserve,
output_reserve,
sold_asset_id,
bought_asset_id,
))
}
fn pre_validate_buy_asset(
sender: &T::AccountId,
sold_asset_id: CurrencyIdOf<T>,
bought_asset_id: CurrencyIdOf<T>,
bought_asset_amount: BalanceOf<T>,
_max_amount_in: BalanceOf<T>,
) -> Result<
(BalanceOf<T>, BalanceOf<T>, BalanceOf<T>, BalanceOf<T>, BalanceOf<T>, BalanceOf<T>),
DispatchError,
> {
ensure!(
!T::MaintenanceStatusProvider::is_maintenance(),
Error::<T>::TradingBlockedByMaintenanceMode
);
ensure!(
!T::DisabledTokens::contains(&sold_asset_id) &&
!T::DisabledTokens::contains(&bought_asset_id),
Error::<T>::FunctionNotAvailableForThisToken
);
ensure!(!(Self::is_pool_empty(sold_asset_id, bought_asset_id)?), Error::<T>::PoolIsEmpty);
let (input_reserve, output_reserve) =
Pallet::<T>::get_reserves(sold_asset_id, bought_asset_id)?;
ensure!(output_reserve > bought_asset_amount, Error::<T>::NotEnoughReserve,);
ensure!(!bought_asset_amount.is_zero(), Error::<T>::ZeroAmount,);
let sold_asset_amount =
Pallet::<T>::calculate_buy_price(input_reserve, output_reserve, bought_asset_amount)?;
let buy_and_burn_amount: BalanceOf<T> = multiply_by_rational_with_rounding(
sold_asset_amount.into(),
T::BuyAndBurnFeePercentage::get(),
10000,
Rounding::Down,
)
.ok_or(Error::<T>::UnexpectedFailure)?
.checked_add(1)
.ok_or(Error::<T>::MathOverflow)?
.try_into()
.map_err(|_| Error::<T>::MathOverflow)?;
let treasury_amount: BalanceOf<T> = multiply_by_rational_with_rounding(
sold_asset_amount.into(),
T::TreasuryFeePercentage::get(),
10000,
Rounding::Down,
)
.ok_or(Error::<T>::UnexpectedFailure)?
.checked_add(1)
.ok_or(Error::<T>::MathOverflow)?
.try_into()
.map_err(|_| Error::<T>::MathOverflow)?;
let pool_fee_amount: BalanceOf<T> = multiply_by_rational_with_rounding(
sold_asset_amount.into(),
T::PoolFeePercentage::get(),
10000,
Rounding::Down,
)
.ok_or(Error::<T>::UnexpectedFailure)?
.checked_add(1)
.ok_or(Error::<T>::MathOverflow)?
.try_into()
.map_err(|_| Error::<T>::MathOverflow)?;
ensure!(input_reserve.checked_add(&sold_asset_amount).is_some(), Error::<T>::MathOverflow);
<T as Config>::Currency::ensure_can_withdraw(
sold_asset_id.into(),
sender,
sold_asset_amount,
WithdrawReasons::all(),
Default::default(),
)
.or(Err(Error::<T>::NotEnoughAssets))?;
Ok((
buy_and_burn_amount,
treasury_amount,
pool_fee_amount,
input_reserve,
output_reserve,
sold_asset_amount,
))
}
fn pre_validate_multiswap_buy_asset(
sender: &T::AccountId,
swap_token_list: Vec<CurrencyIdOf<T>>,
final_bought_asset_amount: BalanceOf<T>,
max_amount_in: BalanceOf<T>,
) -> Result<
(
BalanceOf<T>,
BalanceOf<T>,
BalanceOf<T>,
BalanceOf<T>,
BalanceOf<T>,
CurrencyIdOf<T>,
CurrencyIdOf<T>,
),
DispatchError,
> {
ensure!(
!T::MaintenanceStatusProvider::is_maintenance(),
Error::<T>::TradingBlockedByMaintenanceMode
);
ensure!(swap_token_list.len() > 2_usize, Error::<T>::MultiswapShouldBeAtleastTwoHops);
ensure!(!final_bought_asset_amount.is_zero(), Error::<T>::ZeroAmount,);
ensure!(!max_amount_in.is_zero(), Error::<T>::ZeroAmount,);
let sold_asset_id =
*swap_token_list.get(0).ok_or(Error::<T>::MultiswapShouldBeAtleastTwoHops)?;
let bought_asset_id =
*swap_token_list.get(1).ok_or(Error::<T>::MultiswapShouldBeAtleastTwoHops)?;
ensure!(
!T::DisabledTokens::contains(&sold_asset_id) &&
!T::DisabledTokens::contains(&bought_asset_id),
Error::<T>::FunctionNotAvailableForThisToken
);
let atomic_pairs: Vec<(CurrencyIdOf<T>, CurrencyIdOf<T>)> = swap_token_list
.clone()
.into_iter()
.zip(swap_token_list.clone().into_iter().skip(1))
.collect();
let mut atomic_pairs_hashset = BTreeSet::new();
for (x, y) in atomic_pairs.iter() {
ensure!(!(Self::is_pool_empty(*x, *y)?), Error::<T>::PoolIsEmpty);
if x == y {
return Err(Error::<T>::MultiSwapCantHaveSameTokenConsequetively.into())
} else if x > y {
if !atomic_pairs_hashset.insert((x, y)) {
return Err(Error::<T>::MultiBuyAssetCantHaveSamePoolAtomicSwaps.into())
};
} else
{
if !atomic_pairs_hashset.insert((y, x)) {
return Err(Error::<T>::MultiBuyAssetCantHaveSamePoolAtomicSwaps.into())
};
}
}
let (input_reserve, output_reserve) =
Pallet::<T>::get_reserves(sold_asset_id, bought_asset_id)?;
let buy_and_burn_amount: BalanceOf<T> = multiply_by_rational_with_rounding(
max_amount_in.into(),
T::BuyAndBurnFeePercentage::get(),
10000,
Rounding::Down,
)
.ok_or(Error::<T>::UnexpectedFailure)?
.checked_add(1)
.ok_or(Error::<T>::MathOverflow)?
.try_into()
.map_err(|_| Error::<T>::MathOverflow)?;
let treasury_amount: BalanceOf<T> = multiply_by_rational_with_rounding(
max_amount_in.into(),
T::TreasuryFeePercentage::get(),
10000,
Rounding::Down,
)
.ok_or(Error::<T>::UnexpectedFailure)?
.checked_add(1)
.ok_or(Error::<T>::MathOverflow)?
.try_into()
.map_err(|_| Error::<T>::MathOverflow)?;
let pool_fee_amount: BalanceOf<T> = multiply_by_rational_with_rounding(
max_amount_in.into(),
T::PoolFeePercentage::get(),
10000,
Rounding::Down,
)
.ok_or(Error::<T>::UnexpectedFailure)?
.checked_add(1)
.ok_or(Error::<T>::MathOverflow)?
.try_into()
.map_err(|_| Error::<T>::MathOverflow)?;
let total_fees = buy_and_burn_amount
.checked_add(&treasury_amount)
.and_then(|v| v.checked_add(&pool_fee_amount))
.ok_or(Error::<T>::MathOverflow)?;
ensure!(input_reserve.checked_add(&pool_fee_amount).is_some(), Error::<T>::MathOverflow);
<T as Config>::Currency::ensure_can_withdraw(
sold_asset_id.into(),
sender,
total_fees,
WithdrawReasons::all(),
Default::default(),
)
.or(Err(Error::<T>::NotEnoughAssets))?;
Ok((
buy_and_burn_amount,
treasury_amount,
pool_fee_amount,
input_reserve,
output_reserve,
sold_asset_id,
bought_asset_id,
))
}
}
impl<T: Config> XykFunctionsTrait<T::AccountId, BalanceOf<T>, CurrencyIdOf<T>> for Pallet<T> {
fn create_pool(
sender: T::AccountId,
first_asset_id: CurrencyIdOf<T>,
first_asset_amount: BalanceOf<T>,
second_asset_id: CurrencyIdOf<T>,
second_asset_amount: BalanceOf<T>,
) -> DispatchResult {
let vault: T::AccountId = Pallet::<T>::account_id();
ensure!(
!first_asset_amount.is_zero() && !second_asset_amount.is_zero(),
Error::<T>::ZeroAmount,
);
ensure!(
!Pools::<T>::contains_key((first_asset_id, second_asset_id)),
Error::<T>::PoolAlreadyExists,
);
ensure!(
!Pools::<T>::contains_key((second_asset_id, first_asset_id)),
Error::<T>::PoolAlreadyExists,
);
<T as Config>::Currency::ensure_can_withdraw(
first_asset_id.into(),
&sender,
first_asset_amount,
WithdrawReasons::all(),
Default::default(),
)
.or(Err(Error::<T>::NotEnoughAssets))?;
<T as Config>::Currency::ensure_can_withdraw(
second_asset_id.into(),
&sender,
second_asset_amount,
WithdrawReasons::all(),
Default::default(),
)
.or(Err(Error::<T>::NotEnoughAssets))?;
ensure!(first_asset_id != second_asset_id, Error::<T>::SameAsset,);
let initial_liquidity =
Pallet::<T>::calculate_initial_liquidity(first_asset_amount, second_asset_amount)?;
Pools::<T>::insert(
(first_asset_id, second_asset_id),
(first_asset_amount, second_asset_amount),
);
<T as Config>::Currency::transfer(
first_asset_id.into(),
&sender,
&vault,
first_asset_amount,
ExistenceRequirement::AllowDeath,
)?;
<T as Config>::Currency::transfer(
second_asset_id.into(),
&sender,
&vault,
second_asset_amount,
ExistenceRequirement::AllowDeath,
)?;
let liquidity_asset_id: CurrencyIdOf<T> =
<T as Config>::Currency::create(&sender, initial_liquidity)
.map_err(|_| Error::<T>::LiquidityTokenCreationFailed)?
.into();
LiquidityAssets::<T>::insert((first_asset_id, second_asset_id), Some(liquidity_asset_id));
LiquidityPools::<T>::insert(liquidity_asset_id, Some((first_asset_id, second_asset_id)));
log!(
info,
"create_pool: ({:?}, {:?}, {:?}, {:?}, {:?}) -> ({:?}, {:?})",
sender,
first_asset_id,
first_asset_amount,
second_asset_id,
second_asset_amount,
liquidity_asset_id,
initial_liquidity
);
log!(
info,
"pool-state: [({:?}, {:?}) -> {:?}, ({:?}, {:?}) -> {:?}]",
first_asset_id,
second_asset_id,
first_asset_amount,
second_asset_id,
first_asset_id,
second_asset_amount
);
Pallet::<T>::set_liquidity_asset_info(liquidity_asset_id, first_asset_id, second_asset_id)?;
Pallet::<T>::deposit_event(Event::PoolCreated(
sender,
first_asset_id,
first_asset_amount,
second_asset_id,
second_asset_amount,
));
Ok(())
}
fn sell_asset(
sender: T::AccountId,
sold_asset_id: CurrencyIdOf<T>,
bought_asset_id: CurrencyIdOf<T>,
sold_asset_amount: BalanceOf<T>,
min_amount_out: BalanceOf<T>,
err_upon_bad_slippage: bool,
) -> Result<BalanceOf<T>, DispatchError> {
let (
buy_and_burn_amount,
treasury_amount,
pool_fee_amount,
input_reserve,
output_reserve,
bought_asset_amount,
) = <Pallet<T> as PreValidateSwaps<T::AccountId, BalanceOf<T>, CurrencyIdOf<T>>>::pre_validate_sell_asset(
&sender,
sold_asset_id,
bought_asset_id,
sold_asset_amount,
min_amount_out,
)?;
let vault = Pallet::<T>::account_id();
let treasury_account: T::AccountId = Self::treasury_account_id();
let bnb_treasury_account: T::AccountId = Self::bnb_treasury_account_id();
<T as Config>::Currency::transfer(
sold_asset_id.into(),
&sender,
&vault,
pool_fee_amount,
ExistenceRequirement::KeepAlive,
)?;
<T as Config>::Currency::transfer(
sold_asset_id.into(),
&sender,
&treasury_account,
treasury_amount,
ExistenceRequirement::KeepAlive,
)?;
<T as Config>::Currency::transfer(
sold_asset_id.into(),
&sender,
&bnb_treasury_account,
buy_and_burn_amount,
ExistenceRequirement::KeepAlive,
)?;
Pallet::<T>::set_reserves(
sold_asset_id,
input_reserve.saturating_add(pool_fee_amount),
bought_asset_id,
output_reserve,
)?;
if bought_asset_amount >= min_amount_out {
<T as Config>::Currency::transfer(
sold_asset_id.into(),
&sender,
&vault,
sold_asset_amount
.checked_sub(
&buy_and_burn_amount
.checked_add(&treasury_amount)
.and_then(|v| v.checked_add(&pool_fee_amount))
.ok_or_else(|| DispatchError::from(Error::<T>::SoldAmountTooLow))?,
)
.ok_or_else(|| DispatchError::from(Error::<T>::SoldAmountTooLow))?,
ExistenceRequirement::KeepAlive,
)?;
<T as Config>::Currency::transfer(
bought_asset_id.into(),
&vault,
&sender,
bought_asset_amount,
ExistenceRequirement::KeepAlive,
)?;
let input_reserve_updated = input_reserve.saturating_add(
sold_asset_amount
.checked_sub(&treasury_amount)
.and_then(|v| v.checked_sub(&buy_and_burn_amount))
.ok_or_else(|| DispatchError::from(Error::<T>::SoldAmountTooLow))?,
);
let output_reserve_updated = output_reserve.saturating_sub(bought_asset_amount);
Pallet::<T>::set_reserves(
sold_asset_id,
input_reserve_updated,
bought_asset_id,
output_reserve_updated,
)?;
log!(
info,
"sell_asset: ({:?}, {:?}, {:?}, {:?}, {:?}) -> {:?}",
sender,
sold_asset_id,
bought_asset_id,
sold_asset_amount,
min_amount_out,
bought_asset_amount
);
log!(
info,
"pool-state: [({:?}, {:?}) -> {:?}, ({:?}, {:?}) -> {:?}]",
sold_asset_id,
bought_asset_id,
input_reserve_updated,
bought_asset_id,
sold_asset_id,
output_reserve_updated
);
Pallet::<T>::deposit_event(Event::AssetsSwapped(
sender.clone(),
vec![sold_asset_id, bought_asset_id],
sold_asset_amount,
bought_asset_amount,
));
}
Pallet::<T>::settle_treasury_and_burn(sold_asset_id, buy_and_burn_amount, treasury_amount)?;
if bought_asset_amount < min_amount_out {
if err_upon_bad_slippage {
return Err(DispatchError::from(Error::<T>::InsufficientOutputAmount))
} else {
Pallet::<T>::deposit_event(Event::SellAssetFailedDueToSlippage(
sender,
sold_asset_id,
sold_asset_amount,
bought_asset_id,
bought_asset_amount,
min_amount_out,
));
return Ok(Default::default())
}
}
Ok(bought_asset_amount)
}
fn do_multiswap_sell_asset(
sender: T::AccountId,
swap_token_list: Vec<CurrencyIdOf<T>>,
sold_asset_amount: BalanceOf<T>,
min_amount_out: BalanceOf<T>,
) -> Result<BalanceOf<T>, DispatchError> {
frame_support::storage::with_storage_layer(|| -> Result<BalanceOf<T>, DispatchError> {
<T as Config>::Currency::ensure_can_withdraw(
{ *swap_token_list.get(0).ok_or(Error::<T>::MultiswapShouldBeAtleastTwoHops)? }
.into(),
&sender,
sold_asset_amount,
WithdrawReasons::all(),
Default::default(),
)
.or(Err(Error::<T>::NotEnoughAssets))?;
let atomic_pairs: Vec<(CurrencyIdOf<T>, CurrencyIdOf<T>)> = swap_token_list
.clone()
.into_iter()
.zip(swap_token_list.clone().into_iter().skip(1))
.collect();
let mut atomic_sold_asset_amount = sold_asset_amount;
let mut atomic_bought_asset_amount = BalanceOf::<T>::zero();
for (atomic_sold_asset, atomic_bought_asset) in atomic_pairs.iter() {
atomic_bought_asset_amount = <Self as XykFunctionsTrait<
T::AccountId,
BalanceOf<T>,
CurrencyIdOf<T>,
>>::sell_asset(
sender.clone(),
*atomic_sold_asset,
*atomic_bought_asset,
atomic_sold_asset_amount,
BalanceOf::<T>::zero(),
true,
)?;
atomic_sold_asset_amount = atomic_bought_asset_amount;
}
if atomic_bought_asset_amount < min_amount_out {
return Err(Error::<T>::InsufficientOutputAmount.into())
} else {
return Ok(atomic_bought_asset_amount)
}
})
}
fn multiswap_sell_asset(
sender: T::AccountId,
swap_token_list: Vec<CurrencyIdOf<T>>,
sold_asset_amount: BalanceOf<T>,
min_amount_out: BalanceOf<T>,
_err_upon_bad_slippage: bool,
_err_upon_non_slippage_fail: bool,
) -> Result<BalanceOf<T>, DispatchError> {
let (
fee_swap_buy_and_burn_amount,
fee_swap_treasury_amount,
fee_swap_pool_fee_amount,
fee_swap_input_reserve,
fee_swap_output_reserve,
fee_swap_sold_asset_id,
fee_swap_bought_asset_id,
) = <Pallet<T> as PreValidateSwaps<T::AccountId, BalanceOf<T>, CurrencyIdOf<T>>>::pre_validate_multiswap_sell_asset(
&sender,
swap_token_list.clone(),
sold_asset_amount,
min_amount_out,
)?;
match <Self as XykFunctionsTrait<T::AccountId, BalanceOf<T>, CurrencyIdOf<T>>>::do_multiswap_sell_asset(
sender.clone(),
swap_token_list.clone(),
sold_asset_amount,
min_amount_out,
) {
Ok(bought_asset_amount) => {
Pallet::<T>::deposit_event(Event::AssetsSwapped(
sender.clone(),
swap_token_list.clone(),
sold_asset_amount,
bought_asset_amount,
));
Ok(bought_asset_amount)
},
Err(e) => {
let vault = Pallet::<T>::account_id();
let treasury_account: T::AccountId = Self::treasury_account_id();
let bnb_treasury_account: T::AccountId = Self::bnb_treasury_account_id();
<T as Config>::Currency::transfer(
fee_swap_sold_asset_id,
&sender,
&vault,
fee_swap_pool_fee_amount,
ExistenceRequirement::KeepAlive,
)?;
<T as Config>::Currency::transfer(
fee_swap_sold_asset_id,
&sender,
&treasury_account,
fee_swap_treasury_amount,
ExistenceRequirement::KeepAlive,
)?;
<T as Config>::Currency::transfer(
fee_swap_sold_asset_id,
&sender,
&bnb_treasury_account,
fee_swap_buy_and_burn_amount,
ExistenceRequirement::KeepAlive,
)?;
Pallet::<T>::set_reserves(
fee_swap_sold_asset_id,
fee_swap_input_reserve.saturating_add(fee_swap_pool_fee_amount),
fee_swap_bought_asset_id,
fee_swap_output_reserve,
)?;
Pallet::<T>::settle_treasury_and_burn(
fee_swap_sold_asset_id,
fee_swap_buy_and_burn_amount,
fee_swap_treasury_amount,
)?;
if let DispatchError::Module(module_err) = e {
Pallet::<T>::deposit_event(Event::MultiSwapAssetFailedOnAtomicSwap(
sender.clone(),
swap_token_list.clone(),
sold_asset_amount,
module_err,
));
Ok(Default::default())
} else {
Err(DispatchError::from(Error::<T>::UnexpectedFailure))
}
},
}
}
fn buy_asset(
sender: T::AccountId,
sold_asset_id: CurrencyIdOf<T>,
bought_asset_id: CurrencyIdOf<T>,
bought_asset_amount: BalanceOf<T>,
max_amount_in: BalanceOf<T>,
err_upon_bad_slippage: bool,
) -> Result<BalanceOf<T>, DispatchError> {
let (
buy_and_burn_amount,
treasury_amount,
pool_fee_amount,
input_reserve,
output_reserve,
sold_asset_amount,
) = <Pallet<T> as PreValidateSwaps<T::AccountId, BalanceOf<T>, CurrencyIdOf<T>>>::pre_validate_buy_asset(
&sender,
sold_asset_id,
bought_asset_id,
bought_asset_amount,
max_amount_in,
)?;
let vault = Pallet::<T>::account_id();
let treasury_account: T::AccountId = Self::treasury_account_id();
let bnb_treasury_account: T::AccountId = Self::bnb_treasury_account_id();
<T as Config>::Currency::transfer(
sold_asset_id,
&sender,
&vault,
pool_fee_amount,
ExistenceRequirement::KeepAlive,
)?;
<T as Config>::Currency::transfer(
sold_asset_id,
&sender,
&treasury_account,
treasury_amount,
ExistenceRequirement::KeepAlive,
)?;
<T as Config>::Currency::transfer(
sold_asset_id,
&sender,
&bnb_treasury_account,
buy_and_burn_amount,
ExistenceRequirement::KeepAlive,
)?;
Pallet::<T>::set_reserves(
sold_asset_id,
input_reserve.saturating_add(pool_fee_amount),
bought_asset_id,
output_reserve,
)?;
if sold_asset_amount <= max_amount_in {
<T as Config>::Currency::transfer(
sold_asset_id.into(),
&sender,
&vault,
sold_asset_amount
.checked_sub(
&buy_and_burn_amount
.checked_add(&treasury_amount)
.and_then(|v| v.checked_add(&pool_fee_amount))
.ok_or_else(|| DispatchError::from(Error::<T>::SoldAmountTooLow))?,
)
.ok_or_else(|| DispatchError::from(Error::<T>::SoldAmountTooLow))?,
ExistenceRequirement::KeepAlive,
)?;
<T as Config>::Currency::transfer(
bought_asset_id,
&vault,
&sender,
bought_asset_amount,
ExistenceRequirement::KeepAlive,
)?;
let input_reserve_updated = input_reserve.saturating_add(
sold_asset_amount
.checked_sub(&treasury_amount)
.and_then(|v| v.checked_sub(&buy_and_burn_amount))
.ok_or_else(|| DispatchError::from(Error::<T>::MathOverflow))?,
);
let output_reserve_updated = output_reserve.saturating_sub(bought_asset_amount);
Pallet::<T>::set_reserves(
sold_asset_id,
input_reserve_updated,
bought_asset_id,
output_reserve_updated,
)?;
log!(
info,
"buy_asset: ({:?}, {:?}, {:?}, {:?}, {:?}) -> {:?}",
sender,
sold_asset_id,
bought_asset_id,
bought_asset_amount,
max_amount_in,
sold_asset_amount
);
log!(
info,
"pool-state: [({:?}, {:?}) -> {:?}, ({:?}, {:?}) -> {:?}]",
sold_asset_id,
bought_asset_id,
input_reserve_updated,
bought_asset_id,
sold_asset_id,
output_reserve_updated
);
Pallet::<T>::deposit_event(Event::AssetsSwapped(
sender.clone(),
vec![sold_asset_id, bought_asset_id],
sold_asset_amount,
bought_asset_amount,
));
}
Pallet::<T>::settle_treasury_and_burn(sold_asset_id, buy_and_burn_amount, treasury_amount)?;
if sold_asset_amount > max_amount_in {
if err_upon_bad_slippage {
return Err(DispatchError::from(Error::<T>::InsufficientInputAmount))
} else {
Pallet::<T>::deposit_event(Event::BuyAssetFailedDueToSlippage(
sender,
sold_asset_id,
sold_asset_amount,
bought_asset_id,
bought_asset_amount,
max_amount_in,
));
}
}
Ok(sold_asset_amount)
}
fn do_multiswap_buy_asset(
sender: T::AccountId,
swap_token_list: Vec<CurrencyIdOf<T>>,
bought_asset_amount: BalanceOf<T>,
max_amount_in: BalanceOf<T>,
) -> Result<BalanceOf<T>, DispatchError> {
frame_support::storage::with_storage_layer(|| -> Result<BalanceOf<T>, DispatchError> {
let atomic_pairs: Vec<(CurrencyIdOf<T>, CurrencyIdOf<T>)> = swap_token_list
.clone()
.into_iter()
.zip(swap_token_list.clone().into_iter().skip(1))
.collect();
let mut atomic_sold_asset_amount = BalanceOf::<T>::zero();
let mut atomic_bought_asset_amount = bought_asset_amount;
let mut atomic_swap_buy_amounts_rev: Vec<BalanceOf<T>> = Default::default();
for (atomic_sold_asset, atomic_bought_asset) in atomic_pairs.iter().rev() {
atomic_sold_asset_amount = Self::calculate_buy_price_id(
*atomic_sold_asset,
*atomic_bought_asset,
atomic_bought_asset_amount,
)?;
atomic_swap_buy_amounts_rev.push(atomic_bought_asset_amount);
atomic_bought_asset_amount = atomic_sold_asset_amount;
}
ensure!(atomic_sold_asset_amount <= max_amount_in, Error::<T>::InsufficientInputAmount);
<T as Config>::Currency::ensure_can_withdraw(
{ *swap_token_list.get(0).ok_or(Error::<T>::MultiswapShouldBeAtleastTwoHops)? }
.into(),
&sender,
atomic_sold_asset_amount,
WithdrawReasons::all(),
Default::default(),
)
.or(Err(Error::<T>::NotEnoughAssets))?;
for ((atomic_sold_asset, atomic_bought_asset), atomic_swap_buy_amount) in
atomic_pairs.iter().zip(atomic_swap_buy_amounts_rev.iter().rev())
{
let _ = <Self as XykFunctionsTrait<T::AccountId, BalanceOf<T>, CurrencyIdOf<T>>>::buy_asset(
sender.clone(),
*atomic_sold_asset,
*atomic_bought_asset,
*atomic_swap_buy_amount,
BalanceOf::<T>::max_value(),
true,
)?;
}
return Ok(atomic_sold_asset_amount)
})
}
fn multiswap_buy_asset(
sender: T::AccountId,
swap_token_list: Vec<CurrencyIdOf<T>>,
bought_asset_amount: BalanceOf<T>,
max_amount_in: BalanceOf<T>,
_err_upon_bad_slippage: bool,
_err_upon_non_slippage_fail: bool,
) -> Result<BalanceOf<T>, DispatchError> {
let (
fee_swap_buy_and_burn_amount,
fee_swap_treasury_amount,
fee_swap_pool_fee_amount,
fee_swap_input_reserve,
fee_swap_output_reserve,
fee_swap_sold_asset_id,
fee_swap_bought_asset_id,
) = <Pallet<T> as PreValidateSwaps<T::AccountId, BalanceOf<T>, CurrencyIdOf<T>>>::pre_validate_multiswap_buy_asset(
&sender,
swap_token_list.clone(),
bought_asset_amount,
max_amount_in,
)?;
match <Self as XykFunctionsTrait<T::AccountId, BalanceOf<T>, CurrencyIdOf<T>>>::do_multiswap_buy_asset(
sender.clone(),
swap_token_list.clone(),
bought_asset_amount,
max_amount_in,
) {
Ok(sold_asset_amount) => {
Pallet::<T>::deposit_event(Event::AssetsSwapped(
sender.clone(),
swap_token_list.clone(),
sold_asset_amount,
bought_asset_amount,
));
Ok(sold_asset_amount)
},
Err(e) => {
let vault = Pallet::<T>::account_id();
let treasury_account: T::AccountId = Self::treasury_account_id();
let bnb_treasury_account: T::AccountId = Self::bnb_treasury_account_id();
<T as Config>::Currency::transfer(
fee_swap_sold_asset_id,
&sender,
&vault,
fee_swap_pool_fee_amount,
ExistenceRequirement::KeepAlive,
)?;
<T as Config>::Currency::transfer(
fee_swap_sold_asset_id,
&sender,
&treasury_account,
fee_swap_treasury_amount,
ExistenceRequirement::KeepAlive,
)?;
<T as Config>::Currency::transfer(
fee_swap_sold_asset_id,
&sender,
&bnb_treasury_account,
fee_swap_buy_and_burn_amount,
ExistenceRequirement::KeepAlive,
)?;
Pallet::<T>::set_reserves(
fee_swap_sold_asset_id,
fee_swap_input_reserve.saturating_add(fee_swap_pool_fee_amount),
fee_swap_bought_asset_id,
fee_swap_output_reserve,
)?;
Pallet::<T>::settle_treasury_and_burn(
fee_swap_sold_asset_id,
fee_swap_buy_and_burn_amount,
fee_swap_treasury_amount,
)?;
if let DispatchError::Module(module_err) = e {
Pallet::<T>::deposit_event(Event::MultiSwapAssetFailedOnAtomicSwap(
sender.clone(),
swap_token_list.clone(),
bought_asset_amount,
module_err,
));
Ok(Default::default())
} else {
Err(DispatchError::from(Error::<T>::UnexpectedFailure))
}
},
}
}
fn mint_liquidity(
sender: T::AccountId,
first_asset_id: CurrencyIdOf<T>,
second_asset_id: CurrencyIdOf<T>,
first_asset_amount: BalanceOf<T>,
expected_second_asset_amount: BalanceOf<T>,
activate_minted_liquidity: bool,
) -> Result<(CurrencyIdOf<T>, BalanceOf<T>), DispatchError> {
let vault = Pallet::<T>::account_id();
ensure!(
(LiquidityAssets::<T>::contains_key((first_asset_id, second_asset_id)) ||
LiquidityAssets::<T>::contains_key((second_asset_id, first_asset_id))),
Error::<T>::NoSuchPool,
);
let liquidity_asset_id = Pallet::<T>::get_liquidity_asset(first_asset_id, second_asset_id)?;
let (first_asset_reserve, second_asset_reserve) =
Pallet::<T>::get_reserves(first_asset_id, second_asset_id)?;
let total_liquidity_assets: BalanceOf<T> =
<T as Config>::Currency::total_issuance(liquidity_asset_id.into());
let second_asset_amount = if !(first_asset_reserve.is_zero() &&
second_asset_reserve.is_zero()) &&
!total_liquidity_assets.is_zero()
{
ensure!(!first_asset_reserve.is_zero(), Error::<T>::DivisionByZero);
multiply_by_rational_with_rounding(
first_asset_amount.into(),
second_asset_reserve.into(),
first_asset_reserve.into(),
Rounding::Down,
)
.ok_or(Error::<T>::UnexpectedFailure)?
.checked_add(1)
.ok_or_else(|| DispatchError::from(Error::<T>::MathOverflow))?
.try_into()
.map_err(|_| DispatchError::from(Error::<T>::MathOverflow))?
} else {
expected_second_asset_amount
};
ensure!(
second_asset_amount <= expected_second_asset_amount,
Error::<T>::SecondAssetAmountExceededExpectations,
);
ensure!(
!first_asset_amount.is_zero() && !second_asset_amount.is_zero(),
Error::<T>::ZeroAmount,
);
let liquidity_assets_minted = if total_liquidity_assets.is_zero() {
Pallet::<T>::calculate_initial_liquidity(first_asset_amount, second_asset_amount)?
} else {
multiply_by_rational_with_rounding(
first_asset_amount.into(),
total_liquidity_assets.into(),
first_asset_reserve.into(),
Rounding::Down,
)
.ok_or(Error::<T>::UnexpectedFailure)?
.try_into()
.map_err(|_| DispatchError::from(Error::<T>::MathOverflow))?
};
<T as Config>::Currency::ensure_can_withdraw(
first_asset_id.into(),
&sender,
first_asset_amount,
WithdrawReasons::all(),
Default::default(),
)
.or(Err(Error::<T>::NotEnoughAssets))?;
<T as Config>::Currency::ensure_can_withdraw(
second_asset_id,
&sender,
second_asset_amount,
WithdrawReasons::all(),
Default::default(),
)
.or(Err(Error::<T>::NotEnoughAssets))?;
<T as Config>::Currency::transfer(
first_asset_id,
&sender,
&vault,
first_asset_amount,
ExistenceRequirement::KeepAlive,
)?;
<T as Config>::Currency::transfer(
second_asset_id,
&sender,
&vault,
second_asset_amount,
ExistenceRequirement::KeepAlive,
)?;
<T as Config>::Currency::mint(liquidity_asset_id, &sender, liquidity_assets_minted)?;
if <T::LiquidityMiningRewards as ProofOfStakeRewardsApi<
T::AccountId,
BalanceOf<T>,
CurrencyIdOf<T>,
>>::is_enabled(liquidity_asset_id) &&
activate_minted_liquidity
{
<T::LiquidityMiningRewards as ProofOfStakeRewardsApi<
T::AccountId,
BalanceOf<T>,
CurrencyIdOf<T>,
>>::activate_liquidity(
sender.clone(),
liquidity_asset_id,
liquidity_assets_minted,
Some(ActivateKind::AvailableBalance),
)?;
}
let first_asset_reserve_updated = first_asset_reserve.saturating_add(first_asset_amount);
let second_asset_reserve_updated = second_asset_reserve.saturating_add(second_asset_amount);
Pallet::<T>::set_reserves(
first_asset_id,
first_asset_reserve_updated,
second_asset_id,
second_asset_reserve_updated,
)?;
log!(
info,
"mint_liquidity: ({:?}, {:?}, {:?}, {:?}) -> ({:?}, {:?}, {:?})",
sender,
first_asset_id,
second_asset_id,
first_asset_amount,
second_asset_amount,
liquidity_asset_id,
liquidity_assets_minted
);
log!(
info,
"pool-state: [({:?}, {:?}) -> {:?}, ({:?}, {:?}) -> {:?}]",
first_asset_id,
second_asset_id,
first_asset_reserve_updated,
second_asset_id,
first_asset_id,
second_asset_reserve_updated
);
Pallet::<T>::deposit_event(Event::LiquidityMinted(
sender,
first_asset_id,
first_asset_amount,
second_asset_id,
second_asset_amount,
liquidity_asset_id,
liquidity_assets_minted,
));
Ok((liquidity_asset_id, liquidity_assets_minted))
}
fn do_compound_rewards(
sender: T::AccountId,
liquidity_asset_id: CurrencyIdOf<T>,
amount_permille: Permill,
) -> DispatchResult {
let (first_asset_id, second_asset_id) =
LiquidityPools::<T>::get(liquidity_asset_id).ok_or(Error::<T>::NoSuchLiquidityAsset)?;
ensure!(
!T::DisabledTokens::contains(&first_asset_id) &&
!T::DisabledTokens::contains(&second_asset_id),
Error::<T>::FunctionNotAvailableForThisToken
);
let rewards_id: CurrencyIdOf<T> = Self::native_token_id();
ensure!(
first_asset_id == rewards_id || second_asset_id == rewards_id,
Error::<T>::FunctionNotAvailableForThisToken
);
let rewards_claimed = <T::LiquidityMiningRewards as ProofOfStakeRewardsApi<
T::AccountId,
BalanceOf<T>,
CurrencyIdOf<T>,
>>::claim_rewards_all(sender.clone(), liquidity_asset_id)?;
let rewards_256 = U256::from(rewards_claimed.into())
.saturating_mul(amount_permille.deconstruct().into())
.div(Permill::one().deconstruct());
let rewards_128 = u128::try_from(rewards_256)
.map_err(|_| DispatchError::from(Error::<T>::MathOverflow))?;
let rewards = BalanceOf::<T>::try_from(rewards_128)
.map_err(|_| DispatchError::from(Error::<T>::MathOverflow))?;
<Self as XykFunctionsTrait<T::AccountId, BalanceOf<T>, CurrencyIdOf<T>>>::provide_liquidity_with_conversion(
sender,
first_asset_id,
second_asset_id,
rewards_id,
rewards,
true,
)?;
Ok(())
}
fn provide_liquidity_with_conversion(
sender: T::AccountId,
first_asset_id: CurrencyIdOf<T>,
second_asset_id: CurrencyIdOf<T>,
provided_asset_id: CurrencyIdOf<T>,
provided_asset_amount: BalanceOf<T>,
activate_minted_liquidity: bool,
) -> Result<(CurrencyIdOf<T>, BalanceOf<T>), DispatchError> {
ensure!(!provided_asset_amount.is_zero(), Error::<T>::ZeroAmount,);
ensure!(!(Self::is_pool_empty(first_asset_id, second_asset_id)?), Error::<T>::PoolIsEmpty);
let (first_reserve, second_reserve) =
Pallet::<T>::get_reserves(first_asset_id, second_asset_id)?;
let (reserve, other_reserve, other_asset_id) = if provided_asset_id == first_asset_id {
(first_reserve, second_reserve, second_asset_id)
} else if provided_asset_id == second_asset_id {
(second_reserve, first_reserve, first_asset_id)
} else {
return Err(DispatchError::from(Error::<T>::FunctionNotAvailableForThisToken))
};
<T as Config>::Currency::ensure_can_withdraw(
provided_asset_id,
&sender,
provided_asset_amount,
WithdrawReasons::all(),
Default::default(),
)
.or(Err(Error::<T>::NotEnoughAssets))?;
let swap_amount =
Pallet::<T>::calculate_balanced_sell_amount(provided_asset_amount, reserve)?;
let bought_amount = Pallet::<T>::calculate_sell_price(reserve, other_reserve, swap_amount)?;
let _ =
<Self as XykFunctionsTrait<T::AccountId, BalanceOf<T>, CurrencyIdOf<T>>>::sell_asset(
sender.clone(),
provided_asset_id,
other_asset_id,
swap_amount,
bought_amount,
true,
)?;
let mint_amount = provided_asset_amount
.checked_sub(&swap_amount)
.ok_or_else(|| DispatchError::from(Error::<T>::MathOverflow))?;
log!(
info,
"provide_liquidity_with_conversion: ({:?}, {:?}, {:?}, {:?}, {:?}) -> ({:?}, {:?})",
sender,
first_asset_id,
second_asset_id,
provided_asset_id,
provided_asset_amount,
mint_amount,
bought_amount
);
<Self as XykFunctionsTrait<T::AccountId, BalanceOf<T>, CurrencyIdOf<T>>>::mint_liquidity(
sender,
other_asset_id,
provided_asset_id,
bought_amount,
BalanceOf::<T>::max_value(),
activate_minted_liquidity,
)
}
fn burn_liquidity(
sender: T::AccountId,
first_asset_id: CurrencyIdOf<T>,
second_asset_id: CurrencyIdOf<T>,
liquidity_asset_amount: BalanceOf<T>,
) -> DispatchResult {
let vault = Pallet::<T>::account_id();
ensure!(
!T::DisabledTokens::contains(&first_asset_id) &&
!T::DisabledTokens::contains(&second_asset_id),
Error::<T>::FunctionNotAvailableForThisToken
);
let liquidity_asset_id = Pallet::<T>::get_liquidity_asset(first_asset_id, second_asset_id)?;
let max_instant_unreserve_amount =
<T as pallet::Config>::ActivationReservesProvider::get_max_instant_unreserve_amount(
liquidity_asset_id,
&sender,
);
let (first_asset_reserve, second_asset_reserve) =
Pallet::<T>::get_reserves(first_asset_id, second_asset_id)?;
let liquidity_token_available_balance =
<T as Config>::Currency::available_balance(liquidity_asset_id.into(), &sender);
ensure!(
liquidity_token_available_balance
.checked_add(&max_instant_unreserve_amount)
.ok_or(Error::<T>::MathOverflow)? >=
liquidity_asset_amount,
Error::<T>::NotEnoughAssets,
);
let to_be_deactivated =
liquidity_asset_amount.saturating_sub(liquidity_token_available_balance);
<T::LiquidityMiningRewards as ProofOfStakeRewardsApi<
T::AccountId,
BalanceOf<T>,
CurrencyIdOf<T>,
>>::deactivate_liquidity(sender.clone(), liquidity_asset_id, to_be_deactivated)?;
let (first_asset_amount, second_asset_amount) = Pallet::<T>::get_burn_amount_reserves(
first_asset_reserve,
second_asset_reserve,
liquidity_asset_id,
liquidity_asset_amount,
)?;
let total_liquidity_assets: BalanceOf<T> =
<T as Config>::Currency::total_issuance(liquidity_asset_id.into());
if liquidity_asset_amount == total_liquidity_assets {
ensure!(
(first_asset_reserve == first_asset_amount) &&
(second_asset_reserve == second_asset_amount),
Error::<T>::UnexpectedFailure
);
} else {
ensure!(
(first_asset_reserve >= first_asset_amount) &&
(second_asset_reserve >= second_asset_amount),
Error::<T>::UnexpectedFailure
);
}
ensure!(
!first_asset_amount.is_zero() && !second_asset_amount.is_zero(),
Error::<T>::ZeroAmount,
);
<T as Config>::Currency::transfer(
first_asset_id,
&vault,
&sender,
first_asset_amount,
ExistenceRequirement::KeepAlive,
)?;
<T as Config>::Currency::transfer(
second_asset_id,
&vault,
&sender,
second_asset_amount,
ExistenceRequirement::KeepAlive,
)?;
log!(
info,
"burn_liquidity: ({:?}, {:?}, {:?}, {:?}) -> ({:?}, {:?})",
sender,
first_asset_id,
second_asset_id,
liquidity_asset_amount,
first_asset_amount,
second_asset_amount
);
if liquidity_asset_amount == total_liquidity_assets {
log!(
info,
"pool-state: [({:?}, {:?}) -> Removed, ({:?}, {:?}) -> Removed]",
first_asset_id,
second_asset_id,
second_asset_id,
first_asset_id,
);
Pallet::<T>::set_reserves(
first_asset_id,
BalanceOf::<T>::zero(),
second_asset_id,
BalanceOf::<T>::zero(),
)?;
} else {
let first_asset_reserve_updated =
first_asset_reserve.saturating_sub(first_asset_amount);
let second_asset_reserve_updated =
second_asset_reserve.saturating_sub(second_asset_amount);
Pallet::<T>::set_reserves(
first_asset_id,
first_asset_reserve_updated,
second_asset_id,
second_asset_reserve_updated,
)?;
log!(
info,
"pool-state: [({:?}, {:?}) -> {:?}, ({:?}, {:?}) -> {:?}]",
first_asset_id,
second_asset_id,
first_asset_reserve_updated,
second_asset_id,
first_asset_id,
second_asset_reserve_updated
);
}
<T as Config>::Currency::burn_and_settle(
liquidity_asset_id.into(),
&sender,
liquidity_asset_amount,
)?;
Pallet::<T>::deposit_event(Event::LiquidityBurned(
sender,
first_asset_id,
first_asset_amount,
second_asset_id,
second_asset_amount,
liquidity_asset_id,
liquidity_asset_amount,
));
Ok(())
}
fn get_tokens_required_for_minting(
liquidity_asset_id: CurrencyIdOf<T>,
liquidity_token_amount: BalanceOf<T>,
) -> Result<(CurrencyIdOf<T>, BalanceOf<T>, CurrencyIdOf<T>, BalanceOf<T>), DispatchError> {
let (first_asset_id, second_asset_id) =
LiquidityPools::<T>::get(liquidity_asset_id).ok_or(Error::<T>::NoSuchLiquidityAsset)?;
let (first_asset_reserve, second_asset_reserve) =
Pallet::<T>::get_reserves(first_asset_id, second_asset_id)?;
let total_liquidity_assets: BalanceOf<T> =
<T as Config>::Currency::total_issuance(liquidity_asset_id.into());
ensure!(!total_liquidity_assets.is_zero(), Error::<T>::DivisionByZero);
let second_asset_amount: BalanceOf<T> = multiply_by_rational_with_rounding(
liquidity_token_amount.into(),
second_asset_reserve.into(),
total_liquidity_assets.into(),
Rounding::Down,
)
.ok_or(Error::<T>::UnexpectedFailure)?
.checked_add(1)
.ok_or_else(|| DispatchError::from(Error::<T>::MathOverflow))?
.try_into()
.map_err(|_| DispatchError::from(Error::<T>::MathOverflow))?;
let first_asset_amount: BalanceOf<T> = multiply_by_rational_with_rounding(
liquidity_token_amount.into(),
first_asset_reserve.into(),
total_liquidity_assets.into(),
Rounding::Down,
)
.ok_or(Error::<T>::UnexpectedFailure)?
.checked_add(1)
.ok_or_else(|| DispatchError::from(Error::<T>::MathOverflow))?
.try_into()
.map_err(|_| DispatchError::from(Error::<T>::MathOverflow))?;
log!(
info,
"get_tokens_required_for_minting: ({:?}, {:?}) -> ({:?}, {:?}, {:?}, {:?})",
liquidity_asset_id,
liquidity_token_amount,
first_asset_id,
first_asset_amount,
second_asset_id,
second_asset_amount,
);
Ok((first_asset_id, first_asset_amount, second_asset_id, second_asset_amount))
}
fn is_liquidity_token(liquidity_asset_id: CurrencyIdOf<T>) -> bool {
LiquidityPools::<T>::get(liquidity_asset_id).is_some()
}
}
pub trait AssetMetadataMutationTrait<CurrencyId> {
fn set_asset_info(
asset: CurrencyId,
name: Vec<u8>,
symbol: Vec<u8>,
decimals: u32,
) -> DispatchResult;
}
impl<T: Config> Valuate<BalanceOf<T>, CurrencyIdOf<T>> for Pallet<T> {
fn get_liquidity_asset(
first_asset_id: CurrencyIdOf<T>,
second_asset_id: CurrencyIdOf<T>,
) -> Result<CurrencyIdOf<T>, DispatchError> {
Pallet::<T>::get_liquidity_asset(first_asset_id, second_asset_id)
}
fn get_liquidity_token_mga_pool(
liquidity_token_id: CurrencyIdOf<T>,
) -> Result<(CurrencyIdOf<T>, CurrencyIdOf<T>), DispatchError> {
let (first_token_id, second_token_id) =
LiquidityPools::<T>::get(liquidity_token_id).ok_or(Error::<T>::NoSuchLiquidityAsset)?;
let native_currency_id = Self::native_token_id();
match native_currency_id {
_ if native_currency_id == first_token_id => Ok((first_token_id, second_token_id)),
_ if native_currency_id == second_token_id => Ok((second_token_id, first_token_id)),
_ => Err(Error::<T>::NotMangataLiquidityAsset.into()),
}
}
fn valuate_liquidity_token(
liquidity_token_id: CurrencyIdOf<T>,
liquidity_token_amount: BalanceOf<T>,
) -> BalanceOf<T> {
let (mga_token_id, other_token_id) =
match Self::get_liquidity_token_mga_pool(liquidity_token_id) {
Ok(pool) => pool,
Err(_) => return Default::default(),
};
let mga_token_reserve = match Pallet::<T>::get_reserves(mga_token_id, other_token_id) {
Ok(reserves) => reserves.0,
Err(_) => return Default::default(),
};
let liquidity_token_reserve: BalanceOf<T> =
<T as Config>::Currency::total_issuance(liquidity_token_id.into());
if liquidity_token_reserve.is_zero() {
return Default::default()
}
multiply_by_rational_with_rounding(
mga_token_reserve.into(),
liquidity_token_amount.into(),
liquidity_token_reserve.into(),
Rounding::Down,
)
.map(SaturatedConversion::saturated_into)
.unwrap_or(BalanceOf::<T>::max_value())
}
fn valuate_non_liquidity_token(
non_liquidity_token_id: CurrencyIdOf<T>,
amount: BalanceOf<T>,
) -> BalanceOf<T> {
let native_token_id = Pallet::<T>::native_token_id();
let (native_reserves, token_reserves) =
match Pallet::<T>::get_reserves(native_token_id, non_liquidity_token_id) {
Ok(reserves) => reserves,
Err(_) => return Default::default(),
};
Pallet::<T>::calculate_sell_price_no_fee(token_reserves, native_reserves, amount)
.unwrap_or_default()
}
fn scale_liquidity_by_mga_valuation(
mga_valuation: BalanceOf<T>,
liquidity_token_amount: BalanceOf<T>,
mga_token_amount: BalanceOf<T>,
) -> BalanceOf<T> {
if mga_valuation.is_zero() {
return Default::default()
}
multiply_by_rational_with_rounding(
liquidity_token_amount.into(),
mga_token_amount.into(),
mga_valuation.into(),
Rounding::Down,
)
.map(SaturatedConversion::saturated_into)
.unwrap_or(BalanceOf::<T>::max_value())
}
fn get_pool_state(liquidity_token_id: CurrencyIdOf<T>) -> Option<(BalanceOf<T>, BalanceOf<T>)> {
let (mga_token_id, other_token_id) =
match Self::get_liquidity_token_mga_pool(liquidity_token_id) {
Ok(pool) => pool,
Err(_) => return None,
};
let mga_token_reserve = match Pallet::<T>::get_reserves(mga_token_id, other_token_id) {
Ok(reserves) => reserves.0,
Err(_) => return None,
};
let liquidity_token_reserve: BalanceOf<T> =
<T as Config>::Currency::total_issuance(liquidity_token_id.into());
if liquidity_token_reserve.is_zero() {
return None
}
Some((mga_token_reserve, liquidity_token_reserve))
}
fn get_reserves(
first_asset_id: CurrencyIdOf<T>,
second_asset_id: CurrencyIdOf<T>,
) -> Result<(BalanceOf<T>, BalanceOf<T>), DispatchError> {
Pallet::<T>::get_reserves(first_asset_id, second_asset_id)
}
fn is_liquidity_token(liquidity_asset_id: CurrencyIdOf<T>) -> bool {
LiquidityPools::<T>::get(liquidity_asset_id).is_some()
}
}
impl<T: Config> PoolCreateApi<T::AccountId, BalanceOf<T>, CurrencyIdOf<T>> for Pallet<T> {
fn pool_exists(first: CurrencyIdOf<T>, second: CurrencyIdOf<T>) -> bool {
Pools::<T>::contains_key((first, second)) || Pools::<T>::contains_key((second, first))
}
fn pool_create(
account: T::AccountId,
first: CurrencyIdOf<T>,
first_amount: BalanceOf<T>,
second: CurrencyIdOf<T>,
second_amount: BalanceOf<T>,
) -> Option<(CurrencyIdOf<T>, BalanceOf<T>)> {
match <Self as XykFunctionsTrait<T::AccountId, BalanceOf<T>, CurrencyIdOf<T>>>::create_pool(
account,
first,
first_amount,
second,
second_amount,
) {
Ok(_) => LiquidityAssets::<T>::get((first, second)).map(|asset_id| {
(asset_id, <T as Config>::Currency::total_issuance(asset_id.into()))
}),
Err(e) => {
log!(error, "cannot create pool {:?}!", e);
None
},
}
}
}