From 747448d3d9539e4b8d97ee3c9339be32f49daaac Mon Sep 17 00:00:00 2001 From: Chris Heaney Date: Sun, 12 Nov 2023 13:45:06 +0100 Subject: [PATCH 1/2] remove unused use_spread flag in calculate_base_asset_value --- programs/drift/src/math/amm.rs | 1 - programs/drift/src/math/cp_curve.rs | 5 +---- programs/drift/src/math/position.rs | 19 ++++--------------- programs/drift/src/math/repeg.rs | 2 -- 4 files changed, 5 insertions(+), 22 deletions(-) diff --git a/programs/drift/src/math/amm.rs b/programs/drift/src/math/amm.rs index fc96faf01..c5724ef73 100644 --- a/programs/drift/src/math/amm.rs +++ b/programs/drift/src/math/amm.rs @@ -633,7 +633,6 @@ pub fn calculate_quote_asset_amount_swapped( ) -> DriftResult { let mut quote_asset_reserve_change = match swap_direction { SwapDirection::Add => quote_asset_reserve_before.safe_sub(quote_asset_reserve_after)?, - SwapDirection::Remove => quote_asset_reserve_after.safe_sub(quote_asset_reserve_before)?, }; diff --git a/programs/drift/src/math/cp_curve.rs b/programs/drift/src/math/cp_curve.rs index 486f335bd..3f7ec5d34 100644 --- a/programs/drift/src/math/cp_curve.rs +++ b/programs/drift/src/math/cp_curve.rs @@ -179,7 +179,6 @@ pub fn adjust_k_cost( market_clone.amm.base_asset_amount_with_amm, 0, &market_clone.amm, - false, )?; update_k(&mut market_clone, update_k_result)?; @@ -188,7 +187,6 @@ pub fn adjust_k_cost( market_clone.amm.base_asset_amount_with_amm, current_net_market_value, &market_clone.amm, - false, )?; Ok(cost) @@ -203,7 +201,7 @@ pub fn adjust_k_cost_and_update( ) -> DriftResult { // Find the net market value before adjusting k let current_net_market_value = - calculate_base_asset_value(market.amm.base_asset_amount_with_amm, &market.amm, false)?; + calculate_base_asset_value(market.amm.base_asset_amount_with_amm, &market.amm)?; update_k(market, update_k_result)?; @@ -211,7 +209,6 @@ pub fn adjust_k_cost_and_update( market.amm.base_asset_amount_with_amm, current_net_market_value, &market.amm, - false, )?; Ok(cost) diff --git a/programs/drift/src/math/position.rs b/programs/drift/src/math/position.rs index 449503ccb..4c543d478 100644 --- a/programs/drift/src/math/position.rs +++ b/programs/drift/src/math/position.rs @@ -19,37 +19,26 @@ pub fn calculate_base_asset_value_and_pnl( base_asset_amount: i128, quote_asset_amount: u128, amm: &AMM, - use_spread: bool, ) -> DriftResult<(u128, i128)> { if base_asset_amount == 0 { return Ok((0, 0)); } let swap_direction = swap_direction_to_close_position(base_asset_amount); - let base_asset_value = calculate_base_asset_value(base_asset_amount, amm, use_spread)?; + let base_asset_value = calculate_base_asset_value(base_asset_amount, amm)?; let pnl = calculate_pnl(base_asset_value, quote_asset_amount, swap_direction)?; Ok((base_asset_value, pnl)) } -pub fn calculate_base_asset_value( - base_asset_amount: i128, - amm: &AMM, - use_spread: bool, -) -> DriftResult { +pub fn calculate_base_asset_value(base_asset_amount: i128, amm: &AMM) -> DriftResult { if base_asset_amount == 0 { return Ok(0); } let swap_direction = swap_direction_to_close_position(base_asset_amount); - let (base_asset_reserve, quote_asset_reserve) = if use_spread && amm.base_spread > 0 { - match swap_direction { - SwapDirection::Add => (amm.bid_base_asset_reserve, amm.bid_quote_asset_reserve), - SwapDirection::Remove => (amm.ask_base_asset_reserve, amm.ask_quote_asset_reserve), - } - } else { - (amm.base_asset_reserve, amm.quote_asset_reserve) - }; + let (base_asset_reserve, quote_asset_reserve) = + (amm.base_asset_reserve, amm.quote_asset_reserve); let amm_lp_shares = amm.sqrt_k.safe_sub(amm.user_lp_shares)?; diff --git a/programs/drift/src/math/repeg.rs b/programs/drift/src/math/repeg.rs index bbec5c594..6382a1950 100644 --- a/programs/drift/src/math/repeg.rs +++ b/programs/drift/src/math/repeg.rs @@ -175,7 +175,6 @@ pub fn adjust_peg_cost( market_clone.amm.base_asset_amount_with_amm, 0, &market_clone.amm, - false, )?; market_clone.amm.peg_multiplier = new_peg_candidate; @@ -184,7 +183,6 @@ pub fn adjust_peg_cost( market_clone.amm.base_asset_amount_with_amm, current_net_market_value, &market_clone.amm, - false, )?; cost } else { From 66952966006a607de623e3ca06e651570c119125 Mon Sep 17 00:00:00 2001 From: Chris Heaney Date: Sun, 12 Nov 2023 15:06:13 +0100 Subject: [PATCH 2/2] add tests for max order size accounting for imf factor --- programs/drift/src/math/orders.rs | 106 +++--- programs/drift/src/math/orders/tests.rs | 469 +++++++++++++++++++++++- 2 files changed, 525 insertions(+), 50 deletions(-) diff --git a/programs/drift/src/math/orders.rs b/programs/drift/src/math/orders.rs index ee0e27028..c4dbea8df 100644 --- a/programs/drift/src/math/orders.rs +++ b/programs/drift/src/math/orders.rs @@ -825,37 +825,41 @@ pub fn calculate_max_perp_order_size( ); } - let mut order_size = free_collateral - .safe_sub(OPEN_ORDER_MARGIN_REQUIREMENT.cast()?)? - .safe_mul(BASE_PRECISION_I128 / QUOTE_PRECISION_I128)? - .safe_mul(MARGIN_PRECISION_U128.cast()?)? - .safe_div(margin_ratio.cast()?)? - .safe_mul(PRICE_PRECISION_I128)? - .safe_div(oracle_price_data_price.cast()?)? - .safe_mul(PRICE_PRECISION_I128)? - .safe_div(quote_oracle_price.cast()?)? - .cast::()?; - - let updated_margin_ratio = perp_market - .get_margin_ratio( - worst_case_base_asset_amount - .unsigned_abs() - .safe_add(order_size.cast()?)?, - MarginRequirementType::Initial, - )? - .max(user_custom_margin_ratio); - - if updated_margin_ratio != margin_ratio { - order_size = free_collateral + let calculate_order_size_and_margin_ratio = |margin_ratio: u32| { + let new_order_size = free_collateral .safe_sub(OPEN_ORDER_MARGIN_REQUIREMENT.cast()?)? .safe_mul(BASE_PRECISION_I128 / QUOTE_PRECISION_I128)? .safe_mul(MARGIN_PRECISION_U128.cast()?)? - .safe_div(updated_margin_ratio.cast()?)? + .safe_div(margin_ratio.cast()?)? .safe_mul(PRICE_PRECISION_I128)? .safe_div(oracle_price_data_price.cast()?)? .safe_mul(PRICE_PRECISION_I128)? .safe_div(quote_oracle_price.cast()?)? .cast::()?; + + let new_margin_ratio = perp_market + .get_margin_ratio( + worst_case_base_asset_amount + .unsigned_abs() + .safe_add(new_order_size.cast()?)?, + MarginRequirementType::Initial, + )? + .max(user_custom_margin_ratio); + + Ok((new_order_size, new_margin_ratio)) + }; + + let mut order_size = 0_u64; + let mut updated_margin_ratio = margin_ratio; + for _ in 0..6 { + let (new_order_size, new_margin_ratio) = + calculate_order_size_and_margin_ratio(updated_margin_ratio)?; + order_size = new_order_size; + updated_margin_ratio = new_margin_ratio; + + if new_margin_ratio == margin_ratio { + break; + } } standardize_base_asset_amount( @@ -1056,37 +1060,43 @@ pub fn calculate_max_spot_order_size( let precision_increase = 10i128.pow(spot_market.decimals - 6); - let mut order_size = free_collateral - .safe_sub(OPEN_ORDER_MARGIN_REQUIREMENT.cast()?)? - .safe_mul(precision_increase)? - .safe_mul(SPOT_WEIGHT_PRECISION.cast()?)? - .safe_div(free_collateral_delta.cast()?)? - .safe_mul(PRICE_PRECISION_I128)? - .safe_div(max_oracle_price.cast()?)? - .cast::()?; - - // increasing the worst case token amount with new order size may increase margin ration, - // so need to recalculate free collateral delta with updated margin ratio - let updated_free_collateral_delta = calculate_free_collateral_delta_for_spot( - &spot_market, - worst_case_token_amount - .unsigned_abs() - .safe_add(order_size.cast()?)?, - &strict_oracle_price, - direction, - user_custom_liability_weight, - user_custom_asset_weight, - )?; - - if updated_free_collateral_delta != free_collateral_delta { - order_size = free_collateral + let calculate_order_size_and_free_collateral_delta = |free_collateral_delta: u32| { + let new_order_size = free_collateral .safe_sub(OPEN_ORDER_MARGIN_REQUIREMENT.cast()?)? .safe_mul(precision_increase)? .safe_mul(SPOT_WEIGHT_PRECISION.cast()?)? - .safe_div(updated_free_collateral_delta.cast()?)? + .safe_div(free_collateral_delta.cast()?)? .safe_mul(PRICE_PRECISION_I128)? .safe_div(max_oracle_price.cast()?)? .cast::()?; + + // increasing the worst case token amount with new order size may increase margin ratio, + // so need to recalculate free collateral delta with updated margin ratio + let new_free_collateral_delta = calculate_free_collateral_delta_for_spot( + &spot_market, + worst_case_token_amount + .unsigned_abs() + .safe_add(new_order_size.cast()?)?, + &strict_oracle_price, + direction, + user_custom_liability_weight, + user_custom_asset_weight, + )?; + + Ok((new_order_size, new_free_collateral_delta)) + }; + + let mut order_size = 0_u64; + let mut updated_free_collateral_delta = free_collateral_delta; + for _ in 0..6 { + let (new_order_size, new_free_collateral_delta) = + calculate_order_size_and_free_collateral_delta(updated_free_collateral_delta)?; + order_size = new_order_size; + updated_free_collateral_delta = new_free_collateral_delta; + + if updated_free_collateral_delta == free_collateral_delta { + break; + } } standardize_base_asset_amount( diff --git a/programs/drift/src/math/orders/tests.rs b/programs/drift/src/math/orders/tests.rs index f20945deb..e8c05e014 100644 --- a/programs/drift/src/math/orders/tests.rs +++ b/programs/drift/src/math/orders/tests.rs @@ -1061,7 +1061,7 @@ mod calculate_max_spot_order_size { use crate::state::user::{Order, PerpPosition, SpotPosition, User}; use crate::test_utils::get_pyth_price; use crate::test_utils::*; - use crate::{create_account_info, PositionDirection}; + use crate::{create_account_info, PositionDirection, QUOTE_PRECISION, SPOT_IMF_PRECISION}; use crate::{create_anchor_account_info, MARGIN_PRECISION}; #[test] @@ -1691,6 +1691,222 @@ mod calculate_max_spot_order_size { assert_eq!(total_collateral.unsigned_abs(), margin_requirement); } + + #[test] + pub fn usdc_deposit_and_5x_sol_bid_with_imf() { + 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 _market_map = PerpMarketMap::empty(); + + 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::default_quote_oracle(), + ..SpotMarket::default() + }; + create_anchor_account_info!(usdc_spot_market, SpotMarket, usdc_spot_market_account_info); + let mut sol_spot_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: 9, + 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, + imf_factor: SPOT_IMF_PRECISION / 10, + liquidator_fee: LIQUIDATION_FEE_PRECISION / 1000, + historical_oracle_data: HistoricalOracleData { + last_oracle_price_twap_5min: 110 * PRICE_PRECISION_I64, + ..HistoricalOracleData::default() + }, + ..SpotMarket::default() + }; + create_anchor_account_info!(sol_spot_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: 10000 * SPOT_BALANCE_PRECISION_U64, + ..SpotPosition::default() + }; + spot_positions[1] = SpotPosition { + market_index: 1, + balance_type: SpotBalanceType::Deposit, + ..SpotPosition::default() + }; + let mut user = User { + orders: [Order::default(); 32], + perp_positions: [PerpPosition::default(); 8], + spot_positions, + ..User::default() + }; + + let max_order_size = calculate_max_spot_order_size( + &user, + 1, + PositionDirection::Long, + &PerpMarketMap::empty(), + &spot_market_map, + &mut oracle_map, + ) + .unwrap(); + + // assert_eq!(max_order_size, 454545000000); + + user.spot_positions[1].open_orders = 1; + user.spot_positions[1].open_bids = max_order_size as i64; + + let MarginCalculation { + margin_requirement, + total_collateral, + .. + } = calculate_margin_requirement_and_total_collateral_and_liability_info( + &user, + &PerpMarketMap::empty(), + &spot_market_map, + &mut oracle_map, + MarginContext::standard(MarginRequirementType::Initial).strict(true), + ) + .unwrap(); + + assert!(total_collateral.unsigned_abs() - margin_requirement < 100 * QUOTE_PRECISION); + } + + #[test] + pub fn usdc_deposit_and_5x_sol_ask_with_imf() { + 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 _market_map = PerpMarketMap::empty(); + + 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::default_quote_oracle(), + ..SpotMarket::default() + }; + create_anchor_account_info!(usdc_spot_market, SpotMarket, usdc_spot_market_account_info); + let mut sol_spot_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: 9, + 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, + imf_factor: SPOT_IMF_PRECISION / 10, + liquidator_fee: LIQUIDATION_FEE_PRECISION / 1000, + historical_oracle_data: HistoricalOracleData { + last_oracle_price_twap_5min: 110 * PRICE_PRECISION_I64, + ..HistoricalOracleData::default() + }, + ..SpotMarket::default() + }; + create_anchor_account_info!(sol_spot_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: 10000 * SPOT_BALANCE_PRECISION_U64, + ..SpotPosition::default() + }; + spot_positions[1] = SpotPosition { + market_index: 1, + balance_type: SpotBalanceType::Deposit, + ..SpotPosition::default() + }; + let mut user = User { + orders: [Order::default(); 32], + perp_positions: [PerpPosition::default(); 8], + spot_positions, + ..User::default() + }; + + let max_order_size = calculate_max_spot_order_size( + &user, + 1, + PositionDirection::Short, + &PerpMarketMap::empty(), + &spot_market_map, + &mut oracle_map, + ) + .unwrap(); + + assert_eq!(max_order_size, 90927185437); + + user.spot_positions[1].open_orders = 1; + user.spot_positions[1].open_asks = -(max_order_size as i64); + + let MarginCalculation { + margin_requirement, + total_collateral, + .. + } = calculate_margin_requirement_and_total_collateral_and_liability_info( + &user, + &PerpMarketMap::empty(), + &spot_market_map, + &mut oracle_map, + MarginContext::standard(MarginRequirementType::Initial).strict(true), + ) + .unwrap(); + + assert!(total_collateral.unsigned_abs() - margin_requirement < 1000 * QUOTE_PRECISION); + } } mod calculate_max_perp_order_size { @@ -1718,7 +1934,10 @@ mod calculate_max_perp_order_size { use crate::state::user::{Order, PerpPosition, SpotPosition, User}; use crate::test_utils::get_pyth_price; use crate::test_utils::*; - use crate::{create_account_info, PositionDirection, MARGIN_PRECISION, PRICE_PRECISION_I64}; + use crate::{ + create_account_info, PositionDirection, MARGIN_PRECISION, PRICE_PRECISION_I64, + QUOTE_PRECISION, SPOT_IMF_PRECISION, + }; use crate::{ create_anchor_account_info, MarketStatus, AMM_RESERVE_PRECISION, PEG_PRECISION, PRICE_PRECISION, @@ -2423,6 +2642,252 @@ mod calculate_max_perp_order_size { assert_eq!(total_collateral.unsigned_abs(), margin_requirement); } + + #[test] + pub fn sol_perp_10x_bid_with_imf() { + 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: 1000, + margin_ratio_maintenance: 500, + imf_factor: SPOT_IMF_PRECISION / 100, + 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, + ..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, 365897914000); + + 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!(total_collateral.unsigned_abs() - margin_requirement < 100 * QUOTE_PRECISION); + } + + #[test] + pub fn sol_perp_10x_ask_with_imf() { + 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: 1000, + margin_ratio_maintenance: 500, + imf_factor: SPOT_IMF_PRECISION / 100, + 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, + ..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, 365897914000); + + 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!(total_collateral.unsigned_abs() - margin_requirement < 100 * QUOTE_PRECISION); + } } pub mod validate_fill_price_within_price_bands {