Skip to content

Commit

Permalink
program: trigger limits cant make if limit crosses trigger (#707)
Browse files Browse the repository at this point in the history
* program: add auction params to trigger limits

* trigger limits cant make when price crosses trigger price

* update sdk is_resting_limit_order

* better tests

* add test for sdk
  • Loading branch information
crispheaney authored Nov 22, 2023
1 parent 174dcfe commit dd5607f
Show file tree
Hide file tree
Showing 8 changed files with 470 additions and 42 deletions.
79 changes: 41 additions & 38 deletions programs/drift/src/controller/orders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ use crate::get_struct_values;
use crate::get_then_update_id;
use crate::load_mut;
use crate::math::amm_jit::calculate_amm_jit_liquidity;
use crate::math::auction::calculate_auction_prices;
use crate::math::auction::{calculate_auction_params_for_trigger_order, calculate_auction_prices};
use crate::math::casting::Cast;
use crate::math::constants::{
BASE_PRECISION_U64, FEE_POOL_TO_REVENUE_POOL_THRESHOLD, FIVE_MINUTE, ONE_HOUR, PERP_DECIMALS,
Expand Down Expand Up @@ -2491,28 +2491,16 @@ pub fn trigger_order(
.worst_case_base_asset_amount()?;

{
update_trigger_order_params(
&mut user.orders[order_index],
oracle_price_data,
slot,
state.min_perp_auction_duration,
)?;

let direction = user.orders[order_index].direction;
let base_asset_amount = user.orders[order_index].base_asset_amount;

user.orders[order_index].trigger_condition =
match user.orders[order_index].trigger_condition {
OrderTriggerCondition::Above => OrderTriggerCondition::TriggeredAbove,
OrderTriggerCondition::Below => OrderTriggerCondition::TriggeredBelow,
_ => {
return Err(print_error!(ErrorCode::InvalidTriggerOrderCondition)());
}
};

user.orders[order_index].slot = slot;
let order_type = user.orders[order_index].order_type;
if let OrderType::TriggerMarket = order_type {
user.orders[order_index].auction_duration = state.min_perp_auction_duration;
let (auction_start_price, auction_end_price) =
calculate_auction_prices(oracle_price_data, direction, 0)?;
user.orders[order_index].auction_start_price = auction_start_price;
user.orders[order_index].auction_end_price = auction_end_price;
}

let user_position = user.get_perp_position_mut(market_index)?;
increase_open_bids_and_asks(user_position, &direction, base_asset_amount)?;
}
Expand Down Expand Up @@ -2592,6 +2580,32 @@ pub fn trigger_order(
Ok(())
}

fn update_trigger_order_params(
order: &mut Order,
oracle_price_data: &OraclePriceData,
slot: u64,
min_auction_duration: u8,
) -> DriftResult {
order.trigger_condition = match order.trigger_condition {
OrderTriggerCondition::Above => OrderTriggerCondition::TriggeredAbove,
OrderTriggerCondition::Below => OrderTriggerCondition::TriggeredBelow,
_ => {
return Err(print_error!(ErrorCode::InvalidTriggerOrderCondition)());
}
};

order.slot = slot;

let (auction_duration, auction_start_price, auction_end_price) =
calculate_auction_params_for_trigger_order(order, oracle_price_data, min_auction_duration)?;

order.auction_duration = auction_duration;
order.auction_start_price = auction_start_price;
order.auction_end_price = auction_end_price;

Ok(())
}

pub fn force_cancel_orders(
state: &State,
user: &AccountLoader<User>,
Expand Down Expand Up @@ -4452,27 +4466,16 @@ pub fn trigger_spot_order(
)?;

{
update_trigger_order_params(
&mut user.orders[order_index],
oracle_price_data,
slot,
state.default_spot_auction_duration,
)?;

let direction = user.orders[order_index].direction;
let base_asset_amount = user.orders[order_index].base_asset_amount;

user.orders[order_index].trigger_condition =
match user.orders[order_index].trigger_condition {
OrderTriggerCondition::Above => OrderTriggerCondition::TriggeredAbove,
OrderTriggerCondition::Below => OrderTriggerCondition::TriggeredBelow,
_ => {
return Err(print_error!(ErrorCode::InvalidTriggerOrderCondition)());
}
};
user.orders[order_index].slot = slot;
let order_type = user.orders[order_index].order_type;
if let OrderType::TriggerMarket = order_type {
user.orders[order_index].auction_duration = state.default_spot_auction_duration;
let (auction_start_price, auction_end_price) =
calculate_auction_prices(oracle_price_data, direction, 0)?;
user.orders[order_index].auction_start_price = auction_start_price;
user.orders[order_index].auction_end_price = auction_end_price;
}

let user_position = user.force_get_spot_position_mut(market_index)?;
increase_spot_open_bids_and_asks(user_position, &direction, base_asset_amount.cast()?)?;
}
Expand Down
77 changes: 77 additions & 0 deletions programs/drift/src/controller/orders/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9942,3 +9942,80 @@ pub mod get_maker_orders_info {
assert_eq!(maker_order_price_and_indexes.len(), 64);
}
}

pub mod update_trigger_order_params {
use crate::controller::orders::update_trigger_order_params;
use crate::state::oracle::OraclePriceData;
use crate::state::user::{Order, OrderTriggerCondition, OrderType};
use crate::{PositionDirection, PRICE_PRECISION_I64, PRICE_PRECISION_U64};

#[test]
fn test() {
let mut order = Order {
order_type: OrderType::TriggerMarket,
direction: PositionDirection::Long,
trigger_condition: OrderTriggerCondition::Above,
..Order::default()
};
let oracle_price_data = OraclePriceData {
price: 100 * PRICE_PRECISION_I64,
confidence: 100 * PRICE_PRECISION_U64,
..OraclePriceData::default()
};
let slot = 10;
let min_auction_duration = 10;

update_trigger_order_params(&mut order, &oracle_price_data, slot, min_auction_duration)
.unwrap();

assert_eq!(order.slot, slot);
assert_eq!(order.auction_duration, min_auction_duration);
assert_eq!(
order.trigger_condition,
OrderTriggerCondition::TriggeredAbove
);
assert_eq!(order.auction_start_price, 100000000);
assert_eq!(order.auction_end_price, 100500000);

let mut order = Order {
order_type: OrderType::TriggerMarket,
direction: PositionDirection::Short,
trigger_condition: OrderTriggerCondition::Below,
..Order::default()
};

update_trigger_order_params(&mut order, &oracle_price_data, slot, min_auction_duration)
.unwrap();

assert_eq!(order.slot, slot);
assert_eq!(order.auction_duration, min_auction_duration);
assert_eq!(
order.trigger_condition,
OrderTriggerCondition::TriggeredBelow
);
assert_eq!(order.auction_start_price, 100000000);
assert_eq!(order.auction_end_price, 99500000);

let mut order = Order {
order_type: OrderType::TriggerMarket,
direction: PositionDirection::Short,
trigger_condition: OrderTriggerCondition::TriggeredAbove,
..Order::default()
};

let err =
update_trigger_order_params(&mut order, &oracle_price_data, slot, min_auction_duration);
assert!(err.is_err());

let mut order = Order {
order_type: OrderType::TriggerMarket,
direction: PositionDirection::Short,
trigger_condition: OrderTriggerCondition::TriggeredBelow,
..Order::default()
};

let err =
update_trigger_order_params(&mut order, &oracle_price_data, slot, min_auction_duration);
assert!(err.is_err());
}
}
25 changes: 25 additions & 0 deletions programs/drift/src/math/auction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,3 +211,28 @@ pub fn is_amm_available_liquidity_source(
) -> DriftResult<bool> {
is_auction_complete(order.slot, min_auction_duration, slot)
}

pub fn calculate_auction_params_for_trigger_order(
order: &Order,
oracle_price_data: &OraclePriceData,
min_auction_duration: u8,
) -> DriftResult<(u8, i64, i64)> {
// if trigger limit doesn't cross oracle, no auction
if order.order_type == OrderType::TriggerLimit {
let limit_doesnt_cross_trigger = match order.direction {
PositionDirection::Long => order.price < order.trigger_price,
PositionDirection::Short => order.price > order.trigger_price,
};

if limit_doesnt_cross_trigger {
return Ok((0, 0, 0));
}
}

let auction_duration = min_auction_duration;

let (auction_start_price, auction_end_price) =
calculate_auction_prices(oracle_price_data, order.direction, order.price)?;

Ok((auction_duration, auction_start_price, auction_end_price))
}
118 changes: 118 additions & 0 deletions programs/drift/src/math/auction/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -371,3 +371,121 @@ mod calculate_auction_price {
assert_eq!(price, 3 * PRICE_PRECISION_U64 / 2);
}
}

mod calculate_auction_params_for_trigger_order {
use crate::math::auction::calculate_auction_params_for_trigger_order;
use crate::state::oracle::OraclePriceData;
use crate::state::user::{Order, OrderType};
use crate::{PositionDirection, PRICE_PRECISION_I64, PRICE_PRECISION_U64};

#[test]
fn trigger_limit() {
let mut order = Order {
order_type: OrderType::TriggerLimit,
direction: PositionDirection::Long,
trigger_price: 100 * PRICE_PRECISION_U64,
price: 90 * PRICE_PRECISION_U64,
..Order::default()
};
let oracle_price_data = OraclePriceData {
price: 100 * PRICE_PRECISION_I64,
..OraclePriceData::default()
};
let min_auction_duration = 10;

let (auction_duration, auction_start_price, auction_end_price) =
calculate_auction_params_for_trigger_order(
&order,
&oracle_price_data,
min_auction_duration,
)
.unwrap();
assert_eq!(auction_duration, 0);
assert_eq!(auction_start_price, 0);
assert_eq!(auction_end_price, 0);

order.direction = PositionDirection::Short;
order.price = 110 * PRICE_PRECISION_U64;

let (auction_duration, auction_start_price, auction_end_price) =
calculate_auction_params_for_trigger_order(
&order,
&oracle_price_data,
min_auction_duration,
)
.unwrap();
assert_eq!(auction_duration, 0);
assert_eq!(auction_start_price, 0);
assert_eq!(auction_end_price, 0);

order.direction = PositionDirection::Long;
order.price = 110 * PRICE_PRECISION_U64;

let (auction_duration, auction_start_price, auction_end_price) =
calculate_auction_params_for_trigger_order(
&order,
&oracle_price_data,
min_auction_duration,
)
.unwrap();
assert_eq!(auction_duration, 10);
assert_eq!(auction_start_price, 100000000);
assert_eq!(auction_end_price, 100500000);

order.direction = PositionDirection::Short;
order.price = 90 * PRICE_PRECISION_U64;

let (auction_duration, auction_start_price, auction_end_price) =
calculate_auction_params_for_trigger_order(
&order,
&oracle_price_data,
min_auction_duration,
)
.unwrap();

assert_eq!(auction_duration, 10);
assert_eq!(auction_start_price, 100000000);
assert_eq!(auction_end_price, 99500000);
}

#[test]
fn trigger_market() {
let mut order = Order {
order_type: OrderType::TriggerMarket,
direction: PositionDirection::Long,
trigger_price: 100 * PRICE_PRECISION_U64,
..Order::default()
};
let oracle_price_data = OraclePriceData {
price: 100 * PRICE_PRECISION_I64,
..OraclePriceData::default()
};
let min_auction_duration = 10;

let (auction_duration, auction_start_price, auction_end_price) =
calculate_auction_params_for_trigger_order(
&order,
&oracle_price_data,
min_auction_duration,
)
.unwrap();

assert_eq!(auction_duration, 10);
assert_eq!(auction_start_price, 100000000);
assert_eq!(auction_end_price, 100500000);

order.direction = PositionDirection::Short;

let (auction_duration, auction_start_price, auction_end_price) =
calculate_auction_params_for_trigger_order(
&order,
&oracle_price_data,
min_auction_duration,
)
.unwrap();

assert_eq!(auction_duration, 10);
assert_eq!(auction_start_price, 100000000);
assert_eq!(auction_end_price, 99500000);
}
}
18 changes: 17 additions & 1 deletion programs/drift/src/state/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1183,7 +1183,23 @@ impl Order {
}

pub fn is_resting_limit_order(&self, slot: u64) -> DriftResult<bool> {
Ok(self.is_limit_order() && (self.post_only || self.is_auction_complete(slot)?))
if !self.is_limit_order() {
return Ok(false);
}

if self.order_type == OrderType::TriggerLimit {
return match self.direction {
PositionDirection::Long if self.trigger_price < self.price => {
return Ok(false);
}
PositionDirection::Short if self.trigger_price > self.price => {
return Ok(false);
}
_ => self.is_auction_complete(slot),
};
}

Ok(self.post_only || self.is_auction_complete(slot)?)
}
}

Expand Down
Loading

0 comments on commit dd5607f

Please sign in to comment.