Skip to content

Commit 7d556cf

Browse files
committed
add tests for amm_can_fill_order and is_oracle_low_risk
1 parent ebe72a5 commit 7d556cf

File tree

3 files changed

+333
-2
lines changed

3 files changed

+333
-2
lines changed

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

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13053,3 +13053,94 @@ mod update_maker_fills_map {
1305313053
assert_eq!(*map.get(&maker_key).unwrap(), -2 * fill as i64);
1305413054
}
1305513055
}
13056+
13057+
mod order_is_low_risk_for_amm {
13058+
use super::*;
13059+
use crate::math::orders::get_posted_slot_from_clock_slot;
13060+
use crate::state::user::{OrderBitFlag, OrderStatus};
13061+
13062+
fn base_perp_order() -> Order {
13063+
Order {
13064+
status: OrderStatus::Open,
13065+
market_type: MarketType::Perp,
13066+
slot: 100,
13067+
..Order::default()
13068+
}
13069+
}
13070+
13071+
#[test]
13072+
fn older_than_oracle_delay_returns_true() {
13073+
let order = base_perp_order();
13074+
let clock_slot = 110u64;
13075+
let mm_oracle_delay = 10i64;
13076+
13077+
let is_low = order
13078+
.is_low_risk_for_amm(mm_oracle_delay, 20, clock_slot, false)
13079+
.unwrap();
13080+
assert!(is_low);
13081+
}
13082+
13083+
#[test]
13084+
fn not_older_than_delay_and_auction_not_complete_returns_false() {
13085+
let order = base_perp_order();
13086+
let clock_slot = 110u64;
13087+
13088+
let mm_oracle_delay = 11i64;
13089+
let min_auction_duration = 10u8;
13090+
13091+
let is_low = order
13092+
.is_low_risk_for_amm(mm_oracle_delay, min_auction_duration, clock_slot, false)
13093+
.unwrap();
13094+
assert!(!is_low);
13095+
}
13096+
13097+
#[test]
13098+
fn non_signed_auction_boundary_and_past_duration() {
13099+
let order = base_perp_order(); // slot = 100
13100+
13101+
let is_low_at_boundary = order.is_low_risk_for_amm(1000, 10, 110, false).unwrap();
13102+
assert!(!is_low_at_boundary);
13103+
let is_low_past = order.is_low_risk_for_amm(1000, 10, 111, false).unwrap();
13104+
assert!(is_low_past);
13105+
}
13106+
13107+
#[test]
13108+
fn signed_message_duration_gate() {
13109+
let mut order = base_perp_order();
13110+
order.add_bit_flag(OrderBitFlag::SignedMessage);
13111+
13112+
let posted_slot = 5_000u64;
13113+
order.posted_slot_tail = get_posted_slot_from_clock_slot(posted_slot);
13114+
13115+
let clock_slot_9 = posted_slot + 9;
13116+
let min_auction_duration = 10u8;
13117+
let is_low_9 = order
13118+
.is_low_risk_for_amm(10_000, min_auction_duration, clock_slot_9, false)
13119+
.unwrap();
13120+
assert!(!is_low_9);
13121+
13122+
let clock_slot_10 = posted_slot + 10;
13123+
let is_low_10 = order
13124+
.is_low_risk_for_amm(10_000, min_auction_duration, clock_slot_10, false)
13125+
.unwrap();
13126+
assert!(is_low_10);
13127+
}
13128+
13129+
#[test]
13130+
fn liquidation_always_low_risk() {
13131+
let order = base_perp_order();
13132+
let is_low = order.is_low_risk_for_amm(0, 255, order.slot, true).unwrap();
13133+
assert!(is_low);
13134+
}
13135+
13136+
#[test]
13137+
fn safe_trigger_order_flag_sets_low_risk() {
13138+
let mut order = base_perp_order();
13139+
order.add_bit_flag(OrderBitFlag::SafeTriggerOrder);
13140+
13141+
let is_low = order
13142+
.is_low_risk_for_amm(0, 255, order.slot, false)
13143+
.unwrap();
13144+
assert!(is_low);
13145+
}
13146+
}

programs/drift/src/math/oracle.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,8 @@ pub fn is_oracle_valid_for_action(
101101
oracle_validity,
102102
OracleValidity::Valid
103103
| OracleValidity::StaleForAMM {
104-
immediate: false,
105-
low_risk: true
104+
immediate: true,
105+
low_risk: false
106106
}
107107
)
108108
}

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

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,3 +336,243 @@ mod get_trigger_price {
336336
assert_eq!(clamped_price, large_oracle_price + max_oracle_diff_large);
337337
}
338338
}
339+
340+
mod amm_can_fill_order_tests {
341+
use crate::controller::position::PositionDirection;
342+
use crate::math::oracle::OracleValidity;
343+
use crate::state::fill_mode::FillMode;
344+
use crate::state::oracle::{MMOraclePriceData, OraclePriceData};
345+
use crate::state::paused_operations::PerpOperation;
346+
use crate::state::perp_market::{PerpMarket, AMM};
347+
use crate::state::state::{State, ValidityGuardRails};
348+
use crate::state::user::{Order, OrderStatus};
349+
use crate::PRICE_PRECISION_I64;
350+
351+
fn base_state() -> State {
352+
State {
353+
min_perp_auction_duration: 10,
354+
..State::default()
355+
}
356+
}
357+
358+
fn base_market() -> PerpMarket {
359+
PerpMarket {
360+
amm: AMM {
361+
mm_oracle_price: PRICE_PRECISION_I64,
362+
mm_oracle_slot: 0,
363+
order_step_size: 1,
364+
amm_jit_intensity: 100,
365+
..AMM::default()
366+
},
367+
..PerpMarket::default()
368+
}
369+
}
370+
371+
fn base_order() -> Order {
372+
Order {
373+
status: OrderStatus::Open,
374+
slot: 0,
375+
base_asset_amount: 1,
376+
direction: PositionDirection::Long,
377+
..Order::default()
378+
}
379+
}
380+
381+
fn mm_oracle_ok_and_as_recent() -> (MMOraclePriceData, ValidityGuardRails) {
382+
let exchange = OraclePriceData {
383+
price: PRICE_PRECISION_I64,
384+
confidence: 1,
385+
delay: 5,
386+
has_sufficient_number_of_data_points: true,
387+
sequence_id: Some(100),
388+
};
389+
let mm =
390+
MMOraclePriceData::new(PRICE_PRECISION_I64, 5, 100, OracleValidity::Valid, exchange)
391+
.unwrap();
392+
(mm, ValidityGuardRails::default())
393+
}
394+
395+
#[test]
396+
fn paused_operation_returns_false() {
397+
let mut market = base_market();
398+
// Pause AMM fill
399+
market.paused_operations = PerpOperation::AmmFill as u8;
400+
let order = base_order();
401+
let state = base_state();
402+
let (mm, guard) = mm_oracle_ok_and_as_recent();
403+
404+
let can = market
405+
.amm_can_fill_order(
406+
&order,
407+
10,
408+
FillMode::Fill,
409+
&state,
410+
OracleValidity::Valid,
411+
true,
412+
&mm,
413+
)
414+
.unwrap();
415+
assert!(!can);
416+
}
417+
418+
#[test]
419+
fn mm_oracle_too_volatile_blocks() {
420+
let market = base_market();
421+
let order = base_order();
422+
let state = base_state();
423+
424+
// Create MM oracle data with >1% diff vs exchange to force fallback and block
425+
let exchange = OraclePriceData {
426+
price: PRICE_PRECISION_I64, // 1.0
427+
confidence: 1,
428+
delay: 1,
429+
has_sufficient_number_of_data_points: true,
430+
sequence_id: Some(100),
431+
};
432+
// 3% higher than exchange
433+
let mm = MMOraclePriceData::new(
434+
PRICE_PRECISION_I64 + (PRICE_PRECISION_I64 / 33),
435+
1,
436+
99,
437+
OracleValidity::Valid,
438+
exchange,
439+
)
440+
.unwrap();
441+
442+
let can = market
443+
.amm_can_fill_order(
444+
&order,
445+
10,
446+
FillMode::Fill,
447+
&state,
448+
OracleValidity::Valid,
449+
true,
450+
&mm,
451+
)
452+
.unwrap();
453+
assert!(!can);
454+
}
455+
456+
#[test]
457+
fn low_risk_path_succeeds_when_auction_elapsed() {
458+
let market = base_market();
459+
let mut order = base_order();
460+
order.slot = 0;
461+
462+
let state = base_state();
463+
let (mm, _) = mm_oracle_ok_and_as_recent();
464+
465+
// clock_slot sufficiently beyond min_auction_duration
466+
let can = market
467+
.amm_can_fill_order(
468+
&order,
469+
15,
470+
FillMode::Fill,
471+
&state,
472+
OracleValidity::Valid,
473+
true,
474+
&mm,
475+
)
476+
.unwrap();
477+
assert!(can);
478+
}
479+
480+
#[test]
481+
fn low_risk_path_succeeds_when_auction_elapsed_with_stale_for_immediate() {
482+
let market = base_market();
483+
let mut order = base_order();
484+
order.slot = 0; // order placed at slot 0
485+
486+
let state = base_state();
487+
let (mm, _) = mm_oracle_ok_and_as_recent();
488+
489+
// clock_slot sufficiently beyond min_auction_duration
490+
let can = market
491+
.amm_can_fill_order(
492+
&order,
493+
15,
494+
FillMode::Fill,
495+
&state,
496+
OracleValidity::StaleForAMM {
497+
immediate: true,
498+
low_risk: false,
499+
},
500+
true,
501+
&mm,
502+
)
503+
.unwrap();
504+
assert!(can);
505+
}
506+
507+
#[test]
508+
fn high_risk_immediate_requires_user_and_market_skip() {
509+
let mut market = base_market();
510+
let mut order = base_order();
511+
order.slot = 20;
512+
market.amm.amm_jit_intensity = 100;
513+
514+
let state = base_state();
515+
let (mm, _) = mm_oracle_ok_and_as_recent();
516+
517+
// cnat fill if user cant skip auction duration
518+
let can1 = market
519+
.amm_can_fill_order(
520+
&order,
521+
21,
522+
FillMode::Fill,
523+
&state,
524+
OracleValidity::Valid,
525+
false,
526+
&mm,
527+
)
528+
.unwrap();
529+
assert!(!can1);
530+
531+
// valid oracle for immediate and user can skip, market can skip due to low inventory => can fill
532+
market.amm.base_asset_amount_with_amm = -2; // taker long improves balance
533+
market.amm.order_step_size = 1;
534+
market.amm.base_asset_reserve = 1_000_000;
535+
market.amm.quote_asset_reserve = 1_000_000;
536+
market.amm.sqrt_k = 1_000_000;
537+
market.amm.max_base_asset_reserve = 2_000_000;
538+
market.amm.min_base_asset_reserve = 0;
539+
540+
let can2 = market
541+
.amm_can_fill_order(
542+
&order,
543+
21,
544+
FillMode::Fill,
545+
&state,
546+
OracleValidity::Valid,
547+
true,
548+
&mm,
549+
)
550+
.unwrap();
551+
assert!(can2);
552+
}
553+
554+
#[test]
555+
fn invalid_safe_oracle_validity_blocks_low_risk() {
556+
let market = base_market();
557+
let order = base_order();
558+
let state = base_state();
559+
let (mm, _) = mm_oracle_ok_and_as_recent();
560+
561+
// Order is old but invalid oracle validity
562+
let can = market
563+
.amm_can_fill_order(
564+
&order,
565+
20,
566+
FillMode::Fill,
567+
&state,
568+
OracleValidity::StaleForAMM {
569+
immediate: true,
570+
low_risk: true,
571+
},
572+
true,
573+
&mm,
574+
)
575+
.unwrap();
576+
assert!(!can);
577+
}
578+
}

0 commit comments

Comments
 (0)