Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

program: try to match against multiple of makers orders #316

Merged
merged 9 commits into from
Jan 11, 2023
251 changes: 127 additions & 124 deletions programs/drift/src/controller/orders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -613,7 +613,7 @@ pub fn fill_perp_order(
filler_stats: &AccountLoader<UserStats>,
maker: Option<&AccountLoader<User>>,
maker_stats: Option<&AccountLoader<UserStats>>,
maker_order_id: Option<u32>,
_maker_order_id: Option<u32>,
referrer: Option<&AccountLoader<User>>,
referrer_stats: Option<&AccountLoader<UserStats>>,
clock: &Clock,
Expand Down Expand Up @@ -743,13 +743,12 @@ pub fn fill_perp_order(
(None, None)
};

let (mut maker, mut maker_stats, maker_key, maker_order_index) = sanitize_maker_order(
let (mut maker, mut maker_stats, maker_key, maker_order_indexes) = sanitize_maker_order(
perp_market_map,
spot_market_map,
oracle_map,
maker,
maker_stats,
maker_order_id,
&user_key,
&user.authority,
&user.orders[order_index],
Expand Down Expand Up @@ -814,7 +813,7 @@ pub fn fill_perp_order(
user_stats,
&mut maker.as_deref_mut(),
&mut maker_stats.as_deref_mut(),
maker_order_index,
maker_order_indexes,
maker_key.as_ref(),
&mut filler.as_deref_mut(),
&filler_key,
Expand Down Expand Up @@ -986,7 +985,6 @@ fn sanitize_maker_order<'a>(
oracle_map: &mut OracleMap,
maker: Option<&'a AccountLoader<User>>,
maker_stats: Option<&'a AccountLoader<UserStats>>,
maker_order_id: Option<u32>,
taker_key: &Pubkey,
taker_authority: &Pubkey,
taker_order: &Order,
Expand All @@ -1000,7 +998,7 @@ fn sanitize_maker_order<'a>(
Option<RefMut<'a, User>>,
Option<RefMut<'a, UserStats>>,
Option<Pubkey>,
Option<usize>,
Option<Vec<(usize, u64)>>,
)> {
if maker.is_none() || maker_stats.is_none() {
return Ok((None, None, None, None));
Expand All @@ -1027,129 +1025,113 @@ fn sanitize_maker_order<'a>(
Some(maker_stats)
};

if maker.is_being_liquidated() || maker.is_bankrupt() {
return Ok((None, None, None, None));
}

let market = perp_market_map.get_ref(&taker_order.market_index)?;

let maker_order_id = maker_order_id.ok_or(ErrorCode::MakerOrderNotFound)?;
let maker_order_index = match maker.get_order_index(maker_order_id) {
Ok(order_index) => order_index,
Err(_) => {
msg!("Maker has no order id {}", maker_order_id);
let fallback_order_index = find_fallback_maker_order(
&maker,
match taker_order.direction {
PositionDirection::Long => &PositionDirection::Short,
PositionDirection::Short => &PositionDirection::Long,
},
&MarketType::Perp,
taker_order.market_index,
Some(oracle_price),
slot,
market.amm.order_tick_size,
)?;
let mut maker_order_indexes = find_maker_orders(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maker_order_indexes do we wanna rename this since its the price and index?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah can do

&maker,
match taker_order.direction {
PositionDirection::Long => &PositionDirection::Short,
PositionDirection::Short => &PositionDirection::Long,
},
&MarketType::Perp,
taker_order.market_index,
Some(oracle_price),
slot,
market.amm.order_tick_size,
)?;

if let Some(order_index) = fallback_order_index {
msg!(
"Using fallback maker order id {}",
maker.orders[order_index].order_id
);
order_index
} else {
return Ok((None, None, None, None));
}
}
};
drop(market);

sort_maker_orders(&mut maker_order_indexes, taker_order.direction);

let mut filtered_maker_order_indexes = vec![];
for (maker_order_index, maker_order_price) in maker_order_indexes.iter() {
let maker_order_index = *maker_order_index;
let maker_order_price = *maker_order_price;

{
let maker_order = &maker.orders[maker_order_index];
if !is_maker_for_taker(maker_order, taker_order)? {
msg!("maker cant make for taker");
return Ok((None, None, None, None));
continue;
}

if !are_orders_same_market_but_different_sides(maker_order, taker_order) {
msg!("maker is not same market but different side for taker");
return Ok((None, None, None, None));
continue;
}

if maker.is_being_liquidated() || maker.is_bankrupt() {
return Ok((None, None, None, None));
}
let market = perp_market_map.get_ref(&taker_order.market_index)?;

validate!(
!maker_order.must_be_triggered() || maker_order.triggered(),
ErrorCode::OrderMustBeTriggeredFirst,
"Maker order not triggered"
)?;
let breaches_oracle_price_limits = {
limit_price_breaches_oracle_price_bands(
maker_order_price,
maker_order.direction,
oracle_price,
market.margin_ratio_initial,
market.margin_ratio_maintenance,
)?
};

validate!(
maker_order.market_type == MarketType::Perp,
ErrorCode::InvalidOrderMarketType,
"Maker order not a perp order"
)?
}
drop(market);

let breaches_oracle_price_limits = {
order_breaches_oracle_price_limits(
&maker.orders[maker_order_index],
oracle_price,
slot,
market.amm.order_tick_size,
market.margin_ratio_initial,
market.margin_ratio_maintenance,
)?
};
let should_expire_order = should_expire_order(&maker, maker_order_index, now)?;

drop(market);
let existing_base_asset_amount = maker
.get_perp_position(maker.orders[maker_order_index].market_index)?
.base_asset_amount;
let should_cancel_reduce_only_order = should_cancel_reduce_only_order(
&maker.orders[maker_order_index],
existing_base_asset_amount,
)?;

let should_expire_order = should_expire_order(&maker, maker_order_index, now)?;
if breaches_oracle_price_limits || should_expire_order || should_cancel_reduce_only_order {
let filler_reward = {
let mut market =
perp_market_map.get_ref_mut(&maker.orders[maker_order_index].market_index)?;
pay_keeper_flat_reward_for_perps(
&mut maker,
filler.as_deref_mut(),
market.deref_mut(),
filler_reward,
)?
};

let existing_base_asset_amount = maker
.get_perp_position(maker.orders[maker_order_index].market_index)?
.base_asset_amount;
let should_cancel_reduce_only_order = should_cancel_reduce_only_order(
&maker.orders[maker_order_index],
existing_base_asset_amount,
)?;
let explanation = if breaches_oracle_price_limits {
OrderActionExplanation::OraclePriceBreachedLimitPrice
} else if should_expire_order {
OrderActionExplanation::OrderExpired
} else {
OrderActionExplanation::ReduceOnlyOrderIncreasedPosition
};

// Dont fulfill with a maker order if oracle has diverged significantly, it should be expired or it is position increasing reduce only
if breaches_oracle_price_limits || should_expire_order || should_cancel_reduce_only_order {
let filler_reward = {
let mut market =
perp_market_map.get_ref_mut(&maker.orders[maker_order_index].market_index)?;
pay_keeper_flat_reward_for_perps(
&mut maker,
filler.as_deref_mut(),
market.deref_mut(),
cancel_order(
maker_order_index,
maker.deref_mut(),
&maker_key,
perp_market_map,
spot_market_map,
oracle_map,
now,
slot,
explanation,
Some(filler_key),
filler_reward,
)?
};
false,
)?;

let explanation = if breaches_oracle_price_limits {
OrderActionExplanation::OraclePriceBreachedLimitPrice
} else if should_expire_order {
OrderActionExplanation::OrderExpired
} else {
OrderActionExplanation::ReduceOnlyOrderIncreasedPosition
};
continue;
}

cancel_order(
maker_order_index,
maker.deref_mut(),
&maker_key,
perp_market_map,
spot_market_map,
oracle_map,
now,
slot,
explanation,
Some(filler_key),
filler_reward,
false,
)?;
filtered_maker_order_indexes.push((maker_order_index, maker_order_price));
}

if filtered_maker_order_indexes.is_empty() {
return Ok((None, None, None, None));
}

let market_index = maker.orders[maker_order_index].market_index;
let market_index = taker_order.market_index;
settle_funding_payment(
&mut maker,
&maker_key,
Expand All @@ -1161,10 +1143,21 @@ fn sanitize_maker_order<'a>(
Some(maker),
maker_stats,
Some(maker_key),
Some(maker_order_index),
Some(filtered_maker_order_indexes),
))
}

#[inline(always)]
fn sort_maker_orders(
maker_order_indexes: &mut [(usize, u64)],
taker_order_direction: PositionDirection,
) {
maker_order_indexes.sort_by(|a, b| match taker_order_direction {
PositionDirection::Long => a.1.cmp(&b.1),
PositionDirection::Short => b.1.cmp(&a.1),
});
}

#[allow(clippy::type_complexity)]
fn sanitize_referrer<'a>(
referrer: Option<&'a AccountLoader<User>>,
Expand Down Expand Up @@ -1211,7 +1204,7 @@ fn fulfill_perp_order(
user_stats: &mut UserStats,
maker: &mut Option<&mut User>,
maker_stats: &mut Option<&mut UserStats>,
maker_order_index: Option<usize>,
maker_order_indexes: Option<Vec<(usize, u64)>>,
maker_key: Option<&Pubkey>,
filler: &mut Option<&mut User>,
filler_key: &Pubkey,
Expand All @@ -1236,23 +1229,19 @@ fn fulfill_perp_order(
let user_order_risk_decreasing =
determine_if_user_order_is_risk_decreasing(user, market_index, user_order_index)?;

let maker_order_risk_decreasing =
if let (Some(maker), Some(maker_order_index)) = (maker.as_ref(), maker_order_index) {
determine_if_user_order_is_risk_decreasing(maker, market_index, maker_order_index)?
} else {
false
};
let maker_base_asset_amount_before = if let Some(maker) = maker {
let maker_position_index = get_position_index(&maker.perp_positions, market_index)?;
maker.perp_positions[maker_position_index].base_asset_amount
} else {
0
};

let fulfillment_methods = {
let market = perp_market_map.get_ref(&market_index)?;

determine_perp_fulfillment_methods(
&user.orders[user_order_index],
if let Some(maker) = maker {
Some(&maker.orders[maker_order_index.safe_unwrap()?])
} else {
None
},
&maker_order_indexes.as_ref(),
&market.amm,
reserve_price_before,
valid_oracle_price,
Expand Down Expand Up @@ -1299,15 +1288,15 @@ fn fulfill_perp_order(
*maker_price,
true,
)?,
PerpFulfillmentMethod::Match => fulfill_perp_order_with_match(
PerpFulfillmentMethod::Match(maker_order_index) => fulfill_perp_order_with_match(
market.deref_mut(),
user,
user_stats,
user_order_index,
user_key,
maker.as_deref_mut().safe_unwrap()?,
maker_stats,
maker_order_index.safe_unwrap()?,
*maker_order_index,
maker_key.safe_unwrap()?,
filler,
filler_stats,
Expand All @@ -1324,8 +1313,8 @@ fn fulfill_perp_order(
)?,
};

if fulfillment_method == &PerpFulfillmentMethod::Match && fill_base_asset_amount != 0 {
maker_filled = true;
if let PerpFulfillmentMethod::Match(_) = fulfillment_method {
maker_filled = fill_base_asset_amount != 0;
}

base_asset_amount = base_asset_amount.safe_add(fill_base_asset_amount)?;
Expand All @@ -1339,6 +1328,20 @@ fn fulfill_perp_order(
emit!(order_record)
}

let maker_risk_reducing = if let Some(maker) = maker {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wonder if checking before/after reduce allows for some intermediate risk increasing orders to go through..

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't matter as long as the final state is lower risk, it just affects the margin requirement check we enforce anyway

match get_position_index(&maker.perp_positions, market_index) {
Ok(maker_position_index) => {
maker.perp_positions[maker_position_index]
.base_asset_amount
.signum()
== maker_base_asset_amount_before.signum()
}
Err(_) => true,
}
} else {
false
};

let perp_market = perp_market_map.get_ref(&market_index)?;
let taker_maintenance_margin_buffer = calculate_maintenance_buffer_ratio(
perp_market.margin_ratio_initial,
Expand All @@ -1348,7 +1351,7 @@ fn fulfill_perp_order(
let maker_maintenance_margin_buffer = calculate_maintenance_buffer_ratio(
perp_market.margin_ratio_initial,
perp_market.margin_ratio_maintenance,
maker_order_risk_decreasing,
maker_risk_reducing,
)?;
drop(perp_market);

Expand Down Expand Up @@ -3119,7 +3122,7 @@ fn sanitize_spot_maker_order<'a>(
let initial_margin_ratio = spot_market.get_margin_ratio(&MarginRequirementType::Initial)?;
let maintenance_margin_ratio =
spot_market.get_margin_ratio(&MarginRequirementType::Maintenance)?;
order_breaches_oracle_price_limits(
order_breaches_oracle_price_bands(
&maker.orders[maker_order_index],
oracle_price.price,
slot,
Expand Down
Loading