diff --git a/programs/drift/src/controller/liquidation.rs b/programs/drift/src/controller/liquidation.rs index 9503f24966..9859c11efb 100644 --- a/programs/drift/src/controller/liquidation.rs +++ b/programs/drift/src/controller/liquidation.rs @@ -32,8 +32,9 @@ use crate::math::liquidation::{ calculate_liability_transfer_implied_by_asset_amount, calculate_liability_transfer_to_cover_margin_shortage, calculate_liquidation_multiplier, calculate_max_pct_to_liquidate, calculate_perp_if_fee, calculate_spot_if_fee, - get_liquidation_fee, get_liquidation_order_params, validate_swap_within_liquidation_boundaries, - validate_transfer_satisfies_limit_price, LiquidationMultiplierType, + calculate_transfer_price_as_fee, get_liquidation_fee, get_liquidation_order_params, + validate_swap_within_liquidation_boundaries, validate_transfer_satisfies_limit_price, + LiquidationMultiplierType, }; use crate::math::margin::{ calculate_margin_requirement_and_total_collateral_and_liability_info, @@ -340,6 +341,12 @@ pub fn liquidate_perp( slot, )?; + let transfer_price = market.get_liquidation_transfer_price( + oracle_price, + user.perp_positions[position_index].get_direction(), + )?; + let transfer_price_fee = calculate_transfer_price_as_fee(transfer_price, oracle_price)?; + let if_liquidation_fee = calculate_perp_if_fee( intermediate_margin_calculation.tracked_market_margin_shortage(margin_shortage)?, user_base_asset_amount, @@ -348,6 +355,7 @@ pub fn liquidate_perp( oracle_price, quote_oracle_price, market.if_liquidation_fee, + transfer_price_fee, )?; let mut base_asset_amount_to_cover_margin_shortage = @@ -358,6 +366,7 @@ pub fn liquidate_perp( if_liquidation_fee, oracle_price, quote_oracle_price, + transfer_price_fee, )?; if base_asset_amount_to_cover_margin_shortage != u64::MAX { @@ -411,13 +420,13 @@ pub fn liquidate_perp( // Make sure liquidator enters at better than limit price if let Some(limit_price) = limit_price { // calculate fee in price terms - let oracle_price_u128 = oracle_price.cast::()?; - let fee = oracle_price_u128 + let transfer_price_u128 = transfer_price.cast::()?; + let fee = transfer_price_u128 .safe_mul(liquidator_fee.cast()?)? .safe_div(LIQUIDATION_FEE_PRECISION_U128)?; match user.perp_positions[position_index].get_direction() { PositionDirection::Long => { - let transfer_price = oracle_price_u128.safe_sub(fee)?; + let transfer_price = transfer_price_u128.safe_sub(fee)?; validate!( transfer_price <= limit_price.cast()?, ErrorCode::LiquidationDoesntSatisfyLimitPrice, @@ -427,7 +436,7 @@ pub fn liquidate_perp( )? } PositionDirection::Short => { - let transfer_price = oracle_price_u128.safe_add(fee)?; + let transfer_price = transfer_price_u128.safe_add(fee)?; validate!( transfer_price >= limit_price.cast()?, ErrorCode::LiquidationDoesntSatisfyLimitPrice, @@ -439,9 +448,11 @@ pub fn liquidate_perp( } } - let base_asset_value = - calculate_base_asset_value_with_oracle_price(base_asset_amount.cast()?, oracle_price)? - .cast::()?; + let base_asset_value = calculate_base_asset_value_with_oracle_price( + base_asset_amount.cast()?, + transfer_price.cast()?, + )? + .cast::()?; let liquidator_fee = -base_asset_value .cast::()? @@ -947,6 +958,13 @@ pub fn liquidate_perp_with_fill( .get_price_data("e_spot_market.oracle_id())? .price; let liquidator_fee = market.liquidator_fee; + + let transfer_price = market.get_liquidation_transfer_price( + oracle_price, + user.perp_positions[position_index].get_direction(), + )?; + let transfer_price_fee = calculate_transfer_price_as_fee(transfer_price, oracle_price)?; + let if_liquidation_fee = calculate_perp_if_fee( intermediate_margin_calculation.tracked_market_margin_shortage(margin_shortage)?, user_base_asset_amount, @@ -955,6 +973,7 @@ pub fn liquidate_perp_with_fill( oracle_price, quote_oracle_price, market.if_liquidation_fee, + transfer_price_fee, )?; let base_asset_amount_to_cover_margin_shortage = standardize_base_asset_amount_ceil( calculate_base_asset_amount_to_cover_margin_shortage( @@ -964,6 +983,7 @@ pub fn liquidate_perp_with_fill( if_liquidation_fee, oracle_price, quote_oracle_price, + transfer_price_fee, )?, market.amm.order_step_size, )?; @@ -1023,7 +1043,7 @@ pub fn liquidate_perp_with_fill( market_index, existing_direction, base_asset_amount, - oracle_price, + transfer_price, liquidator_fee_adjusted, )?; diff --git a/programs/drift/src/controller/liquidation/tests.rs b/programs/drift/src/controller/liquidation/tests.rs index e8cf21acde..b404d98e9f 100644 --- a/programs/drift/src/controller/liquidation/tests.rs +++ b/programs/drift/src/controller/liquidation/tests.rs @@ -2375,6 +2375,303 @@ pub mod liquidate_perp { let market_after = perp_market_map.get_ref(&0).unwrap(); assert_eq!(market_after.amm.total_liquidation_fee, 750000) } + + #[test] + pub fn successful_liquidation_long_perp_with_negative_basis_5min() { + let now = 0_i64; + let slot = 0_u64; + + let mut oracle_price = get_pyth_price(100, 6); + let oracle_price_key = + Pubkey::from_str("J83w4HKfqxwcq3BEMMkPFSppX3gqekLyLJBexebFVkix").unwrap(); + let pyth_program = crate::ids::pyth_program::id(); + create_account_info!( + oracle_price, + &oracle_price_key, + &pyth_program, + oracle_account_info + ); + let mut oracle_map = OracleMap::load_one(&oracle_account_info, slot, None).unwrap(); + + let mut market = PerpMarket { + amm: AMM { + base_asset_reserve: 100 * AMM_RESERVE_PRECISION, + quote_asset_reserve: 100 * AMM_RESERVE_PRECISION, + bid_base_asset_reserve: 101 * AMM_RESERVE_PRECISION, + bid_quote_asset_reserve: 99 * AMM_RESERVE_PRECISION, + ask_base_asset_reserve: 99 * AMM_RESERVE_PRECISION, + ask_quote_asset_reserve: 101 * AMM_RESERVE_PRECISION, + sqrt_k: 100 * AMM_RESERVE_PRECISION, + peg_multiplier: 100 * PEG_PRECISION, + max_slippage_ratio: 50, + max_fill_reserve_fraction: 100, + order_step_size: 10000000, + quote_asset_amount: -150 * QUOTE_PRECISION_I128, + base_asset_amount_with_amm: BASE_PRECISION_I128, + oracle: oracle_price_key, + last_mark_price_twap_5min: 100 * PRICE_PRECISION_U64 * 999 / 1000, + historical_oracle_data: HistoricalOracleData { + last_oracle_price_twap: 100 * PRICE_PRECISION_I64, + last_oracle_price_twap_5min: 100 * PRICE_PRECISION_I64, + ..HistoricalOracleData::default() + }, + ..AMM::default() + }, + margin_ratio_initial: 1000, + margin_ratio_maintenance: 500, + number_of_users_with_base: 1, + status: MarketStatus::Initialized, + liquidator_fee: LIQUIDATION_FEE_PRECISION / 100, + if_liquidation_fee: LIQUIDATION_FEE_PRECISION / 100, + ..PerpMarket::default() + }; + create_anchor_account_info!(market, PerpMarket, market_account_info); + let perp_market_map = PerpMarketMap::load_one(&market_account_info, true).unwrap(); + + let mut spot_market = SpotMarket { + market_index: 0, + oracle_source: OracleSource::QuoteAsset, + cumulative_deposit_interest: SPOT_CUMULATIVE_INTEREST_PRECISION, + decimals: 6, + initial_asset_weight: SPOT_WEIGHT_PRECISION, + historical_oracle_data: HistoricalOracleData { + last_oracle_price_twap: PRICE_PRECISION_I64, + last_oracle_price_twap_5min: PRICE_PRECISION_I64, + ..HistoricalOracleData::default() + }, + ..SpotMarket::default() + }; + create_anchor_account_info!(spot_market, SpotMarket, spot_market_account_info); + let spot_market_map = SpotMarketMap::load_one(&spot_market_account_info, true).unwrap(); + + let mut user = User { + orders: get_orders(Order { + market_index: 0, + status: OrderStatus::Open, + order_type: OrderType::Limit, + direction: PositionDirection::Long, + base_asset_amount: BASE_PRECISION_U64, + slot: 0, + ..Order::default() + }), + perp_positions: get_positions(PerpPosition { + market_index: 0, + base_asset_amount: BASE_PRECISION_I64, + quote_asset_amount: -150 * QUOTE_PRECISION_I64, + quote_entry_amount: -150 * QUOTE_PRECISION_I64, + quote_break_even_amount: -150 * QUOTE_PRECISION_I64, + open_orders: 1, + open_bids: BASE_PRECISION_I64, + ..PerpPosition::default() + }), + spot_positions: [SpotPosition::default(); 8], + + ..User::default() + }; + + let mut liquidator = User { + spot_positions: get_spot_positions(SpotPosition { + market_index: 0, + balance_type: SpotBalanceType::Deposit, + scaled_balance: 50 * SPOT_BALANCE_PRECISION_U64, + ..SpotPosition::default() + }), + ..User::default() + }; + + let user_key = Pubkey::default(); + let liquidator_key = Pubkey::default(); + + let mut user_stats = UserStats::default(); + let mut liquidator_stats = UserStats::default(); + let state = State { + liquidation_margin_buffer_ratio: 10, + initial_pct_to_liquidate: LIQUIDATION_PCT_PRECISION as u16, + liquidation_duration: 150, + ..Default::default() + }; + liquidate_perp( + 0, + BASE_PRECISION_U64, + None, + &mut user, + &user_key, + &mut user_stats, + &mut liquidator, + &liquidator_key, + &mut liquidator_stats, + &perp_market_map, + &spot_market_map, + &mut oracle_map, + slot, + now, + &state, + ) + .unwrap(); + + assert_eq!(user.perp_positions[0].base_asset_amount, 0); + assert_eq!(user.perp_positions[0].quote_asset_amount, -51099000); + assert_eq!(user.perp_positions[0].open_orders, 0); + assert_eq!(user.perp_positions[0].open_bids, 0); + + assert_eq!( + liquidator.perp_positions[0].base_asset_amount, + BASE_PRECISION_I64 + ); + assert_eq!(liquidator.perp_positions[0].quote_asset_amount, -98901000); + + let market_after = perp_market_map.get_ref(&0).unwrap(); + assert_eq!(market_after.amm.total_liquidation_fee, 0); + } + + #[test] + pub fn successful_liquidation_short_perp_with_positive_basis_5min() { + let now = 0_i64; + let slot = 0_u64; + + let mut oracle_price = get_pyth_price(100, 6); + let oracle_price_key = + Pubkey::from_str("J83w4HKfqxwcq3BEMMkPFSppX3gqekLyLJBexebFVkix").unwrap(); + let pyth_program = crate::ids::pyth_program::id(); + create_account_info!( + oracle_price, + &oracle_price_key, + &pyth_program, + oracle_account_info + ); + let mut oracle_map = OracleMap::load_one(&oracle_account_info, slot, None).unwrap(); + + let mut market = PerpMarket { + amm: AMM { + base_asset_reserve: 100 * AMM_RESERVE_PRECISION, + quote_asset_reserve: 100 * AMM_RESERVE_PRECISION, + bid_base_asset_reserve: 101 * AMM_RESERVE_PRECISION, + bid_quote_asset_reserve: 99 * AMM_RESERVE_PRECISION, + ask_base_asset_reserve: 99 * AMM_RESERVE_PRECISION, + ask_quote_asset_reserve: 101 * AMM_RESERVE_PRECISION, + sqrt_k: 100 * AMM_RESERVE_PRECISION, + peg_multiplier: 100 * PEG_PRECISION, + max_slippage_ratio: 50, + max_fill_reserve_fraction: 100, + order_step_size: 10000000, + quote_asset_amount: 50 * QUOTE_PRECISION_I128, + base_asset_amount_with_amm: BASE_PRECISION_I128, + oracle: oracle_price_key, + last_mark_price_twap_5min: 100 * PRICE_PRECISION_U64 * 1001 / 1000, + historical_oracle_data: HistoricalOracleData { + last_oracle_price_twap: 100 * PRICE_PRECISION_I64, + last_oracle_price_twap_5min: 100 * PRICE_PRECISION_I64, + ..HistoricalOracleData::default() + }, + funding_period: 3600, + ..AMM::default() + }, + margin_ratio_initial: 1000, + margin_ratio_maintenance: 500, + number_of_users_with_base: 1, + status: MarketStatus::Initialized, + liquidator_fee: LIQUIDATION_FEE_PRECISION / 100, + if_liquidation_fee: LIQUIDATION_FEE_PRECISION / 100, + ..PerpMarket::default() + }; + create_anchor_account_info!(market, PerpMarket, market_account_info); + let perp_market_map = PerpMarketMap::load_one(&market_account_info, true).unwrap(); + + let mut spot_market = SpotMarket { + market_index: 0, + oracle_source: OracleSource::QuoteAsset, + cumulative_deposit_interest: SPOT_CUMULATIVE_INTEREST_PRECISION, + decimals: 6, + initial_asset_weight: SPOT_WEIGHT_PRECISION, + historical_oracle_data: HistoricalOracleData { + last_oracle_price_twap: PRICE_PRECISION_I64, + last_oracle_price_twap_5min: PRICE_PRECISION_I64, + ..HistoricalOracleData::default() + }, + ..SpotMarket::default() + }; + create_anchor_account_info!(spot_market, SpotMarket, spot_market_account_info); + let spot_market_map = SpotMarketMap::load_one(&spot_market_account_info, true).unwrap(); + + let mut user = User { + orders: get_orders(Order { + market_index: 0, + status: OrderStatus::Open, + order_type: OrderType::Limit, + direction: PositionDirection::Short, + base_asset_amount: BASE_PRECISION_U64, + slot: 0, + ..Order::default() + }), + perp_positions: get_positions(PerpPosition { + market_index: 0, + base_asset_amount: -BASE_PRECISION_I64, + quote_asset_amount: 50 * QUOTE_PRECISION_I64, + quote_entry_amount: 50 * QUOTE_PRECISION_I64, + quote_break_even_amount: 50 * QUOTE_PRECISION_I64, + open_orders: 1, + open_asks: -BASE_PRECISION_I64, + ..PerpPosition::default() + }), + spot_positions: [SpotPosition::default(); 8], + + ..User::default() + }; + + let mut liquidator = User { + spot_positions: get_spot_positions(SpotPosition { + market_index: 0, + balance_type: SpotBalanceType::Deposit, + scaled_balance: 50 * SPOT_BALANCE_PRECISION_U64, + ..SpotPosition::default() + }), + ..User::default() + }; + + let user_key = Pubkey::default(); + let liquidator_key = Pubkey::default(); + + let mut user_stats = UserStats::default(); + let mut liquidator_stats = UserStats::default(); + let state = State { + liquidation_margin_buffer_ratio: 10, + initial_pct_to_liquidate: LIQUIDATION_PCT_PRECISION as u16, + liquidation_duration: 150, + ..Default::default() + }; + liquidate_perp( + 0, + BASE_PRECISION_U64, + None, + &mut user, + &user_key, + &mut user_stats, + &mut liquidator, + &liquidator_key, + &mut liquidator_stats, + &perp_market_map, + &spot_market_map, + &mut oracle_map, + slot, + now, + &state, + ) + .unwrap(); + + assert_eq!(user.perp_positions[0].base_asset_amount, 0); + assert_eq!(user.perp_positions[0].quote_asset_amount, -51101000); + assert_eq!(user.perp_positions[0].open_orders, 0); + assert_eq!(user.perp_positions[0].open_bids, 0); + + assert_eq!( + liquidator.perp_positions[0].base_asset_amount, + -BASE_PRECISION_I64 + ); + assert_eq!(liquidator.perp_positions[0].quote_asset_amount, 101101000); + + let market_after = perp_market_map.get_ref(&0).unwrap(); + assert_eq!(market_after.amm.total_liquidation_fee, 0); + } } pub mod liquidate_perp_with_fill { diff --git a/programs/drift/src/math/liquidation.rs b/programs/drift/src/math/liquidation.rs index 24a54afc59..d18252e336 100644 --- a/programs/drift/src/math/liquidation.rs +++ b/programs/drift/src/math/liquidation.rs @@ -38,6 +38,7 @@ pub fn calculate_base_asset_amount_to_cover_margin_shortage( if_liquidation_fee: u32, oracle_price: i64, quote_oracle_price: i64, + transfer_price_fee: u32, ) -> DriftResult { let margin_ratio = margin_ratio.safe_mul(LIQUIDATION_FEE_TO_MARGIN_PRECISION_RATIO)?; @@ -52,7 +53,12 @@ pub fn calculate_base_asset_amount_to_cover_margin_shortage( .cast::()? .safe_mul(quote_oracle_price.cast()?)? .safe_div(PRICE_PRECISION)? - .safe_mul(margin_ratio.safe_sub(liquidation_fee)?.cast()?)? + .safe_mul( + margin_ratio + .safe_sub(liquidation_fee)? + .safe_sub(transfer_price_fee.cast()?)? + .cast()?, + )? .safe_div(LIQUIDATION_FEE_PRECISION_U128)? .safe_sub( oracle_price @@ -369,6 +375,7 @@ pub fn calculate_perp_if_fee( oracle_price: i64, quote_oracle_price: i64, max_if_liquidation_fee: u32, + transfer_price_fee: u32, ) -> DriftResult { let margin_ratio = margin_ratio.safe_mul(LIQUIDATION_FEE_TO_MARGIN_PRECISION_RATIO)?; @@ -385,9 +392,10 @@ pub fn calculate_perp_if_fee( .safe_mul(quote_oracle_price.cast()?)? .safe_div(PRICE_PRECISION)?; - // margin ratio - liquidator fee - (margin shortage / (user base asset amount * price)) + // margin ratio - liquidator fee - transfer price fee - (margin shortage / (user base asset amount * price)) let implied_if_fee = margin_ratio .saturating_sub(liquidator_fee) + .saturating_sub(transfer_price_fee) .saturating_sub( margin_shortage .safe_mul(BASE_PRECISION)? @@ -458,23 +466,23 @@ pub fn get_liquidation_order_params( market_index: u16, existing_direction: PositionDirection, base_asset_amount: u64, - oracle_price: i64, + transfer_price: u64, liquidation_fee: u32, ) -> DriftResult { let direction = existing_direction.opposite(); - let oracle_price_u128 = oracle_price.abs().cast::()?; + let transfer_price_u128 = transfer_price.cast::()?; let limit_price = match direction { - PositionDirection::Long => oracle_price_u128 + PositionDirection::Long => transfer_price_u128 .safe_add( - oracle_price_u128 + transfer_price_u128 .safe_mul(liquidation_fee.cast()?)? .safe_div(LIQUIDATION_FEE_PRECISION_U128)?, )? .cast::()?, - PositionDirection::Short => oracle_price_u128 + PositionDirection::Short => transfer_price_u128 .safe_sub( - oracle_price_u128 + transfer_price_u128 .safe_mul(liquidation_fee.cast()?)? .safe_div(LIQUIDATION_FEE_PRECISION_U128)?, )? @@ -551,3 +559,14 @@ pub fn validate_swap_within_liquidation_boundaries( Ok(()) } + +pub fn calculate_transfer_price_as_fee(transfer_price: u64, oracle_price: i64) -> DriftResult { + let delta = oracle_price.safe_sub(transfer_price.cast()?)?.abs(); + let fee = delta + .cast::()? + .safe_mul(LIQUIDATION_FEE_PRECISION_U128)? + .safe_div(oracle_price.cast::()?)? + .cast::()?; + + Ok(fee) +} diff --git a/programs/drift/src/math/liquidation/tests.rs b/programs/drift/src/math/liquidation/tests.rs index 17315dabbf..caf8e92e85 100644 --- a/programs/drift/src/math/liquidation/tests.rs +++ b/programs/drift/src/math/liquidation/tests.rs @@ -19,6 +19,7 @@ mod calculate_base_asset_amount_to_cover_margin_shortage { 0, oracle_price, PRICE_PRECISION_I64, + 0, ) .unwrap(); @@ -39,6 +40,7 @@ mod calculate_base_asset_amount_to_cover_margin_shortage { 0, oracle_price, quote_oracle_price, + 0, ) .unwrap(); @@ -52,6 +54,7 @@ mod calculate_base_asset_amount_to_cover_margin_shortage { 0, oracle_price, quote_oracle_price, + 0, ) .unwrap(); @@ -71,6 +74,7 @@ mod calculate_base_asset_amount_to_cover_margin_shortage { 0, oracle_price, PRICE_PRECISION_I64, + 0, ) .unwrap(); @@ -91,6 +95,41 @@ mod calculate_base_asset_amount_to_cover_margin_shortage { assert_eq!(base_asset_amount, BASE_PRECISION_U64 * 10 / 9); // must lose 10/9 base } + #[test] + pub fn one_percent_liquidation_fee_and_one_percent_transfer_fee() { + let margin_shortage = 10 * QUOTE_PRECISION; // $10 shortage + let margin_ratio = MARGIN_PRECISION / 10; // 10x leverage + let liquidation_fee = LIQUIDATION_FEE_PRECISION / 100; // 1 percent + let transfer_fee = LIQUIDATION_FEE_PRECISION / 100; // 1 percent + let oracle_price = 100 * PRICE_PRECISION_I64; // $100 / base + let base_asset_amount = calculate_base_asset_amount_to_cover_margin_shortage( + margin_shortage, + margin_ratio, + liquidation_fee, + 0, + oracle_price, + PRICE_PRECISION_I64, + transfer_fee, + ) + .unwrap(); + + let freed_collateral = (base_asset_amount as u128) * (oracle_price as u128) + / PRICE_PRECISION + / AMM_TO_QUOTE_PRECISION_RATIO + * margin_ratio as u128 + / MARGIN_PRECISION_U128; + + let negative_pnl = (base_asset_amount as u128) * (oracle_price as u128) + / PRICE_PRECISION + / AMM_TO_QUOTE_PRECISION_RATIO + * (liquidation_fee as u128 + transfer_fee as u128) + / LIQUIDATION_FEE_PRECISION_U128; + + assert_eq!(freed_collateral - negative_pnl, 10000000); // ~$10 + + assert_eq!(base_asset_amount, BASE_PRECISION_U64 * 125 / 100); // must lose 125/100 base + } + #[test] pub fn one_percent_liquidation_fee_and_one_percent_if_liquidation_fee() { let margin_shortage = 10 * QUOTE_PRECISION; // $10 shortage @@ -105,6 +144,7 @@ mod calculate_base_asset_amount_to_cover_margin_shortage { if_liquidation_fee, oracle_price, PRICE_PRECISION_I64, + 0, ) .unwrap(); @@ -660,11 +700,27 @@ mod calculate_perp_if_fee { oracle_price, quote_price, max_if_fee, + 0, ) .unwrap(); assert_eq!(fee, 19000); // 2% * .95 + let transfer_fee = LIQUIDATION_FEE_PRECISION / 100; // 1% + let fee = calculate_perp_if_fee( + margin_shortage, + base_asset_amount, + margin_ratio, + liquidator_fee, + oracle_price, + quote_price, + max_if_fee, + transfer_fee, + ) + .unwrap(); + + assert_eq!(fee, 9500); // 1% * .95 + let tiny_margin_shortage = QUOTE_PRECISION; let fee = calculate_perp_if_fee( tiny_margin_shortage, @@ -674,6 +730,7 @@ mod calculate_perp_if_fee { oracle_price, quote_price, max_if_fee, + 0, ) .unwrap(); @@ -688,6 +745,7 @@ mod calculate_perp_if_fee { oracle_price, quote_price, max_if_fee, + 0, ) .unwrap(); @@ -702,6 +760,7 @@ mod calculate_perp_if_fee { oracle_price, quote_price, max_if_fee, + 0, ) .unwrap(); @@ -716,6 +775,7 @@ mod calculate_perp_if_fee { zero_oracle_price, quote_price, max_if_fee, + 0, ) .unwrap(); @@ -730,6 +790,7 @@ mod calculate_perp_if_fee { oracle_price, zero_quote_price, max_if_fee, + 0, ) .unwrap(); @@ -744,6 +805,7 @@ mod calculate_perp_if_fee { oracle_price, quote_price, max_if_fee, + 0, ) .unwrap(); diff --git a/programs/drift/src/state/perp_market.rs b/programs/drift/src/state/perp_market.rs index 15d6462c83..5e297e9d98 100644 --- a/programs/drift/src/state/perp_market.rs +++ b/programs/drift/src/state/perp_market.rs @@ -740,15 +740,7 @@ impl PerpMarket { let last_fill_price = self.last_fill_price; - let mark_price_5min_twap = self.amm.last_mark_price_twap_5min; - let last_oracle_price_twap_5min = - self.amm.historical_oracle_data.last_oracle_price_twap_5min; - - let basis_5min = mark_price_5min_twap - .cast::()? - .safe_sub(last_oracle_price_twap_5min)?; - - let oracle_plus_basis_5min = oracle_price.safe_add(basis_5min)?.cast::()?; + let oracle_plus_basis_5min = self.get_oracle_plus_basis_5min(oracle_price)?; let last_funding_basis = self.get_last_funding_basis(oracle_price, now)?; @@ -783,6 +775,22 @@ impl PerpMarket { self.clamp_trigger_price(oracle_price.unsigned_abs(), median_price) } + fn get_oracle_plus_basis_5min(&self, oracle_price: i64) -> DriftResult { + let mark_price_5min_twap = self.amm.last_mark_price_twap_5min; + let last_oracle_price_twap_5min = + self.amm.historical_oracle_data.last_oracle_price_twap_5min; + + if mark_price_5min_twap == 0 || last_oracle_price_twap_5min == 0 { + return Ok(oracle_price.cast::()?); + } + + let basis_5min = mark_price_5min_twap + .cast::()? + .safe_sub(last_oracle_price_twap_5min)?; + + oracle_price.safe_add(basis_5min)?.cast::() + } + #[inline(always)] fn get_last_funding_basis(&self, oracle_price: i64, now: i64) -> DriftResult { if self.amm.last_funding_oracle_twap > 0 { @@ -836,6 +844,23 @@ impl PerpMarket { )) } + pub fn get_liquidation_transfer_price( + &self, + oracle_price: i64, + direction: PositionDirection, + ) -> DriftResult { + let oracle_plus_basis_5min = self.get_oracle_plus_basis_5min(oracle_price)?; + + let oracle_price_abs = oracle_price.unsigned_abs(); + let take_over_price = if direction == PositionDirection::Long { + oracle_plus_basis_5min.min(oracle_price_abs) + } else { + oracle_plus_basis_5min.max(oracle_price_abs) + }; + + self.clamp_trigger_price(oracle_price_abs, take_over_price) + } + #[inline(always)] pub fn get_mm_oracle_price_data( &self,