Skip to content
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
d71c24a
feat: margin type config for override margin type behavior
LukasDeco Jan 29, 2026
39227c4
feat: tests for margin override
LukasDeco Jan 30, 2026
a16dc8f
feat: idl, anchor tests, more cargo tests
LukasDeco Jan 30, 2026
ea34060
feat: helpful comments
LukasDeco Feb 3, 2026
1cad8ef
fix: logic for user margin ratio evaluated on per position basis
LukasDeco Feb 5, 2026
f8ed48b
chore: use maintenance check on cross account place order req on iso …
LukasDeco Feb 9, 2026
033f9e0
feat: margin type config on transfer isolated + tests
LukasDeco Feb 9, 2026
f675af5
feat: transfer to isolated conditional margin overrides + fulfill_per…
LukasDeco Feb 10, 2026
9504a39
fix: recalc is high lev on each perp position calc margin req
LukasDeco Feb 10, 2026
f85cc94
fix: tests and build errors
LukasDeco Feb 11, 2026
6920c50
fix: cargo fmt
LukasDeco Feb 11, 2026
280bd1a
feat: additional test scenarios
LukasDeco Feb 12, 2026
9a98f9e
fix: using wrong margin type on fulfill perp order margin type config
LukasDeco Feb 12, 2026
0148ca2
fix: anchor tests wrong oracle prices on order margin checks
LukasDeco Feb 14, 2026
51089c9
fix: bug with custom margin ratios
LukasDeco Feb 14, 2026
62ad6e6
fix: incorrect comments on anchor tests w oracle prices
LukasDeco Feb 14, 2026
f353b5d
fix: another anchor test fix
LukasDeco Feb 16, 2026
ea85892
feat: isolated transfer anchor tests
LukasDeco Feb 16, 2026
c31ef21
fix: anchor tests broken for iso transfer
LukasDeco Feb 18, 2026
3ec549a
fix: fulfill order pr feedback how define is isolated flag
LukasDeco Feb 24, 2026
cc8ea34
fix: perp order fulfillment maker is isolated flag
LukasDeco Mar 3, 2026
88f7a74
fix: tests for maker_fills map
LukasDeco Mar 3, 2026
6cab857
Merge branch 'master' into lukas/be-67-isolated-longterm-solution-run…
LukasDeco Mar 16, 2026
351bded
Merge branch 'master' into lukas/be-67-isolated-longterm-solution-run…
LukasDeco Mar 28, 2026
3f145d9
fix: install ts later version for ci
LukasDeco Mar 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,10 @@ cd sdk/ && yarn && yarn build && cd ..

## Running Rust Test

For running cargo tests, you'll need version 1.70. You'll also need Solana CLI version 1.16.27

```bash
rustup override set 1.70
cargo test
```

Expand All @@ -60,11 +63,13 @@ bash test-scripts/run-anchor-tests.sh
We've provided a devcontainer `Dockerfile` to help you spin up a dev environment with the correct versions of Rust, Solana, and Anchor for program development.

Build the container and tag it `drift-dev`:

```
cd .devcontainer && docker build -t drift-dev .
```

Open a shell to the container:

```
# Find the container ID first
docker ps
Expand All @@ -83,6 +88,7 @@ Alternatively use an extension provided by your IDE to make use of the dev conta
```

Use the dev container as you would a local build environment:

```
# build program
anchor build
Expand Down
13 changes: 11 additions & 2 deletions programs/drift/src/controller/isolated_position.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use crate::math::liquidation::is_isolated_margin_being_liquidated;
use crate::math::margin::{validate_spot_margin_trading, MarginRequirementType};
use crate::math::safe_math::SafeMath;
use crate::state::events::{DepositDirection, DepositExplanation, DepositRecord};
use crate::state::margin_calculation::MarginTypeConfig;
use crate::state::oracle_map::OracleMap;
use crate::state::perp_market::MarketStatus;
use crate::state::perp_market_map::PerpMarketMap;
Expand Down Expand Up @@ -234,7 +235,10 @@ pub fn transfer_isolated_perp_position_deposit<'c: 'info, 'info>(
&perp_market_map,
&spot_market_map,
oracle_map,
MarginRequirementType::Initial,
MarginTypeConfig::CrossMarginOverride {
margin_requirement_type: MarginRequirementType::Initial,
default_margin_requirement_type: MarginRequirementType::Maintenance,
},
spot_market_index,
amount as u128,
user_stats,
Expand Down Expand Up @@ -298,7 +302,12 @@ pub fn transfer_isolated_perp_position_deposit<'c: 'info, 'info>(
&perp_market_map,
&spot_market_map,
oracle_map,
MarginRequirementType::Initial,
MarginTypeConfig::IsolatedPositionOverride {
margin_requirement_type: MarginRequirementType::Initial,
default_isolated_margin_requirement_type: MarginRequirementType::Maintenance,
cross_margin_requirement_type: MarginRequirementType::Maintenance,
market_index: perp_market_index,
},
0,
0,
user_stats,
Expand Down
56 changes: 50 additions & 6 deletions programs/drift/src/controller/orders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ use crate::state::events::{emit_stack, get_order_action_record, OrderActionRecor
use crate::state::events::{OrderAction, OrderActionExplanation};
use crate::state::fill_mode::FillMode;
use crate::state::fulfillment::{PerpFulfillmentMethod, SpotFulfillmentMethod};
use crate::state::margin_calculation::{MarginCalculation, MarginContext};
use crate::state::margin_calculation::{MarginCalculation, MarginContext, MarginTypeConfig};
use crate::state::oracle::{OraclePriceData, StrictOraclePrice};
use crate::state::oracle_map::OracleMap;
use crate::state::order_params::{
Expand Down Expand Up @@ -379,12 +379,19 @@ pub fn place_perp_order(

// when orders are placed in bulk, only need to check margin on last place
if options.enforce_margin_check && !options.is_liquidation() {
// if isolated position, use the isolated margin calculation
let isolated_market_index = if user.perp_positions[position_index].is_isolated() {
Some(market_index)
} else {
None
};
meets_place_order_margin_requirement(
user,
perp_market_map,
spot_market_map,
oracle_map,
options.risk_increasing,
isolated_market_index,
)?;
}

Expand Down Expand Up @@ -1954,6 +1961,9 @@ fn fulfill_perp_order(
base_asset_amount
)?;

let perp_position = user.get_perp_position(market_index)?;
let is_isolated_position = perp_position.is_isolated();

if !fill_mode.is_liquidation() {
// if the maker is long, the user sold so
let taker_base_asset_amount_delta = if maker_direction == PositionDirection::Long {
Expand All @@ -1962,13 +1972,29 @@ fn fulfill_perp_order(
-(base_asset_amount as i64)
};

let mut context = MarginContext::standard(if user_order_position_decreasing {
let margin_requirement_type = if user_order_position_decreasing {
MarginRequirementType::Maintenance
} else {
MarginRequirementType::Fill
})
.fuel_perp_delta(market_index, taker_base_asset_amount_delta)
.fuel_numerator(user, now);
};

let margin_type_config = if is_isolated_position {
MarginTypeConfig::IsolatedPositionOverride {
market_index,
margin_requirement_type,
default_isolated_margin_requirement_type: MarginRequirementType::Maintenance,
cross_margin_requirement_type: MarginRequirementType::Maintenance,
}
} else {
MarginTypeConfig::CrossMarginOverride {
margin_requirement_type,
default_margin_requirement_type: MarginRequirementType::Maintenance,
}
};

let mut context = MarginContext::standard_with_config(margin_type_config)
.fuel_perp_delta(market_index, taker_base_asset_amount_delta)
.fuel_numerator(user, now);

if oracle_stale_for_margin && !user_order_position_decreasing {
context = context.margin_ratio_override(MARGIN_PRECISION);
Expand Down Expand Up @@ -2031,7 +2057,23 @@ fn fulfill_perp_order(
market_index,
)?;

let mut context = MarginContext::standard(margin_type)
let maker_perp_position = maker.get_perp_position(market_index)?;

let margin_type_config = if maker_perp_position.is_isolated() {
MarginTypeConfig::IsolatedPositionOverride {
market_index,
margin_requirement_type: margin_type,
default_isolated_margin_requirement_type: MarginRequirementType::Maintenance,
cross_margin_requirement_type: MarginRequirementType::Maintenance,
}
} else {
MarginTypeConfig::CrossMarginOverride {
margin_requirement_type: margin_type,
default_margin_requirement_type: MarginRequirementType::Maintenance,
}
};

let mut context = MarginContext::standard_with_config(margin_type_config)
.fuel_perp_delta(market_index, -maker_base_asset_amount_filled)
.fuel_numerator(&maker, now);

Expand Down Expand Up @@ -2203,6 +2245,7 @@ pub fn fulfill_perp_order_with_amm(
user_stats,
fee_structure,
&MarketType::Perp,
//TODO: add a comment on PR here asking what to do, do we need to check if it's an isolated fill and how we handle HLM in an isolated fill context?
user.is_high_leverage_mode(MarginRequirementType::Initial),
)?;
let (base_asset_amount, limit_price) = calculate_base_asset_amount_for_amm_to_fulfill(
Expand Down Expand Up @@ -3844,6 +3887,7 @@ pub fn place_spot_order(
spot_market_map,
oracle_map,
options.risk_increasing,
None, // no isolated positions for spot positions
)?;
}

Expand Down
81 changes: 61 additions & 20 deletions programs/drift/src/math/margin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use crate::math::oracle::LogMode;
use crate::math::position::calculate_base_asset_value_and_pnl_with_oracle_price;

use crate::math::constants::{MARGIN_PRECISION, PRICE_PRECISION_I128, PRICE_PRECISION_I64};
use crate::state::margin_calculation::MarginTypeConfig;
use crate::validate;
use crate::validation;

Expand Down Expand Up @@ -265,19 +266,22 @@ pub fn calculate_margin_requirement_and_total_collateral_and_liability_info(
context: MarginContext,
) -> DriftResult<MarginCalculation> {
let mut calculation = MarginCalculation::new(context);
let cross_margin_requirement_type = context
.margin_type_config
.get_cross_margin_requirement_type();

let mut user_custom_margin_ratio = if context.margin_type == MarginRequirementType::Initial {
user.max_margin_ratio
} else {
0_u32
};
let mut spot_user_custom_margin_ratio =
if cross_margin_requirement_type == MarginRequirementType::Initial {
user.max_margin_ratio
} else {
0_u32
};

if let Some(margin_ratio_override) = context.margin_ratio_override {
user_custom_margin_ratio = margin_ratio_override.max(user_custom_margin_ratio);
spot_user_custom_margin_ratio = margin_ratio_override.max(spot_user_custom_margin_ratio);
}

let user_pool_id = user.pool_id;
let user_high_leverage_mode = user.is_high_leverage_mode(context.margin_type);

for spot_position in user.spot_positions.iter() {
validation::position::validate_spot_position(spot_position)?;
Expand Down Expand Up @@ -406,12 +410,12 @@ pub fn calculate_margin_requirement_and_total_collateral_and_liability_info(
&spot_market,
&strict_oracle_price,
Some(signed_token_amount),
context.margin_type,
cross_margin_requirement_type,
)?
.apply_user_custom_margin_ratio(
&spot_market,
strict_oracle_price.current,
user_custom_margin_ratio,
spot_user_custom_margin_ratio,
)?;

if worst_case_token_amount == 0 {
Expand Down Expand Up @@ -575,22 +579,47 @@ pub fn calculate_margin_requirement_and_total_collateral_and_liability_info(
Some(LogMode::Margin),
)?;

let perp_position_custom_margin_ratio =
if context.margin_type == MarginRequirementType::Initial {
market_position.max_margin_ratio as u32
let position_margin_type = if market_position.is_isolated() {
context
.margin_type_config
.get_isolated_margin_requirement_type(market_position.market_index)
} else {
context
.margin_type_config
.get_cross_margin_requirement_type()
};

let perp_user_custom_margin_ratio =
if position_margin_type == MarginRequirementType::Initial {
user.max_margin_ratio
} else {
0_u32
};

let mut perp_position_custom_margin_ratio =
if position_margin_type == MarginRequirementType::Initial {
perp_user_custom_margin_ratio.max(market_position.max_margin_ratio as u32)
} else {
0_u32
};

if let Some(margin_ratio_override) = context.margin_ratio_override {
perp_position_custom_margin_ratio =
margin_ratio_override.max(perp_position_custom_margin_ratio);
}

let perp_position_user_high_leverage_mode =
user.is_high_leverage_mode(position_margin_type);

let (perp_margin_requirement, weighted_pnl, worst_case_liability_value, base_asset_value) =
calculate_perp_position_value_and_pnl(
market_position,
market,
oracle_price_data,
&strict_quote_price,
context.margin_type,
user_custom_margin_ratio.max(perp_position_custom_margin_ratio),
user_high_leverage_mode,
position_margin_type,
perp_position_custom_margin_ratio,
perp_position_user_high_leverage_mode,
)?;

calculation.update_fuel_perp_bonus(
Expand Down Expand Up @@ -652,7 +681,7 @@ pub fn calculate_margin_requirement_and_total_collateral_and_liability_info(
);
}

if has_perp_liability || calculation.context.margin_type != MarginRequirementType::Initial {
if has_perp_liability || position_margin_type != MarginRequirementType::Initial {
calculation.update_all_liability_oracles_valid(is_oracle_valid_for_action(
quote_oracle_validity,
Some(DriftAction::MarginCalc),
Expand Down Expand Up @@ -744,19 +773,31 @@ pub fn meets_place_order_margin_requirement(
spot_market_map: &SpotMarketMap,
oracle_map: &mut OracleMap,
risk_increasing: bool,
isolated_market_index: Option<u16>,
) -> DriftResult {
let margin_type = if risk_increasing {
MarginRequirementType::Initial
let margin_type_config = if risk_increasing {
match isolated_market_index {
Some(market_index) => MarginTypeConfig::IsolatedPositionOverride {
market_index,
margin_requirement_type: MarginRequirementType::Initial,
default_isolated_margin_requirement_type: MarginRequirementType::Maintenance,
cross_margin_requirement_type: MarginRequirementType::Maintenance,
},
None => MarginTypeConfig::CrossMarginOverride {
margin_requirement_type: MarginRequirementType::Initial,
default_margin_requirement_type: MarginRequirementType::Maintenance,
},
}
} else {
MarginRequirementType::Maintenance
MarginTypeConfig::Default(MarginRequirementType::Maintenance)
};

let calculation = calculate_margin_requirement_and_total_collateral_and_liability_info(
user,
perp_market_map,
spot_market_map,
oracle_map,
MarginContext::standard(margin_type).strict(true),
MarginContext::standard_with_config(margin_type_config).strict(true),
)?;

if !calculation.meets_margin_requirement() {
Expand Down
Loading
Loading