diff --git a/programs/drift/src/math/orders.rs b/programs/drift/src/math/orders.rs index 013563f0bd..b4495afd5c 100644 --- a/programs/drift/src/math/orders.rs +++ b/programs/drift/src/math/orders.rs @@ -809,6 +809,7 @@ pub fn calculate_max_perp_order_size( )?; let user_custom_margin_ratio = user.max_margin_ratio; + let perp_position_margin_ratio = user.perp_positions[position_index].max_margin_ratio as u32; let user_high_leverage_mode = user.is_high_leverage_mode(MarginRequirementType::Initial); let free_collateral_before = total_collateral.safe_sub(margin_requirement.cast()?)?; @@ -838,7 +839,8 @@ pub fn calculate_max_perp_order_size( MarginRequirementType::Initial, user_high_leverage_mode, )? - .max(user_custom_margin_ratio); + .max(user_custom_margin_ratio) + .max(perp_position_margin_ratio); let mut order_size_to_reduce_position = 0_u64; let mut free_collateral_released = 0_i128; @@ -915,7 +917,8 @@ pub fn calculate_max_perp_order_size( MarginRequirementType::Initial, user_high_leverage_mode, )? - .max(user_custom_margin_ratio); + .max(user_custom_margin_ratio) + .max(perp_position_margin_ratio); Ok((new_order_size, new_margin_ratio)) }; diff --git a/programs/drift/src/math/orders/tests.rs b/programs/drift/src/math/orders/tests.rs index 112144451d..52b0e012a8 100644 --- a/programs/drift/src/math/orders/tests.rs +++ b/programs/drift/src/math/orders/tests.rs @@ -2791,6 +2791,252 @@ mod calculate_max_perp_order_size { assert!(total_collateral.unsigned_abs() - margin_requirement < 100 * QUOTE_PRECISION); } + #[test] + pub fn sol_perp_5x_bid_perp_position_margin_ratio() { + 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: 100 * AMM_RESERVE_PRECISION, + bid_quote_asset_reserve: 100 * AMM_RESERVE_PRECISION, + ask_base_asset_reserve: 100 * AMM_RESERVE_PRECISION, + ask_quote_asset_reserve: 100 * 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: 1000, + order_tick_size: 1, + oracle: oracle_price_key, + base_spread: 0, // 1 basis point + historical_oracle_data: HistoricalOracleData { + last_oracle_price: (100 * PRICE_PRECISION) as i64, + last_oracle_price_twap: (100 * PRICE_PRECISION) as i64, + last_oracle_price_twap_5min: (100 * PRICE_PRECISION) as i64, + + ..HistoricalOracleData::default() + }, + ..AMM::default() + }, + margin_ratio_initial: 2000, + margin_ratio_maintenance: 1000, + status: MarketStatus::Initialized, + ..PerpMarket::default_test() + }; + market.amm.max_base_asset_reserve = u128::MAX; + market.amm.min_base_asset_reserve = 0; + + create_anchor_account_info!(market, PerpMarket, market_account_info); + let market_map = PerpMarketMap::load_one(&market_account_info, true).unwrap(); + + let mut usdc_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, + maintenance_asset_weight: SPOT_WEIGHT_PRECISION, + deposit_balance: 10000 * SPOT_BALANCE_PRECISION, + liquidator_fee: 0, + 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!(usdc_spot_market, SpotMarket, usdc_spot_market_account_info); + let spot_market_account_infos = Vec::from([&usdc_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: 10000 * SPOT_BALANCE_PRECISION_U64, + ..SpotPosition::default() + }; + let mut user = User { + orders: [Order::default(); 32], + perp_positions: get_positions(PerpPosition { + market_index: 0, + max_margin_ratio: 2 * MARGIN_PRECISION as u16, + ..PerpPosition::default() + }), + spot_positions, + ..User::default() + }; + + let max_order_size = calculate_max_perp_order_size( + &user, + 0, + 0, + PositionDirection::Long, + &market_map, + &spot_market_map, + &mut oracle_map, + ) + .unwrap(); + + assert_eq!(max_order_size, 49999950000); + + user.perp_positions[0].open_orders = 1; + user.perp_positions[0].open_bids = max_order_size as i64; + + let MarginCalculation { + margin_requirement, + total_collateral, + .. + } = calculate_margin_requirement_and_total_collateral_and_liability_info( + &user, + &market_map, + &spot_market_map, + &mut oracle_map, + MarginContext::standard(MarginRequirementType::Initial).strict(true), + ) + .unwrap(); + + assert_eq!(total_collateral.unsigned_abs(), margin_requirement); + } + + #[test] + pub fn sol_perp_5x_ask_perp_position_margin_ratio() { + 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: 100 * AMM_RESERVE_PRECISION, + bid_quote_asset_reserve: 100 * AMM_RESERVE_PRECISION, + ask_base_asset_reserve: 100 * AMM_RESERVE_PRECISION, + ask_quote_asset_reserve: 100 * 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: 1000, + order_tick_size: 1, + oracle: oracle_price_key, + base_spread: 0, // 1 basis point + historical_oracle_data: HistoricalOracleData { + last_oracle_price: (100 * PRICE_PRECISION) as i64, + last_oracle_price_twap: (100 * PRICE_PRECISION) as i64, + last_oracle_price_twap_5min: (100 * PRICE_PRECISION) as i64, + + ..HistoricalOracleData::default() + }, + ..AMM::default() + }, + margin_ratio_initial: 2000, + margin_ratio_maintenance: 1000, + status: MarketStatus::Initialized, + ..PerpMarket::default_test() + }; + market.amm.max_base_asset_reserve = u128::MAX; + market.amm.min_base_asset_reserve = 0; + + create_anchor_account_info!(market, PerpMarket, market_account_info); + let market_map = PerpMarketMap::load_one(&market_account_info, true).unwrap(); + + let mut usdc_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, + maintenance_asset_weight: SPOT_WEIGHT_PRECISION, + deposit_balance: 10000 * SPOT_BALANCE_PRECISION, + liquidator_fee: 0, + 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!(usdc_spot_market, SpotMarket, usdc_spot_market_account_info); + let spot_market_account_infos = Vec::from([&usdc_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: 10000 * SPOT_BALANCE_PRECISION_U64, + ..SpotPosition::default() + }; + let mut user = User { + orders: [Order::default(); 32], + perp_positions: get_positions(PerpPosition { + market_index: 0, + max_margin_ratio: 2 * MARGIN_PRECISION as u16, + ..PerpPosition::default() + }), + spot_positions, + ..User::default() + }; + + let max_order_size = calculate_max_perp_order_size( + &user, + 0, + 0, + PositionDirection::Short, + &market_map, + &spot_market_map, + &mut oracle_map, + ) + .unwrap(); + + assert_eq!(max_order_size, 49999950000); + + user.perp_positions[0].open_orders = 1; + user.perp_positions[0].open_asks = -(max_order_size as i64); + + let MarginCalculation { + margin_requirement, + total_collateral, + .. + } = calculate_margin_requirement_and_total_collateral_and_liability_info( + &user, + &market_map, + &spot_market_map, + &mut oracle_map, + MarginContext::standard(MarginRequirementType::Initial).strict(true), + ) + .unwrap(); + + assert_eq!(total_collateral.unsigned_abs(), margin_requirement); + } + #[test] pub fn sol_perp_10x_ask_with_imf() { let slot = 0_u64;