Skip to content

Commit

Permalink
CU-1p8545r dutch auction (#410)
Browse files Browse the repository at this point in the history
* dutch auction

Signed-off-by: dzmitry-lahoda <dzmitry@lahoda.pro>

* fixed tests

Signed-off-by: dzmitry-lahoda <dzmitry@lahoda.pro>

* fixed clippy

Signed-off-by: dzmitry-lahoda <dzmitry@lahoda.pro>

* fixed review comments

Signed-off-by: dzmitry-lahoda <dzmitry@lahoda.pro>

* adding tests and fixing bugs

Signed-off-by: dzmitry-lahoda <dzmitry@lahoda.pro>

* refactoring

Signed-off-by: dzmitry-lahoda <dzmitry@lahoda.pro>

* fix fmt

Signed-off-by: dzmitry-lahoda <dzmitry@lahoda.pro>

* fixed udeps

Signed-off-by: dzmitry-lahoda <dzmitry@lahoda.pro>

* fixed clippy

Signed-off-by: dzmitry-lahoda <dzmitry@lahoda.pro>
  • Loading branch information
dzmitry-lahoda committed Dec 30, 2021
1 parent 7ce087d commit b46e402
Show file tree
Hide file tree
Showing 35 changed files with 1,273 additions and 1,170 deletions.
340 changes: 166 additions & 174 deletions Cargo.lock

Large diffs are not rendered by default.

93 changes: 5 additions & 88 deletions frame/composable-traits/src/auction.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
use crate::{
dex::Orderbook,
loans::{DurationSeconds, PriceStructure, Timestamp},
};
use crate::loans::DurationSeconds;
use frame_support::pallet_prelude::*;
use scale_info::TypeInfo;
use sp_runtime::Permill;

#[derive(Decode, Encode, Clone, TypeInfo)]
#[derive(Decode, Encode, Clone, TypeInfo, Debug, PartialEq)]
pub enum AuctionStepFunction {
/// default - direct pass through to dex without steps, just to satisfy defaults and reasonably
/// for testing
Expand All @@ -20,97 +17,17 @@ impl Default for AuctionStepFunction {
}
}

#[derive(Decode, Encode, Clone, PartialEq, TypeInfo)]
pub enum AuctionState<DexOrderId> {
AuctionStarted,
AuctionOnDex(DexOrderId),
AuctionEndedSuccessfully,
/// like DEX does not support asset now or halted
AuctionFatalFailed,
/// so if for some reason system loop is not properly set, than will get timeout
AuctionTimeFailed,
}

impl<DexOrderId> Default for AuctionState<DexOrderId> {
fn default() -> Self {
Self::AuctionStarted
}
}

/// Auction is done via dexes which act each block. Each block decide if intention was satisfied or
/// not. That information is provided via event subscribes which callback into auction.
/// Assuming liquidity providers to be off our local chain, it means that it is high latency
/// external loop.
pub enum AuctionExchangeCallback {
/// success transfer of funds
Success,
/// some technical fail of transaction, can issue new one
RetryFail,
/// cannot retry within current state of system, like assets are not supported
FatalFail,
}

#[derive(Default, Decode, Encode, Clone, TypeInfo)]
#[derive(Default, Decode, Encode, Clone, TypeInfo, Debug, PartialEq)]
pub struct LinearDecrease {
/// The number of seconds until the price reach zero.
/// Seconds after auction start when the price reaches zero
pub total: DurationSeconds,
}

#[derive(Default, Decode, Encode, Clone, TypeInfo)]
#[derive(Default, Decode, Encode, Clone, TypeInfo, Debug, PartialEq)]
pub struct StairstepExponentialDecrease {
// Length of time between price drops
pub step: DurationSeconds,
// Per-step multiplicative factor, usually more than 50%, mostly closer to 100%, but not 100%.
// Drop per unit of `step`.
pub cut: Permill,
}

/// An object from which we can initiate a dutch auction.
// see example of it in clip.sol of makerdao
pub trait DutchAuction {
type OrderId;
type Orderbook: Orderbook;
type AccountId;
type AssetId;
type Balance;
type Order;
type GroupId;

/// Transfer the asset from the provided account to the auction account.
/// The caller is responsible for checking the price at which the auction executed (not known in
/// advance of course).
///
/// Description.
///
/// * `account_id`: the order owner.
/// * `source_account`: the account from which we extract the `amount` of `source_asset_id`
/// from.
/// * `source_asset_id`: the asset we are interested to trade for `target_asset_id`.
/// * `target_account`: the beneficiary of the order.
/// * `total_amount`: the amount of `source_asset_id`.
/// * `price`: the initial price for `total_amount` and some rules.
#[allow(clippy::too_many_arguments)]
fn start(
account_id: &Self::AccountId,
source_asset_id: Self::AssetId,
source_account: &Self::AccountId,
target_asset_id: Self::AssetId,
target_account: &Self::AccountId,
total_amount: Self::Balance,
price: PriceStructure<Self::GroupId, Self::Balance>,
function: AuctionStepFunction,
) -> Result<Self::OrderId, DispatchError>;

/// run existing auctions
/// if some auctions completed, transfer amount to target account
/// `now` current time.
fn off_chain_run_auctions(now: Timestamp) -> DispatchResult;

fn get_auction_state(order: &Self::OrderId) -> Option<Self::Order>;

/// called back from DEX
fn intention_updated(
order: &Self::OrderId,
action_event: AuctionExchangeCallback,
) -> DispatchResult;
}
9 changes: 6 additions & 3 deletions frame/composable-traits/src/currency.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,17 @@ pub trait PriceableAsset
where
Self: Copy,
{
/// if currency has `decimals` of 3, than it will return
fn unit<T: From<u64>>(&self) -> T {
T::from(10u64.pow(self.smallest_unit_exponent()))
T::from(10u64.pow(self.decimals() as u32))
}
fn smallest_unit_exponent(self) -> Exponent;

/// Return the decimals of an asset.
fn decimals(self) -> Exponent;
}

impl PriceableAsset for u128 {
fn smallest_unit_exponent(self) -> Exponent {
fn decimals(self) -> Exponent {
0
}
}
Expand Down
168 changes: 168 additions & 0 deletions frame/composable-traits/src/defi.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
//! Common codes for defi pallets

use codec::{Codec, Decode, Encode, FullCodec};
use frame_support::{pallet_prelude::MaybeSerializeDeserialize, Parameter};
use scale_info::TypeInfo;
use sp_runtime::{
traits::{CheckedAdd, CheckedMul, CheckedSub, Zero},
ArithmeticError, DispatchError, FixedPointOperand,
};

use crate::{
currency::{AssetIdLike, BalanceLike},
math::{LiftedFixedBalance, SafeArithmetic},
};

#[derive(Encode, Decode, TypeInfo, Debug, Clone, PartialEq)]
pub struct Take<Balance> {
/// amount of `base`
pub amount: Balance,
/// direction depends on referenced order type
/// either minimal or maximal amount of `quote` for given unit of `base`
pub limit: Balance,
}

impl<Balance: PartialOrd + Zero + SafeArithmetic> Take<Balance> {
pub fn is_valid(&self) -> bool {
self.amount > Balance::zero() && self.limit > Balance::zero()
}
pub fn new(amount: Balance, limit: Balance) -> Self {
Self { amount, limit }
}

pub fn quote_amount(&self) -> Result<Balance, ArithmeticError> {
self.amount.safe_mul(&self.limit)
}
}

/// take `quote` currency and give `base` currency
#[derive(Encode, Decode, TypeInfo, Debug, Clone, PartialEq)]
pub struct Sell<AssetId, Balance> {
pub pair: CurrencyPair<AssetId>,
pub take: Take<Balance>,
}

impl<AssetId: PartialEq, Balance: PartialOrd + Zero + SafeArithmetic> Sell<AssetId, Balance> {
pub fn is_valid(&self) -> bool {
self.take.is_valid() && self.pair.is_valid()
}
pub fn new(
base: AssetId,
quote: AssetId,
base_amount: Balance,
minimal_base_unit_price_in_quote: Balance,
) -> Self {
Self {
take: Take { amount: base_amount, limit: minimal_base_unit_price_in_quote },
pair: CurrencyPair { base, quote },
}
}
}

/// given `base`, how much `quote` needed for unit
/// see [currency pair](https://www.investopedia.com/terms/c/currencypair.asp)
#[derive(Encode, Decode, TypeInfo, Debug, Clone, PartialEq)]
pub struct CurrencyPair<AssetId> {
/// See [Base Currency](https://www.investopedia.com/terms/b/basecurrency.asp)
pub base: AssetId,
/// counter currency
pub quote: AssetId,
}

impl<AssetId: PartialEq> CurrencyPair<AssetId> {
pub fn is_valid(&self) -> bool {
self.base != self.quote
}
}

/// type parameters for traits in pure defi area
pub trait DeFiEngine {
/// The asset ID type
type AssetId: AssetIdLike;
/// The balance type of an account
type Balance: BalanceLike;
/// The user account identifier type for the runtime
type AccountId;
}

/// take nothing
impl<Balance: Default> Default for Take<Balance> {
fn default() -> Self {
Self { amount: Default::default(), limit: Default::default() }
}
}

impl<AssetId: Default> Default for CurrencyPair<AssetId> {
fn default() -> Self {
Self { base: Default::default(), quote: Default::default() }
}
}

/// default sale is no sale and invalid sale
impl<AssetId: Default, Balance: Default> Default for Sell<AssetId, Balance> {
fn default() -> Self {
Self { pair: Default::default(), take: Default::default() }
}
}

/// order is something that lives some some time until taken
pub trait OrderIdLike:
FullCodec + Copy + Eq + PartialEq + sp_std::fmt::Debug + TypeInfo + sp_std::hash::Hash + Default
{
}
impl<
T: FullCodec
+ Copy
+ Eq
+ PartialEq
+ sp_std::fmt::Debug
+ TypeInfo
+ sp_std::hash::Hash
+ Default,
> OrderIdLike for T
{
}

pub trait SellEngine<Configuration>: DeFiEngine {
type OrderId: OrderIdLike;
/// sell base asset for price given or higher
/// - `from_to` - account requesting sell
fn ask(
from_to: &Self::AccountId,
order: Sell<Self::AssetId, Self::Balance>,
configuration: Configuration,
) -> Result<Self::OrderId, DispatchError>;
/// take order. get not found error if order never existed or was removed.
/// - `take.limit` - for `sell` order it is maximal value are you to pay for `base` in `quote`
/// asset, for `buy`
/// order it is minimal value you are eager to accept for `base`
/// - `take.amount` - amount of
/// `base` you are ready to exchange for this order
fn take(
from_to: &Self::AccountId,
order_id: Self::OrderId,
take: Take<Self::Balance>,
) -> Result<(), DispatchError>;
}

pub trait DeFiComposableConfig: frame_system::Config {
// what.
type AssetId: AssetIdLike + MaybeSerializeDeserialize + Default;

type Balance: BalanceLike
+ Default
+ Parameter
+ Codec
+ Copy
+ Ord
+ CheckedAdd
+ CheckedSub
+ CheckedMul
+ CheckedSub
+ From<u64> // at least 64 bit
+ Zero
+ FixedPointOperand
+ Into<LiftedFixedBalance> // integer part not more than bits in this
+ Into<u128>; // cannot do From<u128>, until LiftedFixedBalance integer part is larger than 128
// bit
}

0 comments on commit b46e402

Please sign in to comment.