Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion frame/composable-support/src/validation.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Module for validating extrinsic inputs
//!
//! This module is made of two main parts that are needed to validate an
//! extrinsic input, the `Validated` struct and the `Valitate` trait.
//! extrinsic input, the `Validated` struct and the `Validate` trait.
//!
//! # Example
//! ## Single Validation
Expand Down
102 changes: 99 additions & 3 deletions frame/composable-traits/src/dex.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
use crate::{
defi::CurrencyPair,
math::{SafeAdd, SafeSub},
};
use codec::{Decode, Encode, MaxEncodedLen};
use frame_support::{traits::Get, BoundedVec, RuntimeDebug};
use scale_info::TypeInfo;
use sp_runtime::{DispatchError, Permill};
use sp_arithmetic::traits::Saturating;
use sp_runtime::{
traits::{CheckedMul, CheckedSub},
ArithmeticError, DispatchError, Permill,
};
use sp_std::vec::Vec;

use crate::defi::CurrencyPair;

/// Trait for automated market maker.
pub trait Amm {
/// The asset ID type
Expand Down Expand Up @@ -144,6 +150,96 @@ pub struct ConstantProductPoolInfo<AccountId, AssetId> {
pub owner_fee: Permill,
}

#[derive(Copy, Clone, PartialEq, Eq)]
pub enum SaleState {
NotStarted,
Ongoing,
Ended,
}

#[derive(RuntimeDebug, Encode, Decode, MaxEncodedLen, Copy, Clone, PartialEq, Eq, TypeInfo)]
pub struct Sale<BlockNumber> {
/// Block at which the sale start.
pub start: BlockNumber,
/// Block at which the sale stop.
pub end: BlockNumber,
/// Initial weight of the base asset of the current pair.
pub initial_weight: Permill,
/// Final weight of the base asset of the current pair.
pub final_weight: Permill,
}

impl<BlockNumber: TryInto<u64> + Ord + Copy + Saturating + SafeAdd + SafeSub> Sale<BlockNumber> {
// TODO unit test
pub fn current_weights(
&self,
current_block: BlockNumber,
) -> Result<(Permill, Permill), DispatchError> {
/* NOTE(hussein-aitlahcen): currently only linear

Linearly decrease the base asset initial_weight to final_weight.
Quote asset weight is simple 1-base_asset_weight

Assuming final_weight < initial_weight
current_weight = initial_weight - (current - start) / (end - start) * (initial_weight - final_weight)
= initial_weight - normalized_current / sale_duration * weight_range
= initial_weight - point_in_sale * weight_range
*/
let normalized_current_block = current_block.safe_sub(&self.start)?;
let point_in_sale = Permill::from_rational(
normalized_current_block.try_into().map_err(|_| ArithmeticError::Overflow)?,
self.duration().try_into().map_err(|_| ArithmeticError::Overflow)?,
);
let weight_range = self
.initial_weight
.checked_sub(&self.final_weight)
.ok_or(ArithmeticError::Underflow)?;
let current_base_weight = self
.initial_weight
.checked_sub(
&point_in_sale.checked_mul(&weight_range).ok_or(ArithmeticError::Overflow)?,
)
.ok_or(ArithmeticError::Underflow)?;
let current_quote_weight = Permill::one()
.checked_sub(&current_base_weight)
.ok_or(ArithmeticError::Underflow)?;
Ok((current_base_weight, current_quote_weight))
}
}

impl<BlockNumber: Copy + Saturating> Sale<BlockNumber> {
pub fn duration(&self) -> BlockNumber {
// NOTE(hussein-aitlahcen): end > start as previously checked by PoolIsValid.
self.end.saturating_sub(self.start)
}
}

impl<BlockNumber: Ord> Sale<BlockNumber> {
pub fn state(&self, current_block: BlockNumber) -> SaleState {
if current_block < self.start {
SaleState::NotStarted
} else if current_block >= self.end {
SaleState::Ended
} else {
SaleState::Ongoing
}
}
}

#[derive(RuntimeDebug, Encode, Decode, MaxEncodedLen, Copy, Clone, PartialEq, Eq, TypeInfo)]
pub struct LiquidityBootstrappingPoolInfo<AccountId, AssetId, BlockNumber> {
/// Owner of the pool
pub owner: AccountId,
/// Asset pair of the pool along their weight.
/// Base asset is the project token.
/// Quote asset is the collateral token.
pub pair: CurrencyPair<AssetId>,
/// Sale period of the LBP.
pub sale: Sale<BlockNumber>,
/// Trading fees.
pub fee: Permill,
}

#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, Clone, PartialEq, Eq, RuntimeDebug)]
pub enum DexRouteNode<PoolId> {
Curve(PoolId),
Expand Down
Binary file added frame/pablo/plots/lbp/lbp_buy_project.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added frame/pablo/plots/lbp/lbp_sell_project.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added frame/pablo/plots/lbp/lbp_spot_price.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added frame/pablo/plots/lbp/lbp_weights.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
157 changes: 145 additions & 12 deletions frame/pablo/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,15 @@ pub use pallet::*;
#[cfg(test)]
mod common_test_functions;
#[cfg(test)]
mod liquidity_bootstrapping_tests;
#[cfg(test)]
mod mock;
#[cfg(test)]
mod stable_swap_tests;
#[cfg(test)]
mod uniswap_tests;

mod liquidity_bootstrapping;
mod stable_swap;
mod uniswap;

Expand All @@ -64,41 +67,62 @@ pub mod pallet {
transactional, PalletId, RuntimeDebug,
};

use frame_system::{ensure_signed, pallet_prelude::OriginFor};
use crate::liquidity_bootstrapping::LiquidityBootstrapping;
use composable_support::validation::Validated;
use composable_traits::{currency::LocalAssets, dex::LiquidityBootstrappingPoolInfo};
use frame_system::{
ensure_signed,
pallet_prelude::{BlockNumberFor, OriginFor},
};
use sp_runtime::{
traits::{AccountIdConversion, Convert, One, Zero},
traits::{AccountIdConversion, BlockNumberProvider, Convert, One, Zero},
Permill,
};

#[derive(RuntimeDebug, Encode, Decode, MaxEncodedLen, Clone, PartialEq, Eq, TypeInfo)]
pub enum PoolInitConfiguration<AssetId> {
pub enum PoolInitConfiguration<AccountId, AssetId, BlockNumber> {
StableSwap {
// TODO consider adding the owner here to allow a third party owner
pair: CurrencyPair<AssetId>,
amplification_coefficient: u16,
fee: Permill,
protocol_fee: Permill,
},
ConstantProduct {
// TODO consider adding the owner here to allow a third party owner
pair: CurrencyPair<AssetId>,
fee: Permill,
owner_fee: Permill,
},
LiquidityBootstrapping(LiquidityBootstrappingPoolInfo<AccountId, AssetId, BlockNumber>),
}

#[derive(RuntimeDebug, Encode, Decode, MaxEncodedLen, Clone, PartialEq, Eq, TypeInfo)]
pub enum PoolConfiguration<AccountId, AssetId> {
pub enum PoolConfiguration<AccountId, AssetId, BlockNumber> {
StableSwap(StableSwapPoolInfo<AccountId, AssetId>),
ConstantProduct(ConstantProductPoolInfo<AccountId, AssetId>),
LiquidityBootstrapping(LiquidityBootstrappingPoolInfo<AccountId, AssetId, BlockNumber>),
}

type AssetIdOf<T> = <T as Config>::AssetId;
type BalanceOf<T> = <T as Config>::Balance;
type AccountIdOf<T> = <T as frame_system::Config>::AccountId;
pub(crate) type AssetIdOf<T> = <T as Config>::AssetId;
pub(crate) type BalanceOf<T> = <T as Config>::Balance;
pub(crate) type AccountIdOf<T> = <T as frame_system::Config>::AccountId;
pub(crate) type LiquidityBootstrappingPoolInfoOf<T> = LiquidityBootstrappingPoolInfo<
<T as frame_system::Config>::AccountId,
<T as Config>::AssetId,
<T as frame_system::Config>::BlockNumber,
>;
type PoolIdOf<T> = <T as Config>::PoolId;
type PoolConfigurationOf<T> =
PoolConfiguration<<T as frame_system::Config>::AccountId, <T as Config>::AssetId>;
type PoolInitConfigurationOf<T> = PoolInitConfiguration<<T as Config>::AssetId>;

type PoolConfigurationOf<T> = PoolConfiguration<
<T as frame_system::Config>::AccountId,
<T as Config>::AssetId,
<T as frame_system::Config>::BlockNumber,
>;
type PoolInitConfigurationOf<T> = PoolInitConfiguration<
<T as frame_system::Config>::AccountId,
<T as Config>::AssetId,
<T as frame_system::Config>::BlockNumber,
>;
// TODO refactor event publishing with cu-23v2y3n
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
Expand Down Expand Up @@ -240,6 +264,25 @@ pub mod pallet {

#[pallet::constant]
type PalletId: Get<PalletId>;

// Used for spot price calculation for LBP
type LocalAssets: LocalAssets<AssetIdOf<Self>>;

/// Minimum duration for a sale.
#[pallet::constant]
type LbpMinSaleDuration: Get<BlockNumberFor<Self>>;

/// Maximum duration for a sale.
#[pallet::constant]
type LbpMaxSaleDuration: Get<BlockNumberFor<Self>>;

/// Maximum initial weight.
#[pallet::constant]
type LbpMaxInitialWeight: Get<Permill>;

/// Minimum final weight.
#[pallet::constant]
type LbpMinFinalWeight: Get<Permill>;
}

#[pallet::pallet]
Expand Down Expand Up @@ -407,6 +450,13 @@ pub mod pallet {
Self::deposit_event(Event::PoolCreated { owner: who.clone(), pool_id });
Ok(pool_id)
},
PoolInitConfiguration::LiquidityBootstrapping(pool_config) => {
let validated_pool_config = Validated::new(pool_config)?;
let pool_id =
LiquidityBootstrapping::<T>::do_create_pool(validated_pool_config)?;
Self::deposit_event(Event::PoolCreated { owner: who.clone(), pool_id });
Ok(pool_id)
},
}
}

Expand Down Expand Up @@ -439,6 +489,8 @@ pub mod pallet {
PoolConfiguration::StableSwap(stable_swap_pool_info) =>
Ok(stable_swap_pool_info.pair),
ConstantProduct(constant_product_pool_info) => Ok(constant_product_pool_info.pair),
PoolConfiguration::LiquidityBootstrapping(liquidity_bootstrapping_pool_info) =>
Ok(liquidity_bootstrapping_pool_info.pair),
}
}

Expand All @@ -463,6 +515,13 @@ pub mod pallet {
asset_id,
amount,
),
PoolConfiguration::LiquidityBootstrapping(liquidity_bootstrapping_pool_info) =>
LiquidityBootstrapping::<T>::get_exchange_value(
liquidity_bootstrapping_pool_info,
pool_account,
asset_id,
amount,
),
}
}

Expand Down Expand Up @@ -514,6 +573,24 @@ pub mod pallet {
minted_lp: mint_amount,
});
},
PoolConfiguration::LiquidityBootstrapping(liquidity_bootstrapping_pool_info) => {
LiquidityBootstrapping::<T>::add_liquidity(
who,
liquidity_bootstrapping_pool_info,
pool_account,
base_amount,
quote_amount,
min_mint_amount,
keep_alive,
)?;
Self::deposit_event(Event::<T>::LiquidityAdded {
who: who.clone(),
pool_id,
base_amount,
quote_amount,
minted_lp: T::Balance::zero(),
});
},
}
// TODO refactor event publishing with cu-23v2y3n
Ok(())
Expand Down Expand Up @@ -565,8 +642,20 @@ pub mod pallet {
total_issuance: updated_lp,
});
},
PoolConfiguration::LiquidityBootstrapping(liquidity_bootstrapping_pool_info) => {
let (base_amount, quote_amount) =
LiquidityBootstrapping::<T>::remove_liquidity(
who,
pool_id,
liquidity_bootstrapping_pool_info,
pool_account,
lp_amount,
min_base_amount,
min_quote_amount,
)?;
Self::deposit_event(Event::PoolDeleted { pool_id, base_amount, quote_amount });
},
}
// TODO refactor event publishing with cu-23v2y3n
Ok(())
}

Expand Down Expand Up @@ -666,6 +755,33 @@ pub mod pallet {

Ok(base_amount)
},
PoolConfiguration::LiquidityBootstrapping(liquidity_bootstrapping_pool_info) => {
let current_block = frame_system::Pallet::<T>::current_block_number();
let (fees, base_amount) = LiquidityBootstrapping::<T>::do_get_exchange(
liquidity_bootstrapping_pool_info,
&pool_account,
pair,
current_block,
quote_amount,
true,
)?;

ensure!(base_amount >= min_receive, Error::<T>::CannotRespectMinimumRequested);

T::Assets::transfer(pair.quote, who, &pool_account, quote_amount, keep_alive)?;
// NOTE(hussein-aitlance): no need to keep alive the pool account
T::Assets::transfer(pair.base, &pool_account, who, base_amount, false)?;
Self::deposit_event(Event::<T>::Swapped {
pool_id,
who: who.clone(),
base_asset: pair.base,
quote_asset: pair.quote,
base_amount,
quote_amount,
fee: fees,
});
Ok(base_amount)
},
}

// TODO refactor event publishing with cu-23v2y3n
Expand Down Expand Up @@ -701,6 +817,15 @@ pub mod pallet {
let quote_amount = Self::get_exchange_value(pool_id, asset_id, amount)?;
Self::exchange(who, pool_id, pair, quote_amount, T::Balance::zero(), keep_alive)
},
PoolConfiguration::LiquidityBootstrapping(liquidity_bootstrapping_pool_info) => {
let pair = if asset_id == liquidity_bootstrapping_pool_info.pair.base {
liquidity_bootstrapping_pool_info.pair
} else {
liquidity_bootstrapping_pool_info.pair.swap()
};
let quote_amount = Self::get_exchange_value(pool_id, asset_id, amount)?;
Self::exchange(who, pool_id, pair, quote_amount, T::Balance::zero(), keep_alive)
},
}
}

Expand All @@ -727,6 +852,14 @@ pub mod pallet {
};
Self::exchange(who, pool_id, pair, amount, T::Balance::zero(), keep_alive)
},
PoolConfiguration::LiquidityBootstrapping(liquidity_bootstrapping_pool_info) => {
let pair = if asset_id == liquidity_bootstrapping_pool_info.pair.base {
liquidity_bootstrapping_pool_info.pair.swap()
} else {
liquidity_bootstrapping_pool_info.pair
};
Self::exchange(who, pool_id, pair, amount, T::Balance::zero(), keep_alive)
},
}
}
}
Expand Down
Loading