Skip to content
9 changes: 7 additions & 2 deletions programs/drift/src/math/margin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ pub fn calculate_size_premium_liability_weight(
imf_factor: u32,
liability_weight: u32,
precision: u128,
is_bounded: bool,
) -> DriftResult<u32> {
if imf_factor == 0 {
return Ok(liability_weight);
Expand All @@ -66,8 +67,12 @@ pub fn calculate_size_premium_liability_weight(
)?
.cast::<u32>()?;

let max_liability_weight = max(liability_weight, size_premium_liability_weight);
Ok(max_liability_weight)
if is_bounded {
let max_liability_weight = max(liability_weight, size_premium_liability_weight);
return Ok(max_liability_weight);
}

Ok(size_premium_liability_weight)
}

pub fn calculate_size_discount_asset_weight(
Expand Down
193 changes: 192 additions & 1 deletion programs/drift/src/math/orders/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1956,7 +1956,7 @@ mod calculate_max_perp_order_size {
use crate::state::perp_market_map::PerpMarketMap;
use crate::state::spot_market::{SpotBalanceType, SpotMarket};
use crate::state::spot_market_map::SpotMarketMap;
use crate::state::user::{Order, PerpPosition, SpotPosition, User};
use crate::state::user::{MarginMode, Order, PerpPosition, SpotPosition, User, UserStatus};
use crate::test_utils::get_pyth_price;
use crate::test_utils::*;
use crate::{
Expand Down Expand Up @@ -3160,6 +3160,192 @@ mod calculate_max_perp_order_size {
assert!(total_collateral.unsigned_abs() - margin_requirement < 100 * QUOTE_PRECISION);
}

#[test]
pub fn sol_perp_hlm_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,
high_leverage_margin_ratio_initial: 100,
high_leverage_margin_ratio_maintenance: 66,
imf_factor: 50,
status: MarketStatus::Active,
..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,
margin_mode: MarginMode::HighLeverage,
..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, 4098356557000); // 4098

let mut spot_positions = [SpotPosition::default(); 8];
spot_positions[0] = SpotPosition {
market_index: 0,
balance_type: SpotBalanceType::Deposit,
scaled_balance: 100 * 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,
margin_mode: MarginMode::HighLeverage,
..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, 84737288000); // 84


let mut spot_positions = [SpotPosition::default(); 8];
spot_positions[0] = SpotPosition {
market_index: 0,
balance_type: SpotBalanceType::Deposit,
scaled_balance: 10 * 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,
margin_mode: MarginMode::HighLeverage,
..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, 9605769000); // 9.6

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 < QUOTE_PRECISION);
}

#[test]
pub fn swift_failure() {
let clock_slot = 0_u64;
Expand Down Expand Up @@ -3385,6 +3571,8 @@ mod calculate_max_perp_order_size {
)
.unwrap();

assert_eq!(max_order_size, 1600000);

user.perp_positions[0].open_orders += 1;
user.perp_positions[0].open_bids += max_order_size as i64;

Expand All @@ -3401,6 +3589,9 @@ mod calculate_max_perp_order_size {
)
.unwrap();

assert_eq!(total_collateral.unsigned_abs(), 2199358529); // ~$2200
assert_eq!(margin_requirement, 2186678676);

assert!(total_collateral.unsigned_abs() - margin_requirement < QUOTE_PRECISION);
}
}
Expand Down
99 changes: 82 additions & 17 deletions programs/drift/src/state/perp_market.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use crate::math::constants::{
PERCENTAGE_PRECISION_I64, PERCENTAGE_PRECISION_U64, PRICE_PRECISION, PRICE_PRECISION_I128,
SPOT_WEIGHT_PRECISION, TWENTY_FOUR_HOUR,
};
use crate::math::helpers::get_proportion_u128;
use crate::math::margin::{
calculate_size_discount_asset_weight, calculate_size_premium_liability_weight,
MarginRequirementType,
Expand Down Expand Up @@ -239,6 +240,39 @@ pub struct PerpMarket {
pub padding: [u8; 24],
}

pub fn _calc_high_leverage_mode_initial_margin_ratio_from_size(
pre_size_adj_margin_ratio: u32,
size_adj_margin_ratio: u32,
default_margin_ratio: u32,
) -> DriftResult<u32> {
let result = if size_adj_margin_ratio < pre_size_adj_margin_ratio {
let size_pct_discount_factor = PERCENTAGE_PRECISION.safe_sub(
((pre_size_adj_margin_ratio.cast::<u128>()?)
.safe_sub(size_adj_margin_ratio.cast::<u128>()?)?
.safe_mul(PERCENTAGE_PRECISION)?
.safe_div((pre_size_adj_margin_ratio.safe_div(5)?).cast::<u128>()?)?),
)?;

let hlm_margin_delta = pre_size_adj_margin_ratio
.saturating_sub(default_margin_ratio)
.max(1);

let hlm_margin_delta_proportion = get_proportion_u128(
hlm_margin_delta.cast()?,
size_pct_discount_factor,
PERCENTAGE_PRECISION,
)?
.cast::<u32>()?;
hlm_margin_delta_proportion + default_margin_ratio
} else if size_adj_margin_ratio == pre_size_adj_margin_ratio {
default_margin_ratio
} else {
size_adj_margin_ratio
};

Ok(result)
}

impl Default for PerpMarket {
fn default() -> Self {
PerpMarket {
Expand Down Expand Up @@ -438,18 +472,19 @@ impl PerpMarket {
user_high_leverage_mode: bool,
) -> DriftResult<u32> {
if self.status == MarketStatus::Settlement {
return Ok(0); // no liability weight on size
return Ok(0);
}

let (margin_ratio_initial, margin_ratio_maintenance) =
if user_high_leverage_mode && self.is_high_leverage_mode_enabled() {
(
self.high_leverage_margin_ratio_initial.cast::<u32>()?,
self.high_leverage_margin_ratio_maintenance.cast::<u32>()?,
)
} else {
(self.margin_ratio_initial, self.margin_ratio_maintenance)
};
let is_high_leverage_user = user_high_leverage_mode && self.is_high_leverage_mode_enabled();

let (margin_ratio_initial, margin_ratio_maintenance) = if is_high_leverage_user {
(
self.high_leverage_margin_ratio_initial.cast::<u32>()?,
self.high_leverage_margin_ratio_maintenance.cast::<u32>()?,
)
} else {
(self.margin_ratio_initial, self.margin_ratio_maintenance)
};

let default_margin_ratio = match margin_type {
MarginRequirementType::Initial => margin_ratio_initial,
Expand All @@ -459,14 +494,44 @@ impl PerpMarket {
MarginRequirementType::Maintenance => margin_ratio_maintenance,
};

let size_adj_margin_ratio = calculate_size_premium_liability_weight(
size,
self.imf_factor,
default_margin_ratio,
MARGIN_PRECISION_U128,
)?;
let margin_ratio = if is_high_leverage_user {
// use HLM maintenance margin but ordinary mode initial/fill margin for size adj calculation
let pre_size_adj_margin_ratio = match margin_type {
MarginRequirementType::Initial => self.margin_ratio_initial,
MarginRequirementType::Fill => {
self.margin_ratio_initial
.safe_add(self.margin_ratio_maintenance)?
/ 2
}
MarginRequirementType::Maintenance => margin_ratio_maintenance,
};

let bound_liability_weight = margin_type == MarginRequirementType::Maintenance;

let size_adj_margin_ratio = calculate_size_premium_liability_weight(
size,
self.imf_factor,
pre_size_adj_margin_ratio,
MARGIN_PRECISION_U128,
bound_liability_weight,
)?;

let margin_ratio = default_margin_ratio.max(size_adj_margin_ratio);
_calc_high_leverage_mode_initial_margin_ratio_from_size(
pre_size_adj_margin_ratio,
size_adj_margin_ratio,
default_margin_ratio,
)?
} else {
let size_adj_margin_ratio = calculate_size_premium_liability_weight(
size,
self.imf_factor,
default_margin_ratio,
MARGIN_PRECISION_U128,
true,
)?;

default_margin_ratio.max(size_adj_margin_ratio)
};

Ok(margin_ratio)
}
Expand Down
Loading
Loading