@@ -2973,7 +2973,9 @@ pub mod fulfill_order {
29732973 use std:: str:: FromStr ;
29742974 use std:: u64;
29752975
2976- use crate :: controller:: orders:: { fulfill_perp_order, validate_market_within_price_band} ;
2976+ use crate :: controller:: orders:: {
2977+ fill_perp_order, fulfill_perp_order, validate_market_within_price_band,
2978+ } ;
29772979 use crate :: controller:: position:: PositionDirection ;
29782980 use crate :: create_anchor_account_info;
29792981 use crate :: error:: ErrorCode ;
@@ -4845,6 +4847,182 @@ pub mod fulfill_order {
48454847 assert_eq ! ( ask_price, 100069968 ) ; // ~ 100.1 * (0.9997)
48464848 }
48474849
4850+ #[ test]
4851+ fn amm_unavailable_from_volatile_mm_oracle ( ) {
4852+ use anchor_lang:: prelude:: { AccountLoader , Clock } ;
4853+
4854+ let slot = 56_u64 ;
4855+ let clock = Clock {
4856+ slot,
4857+ epoch_start_timestamp : 0 ,
4858+ epoch : 0 ,
4859+ leader_schedule_epoch : 0 ,
4860+ unix_timestamp : 0 ,
4861+ } ;
4862+
4863+ let mut oracle_price = get_pyth_price ( 100 , 6 ) ;
4864+ let oracle_price_key =
4865+ Pubkey :: from_str ( "J83w4HKfqxwcq3BEMMkPFSppX3gqekLyLJBexebFVkix" ) . unwrap ( ) ;
4866+ let pyth_program = crate :: ids:: pyth_program:: id ( ) ;
4867+ create_account_info ! (
4868+ oracle_price,
4869+ & oracle_price_key,
4870+ & pyth_program,
4871+ oracle_account_info
4872+ ) ;
4873+ let mut oracle_map = OracleMap :: load_one ( & oracle_account_info, slot, None ) . unwrap ( ) ;
4874+
4875+ let mut market = PerpMarket {
4876+ amm : AMM {
4877+ base_asset_reserve : 100 * AMM_RESERVE_PRECISION ,
4878+ quote_asset_reserve : 100 * AMM_RESERVE_PRECISION ,
4879+ bid_base_asset_reserve : 100 * AMM_RESERVE_PRECISION ,
4880+ bid_quote_asset_reserve : 100 * AMM_RESERVE_PRECISION ,
4881+ ask_base_asset_reserve : 100 * AMM_RESERVE_PRECISION ,
4882+ ask_quote_asset_reserve : 100 * AMM_RESERVE_PRECISION ,
4883+ terminal_quote_asset_reserve : 100 * AMM_RESERVE_PRECISION ,
4884+ sqrt_k : 100 * AMM_RESERVE_PRECISION ,
4885+ peg_multiplier : 100 * PEG_PRECISION ,
4886+ max_slippage_ratio : 50 ,
4887+ max_fill_reserve_fraction : 100 ,
4888+ order_step_size : 1000 ,
4889+ order_tick_size : 1 ,
4890+ oracle : oracle_price_key,
4891+ base_spread : 0 , // 1 basis point
4892+ mm_oracle_price : 102 * PRICE_PRECISION_I64 ,
4893+ mm_oracle_slot : slot,
4894+ historical_oracle_data : HistoricalOracleData {
4895+ last_oracle_price : ( 100 * PRICE_PRECISION ) as i64 ,
4896+ last_oracle_price_twap : ( 100 * PRICE_PRECISION ) as i64 ,
4897+ last_oracle_price_twap_5min : ( 100 * PRICE_PRECISION ) as i64 ,
4898+
4899+ ..HistoricalOracleData :: default ( )
4900+ } ,
4901+ ..AMM :: default ( )
4902+ } ,
4903+ margin_ratio_initial : 1000 ,
4904+ margin_ratio_maintenance : 500 ,
4905+ status : MarketStatus :: Initialized ,
4906+ ..PerpMarket :: default_test ( )
4907+ } ;
4908+ market. amm . max_base_asset_reserve = u128:: MAX ;
4909+ market. amm . min_base_asset_reserve = 0 ;
4910+ market. status = MarketStatus :: Active ;
4911+
4912+ create_anchor_account_info ! ( market, PerpMarket , market_account_info) ;
4913+ let market_map = PerpMarketMap :: load_one ( & market_account_info, true ) . unwrap ( ) ;
4914+
4915+ let mut spot_market = SpotMarket {
4916+ market_index : 0 ,
4917+ oracle_source : OracleSource :: QuoteAsset ,
4918+ cumulative_deposit_interest : SPOT_CUMULATIVE_INTEREST_PRECISION ,
4919+ decimals : 6 ,
4920+ initial_asset_weight : SPOT_WEIGHT_PRECISION ,
4921+ maintenance_asset_weight : SPOT_WEIGHT_PRECISION ,
4922+ historical_oracle_data : HistoricalOracleData :: default_price ( QUOTE_PRECISION_I64 ) ,
4923+ ..SpotMarket :: default ( )
4924+ } ;
4925+ create_anchor_account_info ! ( spot_market, SpotMarket , spot_market_account_info) ;
4926+ let spot_market_map = SpotMarketMap :: load_one ( & spot_market_account_info, true ) . unwrap ( ) ;
4927+
4928+ let mut taker = User {
4929+ orders : get_orders ( Order {
4930+ market_index : 0 ,
4931+ status : OrderStatus :: Open ,
4932+ order_type : OrderType :: Market ,
4933+ direction : PositionDirection :: Long ,
4934+ base_asset_amount : BASE_PRECISION_U64 ,
4935+ slot : 0 ,
4936+ auction_start_price : 0 ,
4937+ auction_end_price : 100 * PRICE_PRECISION_I64 ,
4938+ auction_duration : 0 ,
4939+ price : 150 * PRICE_PRECISION_U64 ,
4940+ order_id : 1 ,
4941+ ..Order :: default ( )
4942+ } ) ,
4943+ perp_positions : get_positions ( PerpPosition {
4944+ market_index : 0 ,
4945+ open_orders : 1 ,
4946+ open_bids : BASE_PRECISION_I64 ,
4947+ ..PerpPosition :: default ( )
4948+ } ) ,
4949+ spot_positions : get_spot_positions ( SpotPosition {
4950+ market_index : 0 ,
4951+ balance_type : SpotBalanceType :: Deposit ,
4952+ scaled_balance : 100 * SPOT_BALANCE_PRECISION_U64 ,
4953+ ..SpotPosition :: default ( )
4954+ } ) ,
4955+ ..User :: default ( )
4956+ } ;
4957+ create_anchor_account_info ! ( taker, User , user_account_info) ;
4958+ let user_account_loader: AccountLoader < User > =
4959+ AccountLoader :: try_from ( & user_account_info) . unwrap ( ) ;
4960+ create_anchor_account_info ! ( UserStats :: default ( ) , UserStats , user_stats_account_info) ;
4961+ let user_stats_account_loader: AccountLoader < UserStats > =
4962+ AccountLoader :: try_from ( & user_stats_account_info) . unwrap ( ) ;
4963+
4964+ let filler_key = Pubkey :: from_str ( "My11111111111111111111111111111111111111111" ) . unwrap ( ) ;
4965+ create_anchor_account_info ! ( User :: default ( ) , & filler_key, User , user_account_info) ;
4966+ let filler_account_loader: AccountLoader < User > =
4967+ AccountLoader :: try_from ( & user_account_info) . unwrap ( ) ;
4968+
4969+ create_anchor_account_info ! ( UserStats :: default ( ) , UserStats , filler_stats_account_info) ;
4970+ let filler_stats_account_loader: AccountLoader < UserStats > =
4971+ AccountLoader :: try_from ( & filler_stats_account_info) . unwrap ( ) ;
4972+
4973+ let state = State {
4974+ min_perp_auction_duration : 1 ,
4975+ default_market_order_time_in_force : 10 ,
4976+ ..State :: default ( )
4977+ } ;
4978+
4979+ let ( base_asset_amount, _) = fill_perp_order (
4980+ 1 ,
4981+ & state,
4982+ & user_account_loader,
4983+ & user_stats_account_loader,
4984+ & spot_market_map,
4985+ & market_map,
4986+ & mut oracle_map,
4987+ & filler_account_loader,
4988+ & filler_stats_account_loader,
4989+ & UserMap :: empty ( ) ,
4990+ & UserStatsMap :: empty ( ) ,
4991+ None ,
4992+ & clock,
4993+ FillMode :: Fill ,
4994+ )
4995+ . unwrap ( ) ;
4996+
4997+ assert_eq ! ( base_asset_amount, 0 ) ;
4998+
4999+ // Will fill if MM oracle price is not too volatile at mm oracle price
5000+ market. amm . mm_oracle_price = 101 * PRICE_PRECISION_I64 ;
5001+ create_anchor_account_info ! ( market, PerpMarket , market_account_info) ;
5002+ let market_map = PerpMarketMap :: load_one ( & market_account_info, true ) . unwrap ( ) ;
5003+
5004+ let ( base_asset_amount, quote_asset_amount) = fill_perp_order (
5005+ 1 ,
5006+ & state,
5007+ & user_account_loader,
5008+ & user_stats_account_loader,
5009+ & spot_market_map,
5010+ & market_map,
5011+ & mut oracle_map,
5012+ & filler_account_loader,
5013+ & filler_stats_account_loader,
5014+ & UserMap :: empty ( ) ,
5015+ & UserStatsMap :: empty ( ) ,
5016+ None ,
5017+ & clock,
5018+ FillMode :: Fill ,
5019+ )
5020+ . unwrap ( ) ;
5021+
5022+ assert_eq ! ( base_asset_amount, BASE_PRECISION_U64 ) ;
5023+ assert_eq ! ( quote_asset_amount, 101010102 ) ;
5024+ }
5025+
48485026 // Add back if we check free collateral in fill again
48495027 // #[test]
48505028 // fn fulfill_with_negative_free_collateral() {
0 commit comments