@@ -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