From 7b0ea4f9cc10b32a53fdabec39c7b945e8e20ad3 Mon Sep 17 00:00:00 2001 From: 0xbigz <83473873+0xbigz@users.noreply.github.com> Date: Wed, 22 Feb 2023 12:17:22 -0500 Subject: [PATCH 01/14] bigz/add-tiers-for-liq-perp-pnl --- programs/drift/src/controller/liquidation.rs | 55 +++++++++++++++----- programs/drift/src/error.rs | 2 + programs/drift/src/math/margin.rs | 22 ++++++-- programs/drift/src/math/margin/tests.rs | 4 +- programs/drift/src/math/orders.rs | 4 +- programs/drift/src/math/orders/tests.rs | 8 +-- programs/drift/src/state/perp_market.rs | 2 +- programs/drift/src/state/spot_market.rs | 2 +- 8 files changed, 72 insertions(+), 27 deletions(-) diff --git a/programs/drift/src/controller/liquidation.rs b/programs/drift/src/controller/liquidation.rs index 76da63d84..6351e6ae8 100644 --- a/programs/drift/src/controller/liquidation.rs +++ b/programs/drift/src/controller/liquidation.rs @@ -36,8 +36,9 @@ use crate::math::liquidation::{ validate_transfer_satisfies_limit_price, LiquidationMultiplierType, }; use crate::math::margin::{ - calculate_margin_requirement_and_total_collateral, meets_initial_margin_requirement, - MarginRequirementType, + calculate_margin_requirement_and_total_collateral, + calculate_margin_requirement_and_total_collateral_and_liability_info, + meets_initial_margin_requirement, MarginRequirementType, }; use crate::math::oracle::DriftAction; use crate::math::orders::{ @@ -55,7 +56,7 @@ use crate::state::events::{ use crate::state::oracle_map::OracleMap; use crate::state::perp_market::MarketStatus; use crate::state::perp_market_map::PerpMarketMap; -use crate::state::spot_market::SpotBalanceType; +use crate::state::spot_market::{AssetTier, SpotBalanceType}; use crate::state::spot_market_map::SpotMarketMap; use crate::state::state::State; use crate::state::user::{MarketType, Order, OrderStatus, OrderType, User, UserStats}; @@ -1551,7 +1552,8 @@ pub fn liquidate_perp_pnl_for_deposit( ) -> DriftResult { // liquidator takes over remaining negative perpetual pnl in exchange for a user deposit // can only be done once the perpetual position's size is 0 - // blocked when the user deposit oracle is deemed invalid + // blocked when 1) user deposit oracle is deemed invalid + // or 2) user has outstanding liability with higher tier validate!( !user.is_bankrupt(), @@ -1609,7 +1611,14 @@ pub fn liquidate_perp_pnl_for_deposit( now, )?; - let (asset_amount, asset_price, asset_decimals, asset_weight, asset_liquidation_multiplier) = { + let ( + asset_amount, + asset_price, + _asset_tier, + asset_decimals, + asset_weight, + asset_liquidation_multiplier, + ) = { let mut asset_market = spot_market_map.get_ref_mut(&asset_market_index)?; let (asset_price_data, validity_guard_rails) = oracle_map.get_price_data_and_guard_rails(&asset_market.oracle)?; @@ -1643,6 +1652,7 @@ pub fn liquidate_perp_pnl_for_deposit( ( token_amount, token_price, + asset_market.asset_tier, asset_market.decimals, asset_market.maintenance_asset_weight, calculate_liquidation_multiplier( @@ -1655,6 +1665,7 @@ pub fn liquidate_perp_pnl_for_deposit( let ( unsettled_pnl, quote_price, + contract_tier, quote_decimals, pnl_liability_weight, pnl_liquidation_multiplier, @@ -1691,6 +1702,7 @@ pub fn liquidate_perp_pnl_for_deposit( ( unsettled_pnl.unsigned_abs(), quote_price, + market.contract_tier, 6_u32, SPOT_WEIGHT_PRECISION, calculate_liquidation_multiplier( @@ -1700,15 +1712,30 @@ pub fn liquidate_perp_pnl_for_deposit( ) }; - let (margin_requirement, total_collateral, margin_requirement_plus_buffer, _) = - calculate_margin_requirement_and_total_collateral( - user, - perp_market_map, - MarginRequirementType::Maintenance, - spot_market_map, - oracle_map, - Some(liquidation_margin_buffer_ratio as u128), - )?; + let ( + margin_requirement, + total_collateral, + margin_requirement_plus_buffer, + _all_oracles_valid, + _, + _, + highest_tier_spot_liability, + highest_tier_perp_liability, + ) = calculate_margin_requirement_and_total_collateral_and_liability_info( + user, + perp_market_map, + MarginRequirementType::Maintenance, + spot_market_map, + oracle_map, + Some(liquidation_margin_buffer_ratio as u128), + false, + )?; + + if contract_tier > highest_tier_perp_liability + || highest_tier_spot_liability > AssetTier::default() + { + return Err(ErrorCode::TierViolationLiquidatingPerpPnl); + } if !user.is_being_liquidated() && total_collateral >= margin_requirement.cast()? { return Err(ErrorCode::SufficientCollateral); diff --git a/programs/drift/src/error.rs b/programs/drift/src/error.rs index 27fe7d465..f83d4fee4 100644 --- a/programs/drift/src/error.rs +++ b/programs/drift/src/error.rs @@ -459,6 +459,8 @@ pub enum ErrorCode { InvalidOracleForSettlePnl, #[msg("MarginOrdersOpen")] MarginOrdersOpen, + #[msg("TierViolationLiquidatingPerpPnl")] + TierViolationLiquidatingPerpPnl, } #[macro_export] diff --git a/programs/drift/src/math/margin.rs b/programs/drift/src/math/margin.rs index fce2151b3..b4196740d 100644 --- a/programs/drift/src/math/margin.rs +++ b/programs/drift/src/math/margin.rs @@ -248,7 +248,7 @@ pub fn calculate_margin_requirement_and_total_collateral_and_liability_info( oracle_map: &mut OracleMap, margin_buffer_ratio: Option, strict: bool, -) -> DriftResult<(u128, i128, u128, bool, u8, bool)> { +) -> DriftResult<(u128, i128, u128, bool, u8, bool, AssetTier, ContractTier)> { let mut total_collateral: i128 = 0; let mut margin_requirement: u128 = 0; let mut margin_requirement_plus_buffer: u128 = 0; @@ -256,6 +256,8 @@ pub fn calculate_margin_requirement_and_total_collateral_and_liability_info( let mut num_spot_liabilities: u8 = 0; let mut num_perp_liabilities: u8 = 0; let mut with_isolated_liability: bool = false; + let mut highest_tier_spot_liablity: AssetTier = AssetTier::default(); + let mut highest_tier_perp_liablity: ContractTier = ContractTier::default(); let user_custom_margin_ratio = if margin_requirement_type == MarginRequirementType::Initial { user.max_margin_ratio @@ -317,7 +319,8 @@ pub fn calculate_margin_requirement_and_total_collateral_and_liability_info( )?; margin_requirement = margin_requirement.safe_add(weighted_token_value)?; - + highest_tier_spot_liablity = + min(highest_tier_spot_liablity, spot_market.asset_tier); num_spot_liabilities += 1; if let Some(margin_buffer_ratio) = margin_buffer_ratio { @@ -428,6 +431,8 @@ pub fn calculate_margin_requirement_and_total_collateral_and_liability_info( )?; margin_requirement = margin_requirement.safe_add(weighted_token_value)?; + highest_tier_spot_liablity = + min(highest_tier_spot_liablity, spot_market.asset_tier); if let Some(margin_buffer_ratio) = margin_buffer_ratio { margin_requirement_plus_buffer = margin_requirement_plus_buffer.safe_add( @@ -444,6 +449,8 @@ pub fn calculate_margin_requirement_and_total_collateral_and_liability_info( } Ordering::Equal => { if spot_position.has_open_order() { + highest_tier_spot_liablity = + min(highest_tier_spot_liablity, spot_market.asset_tier); num_spot_liabilities += 1; } } @@ -525,6 +532,7 @@ pub fn calculate_margin_requirement_and_total_collateral_and_liability_info( || market_position.has_open_order() { num_perp_liabilities += 1; + highest_tier_perp_liablity = min(highest_tier_perp_liablity, market.contract_tier); } with_isolated_liability &= @@ -548,6 +556,8 @@ pub fn calculate_margin_requirement_and_total_collateral_and_liability_info( all_oracles_valid, num_of_liabilities, with_isolated_liability, + highest_tier_spot_liablity, + highest_tier_perp_liablity, )) } @@ -566,6 +576,8 @@ pub fn calculate_margin_requirement_and_total_collateral( all_oracles_valid, _, _, + _, + _, ) = calculate_margin_requirement_and_total_collateral_and_liability_info( user, perp_market_map, @@ -599,6 +611,8 @@ pub fn meets_withdraw_margin_requirement( oracles_valid, num_of_liabilities, includes_isolated_liability, + _, + _, ) = calculate_margin_requirement_and_total_collateral_and_liability_info( user, perp_market_map, @@ -658,6 +672,8 @@ pub fn meets_place_order_margin_requirement( _, num_of_liabilities, includes_isolated_liability, + _, + _, ) = calculate_margin_requirement_and_total_collateral_and_liability_info( user, perp_market_map, @@ -748,7 +764,7 @@ pub fn calculate_max_withdrawable_amount( spot_market_map: &SpotMarketMap, oracle_map: &mut OracleMap, ) -> DriftResult { - let (margin_requirement, total_collateral, _, _, num_of_liabilities, _) = + let (margin_requirement, total_collateral, _, _, num_of_liabilities, _, _, _) = calculate_margin_requirement_and_total_collateral_and_liability_info( user, perp_market_map, diff --git a/programs/drift/src/math/margin/tests.rs b/programs/drift/src/math/margin/tests.rs index 7557169bb..adfb3abf7 100644 --- a/programs/drift/src/math/margin/tests.rs +++ b/programs/drift/src/math/margin/tests.rs @@ -1351,7 +1351,7 @@ mod calculate_margin_requirement_and_total_collateral_and_liability_info { ..User::default() }; - let (margin_requirement, _, _, _, num_of_liabilities, _) = + let (margin_requirement, _, _, _, num_of_liabilities, _, _, _) = calculate_margin_requirement_and_total_collateral_and_liability_info( &user, &perp_market_map, @@ -1463,7 +1463,7 @@ mod calculate_margin_requirement_and_total_collateral_and_liability_info { ..User::default() }; - let (margin_requirement, _, _, _, num_of_liabilities, _) = + let (margin_requirement, _, _, _, num_of_liabilities, _, _, _) = calculate_margin_requirement_and_total_collateral_and_liability_info( &user, &perp_market_map, diff --git a/programs/drift/src/math/orders.rs b/programs/drift/src/math/orders.rs index d92bf6b3e..7bc5c8656 100644 --- a/programs/drift/src/math/orders.rs +++ b/programs/drift/src/math/orders.rs @@ -684,7 +684,7 @@ pub fn calculate_max_perp_order_size( oracle_map: &mut OracleMap, ) -> DriftResult { // calculate initial margin requirement - let (margin_requirement, total_collateral, _, _, _, _) = + let (margin_requirement, total_collateral, _, _, _, _, _, _) = calculate_margin_requirement_and_total_collateral_and_liability_info( user, perp_market_map, @@ -779,7 +779,7 @@ pub fn calculate_max_spot_order_size( oracle_map: &mut OracleMap, ) -> DriftResult { // calculate initial margin requirement - let (margin_requirement, total_collateral, _, _, _, _) = + let (margin_requirement, total_collateral, _, _, _, _, _, _) = calculate_margin_requirement_and_total_collateral_and_liability_info( user, perp_market_map, diff --git a/programs/drift/src/math/orders/tests.rs b/programs/drift/src/math/orders/tests.rs index ba03583a4..ba1523f10 100644 --- a/programs/drift/src/math/orders/tests.rs +++ b/programs/drift/src/math/orders/tests.rs @@ -1543,7 +1543,7 @@ mod calculate_max_spot_order_size { user.spot_positions[1].open_orders = 1; user.spot_positions[1].open_bids = max_order_size as i64; - let (margin_requirement, total_collateral, _, _, _, _) = + let (margin_requirement, total_collateral, _, _, _, _, _, _) = calculate_margin_requirement_and_total_collateral_and_liability_info( &user, &PerpMarketMap::empty(), @@ -1737,7 +1737,7 @@ mod calculate_max_spot_order_size { user.spot_positions[1].open_orders = 1; user.spot_positions[1].open_asks = -(max_order_size as i64); - let (margin_requirement, total_collateral, _, _, _, _) = + let (margin_requirement, total_collateral, _, _, _, _, _, _) = calculate_margin_requirement_and_total_collateral_and_liability_info( &user, &PerpMarketMap::empty(), @@ -1973,7 +1973,7 @@ mod calculate_max_perp_order_size { user.perp_positions[0].open_orders = 1; user.perp_positions[0].open_bids = max_order_size as i64; - let (margin_requirement, total_collateral, _, _, _, _) = + let (margin_requirement, total_collateral, _, _, _, _, _, _) = calculate_margin_requirement_and_total_collateral_and_liability_info( &user, &market_map, @@ -2189,7 +2189,7 @@ mod calculate_max_perp_order_size { user.perp_positions[0].open_orders = 1; user.perp_positions[0].open_asks = -(max_order_size as i64); - let (margin_requirement, total_collateral, _, _, _, _) = + let (margin_requirement, total_collateral, _, _, _, _, _, _) = calculate_margin_requirement_and_total_collateral_and_liability_info( &user, &market_map, diff --git a/programs/drift/src/state/perp_market.rs b/programs/drift/src/state/perp_market.rs index e97b2027a..3bbf43724 100644 --- a/programs/drift/src/state/perp_market.rs +++ b/programs/drift/src/state/perp_market.rs @@ -58,7 +58,7 @@ impl Default for ContractType { } } -#[derive(Clone, Copy, BorshSerialize, BorshDeserialize, PartialEq, Debug, Eq)] +#[derive(Clone, Copy, BorshSerialize, BorshDeserialize, PartialEq, Debug, Eq, PartialOrd, Ord)] pub enum ContractTier { A, // max insurance capped at A level B, // max insurance capped at B level diff --git a/programs/drift/src/state/spot_market.rs b/programs/drift/src/state/spot_market.rs index 1dc6bdad0..59c3735fa 100644 --- a/programs/drift/src/state/spot_market.rs +++ b/programs/drift/src/state/spot_market.rs @@ -354,7 +354,7 @@ impl Default for SpotFulfillmentConfigStatus { } } -#[derive(Clone, Copy, BorshSerialize, BorshDeserialize, PartialEq, Debug, Eq)] +#[derive(Clone, Copy, BorshSerialize, BorshDeserialize, PartialEq, Debug, Eq, PartialOrd, Ord)] pub enum AssetTier { Collateral, // full priviledge Protected, // collateral, but no borrow From 27c5858b4a6de01a30cd374c1380ab862a6fef9f Mon Sep 17 00:00:00 2001 From: 0xbigz <83473873+0xbigz@users.noreply.github.com> Date: Wed, 22 Feb 2023 13:23:54 -0500 Subject: [PATCH 02/14] update idl --- sdk/src/idl/drift.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sdk/src/idl/drift.json b/sdk/src/idl/drift.json index b30865168..682f1da59 100644 --- a/sdk/src/idl/drift.json +++ b/sdk/src/idl/drift.json @@ -8596,6 +8596,11 @@ "code": 6226, "name": "MarginOrdersOpen", "msg": "MarginOrdersOpen" + }, + { + "code": 6227, + "name": "TierViolationLiquidatingPerpPnl", + "msg": "TierViolationLiquidatingPerpPnl" } ] } \ No newline at end of file From cc8adcfce122128065594e61e702c9b1d8dd8b35 Mon Sep 17 00:00:00 2001 From: 0xbigz <83473873+0xbigz@users.noreply.github.com> Date: Wed, 22 Feb 2023 13:32:07 -0500 Subject: [PATCH 03/14] improve liquidation.rs order, rename highest->safest --- programs/drift/src/controller/liquidation.rs | 16 +++++++------- programs/drift/src/math/margin.rs | 22 ++++++++++---------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/programs/drift/src/controller/liquidation.rs b/programs/drift/src/controller/liquidation.rs index 6351e6ae8..033fc3c01 100644 --- a/programs/drift/src/controller/liquidation.rs +++ b/programs/drift/src/controller/liquidation.rs @@ -1719,8 +1719,8 @@ pub fn liquidate_perp_pnl_for_deposit( _all_oracles_valid, _, _, - highest_tier_spot_liability, - highest_tier_perp_liability, + safest_tier_spot_liability, + safest_tier_perp_liability, ) = calculate_margin_requirement_and_total_collateral_and_liability_info( user, perp_market_map, @@ -1731,12 +1731,6 @@ pub fn liquidate_perp_pnl_for_deposit( false, )?; - if contract_tier > highest_tier_perp_liability - || highest_tier_spot_liability > AssetTier::default() - { - return Err(ErrorCode::TierViolationLiquidatingPerpPnl); - } - if !user.is_being_liquidated() && total_collateral >= margin_requirement.cast()? { return Err(ErrorCode::SufficientCollateral); } else if user.is_being_liquidated() @@ -1913,6 +1907,12 @@ pub fn liquidate_perp_pnl_for_deposit( asset_transfer ); return Err(ErrorCode::InvalidLiquidation); + } else { + if contract_tier > safest_tier_perp_liability + || safest_tier_spot_liability > AssetTier::default() + { + return Err(ErrorCode::TierViolationLiquidatingPerpPnl); + } } validate_transfer_satisfies_limit_price( diff --git a/programs/drift/src/math/margin.rs b/programs/drift/src/math/margin.rs index b4196740d..2f3021085 100644 --- a/programs/drift/src/math/margin.rs +++ b/programs/drift/src/math/margin.rs @@ -256,8 +256,8 @@ pub fn calculate_margin_requirement_and_total_collateral_and_liability_info( let mut num_spot_liabilities: u8 = 0; let mut num_perp_liabilities: u8 = 0; let mut with_isolated_liability: bool = false; - let mut highest_tier_spot_liablity: AssetTier = AssetTier::default(); - let mut highest_tier_perp_liablity: ContractTier = ContractTier::default(); + let mut safest_tier_spot_liablity: AssetTier = AssetTier::default(); + let mut safest_tier_perp_liablity: ContractTier = ContractTier::default(); let user_custom_margin_ratio = if margin_requirement_type == MarginRequirementType::Initial { user.max_margin_ratio @@ -319,8 +319,8 @@ pub fn calculate_margin_requirement_and_total_collateral_and_liability_info( )?; margin_requirement = margin_requirement.safe_add(weighted_token_value)?; - highest_tier_spot_liablity = - min(highest_tier_spot_liablity, spot_market.asset_tier); + safest_tier_spot_liablity = + min(safest_tier_spot_liablity, spot_market.asset_tier); num_spot_liabilities += 1; if let Some(margin_buffer_ratio) = margin_buffer_ratio { @@ -431,8 +431,8 @@ pub fn calculate_margin_requirement_and_total_collateral_and_liability_info( )?; margin_requirement = margin_requirement.safe_add(weighted_token_value)?; - highest_tier_spot_liablity = - min(highest_tier_spot_liablity, spot_market.asset_tier); + safest_tier_spot_liablity = + min(safest_tier_spot_liablity, spot_market.asset_tier); if let Some(margin_buffer_ratio) = margin_buffer_ratio { margin_requirement_plus_buffer = margin_requirement_plus_buffer.safe_add( @@ -449,8 +449,8 @@ pub fn calculate_margin_requirement_and_total_collateral_and_liability_info( } Ordering::Equal => { if spot_position.has_open_order() { - highest_tier_spot_liablity = - min(highest_tier_spot_liablity, spot_market.asset_tier); + safest_tier_spot_liablity = + min(safest_tier_spot_liablity, spot_market.asset_tier); num_spot_liabilities += 1; } } @@ -532,7 +532,7 @@ pub fn calculate_margin_requirement_and_total_collateral_and_liability_info( || market_position.has_open_order() { num_perp_liabilities += 1; - highest_tier_perp_liablity = min(highest_tier_perp_liablity, market.contract_tier); + safest_tier_perp_liablity = min(safest_tier_perp_liablity, market.contract_tier); } with_isolated_liability &= @@ -556,8 +556,8 @@ pub fn calculate_margin_requirement_and_total_collateral_and_liability_info( all_oracles_valid, num_of_liabilities, with_isolated_liability, - highest_tier_spot_liablity, - highest_tier_perp_liablity, + safest_tier_spot_liablity, + safest_tier_perp_liablity, )) } From b3768bc7f24af786401184116226002567ad51aa Mon Sep 17 00:00:00 2001 From: 0xbigz <83473873+0xbigz@users.noreply.github.com> Date: Wed, 22 Feb 2023 14:21:56 -0500 Subject: [PATCH 04/14] add basic rust test --- programs/drift/src/controller/liquidation.rs | 13 +- .../drift/src/controller/liquidation/tests.rs | 398 +++++++++++++++++- 2 files changed, 404 insertions(+), 7 deletions(-) diff --git a/programs/drift/src/controller/liquidation.rs b/programs/drift/src/controller/liquidation.rs index 033fc3c01..64d0298b0 100644 --- a/programs/drift/src/controller/liquidation.rs +++ b/programs/drift/src/controller/liquidation.rs @@ -1907,12 +1907,13 @@ pub fn liquidate_perp_pnl_for_deposit( asset_transfer ); return Err(ErrorCode::InvalidLiquidation); - } else { - if contract_tier > safest_tier_perp_liability - || safest_tier_spot_liability > AssetTier::default() - { - return Err(ErrorCode::TierViolationLiquidatingPerpPnl); - } + } + + if contract_tier > safest_tier_perp_liability + || safest_tier_spot_liability > AssetTier::default() + { + msg!("liquidating contract tier={:?} is riskier than outstanding {:?} & {:?}", contract_tier, safest_tier_perp_liability, safest_tier_spot_liability); + return Err(ErrorCode::TierViolationLiquidatingPerpPnl); } validate_transfer_satisfies_limit_price( diff --git a/programs/drift/src/controller/liquidation/tests.rs b/programs/drift/src/controller/liquidation/tests.rs index 3700dec79..afb03be72 100644 --- a/programs/drift/src/controller/liquidation/tests.rs +++ b/programs/drift/src/controller/liquidation/tests.rs @@ -4044,7 +4044,7 @@ pub mod liquidate_perp_pnl_for_deposit { use crate::state::oracle::HistoricalOracleData; use crate::state::oracle::OracleSource; use crate::state::oracle_map::OracleMap; - use crate::state::perp_market::{MarketStatus, PerpMarket, AMM}; + use crate::state::perp_market::{ContractTier, MarketStatus, PerpMarket, AMM}; use crate::state::perp_market_map::PerpMarketMap; use crate::state::spot_market::{SpotBalanceType, SpotMarket}; use crate::state::spot_market_map::SpotMarketMap; @@ -5166,6 +5166,402 @@ pub mod liquidate_perp_pnl_for_deposit { assert_eq!(user.liquidation_start_slot, 0); assert_eq!(user.liquidation_margin_freed, 0); } + + #[test] + pub fn failure_due_to_asset_tier_violation() { + let now = 0_i64; + let slot = 0_u64; + + let mut sol_oracle_price = get_pyth_price(100, 6); + let sol_oracle_price_key = + Pubkey::from_str("J83w4HKfqxwcq3BEMMkPFSppX3gqekLyLJBexebFVkix").unwrap(); + let pyth_program = crate::ids::pyth_program::id(); + create_account_info!( + sol_oracle_price, + &sol_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: sol_oracle_price_key, + ..AMM::default() + }, + margin_ratio_initial: 1000, + margin_ratio_maintenance: 500, + unrealized_pnl_initial_asset_weight: 9000, + unrealized_pnl_maintenance_asset_weight: 10000, + number_of_users_with_base: 1, + status: MarketStatus::Initialized, + liquidator_fee: LIQUIDATION_FEE_PRECISION / 100, + ..PerpMarket::default() + }; + create_anchor_account_info!(market, PerpMarket, market_account_info); + let market_map = PerpMarketMap::load_one(&market_account_info, true).unwrap(); + + let mut usdc_market = SpotMarket { + market_index: 0, + oracle_source: OracleSource::QuoteAsset, + cumulative_deposit_interest: SPOT_CUMULATIVE_INTEREST_PRECISION, + decimals: 6, + initial_asset_weight: SPOT_WEIGHT_PRECISION, + maintenance_asset_weight: SPOT_WEIGHT_PRECISION, + deposit_balance: 200 * SPOT_BALANCE_PRECISION, + liquidator_fee: 0, + ..SpotMarket::default() + }; + create_anchor_account_info!(usdc_market, SpotMarket, usdc_spot_market_account_info); + let mut sol_market = SpotMarket { + market_index: 1, + oracle_source: OracleSource::Pyth, + oracle: sol_oracle_price_key, + cumulative_deposit_interest: SPOT_CUMULATIVE_INTEREST_PRECISION, + cumulative_borrow_interest: SPOT_CUMULATIVE_INTEREST_PRECISION, + decimals: 6, + initial_asset_weight: 8 * SPOT_WEIGHT_PRECISION / 10, + maintenance_asset_weight: 9 * SPOT_WEIGHT_PRECISION / 10, + initial_liability_weight: 12 * SPOT_WEIGHT_PRECISION / 10, + maintenance_liability_weight: 11 * SPOT_WEIGHT_PRECISION / 10, + deposit_balance: SPOT_BALANCE_PRECISION, + borrow_balance: 0, + liquidator_fee: LIQUIDATION_FEE_PRECISION / 1000, + historical_oracle_data: HistoricalOracleData { + last_oracle_price_twap: (sol_oracle_price.agg.price * 99 / 100), + ..HistoricalOracleData::default() + }, + ..SpotMarket::default() + }; + create_anchor_account_info!(sol_market, SpotMarket, sol_spot_market_account_info); + let spot_market_account_infos = Vec::from([ + &usdc_spot_market_account_info, + &sol_spot_market_account_info, + ]); + let spot_market_map = + SpotMarketMap::load_multiple(spot_market_account_infos, true).unwrap(); + + let mut spot_positions = [SpotPosition::default(); 8]; + spot_positions[0] = SpotPosition { + market_index: 0, + balance_type: SpotBalanceType::Deposit, + scaled_balance: 200 * SPOT_BALANCE_PRECISION_U64, + ..SpotPosition::default() + }; + spot_positions[1] = SpotPosition { + market_index: 1, + balance_type: SpotBalanceType::Borrow, + scaled_balance: SPOT_BALANCE_PRECISION_U64, + ..SpotPosition::default() + }; + let mut user = User { + orders: [Order::default(); 32], + perp_positions: get_positions(PerpPosition { + market_index: 0, + quote_asset_amount: -100 * QUOTE_PRECISION_I64, + ..PerpPosition::default() + }), + spot_positions, + ..User::default() + }; + + let mut liquidator = User { + spot_positions: get_spot_positions(SpotPosition { + market_index: 0, + balance_type: SpotBalanceType::Deposit, + scaled_balance: 100 * SPOT_BALANCE_PRECISION_U64, + ..SpotPosition::default() + }), + ..User::default() + }; + + let user_key = Pubkey::default(); + let liquidator_key = Pubkey::default(); + + assert!(liquidate_perp_pnl_for_deposit( + 0, + 0, + 50 * 10_u128.pow(6), // .8 + None, + &mut user, + &user_key, + &mut liquidator, + &liquidator_key, + &market_map, + &spot_market_map, + &mut oracle_map, + now, + slot, + 10, + PERCENTAGE_PRECISION, + 150, + ) + .is_err()); + + // assert_eq!(user.spot_positions[0].scaled_balance, 494445000); + // assert_eq!(user.perp_positions[0].quote_asset_amount, -50000000); + + // assert_eq!( + // liquidator.spot_positions[1].balance_type, + // SpotBalanceType::Deposit + // ); + // assert_eq!(liquidator.spot_positions[1].scaled_balance, 505555000); + // assert_eq!(liquidator.perp_positions[0].quote_asset_amount, -50000000); + } + + #[test] + pub fn failure_due_to_contract_tier_violation() { + let now = 0_i64; + let slot = 0_u64; + + let mut sol_oracle_price = get_pyth_price(100, 6); + let sol_oracle_price_key = + Pubkey::from_str("J83w4HKfqxwcq3BEMMkPFSppX3gqekLyLJBexebFVkix").unwrap(); + let pyth_program = crate::ids::pyth_program::id(); + create_account_info!( + sol_oracle_price, + &sol_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: sol_oracle_price_key, + ..AMM::default() + }, + margin_ratio_initial: 1000, + margin_ratio_maintenance: 500, + unrealized_pnl_initial_asset_weight: 9000, + unrealized_pnl_maintenance_asset_weight: 10000, + number_of_users_with_base: 1, + status: MarketStatus::Initialized, + liquidator_fee: LIQUIDATION_FEE_PRECISION / 100, + market_index: 0, + contract_tier: ContractTier::A, + ..PerpMarket::default() + }; + create_anchor_account_info!(market, PerpMarket, market_account_info); + + let mut bonk_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: 8000, + 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: sol_oracle_price_key, + ..AMM::default() + }, + margin_ratio_initial: 1000, + margin_ratio_maintenance: 500, + unrealized_pnl_initial_asset_weight: 9000, + unrealized_pnl_maintenance_asset_weight: 10000, + number_of_users_with_base: 1, + status: MarketStatus::Initialized, + liquidator_fee: LIQUIDATION_FEE_PRECISION / 100, + contract_tier: ContractTier::Speculative, + market_index: 1, + ..PerpMarket::default() + }; + create_anchor_account_info!(bonk_market, PerpMarket, bonk_market_account_info); + + let market_map = PerpMarketMap::load_multiple( + vec![&market_account_info, &bonk_market_account_info], + true, + ) + .unwrap(); + + let mut usdc_market = SpotMarket { + market_index: 0, + oracle_source: OracleSource::QuoteAsset, + cumulative_deposit_interest: SPOT_CUMULATIVE_INTEREST_PRECISION, + decimals: 6, + initial_asset_weight: SPOT_WEIGHT_PRECISION, + maintenance_asset_weight: SPOT_WEIGHT_PRECISION, + deposit_balance: 200 * SPOT_BALANCE_PRECISION, + liquidator_fee: 0, + ..SpotMarket::default() + }; + create_anchor_account_info!(usdc_market, SpotMarket, usdc_spot_market_account_info); + let mut sol_market = SpotMarket { + market_index: 1, + oracle_source: OracleSource::Pyth, + oracle: sol_oracle_price_key, + cumulative_deposit_interest: SPOT_CUMULATIVE_INTEREST_PRECISION, + cumulative_borrow_interest: SPOT_CUMULATIVE_INTEREST_PRECISION, + decimals: 6, + initial_asset_weight: 8 * SPOT_WEIGHT_PRECISION / 10, + maintenance_asset_weight: 9 * SPOT_WEIGHT_PRECISION / 10, + initial_liability_weight: 12 * SPOT_WEIGHT_PRECISION / 10, + maintenance_liability_weight: 11 * SPOT_WEIGHT_PRECISION / 10, + deposit_balance: SPOT_BALANCE_PRECISION, + borrow_balance: 0, + liquidator_fee: LIQUIDATION_FEE_PRECISION / 1000, + historical_oracle_data: HistoricalOracleData { + last_oracle_price_twap: (sol_oracle_price.agg.price * 99 / 100), + ..HistoricalOracleData::default() + }, + ..SpotMarket::default() + }; + create_anchor_account_info!(sol_market, SpotMarket, sol_spot_market_account_info); + let spot_market_account_infos = Vec::from([ + &usdc_spot_market_account_info, + &sol_spot_market_account_info, + ]); + let spot_market_map = + SpotMarketMap::load_multiple(spot_market_account_infos, true).unwrap(); + + let mut spot_positions = [SpotPosition::default(); 8]; + spot_positions[0] = SpotPosition { + market_index: 0, + balance_type: SpotBalanceType::Deposit, + scaled_balance: 200 * SPOT_BALANCE_PRECISION_U64, + ..SpotPosition::default() + }; + spot_positions[1] = SpotPosition { + market_index: 1, + balance_type: SpotBalanceType::Deposit, + scaled_balance: SPOT_BALANCE_PRECISION_U64/1000, + ..SpotPosition::default() + }; + let mut user = User { + orders: [Order::default(); 32], + perp_positions: get_positions(PerpPosition { + market_index: 0, + quote_asset_amount: -100 * QUOTE_PRECISION_I64, + ..PerpPosition::default() + }), + spot_positions, + ..User::default() + }; + + user.perp_positions[1] = PerpPosition { + market_index: 1, + quote_asset_amount: -150 * QUOTE_PRECISION_I64, + ..PerpPosition::default() + }; + + let mut liquidator = User { + spot_positions: get_spot_positions(SpotPosition { + market_index: 0, + balance_type: SpotBalanceType::Deposit, + scaled_balance: 100 * SPOT_BALANCE_PRECISION_U64, + ..SpotPosition::default() + }), + ..User::default() + }; + + let user_key = Pubkey::default(); + let liquidator_key = Pubkey::default(); + + assert!(liquidate_perp_pnl_for_deposit( + 1, + 0, + 50 * 10_u128.pow(6), // .8 + None, + &mut user, + &user_key, + &mut liquidator, + &liquidator_key, + &market_map, + &spot_market_map, + &mut oracle_map, + now, + slot, + 10, + PERCENTAGE_PRECISION, + 150, + ) + .is_err()); + + liquidate_perp_pnl_for_deposit( + 0, + 0, + 5000 * 10_u128.pow(6), + None, + &mut user, + &user_key, + &mut liquidator, + &liquidator_key, + &market_map, + &spot_market_map, + &mut oracle_map, + now, + slot, + 10, + PERCENTAGE_PRECISION, + 150, + ) + .unwrap(); + + liquidate_perp_pnl_for_deposit( + 1, + 0, + 50 * 10_u128.pow(6), // .8 + None, + &mut user, + &user_key, + &mut liquidator, + &liquidator_key, + &market_map, + &spot_market_map, + &mut oracle_map, + now, + slot, + 10, + PERCENTAGE_PRECISION, + 150, + ) + .unwrap(); + + + assert_eq!(user.spot_positions[0].scaled_balance, 48484849000); + assert_eq!(user.perp_positions[0].quote_asset_amount, 0); + assert_eq!(user.perp_positions[1].quote_asset_amount, -100000000); + + assert_eq!( + liquidator.spot_positions[1].balance_type, + SpotBalanceType::Deposit + ); + assert_eq!(liquidator.spot_positions[1].scaled_balance, 0); + assert_eq!(liquidator.perp_positions[0].quote_asset_amount, -100000000); + } } pub mod resolve_perp_bankruptcy { From c39dea41836f987d4e908c26c37ae6f6ba01005d Mon Sep 17 00:00:00 2001 From: 0xbigz <83473873+0xbigz@users.noreply.github.com> Date: Thu, 23 Feb 2023 09:49:35 -0500 Subject: [PATCH 05/14] fmt --- programs/drift/src/controller/liquidation.rs | 9 +++++++-- programs/drift/src/controller/liquidation/tests.rs | 3 +-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/programs/drift/src/controller/liquidation.rs b/programs/drift/src/controller/liquidation.rs index 64d0298b0..73caf2d25 100644 --- a/programs/drift/src/controller/liquidation.rs +++ b/programs/drift/src/controller/liquidation.rs @@ -1911,8 +1911,13 @@ pub fn liquidate_perp_pnl_for_deposit( if contract_tier > safest_tier_perp_liability || safest_tier_spot_liability > AssetTier::default() - { - msg!("liquidating contract tier={:?} is riskier than outstanding {:?} & {:?}", contract_tier, safest_tier_perp_liability, safest_tier_spot_liability); + { + msg!( + "liquidating contract tier={:?} is riskier than outstanding {:?} & {:?}", + contract_tier, + safest_tier_perp_liability, + safest_tier_spot_liability + ); return Err(ErrorCode::TierViolationLiquidatingPerpPnl); } diff --git a/programs/drift/src/controller/liquidation/tests.rs b/programs/drift/src/controller/liquidation/tests.rs index afb03be72..37a8e0491 100644 --- a/programs/drift/src/controller/liquidation/tests.rs +++ b/programs/drift/src/controller/liquidation/tests.rs @@ -5457,7 +5457,7 @@ pub mod liquidate_perp_pnl_for_deposit { spot_positions[1] = SpotPosition { market_index: 1, balance_type: SpotBalanceType::Deposit, - scaled_balance: SPOT_BALANCE_PRECISION_U64/1000, + scaled_balance: SPOT_BALANCE_PRECISION_U64 / 1000, ..SpotPosition::default() }; let mut user = User { @@ -5550,7 +5550,6 @@ pub mod liquidate_perp_pnl_for_deposit { ) .unwrap(); - assert_eq!(user.spot_positions[0].scaled_balance, 48484849000); assert_eq!(user.perp_positions[0].quote_asset_amount, 0); assert_eq!(user.perp_positions[1].quote_asset_amount, -100000000); From 0f3271e0167b817f89a908ac1b0f55baa6536301 Mon Sep 17 00:00:00 2001 From: 0xbigz <83473873+0xbigz@users.noreply.github.com> Date: Thu, 23 Feb 2023 12:42:43 -0500 Subject: [PATCH 06/14] separate margin calc w/ safest tier calc --- programs/drift/src/controller/liquidation.rs | 11 ++-- .../drift/src/controller/liquidation/tests.rs | 6 ++- programs/drift/src/math/margin.rs | 52 ++++++++++++------- programs/drift/src/math/margin/tests.rs | 4 +- programs/drift/src/math/orders.rs | 4 +- programs/drift/src/math/orders/tests.rs | 8 +-- 6 files changed, 52 insertions(+), 33 deletions(-) diff --git a/programs/drift/src/controller/liquidation.rs b/programs/drift/src/controller/liquidation.rs index 73caf2d25..41099c338 100644 --- a/programs/drift/src/controller/liquidation.rs +++ b/programs/drift/src/controller/liquidation.rs @@ -38,7 +38,7 @@ use crate::math::liquidation::{ use crate::math::margin::{ calculate_margin_requirement_and_total_collateral, calculate_margin_requirement_and_total_collateral_and_liability_info, - meets_initial_margin_requirement, MarginRequirementType, + calculate_user_safest_position_tiers, meets_initial_margin_requirement, MarginRequirementType, }; use crate::math::oracle::DriftAction; use crate::math::orders::{ @@ -1712,6 +1712,9 @@ pub fn liquidate_perp_pnl_for_deposit( ) }; + let (safest_tier_spot_liability, safest_tier_perp_liability) = + calculate_user_safest_position_tiers(user, perp_market_map, spot_market_map)?; + let ( margin_requirement, total_collateral, @@ -1719,8 +1722,6 @@ pub fn liquidate_perp_pnl_for_deposit( _all_oracles_valid, _, _, - safest_tier_spot_liability, - safest_tier_perp_liability, ) = calculate_margin_requirement_and_total_collateral_and_liability_info( user, perp_market_map, @@ -1909,8 +1910,8 @@ pub fn liquidate_perp_pnl_for_deposit( return Err(ErrorCode::InvalidLiquidation); } - if contract_tier > safest_tier_perp_liability - || safest_tier_spot_liability > AssetTier::default() + if safest_tier_perp_liability < contract_tier + || safest_tier_spot_liability < AssetTier::default() { msg!( "liquidating contract tier={:?} is riskier than outstanding {:?} & {:?}", diff --git a/programs/drift/src/controller/liquidation/tests.rs b/programs/drift/src/controller/liquidation/tests.rs index 37a8e0491..4a7bd71b1 100644 --- a/programs/drift/src/controller/liquidation/tests.rs +++ b/programs/drift/src/controller/liquidation/tests.rs @@ -4046,7 +4046,7 @@ pub mod liquidate_perp_pnl_for_deposit { use crate::state::oracle_map::OracleMap; use crate::state::perp_market::{ContractTier, MarketStatus, PerpMarket, AMM}; use crate::state::perp_market_map::PerpMarketMap; - use crate::state::spot_market::{SpotBalanceType, SpotMarket}; + use crate::state::spot_market::{AssetTier, SpotBalanceType, SpotMarket}; use crate::state::spot_market_map::SpotMarketMap; use crate::state::user::{Order, PerpPosition, SpotPosition, User}; use crate::test_utils::*; @@ -5223,6 +5223,7 @@ pub mod liquidate_perp_pnl_for_deposit { maintenance_asset_weight: SPOT_WEIGHT_PRECISION, deposit_balance: 200 * SPOT_BALANCE_PRECISION, liquidator_fee: 0, + asset_tier: AssetTier::Collateral, ..SpotMarket::default() }; create_anchor_account_info!(usdc_market, SpotMarket, usdc_spot_market_account_info); @@ -5244,6 +5245,7 @@ pub mod liquidate_perp_pnl_for_deposit { last_oracle_price_twap: (sol_oracle_price.agg.price * 99 / 100), ..HistoricalOracleData::default() }, + asset_tier: AssetTier::Collateral, ..SpotMarket::default() }; create_anchor_account_info!(sol_market, SpotMarket, sol_spot_market_account_info); @@ -5509,6 +5511,7 @@ pub mod liquidate_perp_pnl_for_deposit { 150, ) .is_err()); + assert_eq!(user.perp_positions[0].quote_asset_amount, -100000000); liquidate_perp_pnl_for_deposit( 0, @@ -5529,6 +5532,7 @@ pub mod liquidate_perp_pnl_for_deposit { 150, ) .unwrap(); + assert_eq!(user.perp_positions[0].quote_asset_amount, 0); liquidate_perp_pnl_for_deposit( 1, diff --git a/programs/drift/src/math/margin.rs b/programs/drift/src/math/margin.rs index 2f3021085..6ad262497 100644 --- a/programs/drift/src/math/margin.rs +++ b/programs/drift/src/math/margin.rs @@ -240,6 +240,37 @@ pub fn calculate_perp_position_value_and_pnl( )) } +pub fn calculate_user_safest_position_tiers( + user: &User, + perp_market_map: &PerpMarketMap, + spot_market_map: &SpotMarketMap, +) -> DriftResult<(AssetTier, ContractTier)> { + let mut safest_tier_spot_liablity: AssetTier = AssetTier::default(); + let mut safest_tier_perp_liablity: ContractTier = ContractTier::default(); + + for spot_position in user.spot_positions.iter() { + if spot_position.scaled_balance == 0 && spot_position.open_orders == 0 { + continue; + } + let spot_market = spot_market_map.get_ref(&spot_position.market_index)?; + safest_tier_spot_liablity = min(safest_tier_spot_liablity, spot_market.asset_tier); + } + + for market_position in user.perp_positions.iter() { + if market_position.base_asset_amount == 0 + && market_position.quote_asset_amount == 0 + && !market_position.has_open_order() + && !market_position.is_lp() + { + continue; + } + let market = &perp_market_map.get_ref(&market_position.market_index)?; + safest_tier_perp_liablity = min(safest_tier_perp_liablity, market.contract_tier); + } + + Ok((safest_tier_spot_liablity, safest_tier_perp_liablity)) +} + pub fn calculate_margin_requirement_and_total_collateral_and_liability_info( user: &User, perp_market_map: &PerpMarketMap, @@ -248,7 +279,7 @@ pub fn calculate_margin_requirement_and_total_collateral_and_liability_info( oracle_map: &mut OracleMap, margin_buffer_ratio: Option, strict: bool, -) -> DriftResult<(u128, i128, u128, bool, u8, bool, AssetTier, ContractTier)> { +) -> DriftResult<(u128, i128, u128, bool, u8, bool)> { let mut total_collateral: i128 = 0; let mut margin_requirement: u128 = 0; let mut margin_requirement_plus_buffer: u128 = 0; @@ -256,8 +287,6 @@ pub fn calculate_margin_requirement_and_total_collateral_and_liability_info( let mut num_spot_liabilities: u8 = 0; let mut num_perp_liabilities: u8 = 0; let mut with_isolated_liability: bool = false; - let mut safest_tier_spot_liablity: AssetTier = AssetTier::default(); - let mut safest_tier_perp_liablity: ContractTier = ContractTier::default(); let user_custom_margin_ratio = if margin_requirement_type == MarginRequirementType::Initial { user.max_margin_ratio @@ -319,8 +348,6 @@ pub fn calculate_margin_requirement_and_total_collateral_and_liability_info( )?; margin_requirement = margin_requirement.safe_add(weighted_token_value)?; - safest_tier_spot_liablity = - min(safest_tier_spot_liablity, spot_market.asset_tier); num_spot_liabilities += 1; if let Some(margin_buffer_ratio) = margin_buffer_ratio { @@ -431,8 +458,6 @@ pub fn calculate_margin_requirement_and_total_collateral_and_liability_info( )?; margin_requirement = margin_requirement.safe_add(weighted_token_value)?; - safest_tier_spot_liablity = - min(safest_tier_spot_liablity, spot_market.asset_tier); if let Some(margin_buffer_ratio) = margin_buffer_ratio { margin_requirement_plus_buffer = margin_requirement_plus_buffer.safe_add( @@ -449,8 +474,6 @@ pub fn calculate_margin_requirement_and_total_collateral_and_liability_info( } Ordering::Equal => { if spot_position.has_open_order() { - safest_tier_spot_liablity = - min(safest_tier_spot_liablity, spot_market.asset_tier); num_spot_liabilities += 1; } } @@ -532,7 +555,6 @@ pub fn calculate_margin_requirement_and_total_collateral_and_liability_info( || market_position.has_open_order() { num_perp_liabilities += 1; - safest_tier_perp_liablity = min(safest_tier_perp_liablity, market.contract_tier); } with_isolated_liability &= @@ -556,8 +578,6 @@ pub fn calculate_margin_requirement_and_total_collateral_and_liability_info( all_oracles_valid, num_of_liabilities, with_isolated_liability, - safest_tier_spot_liablity, - safest_tier_perp_liablity, )) } @@ -576,8 +596,6 @@ pub fn calculate_margin_requirement_and_total_collateral( all_oracles_valid, _, _, - _, - _, ) = calculate_margin_requirement_and_total_collateral_and_liability_info( user, perp_market_map, @@ -611,8 +629,6 @@ pub fn meets_withdraw_margin_requirement( oracles_valid, num_of_liabilities, includes_isolated_liability, - _, - _, ) = calculate_margin_requirement_and_total_collateral_and_liability_info( user, perp_market_map, @@ -672,8 +688,6 @@ pub fn meets_place_order_margin_requirement( _, num_of_liabilities, includes_isolated_liability, - _, - _, ) = calculate_margin_requirement_and_total_collateral_and_liability_info( user, perp_market_map, @@ -764,7 +778,7 @@ pub fn calculate_max_withdrawable_amount( spot_market_map: &SpotMarketMap, oracle_map: &mut OracleMap, ) -> DriftResult { - let (margin_requirement, total_collateral, _, _, num_of_liabilities, _, _, _) = + let (margin_requirement, total_collateral, _, _, num_of_liabilities, _) = calculate_margin_requirement_and_total_collateral_and_liability_info( user, perp_market_map, diff --git a/programs/drift/src/math/margin/tests.rs b/programs/drift/src/math/margin/tests.rs index adfb3abf7..7557169bb 100644 --- a/programs/drift/src/math/margin/tests.rs +++ b/programs/drift/src/math/margin/tests.rs @@ -1351,7 +1351,7 @@ mod calculate_margin_requirement_and_total_collateral_and_liability_info { ..User::default() }; - let (margin_requirement, _, _, _, num_of_liabilities, _, _, _) = + let (margin_requirement, _, _, _, num_of_liabilities, _) = calculate_margin_requirement_and_total_collateral_and_liability_info( &user, &perp_market_map, @@ -1463,7 +1463,7 @@ mod calculate_margin_requirement_and_total_collateral_and_liability_info { ..User::default() }; - let (margin_requirement, _, _, _, num_of_liabilities, _, _, _) = + let (margin_requirement, _, _, _, num_of_liabilities, _) = calculate_margin_requirement_and_total_collateral_and_liability_info( &user, &perp_market_map, diff --git a/programs/drift/src/math/orders.rs b/programs/drift/src/math/orders.rs index 7bc5c8656..d92bf6b3e 100644 --- a/programs/drift/src/math/orders.rs +++ b/programs/drift/src/math/orders.rs @@ -684,7 +684,7 @@ pub fn calculate_max_perp_order_size( oracle_map: &mut OracleMap, ) -> DriftResult { // calculate initial margin requirement - let (margin_requirement, total_collateral, _, _, _, _, _, _) = + let (margin_requirement, total_collateral, _, _, _, _) = calculate_margin_requirement_and_total_collateral_and_liability_info( user, perp_market_map, @@ -779,7 +779,7 @@ pub fn calculate_max_spot_order_size( oracle_map: &mut OracleMap, ) -> DriftResult { // calculate initial margin requirement - let (margin_requirement, total_collateral, _, _, _, _, _, _) = + let (margin_requirement, total_collateral, _, _, _, _) = calculate_margin_requirement_and_total_collateral_and_liability_info( user, perp_market_map, diff --git a/programs/drift/src/math/orders/tests.rs b/programs/drift/src/math/orders/tests.rs index ba1523f10..ba03583a4 100644 --- a/programs/drift/src/math/orders/tests.rs +++ b/programs/drift/src/math/orders/tests.rs @@ -1543,7 +1543,7 @@ mod calculate_max_spot_order_size { user.spot_positions[1].open_orders = 1; user.spot_positions[1].open_bids = max_order_size as i64; - let (margin_requirement, total_collateral, _, _, _, _, _, _) = + let (margin_requirement, total_collateral, _, _, _, _) = calculate_margin_requirement_and_total_collateral_and_liability_info( &user, &PerpMarketMap::empty(), @@ -1737,7 +1737,7 @@ mod calculate_max_spot_order_size { user.spot_positions[1].open_orders = 1; user.spot_positions[1].open_asks = -(max_order_size as i64); - let (margin_requirement, total_collateral, _, _, _, _, _, _) = + let (margin_requirement, total_collateral, _, _, _, _) = calculate_margin_requirement_and_total_collateral_and_liability_info( &user, &PerpMarketMap::empty(), @@ -1973,7 +1973,7 @@ mod calculate_max_perp_order_size { user.perp_positions[0].open_orders = 1; user.perp_positions[0].open_bids = max_order_size as i64; - let (margin_requirement, total_collateral, _, _, _, _, _, _) = + let (margin_requirement, total_collateral, _, _, _, _) = calculate_margin_requirement_and_total_collateral_and_liability_info( &user, &market_map, @@ -2189,7 +2189,7 @@ mod calculate_max_perp_order_size { user.perp_positions[0].open_orders = 1; user.perp_positions[0].open_asks = -(max_order_size as i64); - let (margin_requirement, total_collateral, _, _, _, _, _, _) = + let (margin_requirement, total_collateral, _, _, _, _) = calculate_margin_requirement_and_total_collateral_and_liability_info( &user, &market_map, From 8101a594a19f9ff04e310daff329c390acfc9656 Mon Sep 17 00:00:00 2001 From: 0xbigz <83473873+0xbigz@users.noreply.github.com> Date: Thu, 23 Feb 2023 15:32:21 -0500 Subject: [PATCH 07/14] finish asset tier test, fix liability only spot bug --- .../drift/src/controller/liquidation/tests.rs | 94 ++++++++++++++++--- programs/drift/src/controller/spot_balance.rs | 1 + programs/drift/src/math/margin.rs | 4 +- 3 files changed, 85 insertions(+), 14 deletions(-) diff --git a/programs/drift/src/controller/liquidation/tests.rs b/programs/drift/src/controller/liquidation/tests.rs index 4a7bd71b1..ee1e87b0f 100644 --- a/programs/drift/src/controller/liquidation/tests.rs +++ b/programs/drift/src/controller/liquidation/tests.rs @@ -4027,7 +4027,7 @@ pub mod liquidate_perp_pnl_for_deposit { use anchor_lang::Owner; use solana_program::pubkey::Pubkey; - use crate::controller::liquidation::liquidate_perp_pnl_for_deposit; + use crate::controller::liquidation::{liquidate_perp_pnl_for_deposit, liquidate_spot}; use crate::create_account_info; use crate::create_anchor_account_info; use crate::error::ErrorCode; @@ -4048,7 +4048,7 @@ pub mod liquidate_perp_pnl_for_deposit { use crate::state::perp_market_map::PerpMarketMap; use crate::state::spot_market::{AssetTier, SpotBalanceType, SpotMarket}; use crate::state::spot_market_map::SpotMarketMap; - use crate::state::user::{Order, PerpPosition, SpotPosition, User}; + use crate::state::user::{Order, PerpPosition, SpotPosition, User, UserStatus}; use crate::test_utils::*; use crate::test_utils::{get_positions, get_pyth_price, get_spot_positions}; @@ -5224,6 +5224,12 @@ pub mod liquidate_perp_pnl_for_deposit { deposit_balance: 200 * SPOT_BALANCE_PRECISION, liquidator_fee: 0, asset_tier: AssetTier::Collateral, + historical_oracle_data: HistoricalOracleData { + last_oracle_price_twap: PRICE_PRECISION as i64, + last_oracle_price_twap_5min: PRICE_PRECISION as i64, + + ..HistoricalOracleData::default() + }, ..SpotMarket::default() }; create_anchor_account_info!(usdc_market, SpotMarket, usdc_spot_market_account_info); @@ -5238,11 +5244,13 @@ pub mod liquidate_perp_pnl_for_deposit { maintenance_asset_weight: 9 * SPOT_WEIGHT_PRECISION / 10, initial_liability_weight: 12 * SPOT_WEIGHT_PRECISION / 10, maintenance_liability_weight: 11 * SPOT_WEIGHT_PRECISION / 10, - deposit_balance: SPOT_BALANCE_PRECISION, - borrow_balance: 0, + deposit_balance: 10 * SPOT_BALANCE_PRECISION, + borrow_balance: SPOT_BALANCE_PRECISION, liquidator_fee: LIQUIDATION_FEE_PRECISION / 1000, historical_oracle_data: HistoricalOracleData { last_oracle_price_twap: (sol_oracle_price.agg.price * 99 / 100), + last_oracle_price_twap_5min: (sol_oracle_price.agg.price * 99 / 100), + ..HistoricalOracleData::default() }, asset_tier: AssetTier::Collateral, @@ -5284,7 +5292,7 @@ pub mod liquidate_perp_pnl_for_deposit { spot_positions: get_spot_positions(SpotPosition { market_index: 0, balance_type: SpotBalanceType::Deposit, - scaled_balance: 100 * SPOT_BALANCE_PRECISION_U64, + scaled_balance: 2500 * SPOT_BALANCE_PRECISION_U64, ..SpotPosition::default() }), ..User::default() @@ -5313,15 +5321,75 @@ pub mod liquidate_perp_pnl_for_deposit { ) .is_err()); - // assert_eq!(user.spot_positions[0].scaled_balance, 494445000); - // assert_eq!(user.perp_positions[0].quote_asset_amount, -50000000); + liquidate_spot( + 0, + 1, + 10_u128.pow(9), + None, + &mut user, + &user_key, + &mut liquidator, + &liquidator_key, + &market_map, + &spot_market_map, + &mut oracle_map, + now, + slot, + 10, + PERCENTAGE_PRECISION, + 150, + ) + .unwrap(); + + assert_eq!(user.spot_positions[1].scaled_balance, 0); + + liquidate_perp_pnl_for_deposit( + 0, + 0, + 50 * 10_u128.pow(6), // .8 + None, + &mut user, + &user_key, + &mut liquidator, + &liquidator_key, + &market_map, + &spot_market_map, + &mut oracle_map, + now, + slot, + 10, + PERCENTAGE_PRECISION, + 150, + ) + .unwrap(); + assert_eq!(user.perp_positions[0].quote_asset_amount, -50000000); + assert_eq!(user.spot_positions[0].scaled_balance, 49394850000); // <$50 + assert_eq!(user.status, UserStatus::BeingLiquidated); + + liquidate_perp_pnl_for_deposit( + 0, + 0, + 50 * 10_u128.pow(6), // .8 + None, + &mut user, + &user_key, + &mut liquidator, + &liquidator_key, + &market_map, + &spot_market_map, + &mut oracle_map, + now, + slot, + 10, + PERCENTAGE_PRECISION, + 150, + ) + .unwrap(); + assert_eq!(user.spot_positions[0].scaled_balance, 0); + assert_eq!(user.spot_positions[1].scaled_balance, 0); - // assert_eq!( - // liquidator.spot_positions[1].balance_type, - // SpotBalanceType::Deposit - // ); - // assert_eq!(liquidator.spot_positions[1].scaled_balance, 505555000); - // assert_eq!(liquidator.perp_positions[0].quote_asset_amount, -50000000); + assert_eq!(user.perp_positions[0].quote_asset_amount, -1099099); + assert_eq!(user.status, UserStatus::Bankrupt); } #[test] diff --git a/programs/drift/src/controller/spot_balance.rs b/programs/drift/src/controller/spot_balance.rs index b59ad86be..ddc4769a5 100644 --- a/programs/drift/src/controller/spot_balance.rs +++ b/programs/drift/src/controller/spot_balance.rs @@ -439,6 +439,7 @@ fn decrease_spot_balance( spot_market: &mut SpotMarket, balance_type: &SpotBalanceType, ) -> DriftResult { + msg!("{:?} vs {:?}", spot_market.borrow_balance, delta); match balance_type { SpotBalanceType::Deposit => { spot_market.deposit_balance = spot_market.deposit_balance.safe_sub(delta)? diff --git a/programs/drift/src/math/margin.rs b/programs/drift/src/math/margin.rs index 6ad262497..6ad189c59 100644 --- a/programs/drift/src/math/margin.rs +++ b/programs/drift/src/math/margin.rs @@ -249,7 +249,9 @@ pub fn calculate_user_safest_position_tiers( let mut safest_tier_perp_liablity: ContractTier = ContractTier::default(); for spot_position in user.spot_positions.iter() { - if spot_position.scaled_balance == 0 && spot_position.open_orders == 0 { + if spot_position.scaled_balance == 0 && spot_position.open_orders == 0 + || spot_position.balance_type == SpotBalanceType::Deposit + { continue; } let spot_market = spot_market_map.get_ref(&spot_position.market_index)?; From 825d2f370df0dfaed415f6e2912c9e67732e64a6 Mon Sep 17 00:00:00 2001 From: 0xbigz <83473873+0xbigz@users.noreply.github.com> Date: Fri, 24 Feb 2023 11:00:18 -0500 Subject: [PATCH 08/14] contract tier function impl, incorp other cleanup feedback p1 --- programs/drift/src/controller/liquidation.rs | 35 ++++++++++--------- programs/drift/src/controller/spot_balance.rs | 1 - programs/drift/src/math/margin.rs | 18 +++------- programs/drift/src/state/perp_market.rs | 23 ++++++++++-- 4 files changed, 43 insertions(+), 34 deletions(-) diff --git a/programs/drift/src/controller/liquidation.rs b/programs/drift/src/controller/liquidation.rs index 41099c338..f925b881b 100644 --- a/programs/drift/src/controller/liquidation.rs +++ b/programs/drift/src/controller/liquidation.rs @@ -56,7 +56,7 @@ use crate::state::events::{ use crate::state::oracle_map::OracleMap; use crate::state::perp_market::MarketStatus; use crate::state::perp_market_map::PerpMarketMap; -use crate::state::spot_market::{AssetTier, SpotBalanceType}; +use crate::state::spot_market::SpotBalanceType; use crate::state::spot_market_map::SpotMarketMap; use crate::state::state::State; use crate::state::user::{MarketType, Order, OrderStatus, OrderType, User, UserStats}; @@ -1712,9 +1712,6 @@ pub fn liquidate_perp_pnl_for_deposit( ) }; - let (safest_tier_spot_liability, safest_tier_perp_liability) = - calculate_user_safest_position_tiers(user, perp_market_map, spot_market_map)?; - let ( margin_requirement, total_collateral, @@ -1824,6 +1821,24 @@ pub fn liquidate_perp_pnl_for_deposit( (total_collateral, margin_requirement_plus_buffer) }; + let (safest_tier_spot_liability, safest_tier_perp_liability) = + calculate_user_safest_position_tiers(user, perp_market_map, spot_market_map)?; + msg!( + "liquidating contract tier={:?} pnl is riskier than outstanding {:?} & {:?}", + contract_tier, + safest_tier_perp_liability, + safest_tier_spot_liability + ); + if !(contract_tier.is_as_safe_as(&safest_tier_perp_liability, &safest_tier_spot_liability)) { + msg!( + "liquidating contract tier={:?} pnl is riskier than outstanding {:?} & {:?}", + contract_tier, + safest_tier_perp_liability, + safest_tier_spot_liability + ); + return Err(ErrorCode::TierViolationLiquidatingPerpPnl); + } + let margin_shortage = calculate_margin_shortage( intermediate_margin_requirement_with_buffer, intermediate_total_collateral, @@ -1910,18 +1925,6 @@ pub fn liquidate_perp_pnl_for_deposit( return Err(ErrorCode::InvalidLiquidation); } - if safest_tier_perp_liability < contract_tier - || safest_tier_spot_liability < AssetTier::default() - { - msg!( - "liquidating contract tier={:?} is riskier than outstanding {:?} & {:?}", - contract_tier, - safest_tier_perp_liability, - safest_tier_spot_liability - ); - return Err(ErrorCode::TierViolationLiquidatingPerpPnl); - } - validate_transfer_satisfies_limit_price( asset_transfer, pnl_transfer, diff --git a/programs/drift/src/controller/spot_balance.rs b/programs/drift/src/controller/spot_balance.rs index ddc4769a5..b59ad86be 100644 --- a/programs/drift/src/controller/spot_balance.rs +++ b/programs/drift/src/controller/spot_balance.rs @@ -439,7 +439,6 @@ fn decrease_spot_balance( spot_market: &mut SpotMarket, balance_type: &SpotBalanceType, ) -> DriftResult { - msg!("{:?} vs {:?}", spot_market.borrow_balance, delta); match balance_type { SpotBalanceType::Deposit => { spot_market.deposit_balance = spot_market.deposit_balance.safe_sub(delta)? diff --git a/programs/drift/src/math/margin.rs b/programs/drift/src/math/margin.rs index 6ad189c59..ac6c6f694 100644 --- a/programs/drift/src/math/margin.rs +++ b/programs/drift/src/math/margin.rs @@ -249,9 +249,7 @@ pub fn calculate_user_safest_position_tiers( let mut safest_tier_perp_liablity: ContractTier = ContractTier::default(); for spot_position in user.spot_positions.iter() { - if spot_position.scaled_balance == 0 && spot_position.open_orders == 0 - || spot_position.balance_type == SpotBalanceType::Deposit - { + if spot_position.is_available() || spot_position.balance_type == SpotBalanceType::Deposit { continue; } let spot_market = spot_market_map.get_ref(&spot_position.market_index)?; @@ -259,11 +257,7 @@ pub fn calculate_user_safest_position_tiers( } for market_position in user.perp_positions.iter() { - if market_position.base_asset_amount == 0 - && market_position.quote_asset_amount == 0 - && !market_position.has_open_order() - && !market_position.is_lp() - { + if market_position.is_available() { continue; } let market = &perp_market_map.get_ref(&market_position.market_index)?; @@ -299,7 +293,7 @@ pub fn calculate_margin_requirement_and_total_collateral_and_liability_info( for spot_position in user.spot_positions.iter() { validation::position::validate_spot_position(spot_position)?; - if spot_position.scaled_balance == 0 && spot_position.open_orders == 0 { + if spot_position.is_available() { continue; } @@ -511,11 +505,7 @@ pub fn calculate_margin_requirement_and_total_collateral_and_liability_info( } for market_position in user.perp_positions.iter() { - if market_position.base_asset_amount == 0 - && market_position.quote_asset_amount == 0 - && !market_position.has_open_order() - && !market_position.is_lp() - { + if market_position.is_available() { continue; } diff --git a/programs/drift/src/state/perp_market.rs b/programs/drift/src/state/perp_market.rs index 3bbf43724..a09f5e88a 100644 --- a/programs/drift/src/state/perp_market.rs +++ b/programs/drift/src/state/perp_market.rs @@ -22,7 +22,7 @@ use crate::math::safe_math::SafeMath; use crate::math::stats; use crate::state::oracle::{HistoricalOracleData, OracleSource}; -use crate::state::spot_market::{SpotBalance, SpotBalanceType}; +use crate::state::spot_market::{AssetTier, SpotBalance, SpotBalanceType}; use crate::state::traits::{MarketIndexOffset, Size}; use crate::{AMM_TO_QUOTE_PRECISION_RATIO, PRICE_PRECISION}; use borsh::{BorshDeserialize, BorshSerialize}; @@ -67,10 +67,27 @@ pub enum ContractTier { Isolated, // no insurance, only single position allowed } -impl Default for ContractTier { - fn default() -> Self { +impl ContractTier { + pub fn default() -> Self { ContractTier::Speculative } + + pub fn is_as_safe_as(&self, best_contract: &ContractTier, best_asset: &AssetTier) -> bool { + self.is_as_safe_as_contract(best_contract) && self.is_as_safe_as_asset(best_asset) + } + + pub fn is_as_safe_as_contract(&self, other: &ContractTier) -> bool { + // Contract Tier A safest + self <= other + } + pub fn is_as_safe_as_asset(&self, other: &AssetTier) -> bool { + // allow Contract Tier A,B,C to rank above Assets below Collateral status + if other == &AssetTier::Unlisted { + true + } else { + other >= &AssetTier::Cross && self <= &ContractTier::C + } + } } #[account(zero_copy)] From 80a59e52356ab70dd18f217dd0c953275445ad3d Mon Sep 17 00:00:00 2001 From: 0xbigz <83473873+0xbigz@users.noreply.github.com> Date: Fri, 24 Feb 2023 13:55:56 -0500 Subject: [PATCH 09/14] tests/liquidatePerpPnlForDeposit.ts: fix for new tier rules --- programs/drift/src/controller/liquidation.rs | 6 -- tests/liquidatePerpPnlForDeposit.ts | 74 +++++++++++++++----- 2 files changed, 57 insertions(+), 23 deletions(-) diff --git a/programs/drift/src/controller/liquidation.rs b/programs/drift/src/controller/liquidation.rs index f925b881b..7fbb333bf 100644 --- a/programs/drift/src/controller/liquidation.rs +++ b/programs/drift/src/controller/liquidation.rs @@ -1823,12 +1823,6 @@ pub fn liquidate_perp_pnl_for_deposit( let (safest_tier_spot_liability, safest_tier_perp_liability) = calculate_user_safest_position_tiers(user, perp_market_map, spot_market_map)?; - msg!( - "liquidating contract tier={:?} pnl is riskier than outstanding {:?} & {:?}", - contract_tier, - safest_tier_perp_liability, - safest_tier_spot_liability - ); if !(contract_tier.is_as_safe_as(&safest_tier_perp_liability, &safest_tier_spot_liability)) { msg!( "liquidating contract tier={:?} pnl is riskier than outstanding {:?} & {:?}", diff --git a/tests/liquidatePerpPnlForDeposit.ts b/tests/liquidatePerpPnlForDeposit.ts index 609a6947f..37af0a5ad 100644 --- a/tests/liquidatePerpPnlForDeposit.ts +++ b/tests/liquidatePerpPnlForDeposit.ts @@ -17,6 +17,7 @@ import { EventSubscriber, OracleGuardRails, LIQUIDATION_PCT_PRECISION, + convertToNumber, } from '../sdk/src'; import { @@ -31,6 +32,7 @@ import { printTxLogs, } from './testHelpers'; import { BulkAccountLoader, isVariant } from '../sdk'; +import { QUOTE_PRECISION } from '@drift-labs/sdk'; describe('liquidate perp pnl for deposit', () => { const provider = anchor.AnchorProvider.local(undefined, { @@ -76,7 +78,7 @@ describe('liquidate perp pnl for deposit', () => { provider, // @ts-ignore provider.wallet, - ZERO + new BN(5 * 10 ** 9) ); solOracle = await mockOracle(1); @@ -144,28 +146,19 @@ describe('liquidate perp pnl for deposit', () => { await driftClient.updateOracleGuardRails(oracleGuardRails); await driftClient.openPosition( - PositionDirection.LONG, + PositionDirection.SHORT, new BN(10).mul(BASE_PRECISION), 0, new BN(0) ); - await setFeedPrice(anchor.workspace.Pyth, 0.1, solOracle); - await driftClient.moveAmmToPrice( - 0, - new BN(1).mul(PRICE_PRECISION).div(new BN(10)) - ); - - const txSig = await driftClient.closePosition(0); - printTxLogs(connection, txSig); - const solAmount = new BN(1 * 10 ** 9); [liquidatorDriftClient, liquidatorDriftClientWSOLAccount] = await createUserWithUSDCAndWSOLAccount( provider, usdcMint, chProgram, - solAmount, + solAmount.mul(new BN(2000)), usdcAmount, [0], [0, 1], @@ -181,7 +174,7 @@ describe('liquidate perp pnl for deposit', () => { const spotMarketIndex = 1; await liquidatorDriftClient.deposit( - solAmount, + solAmount.mul(new BN(1000)), spotMarketIndex, liquidatorDriftClientWSOLAccount ); @@ -197,13 +190,49 @@ describe('liquidate perp pnl for deposit', () => { it('liquidate', async () => { await setFeedPrice(anchor.workspace.Pyth, 50, solOracle); + await driftClient.updateInitialPctToLiquidate( + LIQUIDATION_PCT_PRECISION.toNumber() + ); + await driftClient.updateLiquidationDuration(1); + + const txSig0 = await liquidatorDriftClient.liquidatePerp( + await driftClient.getUserAccountPublicKey(), + driftClient.getUserAccount(), + 0, + new BN(175).mul(BASE_PRECISION).div(new BN(10)) + ); + + await printTxLogs(connection, txSig0); + + try { + await liquidatorDriftClient.liquidatePerpPnlForDeposit( + await driftClient.getUserAccountPublicKey(), + driftClient.getUserAccount(), + 0, + 0, + usdcAmount.mul(new BN(100)) + ); + } catch (e) { + console.log('FAILED to perp pnl settle before paying off borrow'); + // console.error(e); + } + + // pay off borrow first (and withdraw all excess in attempt to full pay) + await driftClient.deposit(new BN(5.02 * 10 ** 8), 1, userWSOLAccount); + // await driftClient.withdraw(new BN(1 * 10 ** 8), 1, userWSOLAccount, true); + await driftClient.fetchAccounts(); + + // const u = driftClient.getUserAccount(); + // console.log(u.spotPositions[0]); + // console.log(u.spotPositions[1]); + // console.log(u.perpPositions[0]); const txSig = await liquidatorDriftClient.liquidatePerpPnlForDeposit( await driftClient.getUserAccountPublicKey(), driftClient.getUserAccount(), 0, 0, - usdcAmount.mul(new BN(100)) + usdcAmount.mul(new BN(600)) ); const computeUnits = await findComputeUnitConsumption( @@ -219,13 +248,24 @@ describe('liquidate perp pnl for deposit', () => { .logMessages ); - // assert(driftClient.getUserAccount().isBeingLiquidated); - assert(isVariant(driftClient.getUserAccount().status, 'bankrupt')); + console.log('user status:', driftClient.getUserAccount().status); + console.log( + 'user collateral:', + convertToNumber( + driftClient.getUser().getTotalCollateral(), + QUOTE_PRECISION + ) + ); + // assert(isVariant(driftClient.getUserAccount().status, 'bankrupt')); + assert(isVariant(driftClient.getUserAccount().status, 'beingLiquidated')); assert(driftClient.getUserAccount().nextLiquidationId === 2); assert( driftClient.getUserAccount().spotPositions[0].scaledBalance.eq(ZERO) ); + assert( + driftClient.getUserAccount().spotPositions[1].scaledBalance.gt(ZERO) + ); const liquidationRecord = eventSubscriber.getEventsArray('LiquidationRecord')[0]; @@ -243,7 +283,7 @@ describe('liquidate perp pnl for deposit', () => { console.log(liquidationRecord.liquidatePerpPnlForDeposit.pnlTransfer); assert( liquidationRecord.liquidatePerpPnlForDeposit.pnlTransfer.eq( - new BN(9011005) + new BN(10000000) ) ); assert( From 87586dfbda0bb3fac737762dde78e21d4abeb925 Mon Sep 17 00:00:00 2001 From: 0xbigz <83473873+0xbigz@users.noreply.github.com> Date: Fri, 24 Feb 2023 14:16:35 -0500 Subject: [PATCH 10/14] liqudiation.rs: return early instead of erring on contract tier violation if orders got cancelled --- programs/drift/src/controller/liquidation.rs | 27 +++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/programs/drift/src/controller/liquidation.rs b/programs/drift/src/controller/liquidation.rs index 7fbb333bf..81eda51af 100644 --- a/programs/drift/src/controller/liquidation.rs +++ b/programs/drift/src/controller/liquidation.rs @@ -1756,6 +1756,10 @@ pub fn liquidate_perp_pnl_for_deposit( None, )?; + let (safest_tier_spot_liability, safest_tier_perp_liability) = + calculate_user_safest_position_tiers(user, perp_market_map, spot_market_map)?; + let is_contract_tier_violation = + !(contract_tier.is_as_safe_as(&safest_tier_perp_liability, &safest_tier_spot_liability)); // check if user exited liquidation territory let (intermediate_total_collateral, intermediate_margin_requirement_with_buffer) = if !canceled_order_ids.is_empty() { @@ -1781,9 +1785,10 @@ pub fn liquidate_perp_pnl_for_deposit( .cast::()?; user.increment_margin_freed(margin_freed)?; - if intermediate_total_collateral - >= intermediate_margin_requirement_plus_buffer.cast()? - { + let exiting_liq_territory = intermediate_total_collateral + >= intermediate_margin_requirement_plus_buffer.cast()?; + + if exiting_liq_territory || is_contract_tier_violation { let market = perp_market_map.get_ref(&perp_market_index)?; let market_oracle_price = oracle_map.get_price_data(&market.amm.oracle)?.price; @@ -1809,7 +1814,17 @@ pub fn liquidate_perp_pnl_for_deposit( ..LiquidationRecord::default() }); - user.exit_liquidation(); + if exiting_liq_territory { + user.exit_liquidation(); + } else if is_contract_tier_violation { + msg!( + "return early after cancel orders: liquidating contract tier={:?} pnl is riskier than outstanding {:?} & {:?}", + contract_tier, + safest_tier_perp_liability, + safest_tier_spot_liability + ); + } + return Ok(()); } @@ -1821,9 +1836,7 @@ pub fn liquidate_perp_pnl_for_deposit( (total_collateral, margin_requirement_plus_buffer) }; - let (safest_tier_spot_liability, safest_tier_perp_liability) = - calculate_user_safest_position_tiers(user, perp_market_map, spot_market_map)?; - if !(contract_tier.is_as_safe_as(&safest_tier_perp_liability, &safest_tier_spot_liability)) { + if is_contract_tier_violation { msg!( "liquidating contract tier={:?} pnl is riskier than outstanding {:?} & {:?}", contract_tier, From 90ce5eb253b06121f548e630322f2f0ea01c0cb4 Mon Sep 17 00:00:00 2001 From: 0xbigz <83473873+0xbigz@users.noreply.github.com> Date: Fri, 24 Feb 2023 14:47:57 -0500 Subject: [PATCH 11/14] add tests for asset tier, revert liqPerpPnl margin check to top level one --- programs/drift/src/controller/liquidation.rs | 25 ++++----- programs/drift/src/math/margin/tests.rs | 56 +++++++++++++++++++- 2 files changed, 63 insertions(+), 18 deletions(-) diff --git a/programs/drift/src/controller/liquidation.rs b/programs/drift/src/controller/liquidation.rs index 81eda51af..eff833818 100644 --- a/programs/drift/src/controller/liquidation.rs +++ b/programs/drift/src/controller/liquidation.rs @@ -1712,22 +1712,15 @@ pub fn liquidate_perp_pnl_for_deposit( ) }; - let ( - margin_requirement, - total_collateral, - margin_requirement_plus_buffer, - _all_oracles_valid, - _, - _, - ) = calculate_margin_requirement_and_total_collateral_and_liability_info( - user, - perp_market_map, - MarginRequirementType::Maintenance, - spot_market_map, - oracle_map, - Some(liquidation_margin_buffer_ratio as u128), - false, - )?; + let (margin_requirement, total_collateral, margin_requirement_plus_buffer, _) = + calculate_margin_requirement_and_total_collateral( + user, + perp_market_map, + MarginRequirementType::Maintenance, + spot_market_map, + oracle_map, + Some(liquidation_margin_buffer_ratio as u128), + )?; if !user.is_being_liquidated() && total_collateral >= margin_requirement.cast()? { return Err(ErrorCode::SufficientCollateral); diff --git a/programs/drift/src/math/margin/tests.rs b/programs/drift/src/math/margin/tests.rs index 7557169bb..1b92b8f7a 100644 --- a/programs/drift/src/math/margin/tests.rs +++ b/programs/drift/src/math/margin/tests.rs @@ -16,10 +16,62 @@ mod test { calculate_base_asset_value_and_pnl_with_oracle_price, calculate_position_pnl, }; use crate::state::oracle::OraclePriceData; - use crate::state::perp_market::{PerpMarket, AMM}; - use crate::state::spot_market::{SpotBalanceType, SpotMarket}; + use crate::state::perp_market::{ContractTier, PerpMarket, AMM}; + use crate::state::spot_market::{AssetTier, SpotBalanceType, SpotMarket}; use crate::state::user::{PerpPosition, SpotPosition, User}; + #[test] + fn asset_tier_checks() { + // first is as safe or safer + assert!(ContractTier::A.is_as_safe_as(&ContractTier::A, &AssetTier::default())); + assert!(ContractTier::A.is_as_safe_as(&ContractTier::A, &AssetTier::Cross)); + assert!(ContractTier::B.is_as_safe_as(&ContractTier::default(), &AssetTier::default())); + assert!(ContractTier::C.is_as_safe_as(&ContractTier::Speculative, &AssetTier::Unlisted)); + assert!(ContractTier::C.is_as_safe_as(&ContractTier::C, &AssetTier::Cross)); + assert!(ContractTier::Speculative + .is_as_safe_as(&ContractTier::Speculative, &AssetTier::Unlisted)); + assert!( + ContractTier::Speculative.is_as_safe_as(&ContractTier::Isolated, &AssetTier::Unlisted) + ); + assert!(ContractTier::Speculative + .is_as_safe_as(&ContractTier::default(), &AssetTier::default())); + assert!(ContractTier::Isolated.is_as_safe_as(&ContractTier::Isolated, &AssetTier::Unlisted)); + + // one (or more) of the candidates are safer + assert!(!ContractTier::A.is_as_safe_as(&ContractTier::A, &AssetTier::Collateral)); + assert!(!ContractTier::A.is_as_safe_as(&ContractTier::B, &AssetTier::Collateral)); + assert!(!ContractTier::B.is_as_safe_as(&ContractTier::A, &AssetTier::Collateral)); + assert!(!ContractTier::B.is_as_safe_as(&ContractTier::A, &AssetTier::default())); + assert!(!ContractTier::C.is_as_safe_as(&ContractTier::B, &AssetTier::Cross)); + assert!(!ContractTier::C.is_as_safe_as(&ContractTier::B, &AssetTier::Isolated)); + assert!(!ContractTier::C.is_as_safe_as(&ContractTier::A, &AssetTier::default())); + assert!(!ContractTier::Speculative.is_as_safe_as(&ContractTier::A, &AssetTier::default())); + assert!(!ContractTier::Speculative.is_as_safe_as(&ContractTier::A, &AssetTier::Collateral)); + assert!(!ContractTier::Speculative.is_as_safe_as(&ContractTier::B, &AssetTier::Collateral)); + assert!(!ContractTier::Speculative.is_as_safe_as(&ContractTier::B, &AssetTier::Cross)); + assert!(!ContractTier::Speculative.is_as_safe_as(&ContractTier::C, &AssetTier::Collateral)); + assert!(!ContractTier::Speculative + .is_as_safe_as(&ContractTier::Speculative, &AssetTier::Collateral)); + assert!( + !ContractTier::Speculative.is_as_safe_as(&ContractTier::Speculative, &AssetTier::Cross) + ); + assert!(!ContractTier::Speculative + .is_as_safe_as(&ContractTier::Isolated, &AssetTier::Collateral)); + assert!( + !ContractTier::Speculative.is_as_safe_as(&ContractTier::Isolated, &AssetTier::Cross) + ); + assert!( + !ContractTier::Speculative.is_as_safe_as(&ContractTier::Isolated, &AssetTier::Isolated) + ); + assert!(!ContractTier::Isolated.is_as_safe_as(&ContractTier::A, &AssetTier::default())); + assert!( + !ContractTier::Isolated.is_as_safe_as(&ContractTier::Isolated, &AssetTier::Isolated) + ); + assert!( + !ContractTier::Isolated.is_as_safe_as(&ContractTier::default(), &AssetTier::default()) + ); + } + #[test] fn spot_market_asset_weight() { let mut spot_market = SpotMarket { From f93ceb1c150c93bf2fa143d97b597f66d6baf858 Mon Sep 17 00:00:00 2001 From: 0xbigz <83473873+0xbigz@users.noreply.github.com> Date: Fri, 24 Feb 2023 15:04:37 -0500 Subject: [PATCH 12/14] cleanup --- programs/drift/src/controller/liquidation.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/programs/drift/src/controller/liquidation.rs b/programs/drift/src/controller/liquidation.rs index eff833818..a69996e49 100644 --- a/programs/drift/src/controller/liquidation.rs +++ b/programs/drift/src/controller/liquidation.rs @@ -36,9 +36,8 @@ use crate::math::liquidation::{ validate_transfer_satisfies_limit_price, LiquidationMultiplierType, }; use crate::math::margin::{ - calculate_margin_requirement_and_total_collateral, - calculate_margin_requirement_and_total_collateral_and_liability_info, - calculate_user_safest_position_tiers, meets_initial_margin_requirement, MarginRequirementType, + calculate_margin_requirement_and_total_collateral, calculate_user_safest_position_tiers, + meets_initial_margin_requirement, MarginRequirementType, }; use crate::math::oracle::DriftAction; use crate::math::orders::{ From 20aa6d7bf4764c9a699745647a6aec93ff935181 Mon Sep 17 00:00:00 2001 From: Chris Heaney Date: Fri, 24 Feb 2023 15:37:11 -0500 Subject: [PATCH 13/14] fix import --- tests/liquidatePerpPnlForDeposit.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/liquidatePerpPnlForDeposit.ts b/tests/liquidatePerpPnlForDeposit.ts index 37af0a5ad..2a3e76ce6 100644 --- a/tests/liquidatePerpPnlForDeposit.ts +++ b/tests/liquidatePerpPnlForDeposit.ts @@ -31,8 +31,7 @@ import { initializeSolSpotMarket, printTxLogs, } from './testHelpers'; -import { BulkAccountLoader, isVariant } from '../sdk'; -import { QUOTE_PRECISION } from '@drift-labs/sdk'; +import { BulkAccountLoader, isVariant, QUOTE_PRECISION } from '../sdk'; describe('liquidate perp pnl for deposit', () => { const provider = anchor.AnchorProvider.local(undefined, { From 88202ef1cb51f62cf4e064e494c3cf6d9796a5be Mon Sep 17 00:00:00 2001 From: Chris Heaney Date: Fri, 24 Feb 2023 15:38:31 -0500 Subject: [PATCH 14/14] CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d5e412b3b..b5ba9bb85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Features +- program: account for contract tier in liquidate_perp_pnl_for_deposit ([#368](https://github.com/drift-labs/protocol-v2/pull/368)) - program: simplifications for order fills ([#370](https://github.com/drift-labs/protocol-v2/pull/370)) - program: block atomic fills ([#369](https://github.com/drift-labs/protocol-v2/pull/369)) - program: allow limit orders to go through auction ([#355](https://github.com/drift-labs/protocol-v2/pull/355))