Skip to content

Commit 22574d0

Browse files
authored
program: block amm fills when paused_operations set (#2108)
* block amm fills when paused_operations set * add changelog
1 parent 4d4e5a4 commit 22574d0

File tree

4 files changed

+259
-1
lines changed

4 files changed

+259
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Features
1111

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

1415
### Fixes

programs/drift/src/controller/orders/tests.rs

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5925,6 +5925,164 @@ pub mod fulfill_order {
59255925
assert_eq!(maker_stats.filler_volume_30d, 50251257); // gets filler volume
59265926
assert!(maker.orders[0].is_available());
59275927
}
5928+
5929+
#[test]
5930+
fn paused_operations_blocks_amm_fill() {
5931+
let now = 0_i64;
5932+
let slot = 0_u64;
5933+
5934+
let mut oracle_price = get_pyth_price(100, 6);
5935+
let oracle_price_key =
5936+
Pubkey::from_str("J83w4HKfqxwcq3BEMMkPFSppX3gqekLyLJBexebFVkix").unwrap();
5937+
let pyth_program = crate::ids::pyth_program::id();
5938+
create_account_info!(
5939+
oracle_price,
5940+
&oracle_price_key,
5941+
&pyth_program,
5942+
oracle_account_info
5943+
);
5944+
let mut oracle_map = OracleMap::load_one(&oracle_account_info, slot, None).unwrap();
5945+
5946+
let mut market = PerpMarket {
5947+
amm: AMM {
5948+
base_asset_reserve: 100 * AMM_RESERVE_PRECISION,
5949+
quote_asset_reserve: 100 * AMM_RESERVE_PRECISION,
5950+
bid_base_asset_reserve: 100 * AMM_RESERVE_PRECISION,
5951+
bid_quote_asset_reserve: 100 * AMM_RESERVE_PRECISION,
5952+
ask_base_asset_reserve: 100 * AMM_RESERVE_PRECISION,
5953+
ask_quote_asset_reserve: 100 * AMM_RESERVE_PRECISION,
5954+
sqrt_k: 100 * AMM_RESERVE_PRECISION,
5955+
peg_multiplier: 100 * PEG_PRECISION,
5956+
max_slippage_ratio: 50,
5957+
max_fill_reserve_fraction: 100,
5958+
order_step_size: 1000,
5959+
order_tick_size: 1,
5960+
oracle: oracle_price_key,
5961+
base_spread: 0,
5962+
base_asset_amount_with_amm: -1000000000,
5963+
amm_jit_intensity: 100,
5964+
max_base_asset_reserve: 200 * AMM_RESERVE_PRECISION,
5965+
min_base_asset_reserve: 0,
5966+
historical_oracle_data: HistoricalOracleData {
5967+
last_oracle_price: (100 * PRICE_PRECISION) as i64,
5968+
last_oracle_price_twap: (100 * PRICE_PRECISION) as i64,
5969+
last_oracle_price_twap_5min: (100 * PRICE_PRECISION) as i64,
5970+
..HistoricalOracleData::default()
5971+
},
5972+
..AMM::default()
5973+
},
5974+
margin_ratio_initial: 1000,
5975+
margin_ratio_maintenance: 500,
5976+
status: MarketStatus::Initialized,
5977+
..PerpMarket::default_test()
5978+
};
5979+
5980+
create_anchor_account_info!(market, PerpMarket, market_account_info);
5981+
let market_map = PerpMarketMap::load_one(&market_account_info, true).unwrap();
5982+
5983+
let mut spot_market = SpotMarket {
5984+
market_index: 0,
5985+
oracle_source: OracleSource::QuoteAsset,
5986+
cumulative_deposit_interest: SPOT_CUMULATIVE_INTEREST_PRECISION,
5987+
decimals: 6,
5988+
initial_asset_weight: SPOT_WEIGHT_PRECISION,
5989+
maintenance_asset_weight: SPOT_WEIGHT_PRECISION,
5990+
historical_oracle_data: HistoricalOracleData::default_price(QUOTE_PRECISION_I64),
5991+
..SpotMarket::default()
5992+
};
5993+
create_anchor_account_info!(spot_market, SpotMarket, spot_market_account_info);
5994+
let spot_market_map = SpotMarketMap::load_one(&spot_market_account_info, true).unwrap();
5995+
5996+
let mut taker = User {
5997+
orders: get_orders(Order {
5998+
market_index: 0,
5999+
status: OrderStatus::Open,
6000+
order_type: OrderType::Market,
6001+
direction: PositionDirection::Long,
6002+
base_asset_amount: BASE_PRECISION_U64,
6003+
slot: 0,
6004+
auction_start_price: 0,
6005+
auction_end_price: 100 * PRICE_PRECISION_I64,
6006+
auction_duration: 0,
6007+
price: 150 * PRICE_PRECISION_U64,
6008+
..Order::default()
6009+
}),
6010+
perp_positions: get_positions(PerpPosition {
6011+
market_index: 0,
6012+
open_orders: 1,
6013+
open_bids: BASE_PRECISION_I64,
6014+
..PerpPosition::default()
6015+
}),
6016+
spot_positions: get_spot_positions(SpotPosition {
6017+
market_index: 0,
6018+
balance_type: SpotBalanceType::Deposit,
6019+
scaled_balance: 100 * SPOT_BALANCE_PRECISION_U64,
6020+
..SpotPosition::default()
6021+
}),
6022+
..User::default()
6023+
};
6024+
6025+
let mut filler = User::default();
6026+
let fee_structure = get_fee_structure();
6027+
let (taker_key, _, filler_key) = get_user_keys();
6028+
6029+
let mut taker_stats = UserStats {
6030+
paused_operations: 4,
6031+
..UserStats::default()
6032+
};
6033+
let mut filler_stats = UserStats::default();
6034+
6035+
let order_index = 0;
6036+
let min_auction_duration = 0;
6037+
let user_can_skip_auction_duration = taker
6038+
.can_skip_auction_duration(&taker_stats, false)
6039+
.unwrap();
6040+
let is_amm_available = get_amm_is_available(
6041+
&taker.orders[order_index],
6042+
min_auction_duration,
6043+
&market,
6044+
&mut oracle_map,
6045+
slot,
6046+
user_can_skip_auction_duration,
6047+
);
6048+
6049+
assert!(!user_can_skip_auction_duration);
6050+
assert!(!is_amm_available);
6051+
6052+
let (base_asset_amount, _) = fulfill_perp_order(
6053+
&mut taker,
6054+
order_index,
6055+
&taker_key,
6056+
&mut taker_stats,
6057+
&UserMap::empty(),
6058+
&UserStatsMap::empty(),
6059+
&[],
6060+
&mut Some(&mut filler),
6061+
&filler_key,
6062+
&mut Some(&mut filler_stats),
6063+
None,
6064+
&spot_market_map,
6065+
&market_map,
6066+
&mut oracle_map,
6067+
&fee_structure,
6068+
0,
6069+
Some(market.amm.historical_oracle_data.last_oracle_price),
6070+
now,
6071+
slot,
6072+
is_amm_available,
6073+
FillMode::Fill,
6074+
false,
6075+
&mut None,
6076+
false,
6077+
)
6078+
.unwrap();
6079+
6080+
assert_eq!(base_asset_amount, 0);
6081+
assert_eq!(taker.perp_positions[0].base_asset_amount, 0);
6082+
6083+
let market_after = market_map.get_ref(&0).unwrap();
6084+
assert_eq!(market_after.amm.base_asset_amount_with_amm, -1000000000);
6085+
}
59286086
}
59296087

59306088
pub mod fill_order {

programs/drift/src/state/perp_market.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -957,6 +957,11 @@ impl PerpMarket {
957957
let can_fill_order = if can_fill_low_risk {
958958
true
959959
} else {
960+
if !user_can_skip_auction_duration {
961+
msg!("AMM cannot fill order: user has paused operations");
962+
return Ok(false);
963+
}
964+
960965
let oracle_valid_for_can_fill_immediately = is_oracle_valid_for_action(
961966
safe_oracle_validity,
962967
Some(DriftAction::FillOrderAmmImmediate),

programs/drift/src/state/perp_market/tests.rs

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,7 @@ mod amm_can_fill_order_tests {
406406
use crate::state::paused_operations::PerpOperation;
407407
use crate::state::perp_market::{PerpMarket, AMM};
408408
use crate::state::state::{State, ValidityGuardRails};
409-
use crate::state::user::{Order, OrderStatus};
409+
use crate::state::user::{Order, OrderStatus, User, UserStats};
410410
use crate::PRICE_PRECISION_I64;
411411

412412
fn base_state() -> State {
@@ -636,4 +636,98 @@ mod amm_can_fill_order_tests {
636636
.unwrap();
637637
assert!(!can);
638638
}
639+
640+
#[test]
641+
fn paused_blocks_low_risk_path() {
642+
let slot = 15;
643+
let market = base_market();
644+
let state = base_state();
645+
646+
let taker_stats = UserStats {
647+
paused_operations: 4,
648+
..UserStats::default()
649+
};
650+
651+
let user_can_skip = User::default()
652+
.can_skip_auction_duration(&taker_stats, false)
653+
.unwrap();
654+
655+
let oracle_data = OraclePriceData {
656+
price: PRICE_PRECISION_I64,
657+
confidence: 1,
658+
delay: 0,
659+
has_sufficient_number_of_data_points: true,
660+
sequence_id: Some(100),
661+
};
662+
663+
let mm = MMOraclePriceData::new(
664+
PRICE_PRECISION_I64,
665+
0,
666+
100,
667+
OracleValidity::Valid,
668+
oracle_data,
669+
)
670+
.unwrap();
671+
672+
let order = Order {
673+
status: OrderStatus::Open,
674+
slot,
675+
base_asset_amount: 1,
676+
direction: PositionDirection::Long,
677+
..Order::default()
678+
};
679+
680+
let can_fill = market
681+
.amm_can_fill_order(
682+
&order,
683+
slot,
684+
FillMode::Fill,
685+
&state,
686+
OracleValidity::Valid,
687+
user_can_skip,
688+
&mm,
689+
)
690+
.unwrap();
691+
692+
assert!(!can_fill);
693+
}
694+
695+
#[test]
696+
fn paused_blocks_high_risk_path() {
697+
let slot = 15;
698+
let market = base_market();
699+
let state = base_state();
700+
let (mm, _) = mm_oracle_ok_and_as_recent();
701+
702+
let taker_stats = UserStats {
703+
paused_operations: 4,
704+
..UserStats::default()
705+
};
706+
707+
let user_can_skip = User::default()
708+
.can_skip_auction_duration(&taker_stats, false)
709+
.unwrap();
710+
711+
let order = Order {
712+
status: OrderStatus::Open,
713+
slot: slot - 1,
714+
base_asset_amount: 1,
715+
direction: PositionDirection::Long,
716+
..Order::default()
717+
};
718+
719+
let can_fill = market
720+
.amm_can_fill_order(
721+
&order,
722+
slot,
723+
FillMode::Fill,
724+
&state,
725+
OracleValidity::Valid,
726+
user_can_skip,
727+
&mm,
728+
)
729+
.unwrap();
730+
731+
assert!(!can_fill);
732+
}
639733
}

0 commit comments

Comments
 (0)