//! # Unit Exchange Pallet
//! <!-- Original author of paragraph: @gang
//!
//! ## Overview
//!
//! Pallet that allows a user to exchange between any token and USDU.
//!
//! ### Goals
//!
//! The pallet is designed to make the following possible:
//!
//! * Initialize a pool.
//! * Buy token.
//! * Sell token.
//! * Stake token.
//! * Unstake token.
//! * Set USDU token id.
//! * Set Unit token id.
//! * Get pool liquidity's token.
//!
//! ## Interface
//!
//! ### Permissionless Functions
//!
//! - `create_exchange`: Create an exchange pool.
//! - `buy_token`: Buy token with USDU.
//! - `sell_token` : Sell token for USDU.
//! - `stake_token` : Stake token with USDU.
//! - `unstake_token` : Unstake token to receive token and USDU.
//! - `set_usdu_token_id` : Set USDU token id.
//! - `set_unit_token_id` : Set Unit token id.
//! - `get_liquidity` : Get pool liquidity's token.
//!
//!//! Please refer to the [`Call`] enum and its associated variants for documentation on each
//! function.
#![cfg_attr(not(feature = "std"), no_std)]
/// Edit this file to define custom logic or remove it if it is not needed.
/// Learn more about FRAME and the core library of Substrate FRAME pallets:
/// <https://docs.substrate.io/reference/frame-pallets/>
pub use pallet::*;
use sp_std::{vec, vec::Vec};
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
pub mod weights;
pub use weights::WeightInfo;
mod types;
pub use types::{Bonus, BuyTokenActivity, SellTokenActivity, WorkersRewards};
use traits::{
profile::{ ProfileInspect },
treasury::TreasuryInterface,
newsfeed::{StatusType, NewsfeedInterface},
subaccounts::{AccountOrigin, SubAccounts},
asset::{TokenType, AssetInterface},
};
#[cfg(feature = "runtime-benchmarks")]
use traits::profile::ProfileInterface;
use frame_support::{
pallet_prelude::*,
traits::{
fungibles,
fungibles::{Inspect, Mutate},
tokens::{Balance, Preservation::Expendable},
Time,
},
};
use sp_runtime::traits::{CheckedDiv, CheckedMul};
// use sp_runtime::FixedPointNumber;
// use sp_runtime::traits::One;
use pallet_pool::PoolManager;
use scale_info::prelude::format;
use sp_runtime::{traits::Zero, FixedPointOperand, Perbill, SaturatedConversion, Saturating};
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
pub type MomentOf<T> = <<T as Config>::Time as Time>::Moment;
//pub type BalanceOf<T> = <<T as Config>::Fungibles as Inspect<<T as
// frame_system::Config>::AccountId>>::Balance;
pub type BalanceOf<T> = <T as Config>::AssetBalance;
//pub type AssetId<T> = <<T as Config>::Fungibles as Inspect<<T as
// frame_system::Config>::AccountId>>::AssetId;
/// Asset id type alias.
pub(crate) type AssetId<T> = <T as Config>::AssetId;
/// Type used to represent the token decimals.
pub type Decimals = u8;
/// Type for bonus
pub type BonusOf<T> =
Bonus<AssetId<T>, BalanceOf<T>, <T as frame_system::Config>::AccountId, MomentOf<T>>;
/// Type for sell token activity
pub type SellTokenActivityOf<T> = SellTokenActivity<
<T as frame_system::Config>::AccountId,
AssetId<T>,
BalanceOf<T>,
MomentOf<T>,
>;
/// Type for buy token activity
pub type BuyTokenActivityOf<T> =
BuyTokenActivity<<T as frame_system::Config>::AccountId, AssetId<T>, BalanceOf<T>, MomentOf<T>>;
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_system::pallet_prelude::*;
#[pallet::pallet]
#[pallet::without_storage_info]
pub struct Pallet<T>(_);
/// Configure the pallet by specifying the parameters and types on which it depends.
#[pallet::config]
pub trait Config: frame_system::Config {
/// Because this pallet emits events, it depends on the runtime's definition of an event.
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
type AssetId: Member
+ Parameter
+ Copy
+ MaybeSerializeDeserialize
+ MaxEncodedLen
+ Default
+ Zero
+ From<u32>;
/// Type to access the Assets Pallet.
type Fungibles: fungibles::Inspect<
Self::AccountId,
AssetId = Self::AssetId,
Balance = Self::AssetBalance,
> + //, AssetId = u32> // Hash this
fungibles::Mutate<Self::AccountId>
+ fungibles::metadata::Inspect<Self::AccountId>
+ fungibles::Create<Self::AccountId>
+ AssetInterface<
<Self::Fungibles as Inspect<Self::AccountId>>::AssetId,
<Self::Fungibles as Inspect<Self::AccountId>>::Balance,
Self::AccountId,
Decimals,
TokenType,
>;
type AssetBalance: Balance
+ FixedPointOperand
+ MaxEncodedLen
+ MaybeSerializeDeserialize
+ TypeInfo;
// Type to access the Pool Pallet.
type Pool: PoolManager<Self::AccountId, AssetId<Self>, BalanceOf<Self>>;
/// Pool fee
#[pallet::constant]
type GetExchangePoolFee: Get<(u32, u32)>;
/// Inviter fee
#[pallet::constant]
type GetExchangeInviterFee: Get<(u32, u32)>;
/// Unit Treasury fee
#[pallet::constant]
type GetExchangeUnitTreasuryFee: Get<(u32, u32)>;
/// Token Treasury fee
#[pallet::constant]
type GetExchangeTokenTreasuryFee: Get<(u32, u32)>;
/// Time provider
type Time: Time;
#[pallet::constant]
type StringLimit: Get<u32>;
/// Type to access the profile pallet
type Profile: ProfileInspect<Self::AccountId, Self::StringLimit>;
/// Type to access the treasury pallet
type Treasury: TreasuryInterface<AssetId<Self>, BalanceOf<Self>, Self::AccountId>;
/// Type to access the sub account pallet
type SubAccounts: SubAccounts<Self::AccountId, AccountOrigin>;
/// Newsfeed Interface type.
type Newsfeed: NewsfeedInterface<
Self::AccountId,
StatusType,
<Self::Fungibles as Inspect<Self::AccountId>>::AssetId,
Self::AssetBalance,
>;
/// Helper type to hold traits for benchmarks.
#[cfg(feature = "runtime-benchmarks")]
type ProfileBenchmarkHelper: ProfileInterface<Self::AccountId, Self::StringLimit>;
/// Weight Information for extrinsics in this pallet.
type WeightInfo: WeightInfo;
}
/// BuyTokenActivities
#[pallet::storage]
#[pallet::getter(fn buy_token_activities)]
pub type BuyTokenActivities<T: Config> =
StorageMap<_, Twox64Concat, AssetId<T>, Vec<BuyTokenActivityOf<T>>, ValueQuery>;
/// SellTokenActivities
#[pallet::storage]
#[pallet::getter(fn sell_token_activities)]
pub type SellTokenActivities<T: Config> =
StorageMap<_, Twox64Concat, AssetId<T>, Vec<SellTokenActivityOf<T>>, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn bonuses)]
pub type Bonuses<T: Config> =
StorageMap<_, Blake2_128Concat, T::AccountId, Vec<BonusOf<T>>, ValueQuery>;
// Pallets use events to inform users when important changes are made.
// https://docs.substrate.io/main-docs/build/events-errors/
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
// Buy token event
Buytoken {
who: T::AccountId,
token_id: AssetId<T>,
token_amount: BalanceOf<T>,
usdu_id: AssetId<T>,
usdu_amount: BalanceOf<T>,
},
// Sell token event
Selltoken {
who: T::AccountId,
token_id: AssetId<T>,
token_amount: BalanceOf<T>,
usdu_id: AssetId<T>,
usdu_amount: BalanceOf<T>,
},
}
// Errors inform users that something went wrong.
#[pallet::error]
#[derive(PartialEq)]
pub enum Error<T> {
// USDU not set
USDUNotSet,
/// Unit token not set
UnitTokenNotSet,
/// There was an error with the pool liquidity
PoolLiquidityError,
/// Insufficient input amount
InsufficientInputAmount,
/// Insufficient liquidity
InsufficientLiquidity,
/// Conversion error
ConversionError,
/// Specified deadline has already passed
DeadlinePassed,
/// High slippage
HighSlippage,
/// Pool not initialized
PoolNotInitialized,
/// Pool already initialized
PoolAlreadyInitialized,
/// Token amount calculation error
CalculationError,
/// Not equal denominators for fees
NotEqualDenominatorsForFees,
}
// Dispatchable functions allows users to interact with the pallet and invoke state changes.
//#[pallet::call(weight(<T as Config>::WeightInfo))]
#[pallet::call(weight(<T as Config>::WeightInfo))]
impl<T: Config> Pallet<T> {
#[pallet::call_index(0)]
/// The origin can buy a token with usdu.
///
/// Parameters:
/// - `token_id`: The token id of buy token.
/// - `usdu_amount`: The amount of USDU to buy a token.
/// - `min_token_amount`: The minimum amount of token acceptable to buy.
/// - `deadline`: Timeout of the transaction.
///
/// Emits `Buytoken` event when successful.
pub fn buy_token(
origin: OriginFor<T>,
token_id: AssetId<T>,
usdu_amount: BalanceOf<T>,
min_token_amount: Option<BalanceOf<T>>,
deadline: Option<BlockNumberFor<T>>,
) -> DispatchResult {
let mut who = ensure_signed(origin)?;
// Mutate the origin to transfer funds from the main account
who = T::SubAccounts::get_main_account(who)?;
Self::check_deadline(deadline)?;
let usdu_id = T::Fungibles::get_usdu_asset_id().ok_or(Error::<T>::USDUNotSet)?;
let unit_token_id =
T::Fungibles::get_unit_asset_id().ok_or(Error::<T>::UnitTokenNotSet)?;
let (pool_asset_balance, pool_usdu_balance) =
T::Pool::get_pool_liquidity(token_id).ok_or(Error::<T>::PoolLiquidityError)?;
// Check pool was already initialized
ensure!(
pool_asset_balance > (0u128).saturated_into::<BalanceOf<T>>() &&
pool_usdu_balance > (0u128).saturated_into::<BalanceOf<T>>(),
Error::<T>::PoolNotInitialized
);
// Here, calculate_output_amount is applying the fee to the usdu_amount
let receive_token_amount =
Self::calculate_output_amount(usdu_amount, pool_usdu_balance, pool_asset_balance)?;
Self::check_min_usdu(receive_token_amount, min_token_amount)?;
let pool_account_id = T::Pool::get_pool_account_id();
// transfer usdu to the pool
T::Fungibles::transfer(
usdu_id, // asset
&who, // source
&pool_account_id, // dest
usdu_amount, // amount
Expendable, // preservation
)?;
let (inviter_fee_numerator, inviter_fee_denominator) = T::GetExchangeInviterFee::get();
let inviter_fee =
Perbill::from_rational(inviter_fee_numerator, inviter_fee_denominator) *
usdu_amount; // .5%
let (unit_treasury_fee_numerator, unit_treasury_fee_denominator) =
T::GetExchangeUnitTreasuryFee::get();
let unit_treasury_fee = Perbill::from_rational(
unit_treasury_fee_numerator,
unit_treasury_fee_denominator,
) * usdu_amount; // .5%
let (token_treasury_fee_numerator, token_treasury_fee_denominator) =
T::GetExchangeTokenTreasuryFee::get();
let token_treasury_fee = Perbill::from_rational(
token_treasury_fee_numerator,
token_treasury_fee_denominator,
) * usdu_amount; // .5%
// transfer tokens to the user
T::Fungibles::transfer(
token_id, // asset
&pool_account_id, // source
&who, // dest
receive_token_amount, // amount
Expendable, // preservation
)?;
//get inviter address of the buyer
let inviter_address = T::Profile::get_inviter(who.clone())?;
// Transfer bonuses to the treasury account
T::Fungibles::transfer(
usdu_id, // asset
&pool_account_id, // source
&T::Treasury::get_treasury_account(), // dest
unit_treasury_fee + token_treasury_fee, // amount
Expendable, // preservation
)?;
// record transfer to unit treasury
T::Treasury::transfer_usdu_from_pool_to_treasury(
token_id,
unit_token_id,
unit_treasury_fee,
usdu_id,
);
// record transfer to token treasury
T::Treasury::transfer_usdu_from_pool_to_treasury(
token_id,
token_id,
token_treasury_fee,
usdu_id,
);
// transfer fee to the inviter of the buyer
T::Fungibles::transfer(
usdu_id, // asset
&pool_account_id, // source
&inviter_address, // dest
inviter_fee, // amount
Expendable, // preservation
)?;
// Create bonus to store trail
let bonus_paid = Bonus {
asset_id: token_id,
usdu_id,
origin: who.clone(),
reserve_asset: pool_asset_balance,
reserve_usdu: pool_usdu_balance,
total_asset_purchased: receive_token_amount,
usdu_amount,
usdu_fee: inviter_fee,
time: T::Time::now(),
is_buy: true,
};
Bonuses::<T>::mutate(&inviter_address, |n| n.push(bonus_paid));
// Update the amounts in the pool, substracting the bonuses
T::Pool::update_buy(
token_id,
receive_token_amount,
usdu_amount - inviter_fee - unit_treasury_fee - token_treasury_fee,
)?;
BuyTokenActivities::<T>::try_mutate(
token_id,
|activities| -> Result<(), DispatchError> {
let activity = BuyTokenActivity {
account: who.clone(),
asset_id: token_id,
amount: receive_token_amount,
usdu_id,
usdu_amount,
created_at: T::Time::now(),
};
activities.push(activity);
Ok(())
},
)?;
Self::deposit_event(Event::Buytoken {
who: who.clone(),
token_id,
token_amount: receive_token_amount,
usdu_id: unit_token_id,
usdu_amount,
});
// Post a status.
T::Newsfeed::post_status(
who,
StatusType::Buy,
format!("Bought {:?} ${:?} for {:?}", receive_token_amount, token_id, usdu_amount)
.as_bytes()
.to_vec(),
token_id,
None, // optional account
Some(receive_token_amount), // optional amount
Some(usdu_amount), // Second optional amount
);
Ok(())
}
#[pallet::call_index(1)]
/// The origin can sell a token for USDU.
///
/// Parameters:
/// - `token_id`: The token id of sell token.
/// - `token_amount`: The amount of token to sell.
/// - `min_usdu_amount`: The minimum amount of USDU acceptable to sell.
/// - `deadline`: Timeout of the transaction.
///
/// Emits `SellToken` event when successful.
pub fn sell_token(
origin: OriginFor<T>,
token_id: AssetId<T>,
token_amount: BalanceOf<T>,
min_usdu_out: Option<BalanceOf<T>>,
deadline: Option<BlockNumberFor<T>>,
) -> DispatchResult {
let mut who = ensure_signed(origin)?;
// Mutate the origin to transfer funds from the main account
who = T::SubAccounts::get_main_account(who)?;
Self::check_deadline(deadline)?;
let usdu_id = T::Fungibles::get_usdu_asset_id().ok_or(Error::<T>::USDUNotSet)?;
let (pool_asset_id_balance, pool_usdu_balance) =
T::Pool::get_pool_liquidity(token_id).ok_or(Error::<T>::PoolLiquidityError)?;
// Check pool was already initialized
ensure!(
pool_asset_id_balance > (0u128).saturated_into::<BalanceOf<T>>() &&
pool_usdu_balance > (0u128).saturated_into::<BalanceOf<T>>(),
Error::<T>::PoolNotInitialized
);
let usdu_amount = Self::calculate_output_amount_no_fee(
// fix from here
token_amount,
pool_asset_id_balance,
pool_usdu_balance,
)?;
let pool_id = T::Pool::get_pool_account_id();
let (fee_numerator, fee_denominator) = T::GetExchangePoolFee::get();
let pool_fee = Perbill::from_rational(fee_numerator, fee_denominator) * usdu_amount;
let (inviter_fee_numerator, inviter_fee_denominator) = T::GetExchangeInviterFee::get();
let inviter_fee =
Perbill::from_rational(inviter_fee_numerator, inviter_fee_denominator) *
usdu_amount;
let (unit_treasury_fee_numerator, unit_treasury_fee_denominator) =
T::GetExchangeUnitTreasuryFee::get();
let unit_treasury_fee = Perbill::from_rational(
unit_treasury_fee_numerator,
unit_treasury_fee_denominator,
) * usdu_amount;
let (token_treasury_fee_numerator, token_treasury_fee_denominator) =
T::GetExchangeTokenTreasuryFee::get();
let token_treasury_fee = Perbill::from_rational(
token_treasury_fee_numerator,
token_treasury_fee_denominator,
) * usdu_amount;
let receive_usdu_amount =
usdu_amount - pool_fee - inviter_fee - unit_treasury_fee - token_treasury_fee;
Self::check_min_usdu(receive_usdu_amount, min_usdu_out)?;
// transfer tokens to the user
T::Fungibles::transfer(usdu_id, &pool_id, &who, receive_usdu_amount, Expendable)?;
// transfer tokens to the exchange pool
T::Fungibles::transfer(token_id, &who, &pool_id, token_amount, Expendable)?;
//get inviter address of the buyer
let inviter_address = T::Profile::get_inviter(who.clone())?;
// Transfer bonuses to the treasuries
T::Fungibles::transfer(
usdu_id,
&pool_id,
&T::Treasury::get_treasury_account(),
unit_treasury_fee + token_treasury_fee,
Expendable,
)?;
// update transfer to token treasury
T::Treasury::transfer_usdu_from_pool_to_treasury(
token_id,
token_id,
token_treasury_fee,
usdu_id,
);
// update transfer to unit treasury
T::Treasury::transfer_usdu_from_pool_to_treasury(
token_id,
usdu_id,
unit_treasury_fee,
usdu_id,
);
// transfer fee to the inviter of the buyer
T::Fungibles::transfer(usdu_id, &pool_id, &inviter_address, inviter_fee, Expendable)?;
// Create bonus to store trail
let bonus_paid = Bonus {
asset_id: token_id,
usdu_id,
origin: who.clone(),
reserve_asset: pool_asset_id_balance,
reserve_usdu: pool_usdu_balance,
total_asset_purchased: token_amount,
usdu_amount: receive_usdu_amount,
usdu_fee: inviter_fee,
time: T::Time::now(),
is_buy: false,
};
Bonuses::<T>::mutate(&inviter_address, |n| n.push(bonus_paid));
// Update the amounts in the pool, substracting the bonuses
T::Pool::update_sell(
token_id,
token_amount,
receive_usdu_amount - inviter_fee - unit_treasury_fee - token_treasury_fee, /* TODO: Check this */
)?;
SellTokenActivities::<T>::try_mutate(
token_id,
|activities| -> Result<(), DispatchError> {
let activity = SellTokenActivity {
account: who.clone(),
asset_id: token_id,
amount: token_amount,
usdu_id,
usdu_amount: receive_usdu_amount,
created_at: T::Time::now(),
};
activities.push(activity);
Ok(())
},
)?;
Self::deposit_event(Event::Selltoken {
who: who.clone(),
token_id,
token_amount,
usdu_id,
usdu_amount: receive_usdu_amount,
});
// Post a status.
T::Newsfeed::post_status(
who,
StatusType::Sell,
format!("Sold {:?} ${:?} for {:?}", token_amount, token_id, receive_usdu_amount)
.as_bytes()
.to_vec(),
token_id,
None, // Optional account
Some(token_amount), // Optional amount
Some(receive_usdu_amount), // Second optional amount
);
Ok(())
}
#[pallet::call_index(2)]
#[pallet::weight({ 0 })]
/// The origin can create an exchange.
///
/// Parameters:
/// - `token_id`: The token id of exchange token.
/// - `token_amount`: The amount of token to initialize the pool.
/// - `usdu_amount`: The amount of usdu to initialize the pool.
/// - `deadline`: Timeout of the transaction.
pub fn create_exchange(
origin: OriginFor<T>,
token_id: AssetId<T>,
token_amount: BalanceOf<T>,
usdu_amount: BalanceOf<T>,
) -> DispatchResult {
let mut who = ensure_signed(origin)?;
// Mutate the origin to transfer funds from the main account
who = T::SubAccounts::get_main_account(who)?;
// Check if pool was already initialized by checking balances are not zero
let (pool_asset_id_balance, pool_usdu_balance) =
T::Pool::get_pool_liquidity(token_id).ok_or(Error::<T>::PoolLiquidityError)?;
ensure!(
pool_asset_id_balance == (0u128).saturated_into::<BalanceOf<T>>() &&
pool_usdu_balance == (0u128).saturated_into::<BalanceOf<T>>(),
Error::<T>::PoolAlreadyInitialized
);
let usdu_id = T::Fungibles::get_usdu_asset_id().ok_or(Error::<T>::USDUNotSet)?;
T::Pool::stake(&who, token_id, token_amount, usdu_id, usdu_amount)?;
// Post a status.
T::Newsfeed::post_status(
who,
StatusType::Stake,
format!("Staked {:?} ${:?} and {:?}", token_amount, token_id, usdu_amount)
.as_bytes()
.to_vec(),
token_id,
None, // Otional account
Some(token_amount), // Optional amount
Some(usdu_amount), // Second optional amount
);
Ok(())
}
#[pallet::call_index(3)]
/// The origin can stake a token with USDU.
///
/// Parameters:
/// - `token_id`: The token id of stake token.
/// - `usdu_amount`: The amount of usdu to stake.
/// - `max_tokens`: The maximum amount of token acceptable to stake.
/// - `deadline`: Timeout of the transaction.
pub fn stake_token(
origin: OriginFor<T>,
token_id: AssetId<T>,
usdu_amount: BalanceOf<T>,
max_tokens: Option<BalanceOf<T>>,
deadline: Option<BlockNumberFor<T>>,
) -> DispatchResult {
let mut who = ensure_signed(origin)?;
// Mutate the origin to transfer funds from the main account
who = T::SubAccounts::get_main_account(who)?;
Self::check_deadline(deadline)?;
let usdu_id = T::Fungibles::get_usdu_asset_id().ok_or(Error::<T>::USDUNotSet)?;
// Getting liquidity from the pool
let (pool_asset_id_balance, pool_usdu_balance) =
T::Pool::get_pool_liquidity(token_id).ok_or(Error::<T>::PoolLiquidityError)?;
// Check pool was already initialized
ensure!(
pool_asset_id_balance > (0u128).saturated_into::<BalanceOf<T>>() &&
pool_usdu_balance > (0u128).saturated_into::<BalanceOf<T>>(),
Error::<T>::PoolNotInitialized
);
let intermediate_result = usdu_amount
.checked_mul(&pool_asset_id_balance)
.ok_or(Error::<T>::CalculationError)?;
let token_amount = intermediate_result
.checked_div(&pool_usdu_balance)
.ok_or(Error::<T>::CalculationError)?;
// Ensure slippage is within tolerance if max_tokens is set
if let Some(max_tokens) = max_tokens {
ensure!(token_amount <= max_tokens, Error::<T>::HighSlippage);
}
// Main pool call to stake
T::Pool::stake(&who, token_id, token_amount, usdu_id, usdu_amount)?;
// Post a status.
// Evaluate moving this to pool pallet.
T::Newsfeed::post_status(
who,
StatusType::UnStake,
format!("UnStaked {:?} ${:?}", usdu_amount, token_id).as_bytes().to_vec(),
token_id,
None, // Optional account
Some(usdu_amount), // Optional amount
None, // Second optional amount
);
Ok(())
}
#[pallet::call_index(4)]
/// The origin can unstake a token with USDU.
///
/// Parameters:
/// - `token_id`: The token id of unstake token.
/// - `remove_share`: The amount of share to remove.
/// - `min_tokens`: The minimum amount of token acceptable to unstake.
/// - `min_liquidity`: The minimum amount of liquidity acceptable to unstake.
/// - `deadline`: Timeout of the transaction.
pub fn unstake_token(
origin: OriginFor<T>,
token_id: AssetId<T>,
remove_share: BalanceOf<T>,
min_tokens: Option<BalanceOf<T>>,
min_liquidity: Option<BalanceOf<T>>,
deadline: Option<BlockNumberFor<T>>,
) -> DispatchResult {
let mut who = ensure_signed(origin)?;
// Mutate the origin to transfer funds from the main account
who = T::SubAccounts::get_main_account(who)?;
Self::check_deadline(deadline)?;
let usdu_id = T::Fungibles::get_usdu_asset_id().ok_or(Error::<T>::USDUNotSet)?;
T::Pool::unstake(&who, token_id, usdu_id, remove_share, min_tokens, min_liquidity)?;
Ok(())
}
}
impl<T: Config> Pallet<T> {
pub fn u32_to_asset_balance(input: u32) -> BalanceOf<T> {
input.into()
}
pub fn asset_balance_to_u64(input: BalanceOf<T>) -> Result<u64, Error<T>> {
TryInto::<u64>::try_into(input).ok().ok_or(Error::<T>::ConversionError)
}
pub fn u64_to_asset_balance(input: u64) -> Result<BalanceOf<T>, Error<T>> {
input.try_into().ok().ok_or(Error::<T>::ConversionError)
}
pub fn u64_to_fungible_balance(input: u64) -> Result<BalanceOf<T>, Error<T>> {
input.try_into().ok().ok_or(Error::<T>::ConversionError)
}
/// Get how much target amount will be got for specific supply amount.
pub fn get_target_amount(
supply_pool: BalanceOf<T>,
target_pool: BalanceOf<T>,
supply_amount: BalanceOf<T>,
) -> BalanceOf<T> {
if supply_pool == (0u128).saturated_into::<BalanceOf<T>>() ||
target_pool == (0u128).saturated_into::<BalanceOf<T>>() ||
supply_amount == (0u128).saturated_into::<BalanceOf<T>>()
{
(0u128).saturated_into::<BalanceOf<T>>()
} else {
let (fee_numerator, fee_denominator) = T::GetExchangePoolFee::get();
let token_amount_with_fee = supply_pool.saturating_mul(
fee_denominator.saturating_sub(fee_numerator).saturated_into::<BalanceOf<T>>(),
);
let numerator = token_amount_with_fee.saturating_mul(target_pool);
let denominator = supply_pool
.saturating_mul(fee_denominator.saturated_into::<BalanceOf<T>>())
.saturating_add(token_amount_with_fee);
numerator / denominator
}
}
/// Get how much supply amount will be paid for specific target amount.
pub fn get_supply_amount(
supply_pool: BalanceOf<T>,
target_pool: BalanceOf<T>,
target_amount: BalanceOf<T>,
) -> BalanceOf<T> {
if target_amount == (0u128).saturated_into::<BalanceOf<T>>() ||
supply_pool == (0u128).saturated_into::<BalanceOf<T>>() ||
target_pool == (0u128).saturated_into::<BalanceOf<T>>()
{
(0u128).saturated_into::<BalanceOf<T>>()
} else {
let (fee_numerator, fee_denominator) = T::GetExchangePoolFee::get();
let numerator = supply_pool
.saturating_mul(target_amount.saturated_into::<BalanceOf<T>>())
.saturating_mul(fee_denominator.saturated_into::<BalanceOf<T>>());
let denominator = target_pool.saturating_sub(target_amount).saturating_mul(
fee_denominator
.saturated_into::<BalanceOf<T>>()
.saturating_sub(fee_numerator.saturated_into::<BalanceOf<T>>()),
);
(numerator / denominator).saturating_add((1u128).saturated_into::<BalanceOf<T>>())
}
}
pub fn get_liquidity(token_id: AssetId<T>) -> Option<(BalanceOf<T>, BalanceOf<T>)> {
T::Pool::get_pool_liquidity(token_id)
}
// given an input amount of an asset and pair reserves, returns the maximum output amount of
// the other asset
pub fn calculate_output_amount(
amount_in: BalanceOf<T>,
reserve_in: BalanceOf<T>,
reserve_out: BalanceOf<T>,
) -> Result<BalanceOf<T>, Error<T>> {
ensure!(
amount_in != (0u128).saturated_into::<BalanceOf<T>>(),
Error::<T>::InsufficientInputAmount
);
ensure!(
reserve_in != (0u128).saturated_into::<BalanceOf<T>>() &&
reserve_out != (0u128).saturated_into::<BalanceOf<T>>(),
Error::<T>::InsufficientLiquidity
);
let (pool_fee_numerator, pool_fee_denominator) = T::GetExchangePoolFee::get(); // example: 1/200 = 0.5%
let (inviter_fee_numerator, inviter_fee_denominator) = T::GetExchangeInviterFee::get(); // example: 1/200 = 0.5%
let (unit_treasury_fee_numerator, unit_treasury_fee_denominator) =
T::GetExchangeUnitTreasuryFee::get(); // example: 1/200 = 0.5%
let (token_treasury_fee_numerator, token_treasury_fee_denominator) =
T::GetExchangeTokenTreasuryFee::get(); // example: 1/200 = 0.5%
// if all denominators are equal, then add all numerators and keep the same denominator
if pool_fee_denominator == inviter_fee_denominator &&
pool_fee_denominator == unit_treasury_fee_denominator &&
pool_fee_denominator == token_treasury_fee_denominator
{
let fee_numerator = pool_fee_numerator +
inviter_fee_numerator +
unit_treasury_fee_numerator +
token_treasury_fee_numerator;
let fee_denominator = pool_fee_denominator;
ensure!(fee_numerator < fee_denominator, Error::<T>::InsufficientLiquidity);
let fee = fee_denominator - fee_numerator;
let amount_in_with_fee =
amount_in.saturating_mul(fee.saturated_into::<BalanceOf<T>>());
let numerator = amount_in_with_fee.saturating_mul(reserve_out);
let denominator = reserve_in
.saturating_mul(fee_denominator.saturated_into::<BalanceOf<T>>())
.saturating_add(amount_in_with_fee);
return Ok(numerator / denominator);
} else {
// Find a common denominator
let common_denominator = pool_fee_denominator
.saturating_mul(inviter_fee_denominator)
.saturating_mul(unit_treasury_fee_denominator)
.saturating_mul(token_treasury_fee_denominator);
// Calculate the equivalent numerators with the common denominator
let pool_fee_numerator_with_common_denominator =
pool_fee_numerator.saturating_mul(common_denominator / pool_fee_denominator);
let inviter_fee_numerator_with_common_denominator = inviter_fee_numerator
.saturating_mul(common_denominator / inviter_fee_denominator);
let unit_treasury_fee_numerator_with_common_denominator =
unit_treasury_fee_numerator
.saturating_mul(common_denominator / unit_treasury_fee_denominator);
let token_treasury_fee_numerator_with_common_denominator =
token_treasury_fee_numerator
.saturating_mul(common_denominator / token_treasury_fee_denominator);
// Sum of numerators with the common denominator
let fee_numerator = pool_fee_numerator_with_common_denominator +
inviter_fee_numerator_with_common_denominator +
unit_treasury_fee_numerator_with_common_denominator +
token_treasury_fee_numerator_with_common_denominator;
let fee_denominator = common_denominator;
ensure!(fee_numerator < fee_denominator, Error::<T>::InsufficientLiquidity);
let fee = fee_denominator - fee_numerator;
let amount_in_with_fee =
amount_in.saturating_mul(fee.saturated_into::<BalanceOf<T>>());
let numerator = amount_in_with_fee.saturating_mul(reserve_out);
let denominator = reserve_in
.saturating_mul(fee_denominator.saturated_into::<BalanceOf<T>>())
.saturating_add(amount_in_with_fee);
return Ok(numerator / denominator);
}
}
pub fn calculate_output_amount_no_fee(
amount_in: BalanceOf<T>,
reserve_in: BalanceOf<T>,
reserve_out: BalanceOf<T>,
) -> Result<BalanceOf<T>, Error<T>> {
ensure!(
amount_in != (0u128).saturated_into::<BalanceOf<T>>(),
Error::<T>::InsufficientInputAmount
);
ensure!(
reserve_in != (0u128).saturated_into::<BalanceOf<T>>() &&
reserve_out != (0u128).saturated_into::<BalanceOf<T>>(),
Error::<T>::InsufficientLiquidity
);
let numerator = amount_in.saturating_mul(reserve_out);
let denominator = reserve_in.saturating_add(amount_in);
return Ok(numerator / denominator);
}
fn check_deadline(deadline: Option<BlockNumberFor<T>>) -> Result<(), Error<T>> {
if let Some(deadline) = deadline {
ensure!(
deadline >= <frame_system::Pallet<T>>::block_number(),
Error::DeadlinePassed
);
}
Ok(())
}
fn check_min_usdu(
receive_usdu_amount: BalanceOf<T>,
min_usdu_out: Option<BalanceOf<T>>,
) -> Result<(), Error<T>> {
if let Some(min_usdu_out) = min_usdu_out {
ensure!(receive_usdu_amount >= min_usdu_out, Error::<T>::HighSlippage);
}
Ok(())
}
}
impl<T: Config> WorkersRewards<AssetId<T>, T::AccountId, BalanceOf<T>> for Pallet<T> {
fn buy_token(
pool_workers: T::AccountId,
token_id: AssetId<T>,
usdu_amount: BalanceOf<T>,
) -> DispatchResult {
let usdu_id = T::Fungibles::get_usdu_asset_id().ok_or(Error::<T>::USDUNotSet)?;
let unit_token_id =
T::Fungibles::get_unit_asset_id().ok_or(Error::<T>::UnitTokenNotSet)?;
let (pool_asset_balance, pool_usdu_balance) =
T::Pool::get_pool_liquidity(token_id).ok_or(Error::<T>::PoolLiquidityError)?;
let receive_token_amount =
Self::calculate_output_amount(usdu_amount, pool_usdu_balance, pool_asset_balance)?;
let pool_account_id = T::Pool::get_pool_account_id();
// transfer usdu to the pool
T::Fungibles::transfer(
usdu_id, // asset
&pool_workers, // source
&pool_account_id, // dest
usdu_amount, // amount
Expendable, // preservation
)?;
// get fees, this is 1/200 = 0.5% of the usdu_amount. Under the assumption that the pool
// is charging a 2% fee. The 0.5% that is left stays in the pool.
let unit_treasury_fee = Perbill::from_rational(1u32, 200u32) * usdu_amount; // .5%
let token_treasury_fee = Perbill::from_rational(1u32, 200u32) * usdu_amount; // .5%
let all_fees = unit_treasury_fee + token_treasury_fee;
// transfer tokens to the user
T::Fungibles::transfer(
token_id, // asset
&pool_account_id, // source
&pool_workers, // dest
receive_token_amount - all_fees, // amount
Expendable, // preservation
)?;
// Transfer bonuses to the treasury account
T::Fungibles::transfer(
usdu_id, // asset
&pool_account_id, // source
&T::Treasury::get_treasury_account(), // dest
unit_treasury_fee + token_treasury_fee, // amount
Expendable, // preservation
)?;
// record transfer to unit treasury
T::Treasury::transfer_usdu_from_pool_to_treasury(
token_id,
unit_token_id,
unit_treasury_fee,
usdu_id,
);
// record transfer to token treasury
T::Treasury::transfer_usdu_from_pool_to_treasury(
token_id,
token_id,
token_treasury_fee,
usdu_id,
);
// Update the amounts in the pool, substracting the bonuses
T::Pool::update_buy(
token_id,
receive_token_amount,
usdu_amount - unit_treasury_fee - token_treasury_fee,
)?;
BuyTokenActivities::<T>::try_mutate(
token_id,
|activities| -> Result<(), DispatchError> {
let activity = BuyTokenActivity {
account: pool_workers.clone(),
asset_id: token_id,
amount: receive_token_amount,
usdu_id,
usdu_amount,
created_at: T::Time::now(),
};
activities.push(activity);
Ok(())
},
)?;
Self::deposit_event(Event::Buytoken {
who: pool_workers.clone(),
token_id,
token_amount: receive_token_amount,
usdu_id: unit_token_id,
usdu_amount,
});
// Post a status.
T::Newsfeed::post_status(
pool_workers,
StatusType::Buy,
format!("Bought {:?} ${:?} for {:?}", receive_token_amount, token_id, usdu_amount)
.as_bytes()
.to_vec(),
token_id,
None, // optional account
Some(receive_token_amount), // optional amount
Some(usdu_amount), // Second optional amount
);
Ok(())
}
fn sell_token(
pool_workers: T::AccountId,
token_id: AssetId<T>,
token_amount: BalanceOf<T>,
) -> DispatchResult {
let usdu_id = T::Fungibles::get_usdu_asset_id().ok_or(Error::<T>::USDUNotSet)?;
let (pool_asset_id_balance, pool_usdu_balance) =
T::Pool::get_pool_liquidity(token_id).ok_or(Error::<T>::PoolLiquidityError)?;
let receive_usdu_amount = Self::calculate_output_amount(
token_amount,
pool_asset_id_balance,
pool_usdu_balance,
)?;
let pool_id = T::Pool::get_pool_account_id();
// get fees, this is 1/200 = 0.5% of the usdu_amount. Under the assumption that the pool
// is charging a 2% fee. The 0.5% that is left stays in the pool.
let unit_treasury_fee = Perbill::from_rational(1u32, 200u32) * receive_usdu_amount; // .5%
let token_treasury_fee = Perbill::from_rational(1u32, 200u32) * receive_usdu_amount; // .5%
// transfer tokens to the user
T::Fungibles::transfer(
usdu_id,
&pool_id,
&pool_workers,
receive_usdu_amount - unit_treasury_fee - token_treasury_fee,
Expendable,
)?;
// transfer tokens to the exchange pool
T::Fungibles::transfer(token_id, &pool_workers, &pool_id, token_amount, Expendable)?;
// Transfer bonuses to the treasuries
T::Fungibles::transfer(
usdu_id,
&pool_id,
&T::Treasury::get_treasury_account(),
unit_treasury_fee + token_treasury_fee,
Expendable,
)?;
// update transfer to token treasury
T::Treasury::transfer_usdu_from_pool_to_treasury(
token_id,
token_id,
token_treasury_fee,
usdu_id,
);
// update transfer to unit treasury
T::Treasury::transfer_usdu_from_pool_to_treasury(
token_id,
usdu_id,
unit_treasury_fee,
usdu_id,
);
// Update the amounts in the pool, substracting the bonuses
T::Pool::update_sell(
token_id,
token_amount,
receive_usdu_amount - unit_treasury_fee - token_treasury_fee,
)?;
SellTokenActivities::<T>::try_mutate(
token_id,
|activities| -> Result<(), DispatchError> {
let activity = SellTokenActivity {
account: pool_workers.clone(),
asset_id: token_id,
amount: token_amount,
usdu_id,
usdu_amount: receive_usdu_amount,
created_at: T::Time::now(),
};
activities.push(activity);
Ok(())
},
)?;
Self::deposit_event(Event::Selltoken {
who: pool_workers.clone(),
token_id,
token_amount,
usdu_id,
usdu_amount: receive_usdu_amount,
});
// Post a status.
T::Newsfeed::post_status(
pool_workers,
StatusType::Sell,
format!("Sold {:?} ${:?} for {:?}", token_amount, token_id, receive_usdu_amount)
.as_bytes()
.to_vec(),
token_id,
None, // Optional account
Some(token_amount), // Optional amount
Some(receive_usdu_amount), // Second optional amount
);
Ok(())
}
}
}