Skip to content

Commit

Permalink
program: derive auction for crossing limit with no duration (#802)
Browse files Browse the repository at this point in the history
* program: derive auction for crossing limit with no duration

* modify order should default to setting auction params as empty so place can derive auction

* tweak modify order again

* tweaks

* add tests
  • Loading branch information
crispheaney committed Dec 29, 2023
1 parent 1c1714d commit e9ac408
Show file tree
Hide file tree
Showing 5 changed files with 380 additions and 11 deletions.
28 changes: 18 additions & 10 deletions programs/drift/src/controller/orders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ pub fn place_perp_order(
spot_market_map: &SpotMarketMap,
oracle_map: &mut OracleMap,
clock: &Clock,
params: OrderParams,
mut params: OrderParams,
mut options: PlaceOrderOptions,
) -> DriftResult {
let now = clock.unix_timestamp;
Expand Down Expand Up @@ -220,6 +220,10 @@ pub fn place_perp_order(
};

let oracle_price_data = oracle_map.get_price_data(&market.amm.oracle)?;

// updates auction params for crossing limit orders w/out auction duration
params.update_perp_auction_params(market, oracle_price_data.price)?;

let (auction_start_price, auction_end_price, auction_duration) = get_auction_params(
&params,
oracle_price_data,
Expand Down Expand Up @@ -813,15 +817,19 @@ fn merge_modify_order_params_with_existing_order(
let oracle_price_offset = modify_order_params
.oracle_price_offset
.or(Some(existing_order.oracle_price_offset));
let auction_duration = modify_order_params
.auction_duration
.or(Some(existing_order.auction_duration));
let auction_start_price = modify_order_params
.auction_start_price
.or(Some(existing_order.auction_start_price));
let auction_end_price = modify_order_params
.auction_end_price
.or(Some(existing_order.auction_end_price));
let (auction_duration, auction_start_price, auction_end_price) =
if modify_order_params.auction_duration.is_some()
&& modify_order_params.auction_start_price.is_some()
&& modify_order_params.auction_end_price.is_some()
{
(
modify_order_params.auction_duration,
modify_order_params.auction_start_price,
modify_order_params.auction_end_price,
)
} else {
(None, None, None)
};

Ok(OrderParams {
order_type,
Expand Down
81 changes: 80 additions & 1 deletion programs/drift/src/state/order_params.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
use crate::controller::position::PositionDirection;
use crate::error::DriftResult;
use crate::math::casting::Cast;
use crate::math::safe_math::SafeMath;
use crate::state::perp_market::PerpMarket;
use crate::state::user::{MarketType, OrderTriggerCondition, OrderType};
use crate::PERCENTAGE_PRECISION_U64;
use anchor_lang::prelude::*;
use borsh::{BorshDeserialize, BorshSerialize};
use std::ops::Div;

#[derive(AnchorSerialize, AnchorDeserialize, Clone, Default, Copy)]
#[cfg(test)]
mod tests;

#[derive(AnchorSerialize, AnchorDeserialize, Clone, Default, Copy, Eq, PartialEq, Debug)]
pub struct OrderParams {
pub order_type: OrderType,
pub market_type: MarketType,
Expand All @@ -24,6 +33,76 @@ pub struct OrderParams {
pub auction_end_price: Option<i64>,
}

impl OrderParams {
pub fn update_perp_auction_params(
&mut self,
perp_market: &PerpMarket,
oracle_price: i64,
) -> DriftResult {
if self.order_type != OrderType::Limit {
return Ok(());
}

if self.auction_duration.is_some() {
return Ok(());
}

if self.post_only != PostOnlyParam::None {
return Ok(());
}

if self.immediate_or_cancel {
return Ok(());
}

if self.oracle_price_offset.is_some() || self.price == 0 {
return Ok(());
}

match self.direction {
PositionDirection::Long => {
let ask_premium = perp_market.amm.last_ask_premium()?;
let est_ask = oracle_price.safe_add(ask_premium)?.cast()?;
if self.price > est_ask {
let auction_duration =
get_auction_duration(self.price.safe_sub(est_ask)?, est_ask)?;
let auction_start_price = est_ask as i64;
let auction_end_price = self.price as i64;
msg!("derived auction params for limit order. duration = {} start_price = {} end_price = {}", auction_duration, auction_start_price, auction_end_price);
self.auction_duration = Some(auction_duration);
self.auction_start_price = Some(auction_start_price);
self.auction_end_price = Some(auction_end_price);
}
}
PositionDirection::Short => {
let bid_discount = perp_market.amm.last_bid_discount()?;
let est_bid = oracle_price.safe_sub(bid_discount)?.cast()?;
if self.price < est_bid {
let auction_duration =
get_auction_duration(est_bid.safe_sub(self.price)?, est_bid)?;
let auction_start_price = est_bid as i64;
let auction_end_price = self.price as i64;
msg!("derived auction params for limit order. duration = {} start_price = {} end_price = {}", auction_duration, auction_start_price, auction_end_price);
self.auction_duration = Some(auction_duration);
self.auction_start_price = Some(auction_start_price);
self.auction_end_price = Some(auction_end_price);
}
}
}

Ok(())
}
}

fn get_auction_duration(price_diff: u64, price: u64) -> DriftResult<u8> {
let percent_diff = price_diff.safe_mul(PERCENTAGE_PRECISION_U64)?.div(price);

Ok(percent_diff
.safe_mul(60)?
.safe_div_ceil(PERCENTAGE_PRECISION_U64 / 100)? // 1% = 60 seconds
.clamp(10, 60) as u8)
}

#[derive(Clone, Copy, BorshSerialize, BorshDeserialize, PartialEq, Debug, Eq)]
pub enum PostOnlyParam {
None,
Expand Down
223 changes: 223 additions & 0 deletions programs/drift/src/state/order_params/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
mod get_auction_duration {
use crate::state::order_params::get_auction_duration;
use crate::PRICE_PRECISION_U64;

#[test]
fn test() {
let price_diff = 0;
let price = 100 * PRICE_PRECISION_U64;

let duration = get_auction_duration(price_diff, price).unwrap();
assert_eq!(duration, 10);

let price_diff = PRICE_PRECISION_U64 / 10;
let price = 100 * PRICE_PRECISION_U64;

let duration = get_auction_duration(price_diff, price).unwrap();
assert_eq!(duration, 10);

let price_diff = PRICE_PRECISION_U64 / 2;
let price = 100 * PRICE_PRECISION_U64;

let duration = get_auction_duration(price_diff, price).unwrap();
assert_eq!(duration, 30);

let price_diff = PRICE_PRECISION_U64;
let price = 100 * PRICE_PRECISION_U64;

let duration = get_auction_duration(price_diff, price).unwrap();
assert_eq!(duration, 60);

let price_diff = 2 * PRICE_PRECISION_U64;
let price = 100 * PRICE_PRECISION_U64;

let duration = get_auction_duration(price_diff, price).unwrap();
assert_eq!(duration, 60);
}
}

mod update_perp_auction_params {
use crate::state::order_params::PostOnlyParam;
use crate::state::perp_market::{PerpMarket, AMM};
use crate::state::user::OrderType;
use crate::{
OrderParams, PositionDirection, AMM_RESERVE_PRECISION, BID_ASK_SPREAD_PRECISION,
PEG_PRECISION, PRICE_PRECISION_I64, PRICE_PRECISION_U64,
};

#[test]
fn test() {
let oracle_price = 100 * PRICE_PRECISION_I64;
let mut amm = AMM {
base_asset_reserve: 100 * AMM_RESERVE_PRECISION,
quote_asset_reserve: 100 * AMM_RESERVE_PRECISION,
short_spread: (BID_ASK_SPREAD_PRECISION / 100) as u32,
long_spread: (BID_ASK_SPREAD_PRECISION / 100) as u32,
sqrt_k: 100 * AMM_RESERVE_PRECISION,
peg_multiplier: 100 * PEG_PRECISION,
..AMM::default()
};
amm.historical_oracle_data.last_oracle_price = oracle_price;
let perp_market = PerpMarket {
amm,
..PerpMarket::default()
};

let order_params_before = OrderParams {
order_type: OrderType::Market,
..OrderParams::default()
};
let mut order_params_after = order_params_before;
order_params_after
.update_perp_auction_params(&perp_market, oracle_price)
.unwrap();
assert_eq!(order_params_before, order_params_after);

let order_params_before = OrderParams {
order_type: OrderType::Limit,
auction_duration: Some(0),
..OrderParams::default()
};
let mut order_params_after = order_params_before;
order_params_after
.update_perp_auction_params(&perp_market, oracle_price)
.unwrap();
assert_eq!(order_params_before, order_params_after);

let order_params_before = OrderParams {
order_type: OrderType::Limit,
auction_duration: None,
post_only: PostOnlyParam::MustPostOnly,
..OrderParams::default()
};
let mut order_params_after = order_params_before;
order_params_after
.update_perp_auction_params(&perp_market, oracle_price)
.unwrap();
assert_eq!(order_params_before, order_params_after);

let order_params_before = OrderParams {
order_type: OrderType::Limit,
auction_duration: None,
post_only: PostOnlyParam::None,
immediate_or_cancel: true,
..OrderParams::default()
};
let mut order_params_after = order_params_before;
order_params_after
.update_perp_auction_params(&perp_market, oracle_price)
.unwrap();
assert_eq!(order_params_before, order_params_after);

let order_params_before = OrderParams {
order_type: OrderType::Limit,
auction_duration: None,
post_only: PostOnlyParam::None,
immediate_or_cancel: false,
oracle_price_offset: Some(0),
..OrderParams::default()
};
let mut order_params_after = order_params_before;
order_params_after
.update_perp_auction_params(&perp_market, oracle_price)
.unwrap();
assert_eq!(order_params_before, order_params_after);

let order_params_before = OrderParams {
order_type: OrderType::Limit,
auction_duration: None,
post_only: PostOnlyParam::None,
immediate_or_cancel: false,
oracle_price_offset: None,
price: 0,
..OrderParams::default()
};
let mut order_params_after = order_params_before;
order_params_after
.update_perp_auction_params(&perp_market, oracle_price)
.unwrap();
assert_eq!(order_params_before, order_params_after);

let order_params_before = OrderParams {
order_type: OrderType::Limit,
auction_duration: None,
post_only: PostOnlyParam::None,
immediate_or_cancel: false,
oracle_price_offset: None,
price: 100 * PRICE_PRECISION_U64,
direction: PositionDirection::Long,
..OrderParams::default()
};
let mut order_params_after = order_params_before;
order_params_after
.update_perp_auction_params(&perp_market, oracle_price)
.unwrap();
assert_eq!(order_params_before, order_params_after);

let order_params_before = OrderParams {
order_type: OrderType::Limit,
auction_duration: None,
post_only: PostOnlyParam::None,
immediate_or_cancel: false,
oracle_price_offset: None,
price: 102 * PRICE_PRECISION_U64,
direction: PositionDirection::Long,
..OrderParams::default()
};
let mut order_params_after = order_params_before;
order_params_after
.update_perp_auction_params(&perp_market, oracle_price)
.unwrap();
assert_ne!(order_params_before, order_params_after);
assert_eq!(order_params_after.auction_duration, Some(60));
assert_eq!(
order_params_after.auction_start_price,
Some(101 * PRICE_PRECISION_I64)
);
assert_eq!(
order_params_after.auction_end_price,
Some(102 * PRICE_PRECISION_I64)
);

let order_params_before = OrderParams {
order_type: OrderType::Limit,
auction_duration: None,
post_only: PostOnlyParam::None,
immediate_or_cancel: false,
oracle_price_offset: None,
price: 100 * PRICE_PRECISION_U64,
direction: PositionDirection::Short,
..OrderParams::default()
};
let mut order_params_after = order_params_before;
order_params_after
.update_perp_auction_params(&perp_market, oracle_price)
.unwrap();
assert_eq!(order_params_before, order_params_after);

let order_params_before = OrderParams {
order_type: OrderType::Limit,
auction_duration: None,
post_only: PostOnlyParam::None,
immediate_or_cancel: false,
oracle_price_offset: None,
price: 98 * PRICE_PRECISION_U64,
direction: PositionDirection::Short,
..OrderParams::default()
};
let mut order_params_after = order_params_before;
order_params_after
.update_perp_auction_params(&perp_market, oracle_price)
.unwrap();
assert_ne!(order_params_before, order_params_after);
assert_eq!(order_params_after.auction_duration, Some(60));
assert_eq!(
order_params_after.auction_start_price,
Some(99 * PRICE_PRECISION_I64)
);
assert_eq!(
order_params_after.auction_end_price,
Some(98 * PRICE_PRECISION_I64)
);
}
}
Loading

0 comments on commit e9ac408

Please sign in to comment.