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: add liquidation via fill #1106

Merged
merged 17 commits into from
Jul 30, 2024
Merged
Show file tree
Hide file tree
Changes from 7 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
484 changes: 475 additions & 9 deletions programs/drift/src/controller/liquidation.rs

Large diffs are not rendered by default.

454 changes: 454 additions & 0 deletions programs/drift/src/controller/liquidation/tests.rs

Large diffs are not rendered by default.

128 changes: 75 additions & 53 deletions programs/drift/src/controller/orders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,13 +104,15 @@ pub fn place_perp_order(
let now = clock.unix_timestamp;
let slot = clock.slot;

validate_user_not_being_liquidated(
user,
perp_market_map,
spot_market_map,
oracle_map,
state.liquidation_margin_buffer_ratio,
)?;
if !options.is_liquidation() {
validate_user_not_being_liquidated(
user,
perp_market_map,
spot_market_map,
oracle_map,
state.liquidation_margin_buffer_ratio,
)?;
}

validate!(!user.is_bankrupt(), ErrorCode::UserBankrupt)?;

Expand Down Expand Up @@ -207,7 +209,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)?;
// dont modify if it's a liquidation
if !options.is_liquidation() {
params.update_perp_auction_params(market, oracle_price_data.price)?;
}

let (auction_start_price, auction_end_price, auction_duration) = get_auction_params(
&params,
Expand Down Expand Up @@ -306,7 +311,7 @@ pub fn place_perp_order(
options.update_risk_increasing(risk_increasing);

// when orders are placed in bulk, only need to check margin on last place
if options.enforce_margin_check {
if options.enforce_margin_check && !options.is_liquidation() {
meets_place_order_margin_requirement(
user,
perp_market_map,
Expand Down Expand Up @@ -876,7 +881,7 @@ pub fn fill_perp_order(
jit_maker_order_id: Option<u32>,
clock: &Clock,
fill_mode: FillMode,
) -> DriftResult<u64> {
) -> DriftResult<(u64, u64)> {
let now = clock.unix_timestamp;
let slot = clock.slot;

Expand Down Expand Up @@ -940,20 +945,22 @@ pub fn fill_perp_order(

if user.is_bankrupt() {
msg!("user is bankrupt");
return Ok(0);
return Ok((0, 0));
}

match validate_user_not_being_liquidated(
user,
perp_market_map,
spot_market_map,
oracle_map,
state.liquidation_margin_buffer_ratio,
) {
Ok(_) => {}
Err(_) => {
msg!("user is being liquidated");
return Ok(0);
if !fill_mode.is_liquidation() {
match validate_user_not_being_liquidated(
user,
perp_market_map,
spot_market_map,
oracle_map,
state.liquidation_margin_buffer_ratio,
) {
Ok(_) => {}
Err(_) => {
msg!("user is being liquidated");
return Ok((0, 0));
}
}
}

Expand Down Expand Up @@ -1031,13 +1038,18 @@ pub fn fill_perp_order(
slot,
)?;

let referrer_info = get_referrer_info(
user_stats,
&user_key,
makers_and_referrer,
makers_and_referrer_stats,
slot,
)?;
// no referrer bonus for liquidations
Copy link
Member

Choose a reason for hiding this comment

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

👍

let referrer_info = if !fill_mode.is_liquidation() {
get_referrer_info(
user_stats,
&user_key,
makers_and_referrer,
makers_and_referrer_stats,
slot,
)?
} else {
None
};

let oracle_too_divergent_with_twap_5min = is_oracle_too_divergent_with_twap_5min(
oracle_price,
Expand All @@ -1052,7 +1064,7 @@ pub fn fill_perp_order(
if let Some(filler) = filler.as_deref_mut() {
filler.update_last_active_slot(slot);
}
return Ok(0);
return Ok((0, 0));
}

validate_perp_fill_possible(state, user, order_index, slot, makers_and_referrer.0.len())?;
Expand Down Expand Up @@ -1104,7 +1116,7 @@ pub fn fill_perp_order(
false,
)?;

return Ok(0);
return Ok((0, 0));
}

let (base_asset_amount, quote_asset_amount) = fulfill_perp_order(
Expand Down Expand Up @@ -1189,7 +1201,7 @@ pub fn fill_perp_order(
}

if base_asset_amount == 0 {
return Ok(0);
return Ok((base_asset_amount, quote_asset_amount));
}

{
Expand Down Expand Up @@ -1227,7 +1239,7 @@ pub fn fill_perp_order(

user.update_last_active_slot(slot);

Ok(base_asset_amount)
Ok((base_asset_amount, quote_asset_amount))
}

pub fn validate_market_within_price_band(
Expand Down Expand Up @@ -1591,6 +1603,7 @@ fn fulfill_perp_order(
None,
*maker_price,
AMMLiquiditySplit::Shared,
fill_mode.is_liquidation(),
)?;

(fill_base_asset_amount, fill_quote_asset_amount)
Expand Down Expand Up @@ -1633,6 +1646,7 @@ fn fulfill_perp_order(
slot,
fee_structure,
oracle_map,
fill_mode.is_liquidation(),
)?;

if maker_fill_base_asset_amount != 0 {
Expand Down Expand Up @@ -1673,26 +1687,28 @@ fn fulfill_perp_order(
base_asset_amount
)?;

let taker_margin_calculation =
calculate_margin_requirement_and_total_collateral_and_liability_info(
user,
perp_market_map,
spot_market_map,
oracle_map,
MarginContext::standard(if user_order_position_decreasing {
MarginRequirementType::Maintenance
} else {
MarginRequirementType::Fill
}),
)?;
if !fill_mode.is_liquidation() {
let taker_margin_calculation =
calculate_margin_requirement_and_total_collateral_and_liability_info(
user,
perp_market_map,
spot_market_map,
oracle_map,
MarginContext::standard(if user_order_position_decreasing {
MarginRequirementType::Maintenance
} else {
MarginRequirementType::Fill
}),
)?;

if !taker_margin_calculation.meets_margin_requirement() {
msg!(
"taker breached fill requirements (margin requirement {}) (total_collateral {})",
taker_margin_calculation.margin_requirement,
taker_margin_calculation.total_collateral
);
return Err(ErrorCode::InsufficientCollateral);
if !taker_margin_calculation.meets_margin_requirement() {
msg!(
"taker breached fill requirements (margin requirement {}) (total_collateral {})",
taker_margin_calculation.margin_requirement,
taker_margin_calculation.total_collateral
);
return Err(ErrorCode::InsufficientCollateral);
}
}

for (maker_key, maker_base_asset_amount_filled) in maker_fills {
Expand Down Expand Up @@ -1808,6 +1824,7 @@ pub fn fulfill_perp_order_with_amm(
override_base_asset_amount: Option<u64>,
override_fill_price: Option<u64>,
liquidity_split: AMMLiquiditySplit,
is_liquidation: bool,
) -> DriftResult<(u64, u64)> {
let position_index = get_position_index(&user.perp_positions, market.market_index)?;
let existing_base_asset_amount = user.perp_positions[position_index].base_asset_amount;
Expand Down Expand Up @@ -2040,6 +2057,7 @@ pub fn fulfill_perp_order_with_amm(

let fill_record_id = get_then_update_id!(market, next_fill_record_id);
let order_action_explanation = match (override_base_asset_amount, override_fill_price) {
_ if is_liquidation => OrderActionExplanation::Liquidation,
(Some(_), Some(_)) => liquidity_split.get_order_action_explanation(),
_ => OrderActionExplanation::OrderFilledWithAMM,
};
Expand Down Expand Up @@ -2103,6 +2121,7 @@ pub fn fulfill_perp_order_with_match(
slot: u64,
fee_structure: &FeeStructure,
oracle_map: &mut OracleMap,
is_liquidation: bool,
) -> DriftResult<(u64, u64, u64)> {
if !are_orders_same_market_but_different_sides(
&maker.orders[maker_order_index],
Expand Down Expand Up @@ -2214,6 +2233,7 @@ pub fn fulfill_perp_order_with_match(
Some(jit_base_asset_amount),
Some(maker_price), // match the makers price
amm_liquidity_split,
is_liquidation,
)?;

total_base_asset_amount = base_asset_amount_filled_by_amm;
Expand Down Expand Up @@ -2423,7 +2443,9 @@ pub fn fulfill_perp_order_with_match(
)?;

let fill_record_id = get_then_update_id!(market, next_fill_record_id);
let order_action_explanation = if maker.orders[maker_order_index].is_jit_maker() {
let order_action_explanation = if is_liquidation {
OrderActionExplanation::Liquidation
} else if maker.orders[maker_order_index].is_jit_maker() {
OrderActionExplanation::OrderFilledWithMatchJit
} else {
OrderActionExplanation::OrderFilledWithMatch
Expand Down
Loading
Loading