Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Features

- program: block amm fills when paused_operations set [#2108](https://github.com/drift-labs/protocol-v2/pull/2108)
- program: remove same slot matching restriction [#2104](https://github.com/drift-labs/protocol-v2/pull/2104)

### Fixes
Expand Down
158 changes: 158 additions & 0 deletions programs/drift/src/controller/orders/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5925,6 +5925,164 @@ pub mod fulfill_order {
assert_eq!(maker_stats.filler_volume_30d, 50251257); // gets filler volume
assert!(maker.orders[0].is_available());
}

#[test]
fn paused_operations_blocks_amm_fill() {
let now = 0_i64;
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,
base_asset_amount_with_amm: -1000000000,
amm_jit_intensity: 100,
max_base_asset_reserve: 200 * AMM_RESERVE_PRECISION,
min_base_asset_reserve: 0,
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,
status: MarketStatus::Initialized,
..PerpMarket::default_test()
};

create_anchor_account_info!(market, PerpMarket, market_account_info);
let market_map = PerpMarketMap::load_one(&market_account_info, true).unwrap();

let mut 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,
historical_oracle_data: HistoricalOracleData::default_price(QUOTE_PRECISION_I64),
..SpotMarket::default()
};
create_anchor_account_info!(spot_market, SpotMarket, spot_market_account_info);
let spot_market_map = SpotMarketMap::load_one(&spot_market_account_info, true).unwrap();

let mut taker = User {
orders: get_orders(Order {
market_index: 0,
status: OrderStatus::Open,
order_type: OrderType::Market,
direction: PositionDirection::Long,
base_asset_amount: BASE_PRECISION_U64,
slot: 0,
auction_start_price: 0,
auction_end_price: 100 * PRICE_PRECISION_I64,
auction_duration: 0,
price: 150 * PRICE_PRECISION_U64,
..Order::default()
}),
perp_positions: get_positions(PerpPosition {
market_index: 0,
open_orders: 1,
open_bids: BASE_PRECISION_I64,
..PerpPosition::default()
}),
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 mut filler = User::default();
let fee_structure = get_fee_structure();
let (taker_key, _, filler_key) = get_user_keys();

let mut taker_stats = UserStats {
paused_operations: 4,
..UserStats::default()
};
let mut filler_stats = UserStats::default();

let order_index = 0;
let min_auction_duration = 0;
let user_can_skip_auction_duration = taker
.can_skip_auction_duration(&taker_stats, false)
.unwrap();
let is_amm_available = get_amm_is_available(
&taker.orders[order_index],
min_auction_duration,
&market,
&mut oracle_map,
slot,
user_can_skip_auction_duration,
);

assert!(!user_can_skip_auction_duration);
assert!(!is_amm_available);

let (base_asset_amount, _) = fulfill_perp_order(
&mut taker,
order_index,
&taker_key,
&mut taker_stats,
&UserMap::empty(),
&UserStatsMap::empty(),
&[],
&mut Some(&mut filler),
&filler_key,
&mut Some(&mut filler_stats),
None,
&spot_market_map,
&market_map,
&mut oracle_map,
&fee_structure,
0,
Some(market.amm.historical_oracle_data.last_oracle_price),
now,
slot,
is_amm_available,
FillMode::Fill,
false,
&mut None,
false,
)
.unwrap();

assert_eq!(base_asset_amount, 0);
assert_eq!(taker.perp_positions[0].base_asset_amount, 0);

let market_after = market_map.get_ref(&0).unwrap();
assert_eq!(market_after.amm.base_asset_amount_with_amm, -1000000000);
}
}

pub mod fill_order {
Expand Down
5 changes: 5 additions & 0 deletions programs/drift/src/state/perp_market.rs
Original file line number Diff line number Diff line change
Expand Up @@ -957,6 +957,11 @@ impl PerpMarket {
let can_fill_order = if can_fill_low_risk {
true
} else {
if !user_can_skip_auction_duration {
msg!("AMM cannot fill order: user has paused operations");
return Ok(false);
}

let oracle_valid_for_can_fill_immediately = is_oracle_valid_for_action(
safe_oracle_validity,
Some(DriftAction::FillOrderAmmImmediate),
Expand Down
96 changes: 95 additions & 1 deletion programs/drift/src/state/perp_market/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,7 @@ mod amm_can_fill_order_tests {
use crate::state::paused_operations::PerpOperation;
use crate::state::perp_market::{PerpMarket, AMM};
use crate::state::state::{State, ValidityGuardRails};
use crate::state::user::{Order, OrderStatus};
use crate::state::user::{Order, OrderStatus, User, UserStats};
use crate::PRICE_PRECISION_I64;

fn base_state() -> State {
Expand Down Expand Up @@ -636,4 +636,98 @@ mod amm_can_fill_order_tests {
.unwrap();
assert!(!can);
}

#[test]
fn paused_blocks_low_risk_path() {
let slot = 15;
let market = base_market();
let state = base_state();

let taker_stats = UserStats {
paused_operations: 4,
..UserStats::default()
};

let user_can_skip = User::default()
.can_skip_auction_duration(&taker_stats, false)
.unwrap();

let oracle_data = OraclePriceData {
price: PRICE_PRECISION_I64,
confidence: 1,
delay: 0,
has_sufficient_number_of_data_points: true,
sequence_id: Some(100),
};

let mm = MMOraclePriceData::new(
PRICE_PRECISION_I64,
0,
100,
OracleValidity::Valid,
oracle_data,
)
.unwrap();

let order = Order {
status: OrderStatus::Open,
slot,
base_asset_amount: 1,
direction: PositionDirection::Long,
..Order::default()
};

let can_fill = market
.amm_can_fill_order(
&order,
slot,
FillMode::Fill,
&state,
OracleValidity::Valid,
user_can_skip,
&mm,
)
.unwrap();

assert!(!can_fill);
}

#[test]
fn paused_blocks_high_risk_path() {
let slot = 15;
let market = base_market();
let state = base_state();
let (mm, _) = mm_oracle_ok_and_as_recent();

let taker_stats = UserStats {
paused_operations: 4,
..UserStats::default()
};

let user_can_skip = User::default()
.can_skip_auction_duration(&taker_stats, false)
.unwrap();

let order = Order {
status: OrderStatus::Open,
slot: slot - 1,
base_asset_amount: 1,
direction: PositionDirection::Long,
..Order::default()
};

let can_fill = market
.amm_can_fill_order(
&order,
slot,
FillMode::Fill,
&state,
OracleValidity::Valid,
user_can_skip,
&mm,
)
.unwrap();

assert!(!can_fill);
}
}
Loading