From 3ee5b0662bc6350397f7ba964f884ad5b92a8288 Mon Sep 17 00:00:00 2001 From: Chris Heaney Date: Thu, 29 Jun 2023 20:06:29 -0400 Subject: [PATCH 01/18] bubble up quote asset amount filled for spot fills --- programs/drift/src/controller/orders.rs | 32 ++++++++++--------- programs/drift/src/controller/orders/tests.rs | 30 ++++++++--------- 2 files changed, 32 insertions(+), 30 deletions(-) diff --git a/programs/drift/src/controller/orders.rs b/programs/drift/src/controller/orders.rs index 8155e1cd3..c4980b4e1 100644 --- a/programs/drift/src/controller/orders.rs +++ b/programs/drift/src/controller/orders.rs @@ -3215,7 +3215,7 @@ pub fn fill_spot_order( return Ok(0); } - let (base_asset_amount, _updated_user_state) = fulfill_spot_order( + let (base_asset_amount, _quote_asset_amount) = fulfill_spot_order( user, order_index, &user_key, @@ -3479,7 +3479,7 @@ fn fulfill_spot_order( slot: u64, fee_structure: &FeeStructure, fulfillment_params: &mut dyn SpotFulfillmentParams, -) -> DriftResult<(u64, bool)> { +) -> DriftResult<(u64, u64)> { let base_market_index = user.orders[user_order_index].market_index; let fulfillment_methods = determine_spot_fulfillment_methods( @@ -3492,12 +3492,13 @@ fn fulfill_spot_order( let mut base_market = spot_market_map.get_ref_mut(&base_market_index)?; let mut base_asset_amount = 0_u64; + let mut quote_asset_amount = 0_u64; for fulfillment_method in fulfillment_methods.iter() { if user.orders[user_order_index].status != OrderStatus::Open { break; } - let base_filled = match fulfillment_method { + let (base_filled, quote_filled) = match fulfillment_method { SpotFulfillmentMethod::Match => fulfill_spot_order_with_match( &mut base_market, &mut quote_market, @@ -3536,6 +3537,7 @@ fn fulfill_spot_order( }; base_asset_amount = base_asset_amount.safe_add(base_filled)?; + quote_asset_amount = quote_asset_amount.safe_add(quote_filled)?; } let initial_margin_ratio = base_market.get_margin_ratio(&MarginRequirementType::Initial)?; @@ -3589,7 +3591,7 @@ fn fulfill_spot_order( } } - Ok((base_asset_amount, base_asset_amount != 0)) + Ok((base_asset_amount, quote_asset_amount)) } pub fn fulfill_spot_order_with_match( @@ -3610,12 +3612,12 @@ pub fn fulfill_spot_order_with_match( slot: u64, oracle_map: &mut OracleMap, fee_structure: &FeeStructure, -) -> DriftResult { +) -> DriftResult<(u64, u64)> { if !are_orders_same_market_but_different_sides( &maker.orders[maker_order_index], &taker.orders[taker_order_index], ) { - return Ok(0_u64); + return Ok((0_u64, 0_u64)); } let market_index = taker.orders[taker_order_index].market_index; @@ -3628,7 +3630,7 @@ pub fn fulfill_spot_order_with_match( )? { Some(price) => price, None => { - return Ok(0_u64); + return Ok((0_u64, 0_u64)); } }; @@ -3667,7 +3669,7 @@ pub fn fulfill_spot_order_with_match( maker_price, taker_price ); - return Ok(0_u64); + return Ok((0_u64, 0_u64)); } let (taker_max_base_asset_amount, taker_max_quote_asset_amount) = @@ -3719,7 +3721,7 @@ pub fn fulfill_spot_order_with_match( )?; if base_asset_amount == 0 { - return Ok(0_u64); + return Ok((0_u64, 0_u64)); } let base_precision = base_market.get_precision(); @@ -3939,7 +3941,7 @@ pub fn fulfill_spot_order_with_match( maker.spot_positions[maker_spot_position_index].open_orders -= 1; } - Ok(base_asset_amount) + Ok((base_asset_amount, quote_asset_amount)) } pub fn fulfill_spot_order_with_external_market( @@ -3957,7 +3959,7 @@ pub fn fulfill_spot_order_with_external_market( oracle_map: &mut OracleMap, fee_structure: &FeeStructure, fulfillment_params: &mut dyn SpotFulfillmentParams, -) -> DriftResult { +) -> DriftResult<(u64, u64)> { let oracle_price = oracle_map.get_price_data(&base_market.oracle)?.price; let taker_price = taker.orders[taker_order_index].get_limit_price( Some(oracle_price), @@ -4032,7 +4034,7 @@ pub fn fulfill_spot_order_with_external_market( ask.safe_add(ask / 100)? } else { msg!("External market has no ask"); - return Ok(0); + return Ok((0, 0)); } } PositionDirection::Short => { @@ -4040,7 +4042,7 @@ pub fn fulfill_spot_order_with_external_market( bid.safe_sub(bid / 100)? } else { msg!("External market has no bid"); - return Ok(0); + return Ok((0, 0)); } } } @@ -4062,7 +4064,7 @@ pub fn fulfill_spot_order_with_external_market( )?; if base_asset_amount_filled == 0 { - return Ok(0); + return Ok((0, 0)); } update_spot_balances( @@ -4233,7 +4235,7 @@ pub fn fulfill_spot_order_with_external_market( .open_orders -= 1; } - Ok(base_asset_amount_filled) + Ok((base_asset_amount_filled, quote_asset_amount_filled)) } pub fn trigger_spot_order( diff --git a/programs/drift/src/controller/orders/tests.rs b/programs/drift/src/controller/orders/tests.rs index faa9be9bf..c4c3c10a0 100644 --- a/programs/drift/src/controller/orders/tests.rs +++ b/programs/drift/src/controller/orders/tests.rs @@ -5265,7 +5265,7 @@ pub mod fulfill_spot_order_with_match { let mut taker_stats = UserStats::default(); let mut maker_stats = UserStats::default(); - let base_asset_amount = fulfill_spot_order_with_match( + let (base_asset_amount, _) = fulfill_spot_order_with_match( &mut base_market, &mut quote_market, &mut taker, @@ -5364,7 +5364,7 @@ pub mod fulfill_spot_order_with_match { let mut taker_stats = UserStats::default(); let mut maker_stats = UserStats::default(); - let base_asset_amount = fulfill_spot_order_with_match( + let (base_asset_amount, _) = fulfill_spot_order_with_match( &mut base_market, &mut quote_market, &mut taker, @@ -5463,7 +5463,7 @@ pub mod fulfill_spot_order_with_match { let mut taker_stats = UserStats::default(); let mut maker_stats = UserStats::default(); - let base_asset_amount = fulfill_spot_order_with_match( + let (base_asset_amount, _) = fulfill_spot_order_with_match( &mut base_market, &mut quote_market, &mut taker, @@ -5562,7 +5562,7 @@ pub mod fulfill_spot_order_with_match { let mut taker_stats = UserStats::default(); let mut maker_stats = UserStats::default(); - let base_asset_amount = fulfill_spot_order_with_match( + let (base_asset_amount, _) = fulfill_spot_order_with_match( &mut base_market, &mut quote_market, &mut taker, @@ -5661,7 +5661,7 @@ pub mod fulfill_spot_order_with_match { let mut taker_stats = UserStats::default(); let mut maker_stats = UserStats::default(); - let base_asset_amount = fulfill_spot_order_with_match( + let (base_asset_amount, _) = fulfill_spot_order_with_match( &mut base_market, &mut quote_market, &mut taker, @@ -5798,7 +5798,7 @@ pub mod fulfill_spot_order_with_match { let mut taker_stats = UserStats::default(); let mut maker_stats = UserStats::default(); - let base_asset_amount = fulfill_spot_order_with_match( + let (base_asset_amount, _) = fulfill_spot_order_with_match( &mut base_market, &mut quote_market, &mut taker, @@ -6307,7 +6307,7 @@ pub mod fulfill_spot_order_with_match { let mut taker_stats = UserStats::default(); let mut maker_stats = UserStats::default(); - let base_asset_amount = fulfill_spot_order_with_match( + let (base_asset_amount, _) = fulfill_spot_order_with_match( &mut base_market, &mut quote_market, &mut taker, @@ -6404,7 +6404,7 @@ pub mod fulfill_spot_order_with_match { let mut taker_stats = UserStats::default(); let mut maker_stats = UserStats::default(); - let base_filled = fulfill_spot_order_with_match( + let (base_filled, _) = fulfill_spot_order_with_match( &mut base_market, &mut quote_market, &mut taker, @@ -6501,7 +6501,7 @@ pub mod fulfill_spot_order_with_match { let mut taker_stats = UserStats::default(); let mut maker_stats = UserStats::default(); - let base_filled = fulfill_spot_order_with_match( + let (base_filled, _) = fulfill_spot_order_with_match( &mut base_market, &mut quote_market, &mut taker, @@ -6598,7 +6598,7 @@ pub mod fulfill_spot_order_with_match { let mut taker_stats = UserStats::default(); let mut maker_stats = UserStats::default(); - let base_filled = fulfill_spot_order_with_match( + let (base_filled, _) = fulfill_spot_order_with_match( &mut base_market, &mut quote_market, &mut taker, @@ -6695,7 +6695,7 @@ pub mod fulfill_spot_order_with_match { let mut taker_stats = UserStats::default(); let mut maker_stats = UserStats::default(); - let base_filled = fulfill_spot_order_with_match( + let (base_filled, _) = fulfill_spot_order_with_match( &mut base_market, &mut quote_market, &mut taker, @@ -6792,7 +6792,7 @@ pub mod fulfill_spot_order_with_match { let mut taker_stats = UserStats::default(); let mut maker_stats = UserStats::default(); - let base_filled = fulfill_spot_order_with_match( + let (base_filled, _) = fulfill_spot_order_with_match( &mut base_market, &mut quote_market, &mut taker, @@ -6889,7 +6889,7 @@ pub mod fulfill_spot_order_with_match { let mut taker_stats = UserStats::default(); let mut maker_stats = UserStats::default(); - let base_filled = fulfill_spot_order_with_match( + let (base_filled, _) = fulfill_spot_order_with_match( &mut base_market, &mut quote_market, &mut taker, @@ -6986,7 +6986,7 @@ pub mod fulfill_spot_order_with_match { let mut taker_stats = UserStats::default(); let mut maker_stats = UserStats::default(); - let base_filled = fulfill_spot_order_with_match( + let (base_filled, _) = fulfill_spot_order_with_match( &mut base_market, &mut quote_market, &mut taker, @@ -7083,7 +7083,7 @@ pub mod fulfill_spot_order_with_match { let mut taker_stats = UserStats::default(); let mut maker_stats = UserStats::default(); - let base_filled = fulfill_spot_order_with_match( + let (base_filled, _) = fulfill_spot_order_with_match( &mut base_market, &mut quote_market, &mut taker, From e7509fbc86b9d9d4aeae30f7d20cd574bcafef81 Mon Sep 17 00:00:00 2001 From: Chris Heaney Date: Thu, 29 Jun 2023 20:30:05 -0400 Subject: [PATCH 02/18] bubble up qutoe asset amount for fill_perp --- programs/drift/src/controller/orders.rs | 71 +++++++++---------- programs/drift/src/controller/orders/tests.rs | 6 +- 2 files changed, 36 insertions(+), 41 deletions(-) diff --git a/programs/drift/src/controller/orders.rs b/programs/drift/src/controller/orders.rs index c4980b4e1..202267cb7 100644 --- a/programs/drift/src/controller/orders.rs +++ b/programs/drift/src/controller/orders.rs @@ -853,7 +853,7 @@ pub fn fill_perp_order( makers_and_referrer_stats: &UserStatsMap, jit_maker_order_id: Option, clock: &Clock, -) -> DriftResult<(u64, bool)> { +) -> DriftResult { let now = clock.unix_timestamp; let slot = clock.slot; @@ -909,7 +909,7 @@ pub fn fill_perp_order( if user.is_bankrupt() { msg!("user is bankrupt"); - return Ok((0, false)); + return Ok(0); } match validate_user_not_being_liquidated( @@ -922,7 +922,7 @@ pub fn fill_perp_order( Ok(_) => {} Err(_) => { msg!("user is being liquidated"); - return Ok((0, false)); + return Ok(0); } } @@ -1044,41 +1044,38 @@ pub fn fill_perp_order( false, )?; - return Ok((0, true)); + return Ok(0); } - let (base_asset_amount, potentially_risk_increasing, mut updated_user_state) = - fulfill_perp_order( - user, - order_index, - &user_key, - user_stats, - makers_and_referrer, - makers_and_referrer_stats, - &maker_orders_info, - &mut filler.as_deref_mut(), - &filler_key, - &mut filler_stats.as_deref_mut(), - referrer_info, - spot_market_map, - perp_market_map, - oracle_map, - &state.perp_fee_structure, - reserve_price_before, - valid_oracle_price, - now, - slot, - state.min_perp_auction_duration, - amm_is_available, - )?; + let (base_asset_amount, _quote_asset_amount, potentially_risk_increasing) = fulfill_perp_order( + user, + order_index, + &user_key, + user_stats, + makers_and_referrer, + makers_and_referrer_stats, + &maker_orders_info, + &mut filler.as_deref_mut(), + &filler_key, + &mut filler_stats.as_deref_mut(), + referrer_info, + spot_market_map, + perp_market_map, + oracle_map, + &state.perp_fee_structure, + reserve_price_before, + valid_oracle_price, + now, + slot, + state.min_perp_auction_duration, + amm_is_available, + )?; let base_asset_amount_after = user.perp_positions[position_index].base_asset_amount; let should_cancel_reduce_only = should_cancel_reduce_only_order(&user.orders[order_index], base_asset_amount_after)?; if should_cancel_reduce_only { - updated_user_state = true; - let filler_reward = { let mut market = perp_market_map.get_ref_mut(&market_index)?; pay_keeper_flat_reward_for_perps( @@ -1108,8 +1105,8 @@ pub fn fill_perp_order( )? } - if !updated_user_state { - return Ok((base_asset_amount, updated_user_state)); + if base_asset_amount == 0 { + return Ok(0); } { @@ -1152,7 +1149,7 @@ pub fn fill_perp_order( user.update_last_active_slot(slot); - Ok((base_asset_amount, updated_user_state)) + Ok(base_asset_amount) } pub fn validate_market_within_price_band( @@ -1448,7 +1445,7 @@ fn fulfill_perp_order( slot: u64, min_auction_duration: u8, amm_is_available: bool, -) -> DriftResult<(u64, bool, bool)> { +) -> DriftResult<(u64, u64, bool)> { let market_index = user.orders[user_order_index].market_index; let user_position_index = get_position_index(&user.perp_positions, market_index)?; @@ -1474,7 +1471,7 @@ fn fulfill_perp_order( }; if fulfillment_methods.is_empty() { - return Ok((0, false, false)); + return Ok((0, 0, false)); } let mut base_asset_amount = 0_u64; @@ -1636,9 +1633,7 @@ fn fulfill_perp_order( || position_base_asset_amount_before.signum() != position_base_asset_amount_after.signum() || position_base_asset_amount_before.abs() < position_base_asset_amount_after.abs(); - let updated_user_state = base_asset_amount != 0; - - Ok((base_asset_amount, risk_increasing, updated_user_state)) + Ok((base_asset_amount, quote_asset_amount, risk_increasing)) } #[allow(clippy::type_complexity)] diff --git a/programs/drift/src/controller/orders/tests.rs b/programs/drift/src/controller/orders/tests.rs index c4c3c10a0..338ea0621 100644 --- a/programs/drift/src/controller/orders/tests.rs +++ b/programs/drift/src/controller/orders/tests.rs @@ -4155,7 +4155,7 @@ pub mod fill_order { ..State::default() }; - let (base_asset_amount, _) = fill_perp_order( + let base_asset_amount = fill_perp_order( 1, &state, &user_account_loader, @@ -4362,7 +4362,7 @@ pub mod fill_order { ..State::default() }; - let (base_asset_amount, _) = fill_perp_order( + let base_asset_amount = fill_perp_order( 1, &state, &user_account_loader, @@ -4489,7 +4489,7 @@ pub mod fill_order { unix_timestamp: 11, }; - let (base_asset_amount, _) = fill_perp_order( + let base_asset_amount = fill_perp_order( 1, &state, &user_account_loader, From fbff73577d2d23343b6fc87e2720c7358e2c1c29 Mon Sep 17 00:00:00 2001 From: Chris Heaney Date: Fri, 30 Jun 2023 15:30:02 -0400 Subject: [PATCH 03/18] init --- programs/drift/src/controller/orders.rs | 80 +++++++++++--- programs/drift/src/controller/orders/tests.rs | 3 + programs/drift/src/math/orders.rs | 104 +++++++++++++++++- 3 files changed, 165 insertions(+), 22 deletions(-) diff --git a/programs/drift/src/controller/orders.rs b/programs/drift/src/controller/orders.rs index 202267cb7..47bf8017d 100644 --- a/programs/drift/src/controller/orders.rs +++ b/programs/drift/src/controller/orders.rs @@ -868,8 +868,13 @@ pub fn fill_perp_order( .position(|order| order.order_id == order_id) .ok_or_else(print_error!(ErrorCode::OrderDoesNotExist))?; - let (order_status, market_index, order_market_type) = - get_struct_values!(user.orders[order_index], status, market_index, market_type); + let (order_status, market_index, order_market_type, order_direction) = get_struct_values!( + user.orders[order_index], + status, + market_index, + market_type, + direction + ); validate!( order_market_type == MarketType::Perp, @@ -927,10 +932,10 @@ pub fn fill_perp_order( } let reserve_price_before: u64; - let oracle_reserve_price_spread_pct_before: i64; let is_oracle_valid: bool; let oracle_validity: OracleValidity; let oracle_price: i64; + let oracle_twap_5min: i64; let mut amm_is_available = !state.amm_paused()?; { let market = &mut perp_market_map.get_ref_mut(&market_index)?; @@ -953,11 +958,11 @@ pub fn fill_perp_order( is_oracle_valid_for_action(oracle_validity, Some(DriftAction::FillOrderAmm))?; reserve_price_before = market.amm.reserve_price()?; - oracle_reserve_price_spread_pct_before = amm::calculate_oracle_twap_5min_mark_spread_pct( - &market.amm, - Some(reserve_price_before), - )?; oracle_price = oracle_price_data.price; + oracle_twap_5min = market + .amm + .historical_oracle_data + .last_oracle_price_twap_5min; } // allow oracle price to be used to calculate limit price if it's valid or stale for amm @@ -1047,7 +1052,7 @@ pub fn fill_perp_order( return Ok(0); } - let (base_asset_amount, _quote_asset_amount, potentially_risk_increasing) = fulfill_perp_order( + let (base_asset_amount, quote_asset_amount, _potentially_risk_increasing) = fulfill_perp_order( user, order_index, &user_key, @@ -1071,6 +1076,19 @@ pub fn fill_perp_order( amm_is_available, )?; + if base_asset_amount != 0 { + let fill_price = + calculate_fill_price(quote_asset_amount, base_asset_amount, BASE_PRECISION_U64)?; + + validate_fill_price_within_price_bands( + fill_price, + order_direction, + oracle_price, + oracle_twap_5min, + perp_market_map.get_ref(&market_index)?.margin_ratio_initial, + )?; + } + let base_asset_amount_after = user.perp_positions[position_index].base_asset_amount; let should_cancel_reduce_only = should_cancel_reduce_only_order(&user.orders[order_index], base_asset_amount_after)?; @@ -1111,12 +1129,6 @@ pub fn fill_perp_order( { let market = perp_market_map.get_ref(&market_index)?; - validate_market_within_price_band( - &market, - state, - potentially_risk_increasing, - Some(oracle_reserve_price_spread_pct_before), - )?; let open_interest = market.get_open_interest(); let max_open_interest = market.amm.max_open_interest; @@ -1572,6 +1584,14 @@ fn fulfill_perp_order( .update_volume_24h(fill_quote_asset_amount, user_order_direction, now)?; } + validate!( + (base_asset_amount > 0) == (quote_asset_amount > 0), + ErrorCode::DefaultError, + "invalid fill base = {} quote = {}", + base_asset_amount, + quote_asset_amount + )?; + let perp_market = perp_market_map.get_ref(&market_index)?; let taker_maintenance_margin_buffer = calculate_maintenance_buffer_ratio( perp_market.margin_ratio_initial, @@ -3084,8 +3104,13 @@ pub fn fill_spot_order( .position(|order| order.order_id == order_id) .ok_or_else(print_error!(ErrorCode::OrderDoesNotExist))?; - let (order_status, order_market_index, order_market_type) = - get_struct_values!(user.orders[order_index], status, market_index, market_type); + let (order_status, order_market_index, order_market_type, order_direction) = get_struct_values!( + user.orders[order_index], + status, + market_index, + market_type, + direction + ); { let spot_market = spot_market_map.get_ref(&order_market_index)?; @@ -3210,7 +3235,7 @@ pub fn fill_spot_order( return Ok(0); } - let (base_asset_amount, _quote_asset_amount) = fulfill_spot_order( + let (base_asset_amount, quote_asset_amount) = fulfill_spot_order( user, order_index, &user_key, @@ -3231,6 +3256,27 @@ pub fn fill_spot_order( fulfillment_params, )?; + if base_asset_amount != 0 { + let spot_market = spot_market_map.get_ref(&order_market_index)?; + let fill_price = calculate_fill_price( + quote_asset_amount, + base_asset_amount, + spot_market.get_precision(), + )?; + + let oracle_price = oracle_map.get_price_data(&spot_market.oracle)?.price; + let oracle_twap_5min = spot_market + .historical_oracle_data + .last_oracle_price_twap_5min; + validate_fill_price_within_price_bands( + fill_price, + order_direction, + oracle_price, + oracle_twap_5min, + spot_market.get_margin_ratio(&MarginRequirementType::Initial)?, + )?; + } + let is_open = user.orders[order_index].status == OrderStatus::Open; let is_reduce_only = user.orders[order_index].reduce_only; let should_cancel_reduce_only = if is_open && is_reduce_only { diff --git a/programs/drift/src/controller/orders/tests.rs b/programs/drift/src/controller/orders/tests.rs index 338ea0621..f9ff50746 100644 --- a/programs/drift/src/controller/orders/tests.rs +++ b/programs/drift/src/controller/orders/tests.rs @@ -7121,6 +7121,7 @@ pub mod fulfill_spot_order { LAMPORTS_PER_SOL_I64, LAMPORTS_PER_SOL_U64, PRICE_PRECISION_I64, PRICE_PRECISION_U64, SPOT_BALANCE_PRECISION, SPOT_BALANCE_PRECISION_U64, }; + use crate::state::oracle::HistoricalOracleData; use crate::state::perp_market_map::PerpMarketMap; use crate::state::spot_fulfillment_params::TestFulfillmentParams; use crate::state::spot_market::{SpotBalanceType, SpotMarket}; @@ -7333,12 +7334,14 @@ pub mod fulfill_spot_order { market_index: 1, deposit_balance: SPOT_BALANCE_PRECISION, oracle: oracle_price_key, + historical_oracle_data: HistoricalOracleData::default_price(100 * PRICE_PRECISION_I64), ..SpotMarket::default_base_market() }; create_anchor_account_info!(base_market, SpotMarket, base_market_account_info); let mut second_base_market = SpotMarket { market_index: 2, deposit_balance: SPOT_BALANCE_PRECISION, + historical_oracle_data: HistoricalOracleData::default_price(100 * PRICE_PRECISION_I64), ..SpotMarket::default_base_market() }; create_anchor_account_info!( diff --git a/programs/drift/src/math/orders.rs b/programs/drift/src/math/orders.rs index 52917c2ce..98a6a2004 100644 --- a/programs/drift/src/math/orders.rs +++ b/programs/drift/src/math/orders.rs @@ -401,6 +401,88 @@ pub fn limit_price_breaches_oracle_price_bands( } } +pub fn validate_fill_price_within_price_bands( + fill_price: u64, + direction: PositionDirection, + oracle_price: i64, + oracle_twap_5min: i64, + margin_ratio_initial: u32, +) -> DriftResult { + let oracle_price = oracle_price.unsigned_abs(); + let oracle_twap_5min = oracle_twap_5min.unsigned_abs(); + + let max_oracle_diff = margin_ratio_initial.cast::()?; + let max_oracle_twap_diff = MARGIN_PRECISION_U128 / 2; // 50% + + if direction == PositionDirection::Long { + if fill_price < oracle_price && fill_price < oracle_twap_5min { + return Ok(()); + } + + let percent_diff = fill_price + .saturating_sub(oracle_price) + .cast::()? + .safe_mul(MARGIN_PRECISION_U128)? + .safe_div(oracle_price.cast()?)?; + + validate!( + percent_diff < max_oracle_diff, + ErrorCode::PriceBandsBreached, + "Fill Price Breaches Oracle Price Bands: {} >= {}", + fill_price, + oracle_price + )?; + + let percent_diff = fill_price + .saturating_sub(oracle_twap_5min) + .cast::()? + .safe_mul(MARGIN_PRECISION_U128)? + .safe_div(oracle_twap_5min.cast()?)?; + + validate!( + percent_diff < max_oracle_twap_diff, + ErrorCode::PriceBandsBreached, + "Fill Price Breaches Oracle TWAP Price Bands: {} >= {}", + fill_price, + oracle_twap_5min + )?; + } else { + if fill_price > oracle_price && fill_price > oracle_twap_5min { + return Ok(()); + } + + let percent_diff = oracle_price + .saturating_sub(fill_price) + .cast::()? + .safe_mul(MARGIN_PRECISION_U128)? + .safe_div(oracle_price.cast()?)?; + + validate!( + percent_diff < max_oracle_diff, + ErrorCode::PriceBandsBreached, + "Fill Price Breaches Oracle Price Bands: {} <= {}", + fill_price, + oracle_price + )?; + + let percent_diff = oracle_twap_5min + .saturating_sub(fill_price) + .cast::()? + .safe_mul(MARGIN_PRECISION_U128)? + .safe_div(oracle_twap_5min.cast()?)?; + + validate!( + percent_diff < max_oracle_twap_diff, + ErrorCode::PriceBandsBreached, + "Fill Price Breaches Oracle TWAP Price Bands: {} <= {}", + fill_price, + oracle_twap_5min + )?; + } + + Ok(()) +} + pub fn order_satisfies_trigger_condition(order: &Order, oracle_price: u64) -> DriftResult { match order.trigger_condition { OrderTriggerCondition::Above => Ok(oracle_price > order.trigger_price), @@ -502,11 +584,11 @@ pub fn validate_fill_price( quote_asset_amount }; - let fill_price = rounded_quote_asset_amount - .cast::()? - .safe_mul(base_precision as u128)? - .safe_div(base_asset_amount.cast()?)? - .cast::()?; + let fill_price = calculate_fill_price( + rounded_quote_asset_amount, + base_asset_amount, + base_precision, + )?; if order_direction == PositionDirection::Long && fill_price > order_limit_price { msg!( @@ -535,6 +617,18 @@ pub fn validate_fill_price( Ok(()) } +pub fn calculate_fill_price( + quote_asset_amount: u64, + base_asset_amount: u64, + base_precision: u64, +) -> DriftResult { + quote_asset_amount + .cast::()? + .safe_mul(base_precision as u128)? + .safe_div(base_asset_amount.cast()?)? + .cast::() +} + pub fn get_fallback_price( direction: &PositionDirection, bid_price: u64, From 55c0c7f093b27647b5ddd97757cd4a185165040f Mon Sep 17 00:00:00 2001 From: Chris Heaney Date: Fri, 30 Jun 2023 15:51:01 -0400 Subject: [PATCH 04/18] remove possibly risk increasing --- programs/drift/src/controller/orders.rs | 17 ++++------------- .../src/controller/orders/amm_jit_tests.rs | 14 +++++++------- .../src/controller/orders/amm_lp_jit_tests.rs | 10 +++++----- programs/drift/src/controller/orders/tests.rs | 14 +++++++------- 4 files changed, 23 insertions(+), 32 deletions(-) diff --git a/programs/drift/src/controller/orders.rs b/programs/drift/src/controller/orders.rs index 47bf8017d..c0ffcf4f3 100644 --- a/programs/drift/src/controller/orders.rs +++ b/programs/drift/src/controller/orders.rs @@ -1052,7 +1052,7 @@ pub fn fill_perp_order( return Ok(0); } - let (base_asset_amount, quote_asset_amount, _potentially_risk_increasing) = fulfill_perp_order( + let (base_asset_amount, quote_asset_amount) = fulfill_perp_order( user, order_index, &user_key, @@ -1457,12 +1457,9 @@ fn fulfill_perp_order( slot: u64, min_auction_duration: u8, amm_is_available: bool, -) -> DriftResult<(u64, u64, bool)> { +) -> DriftResult<(u64, u64)> { let market_index = user.orders[user_order_index].market_index; - let user_position_index = get_position_index(&user.perp_positions, market_index)?; - let position_base_asset_amount_before = - user.perp_positions[user_position_index].base_asset_amount; let user_order_risk_decreasing = determine_if_user_order_is_risk_decreasing(user, market_index, user_order_index)?; @@ -1483,7 +1480,7 @@ fn fulfill_perp_order( }; if fulfillment_methods.is_empty() { - return Ok((0, 0, false)); + return Ok((0, 0)); } let mut base_asset_amount = 0_u64; @@ -1647,13 +1644,7 @@ fn fulfill_perp_order( } } - let position_base_asset_amount_after = - user.perp_positions[user_position_index].base_asset_amount; - let risk_increasing = position_base_asset_amount_before == 0 - || position_base_asset_amount_before.signum() != position_base_asset_amount_after.signum() - || position_base_asset_amount_before.abs() < position_base_asset_amount_after.abs(); - - Ok((base_asset_amount, quote_asset_amount, risk_increasing)) + Ok((base_asset_amount, quote_asset_amount)) } #[allow(clippy::type_complexity)] diff --git a/programs/drift/src/controller/orders/amm_jit_tests.rs b/programs/drift/src/controller/orders/amm_jit_tests.rs index 668adc8a2..477991536 100644 --- a/programs/drift/src/controller/orders/amm_jit_tests.rs +++ b/programs/drift/src/controller/orders/amm_jit_tests.rs @@ -1070,7 +1070,7 @@ pub mod amm_jit { let maker_and_referrer_stats = UserStatsMap::load_one(&maker_stats_account_info).unwrap(); let mut filler_stats = UserStats::default(); - let (base_asset_amount, _, _) = fulfill_perp_order( + let (base_asset_amount, _) = fulfill_perp_order( &mut taker, 0, &taker_key, @@ -1282,7 +1282,7 @@ pub mod amm_jit { assert_eq!(market.amm.total_mm_fee, 0); assert_eq!(market.amm.total_fee_withdrawn, 0); - let (base_asset_amount, _, _) = fulfill_perp_order( + let (base_asset_amount, _) = fulfill_perp_order( &mut taker, 0, &taker_key, @@ -1492,7 +1492,7 @@ pub mod amm_jit { assert_eq!(market.amm.total_mm_fee, 0); assert_eq!(market.amm.total_fee_withdrawn, 0); - let (base_asset_amount, _, _) = fulfill_perp_order( + let (base_asset_amount, _) = fulfill_perp_order( &mut taker, 0, &taker_key, @@ -1725,7 +1725,7 @@ pub mod amm_jit { assert_eq!(market.amm.total_mm_fee, 0); assert_eq!(market.amm.total_fee_withdrawn, 0); - let (base_asset_amount, _, _) = fulfill_perp_order( + let (base_asset_amount, _) = fulfill_perp_order( &mut taker, 0, &taker_key, @@ -2115,7 +2115,7 @@ pub mod amm_jit { assert_eq!(market.amm.total_fee_withdrawn, 0); // fulfill with match - let (base_asset_amount, _, _) = fulfill_perp_order( + let (base_asset_amount, _) = fulfill_perp_order( &mut taker, 0, &taker_key, @@ -2313,7 +2313,7 @@ pub mod amm_jit { assert_eq!(market.amm.total_fee_withdrawn, 0); // fulfill with match - let (base_asset_amount, _, _) = fulfill_perp_order( + let (base_asset_amount, _) = fulfill_perp_order( &mut taker, 0, &taker_key, @@ -3073,7 +3073,7 @@ pub mod amm_jit { assert_eq!(taker.perp_positions[0].open_orders, 1); // fulfill with match - let (base_asset_amount, _, _) = fulfill_perp_order( + let (base_asset_amount, _) = fulfill_perp_order( &mut taker, 0, &taker_key, diff --git a/programs/drift/src/controller/orders/amm_lp_jit_tests.rs b/programs/drift/src/controller/orders/amm_lp_jit_tests.rs index 8dff92930..2afb194e8 100644 --- a/programs/drift/src/controller/orders/amm_lp_jit_tests.rs +++ b/programs/drift/src/controller/orders/amm_lp_jit_tests.rs @@ -1511,7 +1511,7 @@ pub mod amm_lp_jit { let maker_and_referrer_stats = UserStatsMap::load_one(&maker_stats_account_info).unwrap(); let mut filler_stats = UserStats::default(); - let (base_asset_amount, _, _) = fulfill_perp_order( + let (base_asset_amount, _) = fulfill_perp_order( &mut taker, 0, &taker_key, @@ -1726,7 +1726,7 @@ pub mod amm_lp_jit { assert_eq!(market.amm.total_mm_fee, 0); assert_eq!(market.amm.total_fee_withdrawn, 0); - let (base_asset_amount, _, _) = fulfill_perp_order( + let (base_asset_amount, _) = fulfill_perp_order( &mut taker, 0, &taker_key, @@ -2122,7 +2122,7 @@ pub mod amm_lp_jit { assert_eq!(market.amm.total_fee_withdrawn, 0); // fulfill with match - let (base_asset_amount, _, _) = fulfill_perp_order( + let (base_asset_amount, _) = fulfill_perp_order( &mut taker, 0, &taker_key, @@ -2323,7 +2323,7 @@ pub mod amm_lp_jit { assert_eq!(market.amm.total_fee_withdrawn, 0); // fulfill with match - let (base_asset_amount, _, _) = fulfill_perp_order( + let (base_asset_amount, _) = fulfill_perp_order( &mut taker, 0, &taker_key, @@ -3086,7 +3086,7 @@ pub mod amm_lp_jit { assert_eq!(taker.perp_positions[0].open_orders, 1); // fulfill with match - let (base_asset_amount, _, _) = fulfill_perp_order( + let (base_asset_amount, _) = fulfill_perp_order( &mut taker, 0, &taker_key, diff --git a/programs/drift/src/controller/orders/tests.rs b/programs/drift/src/controller/orders/tests.rs index f9ff50746..13ba33126 100644 --- a/programs/drift/src/controller/orders/tests.rs +++ b/programs/drift/src/controller/orders/tests.rs @@ -2636,7 +2636,7 @@ pub mod fulfill_order { let mut filler_stats = UserStats::default(); - let (base_asset_amount, _, _) = fulfill_perp_order( + let (base_asset_amount, _) = fulfill_perp_order( &mut taker, 0, &taker_key, @@ -2879,7 +2879,7 @@ pub mod fulfill_order { let mut filler_stats = UserStats::default(); - let (base_asset_amount, _, _) = fulfill_perp_order( + let (base_asset_amount, _) = fulfill_perp_order( &mut taker, 0, &taker_key, @@ -3070,7 +3070,7 @@ pub mod fulfill_order { let mut filler_stats = UserStats::default(); - let (base_asset_amount, _, _) = fulfill_perp_order( + let (base_asset_amount, _) = fulfill_perp_order( &mut taker, 0, &taker_key, @@ -3274,7 +3274,7 @@ pub mod fulfill_order { create_anchor_account_info!(maker_stats, UserStats, maker_stats_account_info); let maker_and_referrer_stats = UserStatsMap::load_one(&maker_stats_account_info).unwrap(); - let (base_asset_amount, _, _) = fulfill_perp_order( + let (base_asset_amount, _) = fulfill_perp_order( &mut taker, 0, &taker_key, @@ -3438,7 +3438,7 @@ pub mod fulfill_order { let mut taker_stats = UserStats::default(); - let (base_asset_amount, _, _) = fulfill_perp_order( + let (base_asset_amount, _) = fulfill_perp_order( &mut taker, 0, &taker_key, @@ -3606,7 +3606,7 @@ pub mod fulfill_order { // // let mut taker_stats = UserStats::default(); // - // let (base_asset_amount, _, _) = fulfill_perp_order( + // let (base_asset_amount, _) = fulfill_perp_order( // &mut taker, // 0, // &taker_key, @@ -3839,7 +3839,7 @@ pub mod fulfill_order { let taker_before = taker; let maker_before = maker; - let (base_asset_amount, _, _) = fulfill_perp_order( + let (base_asset_amount, _) = fulfill_perp_order( &mut taker, 0, &taker_key, From f5721f9e22deb1e8fb67dc4e142fa725c7c2d66b Mon Sep 17 00:00:00 2001 From: Chris Heaney Date: Fri, 30 Jun 2023 15:59:55 -0400 Subject: [PATCH 05/18] validate base/quote in fulfill_spot_order --- programs/drift/src/controller/orders.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/programs/drift/src/controller/orders.rs b/programs/drift/src/controller/orders.rs index c0ffcf4f3..23b3a7509 100644 --- a/programs/drift/src/controller/orders.rs +++ b/programs/drift/src/controller/orders.rs @@ -3572,6 +3572,14 @@ fn fulfill_spot_order( quote_asset_amount = quote_asset_amount.safe_add(quote_filled)?; } + validate!( + (base_asset_amount > 0) == (quote_asset_amount > 0), + ErrorCode::DefaultError, + "invalid fill base = {} quote = {}", + base_asset_amount, + quote_asset_amount + )?; + let initial_margin_ratio = base_market.get_margin_ratio(&MarginRequirementType::Initial)?; let maintenance_margin_ratio = base_market.get_margin_ratio(&MarginRequirementType::Maintenance)?; From 039b4fff6427930095161ef8648d5def97c7bcf6 Mon Sep 17 00:00:00 2001 From: Chris Heaney Date: Sat, 1 Jul 2023 11:01:43 -0400 Subject: [PATCH 06/18] add cargo tests for validation logic --- programs/drift/src/controller/orders/tests.rs | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) diff --git a/programs/drift/src/controller/orders/tests.rs b/programs/drift/src/controller/orders/tests.rs index 13ba33126..b6dcab292 100644 --- a/programs/drift/src/controller/orders/tests.rs +++ b/programs/drift/src/controller/orders/tests.rs @@ -9326,3 +9326,120 @@ pub mod get_maker_orders_info { assert_eq!(maker_order_price_and_indexes.len(), 64); } } + +pub mod validate_fill_price_within_price_bands { + use crate::math::orders::validate_fill_price_within_price_bands; + use crate::{PositionDirection, MARGIN_PRECISION, PRICE_PRECISION_I64, PRICE_PRECISION_U64}; + + #[test] + fn valid_long() { + let oracle_price = 100 * PRICE_PRECISION_I64; + let twap = oracle_price; + let fill_price = 105 * PRICE_PRECISION_U64; + let direction = PositionDirection::Long; + let margin_ratio_initial = MARGIN_PRECISION / 10; + + assert!(validate_fill_price_within_price_bands( + fill_price, + direction, + oracle_price, + twap, + margin_ratio_initial, + ) + .is_ok()) + } + + #[test] + fn valid_short() { + let oracle_price = 100 * PRICE_PRECISION_I64; + let twap = oracle_price; + let fill_price = 95 * PRICE_PRECISION_U64; + let direction = PositionDirection::Short; + let margin_ratio_initial = MARGIN_PRECISION / 10; + + assert!(validate_fill_price_within_price_bands( + fill_price, + direction, + oracle_price, + twap, + margin_ratio_initial, + ) + .is_ok()) + } + + #[test] + fn invalid_long_breaches_oracle() { + let oracle_price = 100 * PRICE_PRECISION_I64; + let twap = oracle_price; + // 11% greater than oracle price + let fill_price = 111 * PRICE_PRECISION_U64; + let direction = PositionDirection::Long; + let margin_ratio_initial = MARGIN_PRECISION / 10; // 10x + + assert!(validate_fill_price_within_price_bands( + fill_price, + direction, + oracle_price, + twap, + margin_ratio_initial, + ) + .is_err()) + } + + #[test] + fn invalid_short_breaches_oracle() { + let oracle_price = 100 * PRICE_PRECISION_I64; + let twap = oracle_price; + // 11% less than oracle price + let fill_price = 89 * PRICE_PRECISION_U64; + let direction = PositionDirection::Short; + let margin_ratio_initial = MARGIN_PRECISION / 10; // 10x + + assert!(validate_fill_price_within_price_bands( + fill_price, + direction, + oracle_price, + twap, + margin_ratio_initial, + ) + .is_err()) + } + + #[test] + fn invalid_long_breaches_oracle_twap() { + let oracle_price = 150 * PRICE_PRECISION_I64; + let twap = 100 * PRICE_PRECISION_I64; + // 50% greater than twap + let fill_price = 150 * PRICE_PRECISION_U64; + let direction = PositionDirection::Long; + let margin_ratio_initial = MARGIN_PRECISION / 10; // 10x + + assert!(validate_fill_price_within_price_bands( + fill_price, + direction, + oracle_price, + twap, + margin_ratio_initial, + ) + .is_err()) + } + + #[test] + fn invalid_short_breaches_oracle_twap() { + let oracle_price = 50 * PRICE_PRECISION_I64; + let twap = 100 * PRICE_PRECISION_I64; + // 50% less than twap + let fill_price = 50 * PRICE_PRECISION_U64; + let direction = PositionDirection::Short; + let margin_ratio_initial = MARGIN_PRECISION / 10; // 10x + + assert!(validate_fill_price_within_price_bands( + fill_price, + direction, + oracle_price, + twap, + margin_ratio_initial, + ) + .is_err()) + } +} From c1c7ba6cc48f2ea87ce7020c798c203372676959 Mon Sep 17 00:00:00 2001 From: Chris Heaney Date: Sat, 1 Jul 2023 11:24:29 -0400 Subject: [PATCH 07/18] update cumulative interest --- programs/drift/src/controller/orders.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/programs/drift/src/controller/orders.rs b/programs/drift/src/controller/orders.rs index 23b3a7509..c2dab02b2 100644 --- a/programs/drift/src/controller/orders.rs +++ b/programs/drift/src/controller/orders.rs @@ -15,6 +15,7 @@ use crate::controller::position::{ }; use crate::controller::spot_balance::{ transfer_spot_balance_to_revenue_pool, update_spot_balances, + update_spot_market_cumulative_interest, }; use crate::controller::spot_position::{ decrease_spot_open_bids_and_asks, increase_spot_open_bids_and_asks, @@ -3149,6 +3150,16 @@ pub fn fill_spot_order( } } + { + let mut base_market = spot_market_map.get_ref_mut(&order_market_index)?; + let oracle_price_data = oracle_map.get_price_data(&base_market.oracle)?; + update_spot_market_cumulative_interest(&mut base_market, Some(oracle_price_data), now)?; + + let mut quote_market = spot_market_map.get_quote_spot_market_mut()?; + let oracle_price_data = oracle_map.get_price_data("e_market.oracle)?; + update_spot_market_cumulative_interest(&mut quote_market, Some(oracle_price_data), now)?; + } + let is_filler_taker = user_key == filler_key; let is_filler_maker = maker.map_or(false, |maker| maker.key() == filler_key); let (mut filler, mut filler_stats) = if !is_filler_maker && !is_filler_taker { From 60e3076438091c9cd0d6e7127b1ae9be132e54a3 Mon Sep 17 00:00:00 2001 From: Chris Heaney Date: Sat, 1 Jul 2023 12:14:59 -0400 Subject: [PATCH 08/18] initial stab at checking limit price before fill --- programs/drift/src/controller/orders.rs | 29 +++++++++++++++- .../src/controller/orders/amm_jit_tests.rs | 4 +-- .../src/controller/orders/amm_lp_jit_tests.rs | 14 ++++---- programs/drift/src/controller/orders/tests.rs | 2 ++ programs/drift/src/math/fulfillment.rs | 24 ++++++++++++-- programs/drift/src/math/fulfillment/tests.rs | 33 ++++++++++++------- 6 files changed, 82 insertions(+), 24 deletions(-) diff --git a/programs/drift/src/controller/orders.rs b/programs/drift/src/controller/orders.rs index c2dab02b2..479f283ac 100644 --- a/programs/drift/src/controller/orders.rs +++ b/programs/drift/src/controller/orders.rs @@ -1473,9 +1473,10 @@ fn fulfill_perp_order( maker_orders_info, &market.amm, reserve_price_before, - Some(oracle_price), + oracle_price, amm_is_available, slot, + market.margin_ratio_initial, min_auction_duration, )? }; @@ -3534,6 +3535,32 @@ fn fulfill_spot_order( let mut quote_market = spot_market_map.get_quote_spot_market_mut()?; let mut base_market = spot_market_map.get_ref_mut(&base_market_index)?; + let oracle_price = oracle_map.get_price_data(&base_market.oracle)?.price; + let taker_price = user.orders[user_order_index].get_limit_price( + Some(oracle_price), + None, + slot, + base_market.order_tick_size, + )?; + + if let Some(taker_price) = taker_price { + let may_breach_price_band = validate_fill_price_within_price_bands( + taker_price, + user.orders[user_order_index].direction, + oracle_price, + base_market + .historical_oracle_data + .last_oracle_price_twap_5min, + base_market.get_margin_ratio(&MarginRequirementType::Initial)?, + ) + .is_err(); + + if may_breach_price_band { + msg!("Cant fill because taker price may breach price band"); + return Ok((0, 0)); + } + } + let mut base_asset_amount = 0_u64; let mut quote_asset_amount = 0_u64; for fulfillment_method in fulfillment_methods.iter() { diff --git a/programs/drift/src/controller/orders/amm_jit_tests.rs b/programs/drift/src/controller/orders/amm_jit_tests.rs index 477991536..ae71fb701 100644 --- a/programs/drift/src/controller/orders/amm_jit_tests.rs +++ b/programs/drift/src/controller/orders/amm_jit_tests.rs @@ -2400,7 +2400,7 @@ pub mod amm_jit { }, ..AMM::default() }, - margin_ratio_initial: 1000, + margin_ratio_initial: 10000, margin_ratio_maintenance: 500, status: MarketStatus::Initialized, ..PerpMarket::default_test() @@ -2680,7 +2680,7 @@ pub mod amm_jit { ..AMM::default() }, - margin_ratio_initial: 1000, + margin_ratio_initial: 10000, margin_ratio_maintenance: 500, status: MarketStatus::Initialized, ..PerpMarket::default_test() diff --git a/programs/drift/src/controller/orders/amm_lp_jit_tests.rs b/programs/drift/src/controller/orders/amm_lp_jit_tests.rs index 2afb194e8..e907f7039 100644 --- a/programs/drift/src/controller/orders/amm_lp_jit_tests.rs +++ b/programs/drift/src/controller/orders/amm_lp_jit_tests.rs @@ -299,7 +299,7 @@ pub mod amm_lp_jit { let now = 0_i64; let slot = 0_u64; - let mut oracle_price = get_pyth_price(21, 6); + 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(); @@ -333,9 +333,9 @@ pub mod amm_lp_jit { long_spread: 20000, short_spread: 20000, historical_oracle_data: HistoricalOracleData { - last_oracle_price: (21 * PRICE_PRECISION) as i64, - last_oracle_price_twap: (21 * PRICE_PRECISION) as i64, - last_oracle_price_twap_5min: (21 * PRICE_PRECISION) as i64, + 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() }, @@ -343,7 +343,7 @@ pub mod amm_lp_jit { concentration_coef: CONCENTRATION_PRECISION + 1, ..AMM::default() }, - margin_ratio_initial: 1000, + margin_ratio_initial: 10000, margin_ratio_maintenance: 500, status: MarketStatus::Active, ..PerpMarket::default_test() @@ -2410,7 +2410,7 @@ pub mod amm_lp_jit { }, ..AMM::default() }, - margin_ratio_initial: 1000, + margin_ratio_initial: 10000, margin_ratio_maintenance: 500, status: MarketStatus::Initialized, ..PerpMarket::default_test() @@ -2690,7 +2690,7 @@ pub mod amm_lp_jit { ..AMM::default() }, - margin_ratio_initial: 1000, + margin_ratio_initial: 10000, margin_ratio_maintenance: 500, status: MarketStatus::Initialized, ..PerpMarket::default_test() diff --git a/programs/drift/src/controller/orders/tests.rs b/programs/drift/src/controller/orders/tests.rs index b6dcab292..9b5180bff 100644 --- a/programs/drift/src/controller/orders/tests.rs +++ b/programs/drift/src/controller/orders/tests.rs @@ -3655,6 +3655,7 @@ pub mod fulfill_order { order_tick_size: 1, historical_oracle_data: HistoricalOracleData { last_oracle_price: 100 * PRICE_PRECISION_I64, + last_oracle_price_twap_5min: 100 * PRICE_PRECISION_I64, ..HistoricalOracleData::default() }, max_slippage_ratio: 50, @@ -3681,6 +3682,7 @@ pub mod fulfill_order { order_tick_size: 1, historical_oracle_data: HistoricalOracleData { last_oracle_price: 20000 * PRICE_PRECISION_I64, + last_oracle_price_twap_5min: 20000 * PRICE_PRECISION_I64, ..HistoricalOracleData::default() }, max_slippage_ratio: 50, diff --git a/programs/drift/src/math/fulfillment.rs b/programs/drift/src/math/fulfillment.rs index b9fd43fbb..176535d5d 100644 --- a/programs/drift/src/math/fulfillment.rs +++ b/programs/drift/src/math/fulfillment.rs @@ -2,9 +2,11 @@ use crate::controller::position::PositionDirection; use crate::error::DriftResult; use crate::math::auction::is_amm_available_liquidity_source; use crate::math::matching::do_orders_cross; +use crate::math::orders::validate_fill_price_within_price_bands; use crate::state::fulfillment::{PerpFulfillmentMethod, SpotFulfillmentMethod}; use crate::state::perp_market::AMM; use crate::state::user::Order; +use solana_program::msg; use solana_program::pubkey::Pubkey; #[cfg(test)] @@ -15,19 +17,35 @@ pub fn determine_perp_fulfillment_methods( maker_orders_info: &[(Pubkey, usize, u64)], amm: &AMM, amm_reserve_price: u64, - valid_oracle_price: Option, + oracle_price: i64, amm_is_available: bool, slot: u64, + margin_ratio_initial: u32, min_auction_duration: u8, ) -> DriftResult> { let mut fulfillment_methods = Vec::with_capacity(8); let can_fill_with_amm = amm_is_available - && valid_oracle_price.is_some() && is_amm_available_liquidity_source(taker_order, min_auction_duration, slot)?; let taker_price = - taker_order.get_limit_price(valid_oracle_price, None, slot, amm.order_tick_size)?; + taker_order.get_limit_price(Some(oracle_price), None, slot, amm.order_tick_size)?; + + if let Some(taker_price) = taker_price { + let may_breach_price_bands = validate_fill_price_within_price_bands( + taker_price, + taker_order.direction, + oracle_price, + amm.historical_oracle_data.last_oracle_price_twap_5min, + margin_ratio_initial, + ) + .is_err(); + + if may_breach_price_bands { + msg!("Cant fill order as limit price may breach price bands"); + return Ok(fulfillment_methods); + } + } let maker_direction = taker_order.direction.opposite(); diff --git a/programs/drift/src/math/fulfillment/tests.rs b/programs/drift/src/math/fulfillment/tests.rs index 7b0af30ce..90d31d81b 100644 --- a/programs/drift/src/math/fulfillment/tests.rs +++ b/programs/drift/src/math/fulfillment/tests.rs @@ -58,10 +58,11 @@ mod determine_perp_fulfillment_methods { &[(Pubkey::default(), 0, 103 * PRICE_PRECISION_U64)], &market.amm, market.amm.reserve_price().unwrap(), - Some(oracle_price), + oracle_price, true, 0, 0, + 0, ) .unwrap(); @@ -115,10 +116,11 @@ mod determine_perp_fulfillment_methods { &[(Pubkey::default(), 0, 99 * PRICE_PRECISION_U64)], &market.amm, market.amm.reserve_price().unwrap(), - Some(oracle_price), + oracle_price, true, 0, 0, + 0, ) .unwrap(); @@ -184,10 +186,11 @@ mod determine_perp_fulfillment_methods { &[(Pubkey::default(), 0, 101 * PRICE_PRECISION_U64)], &market.amm, market.amm.reserve_price().unwrap(), - Some(oracle_price), + oracle_price, true, 0, 0, + 0, ) .unwrap(); @@ -251,10 +254,11 @@ mod determine_perp_fulfillment_methods { ], &market.amm, market.amm.reserve_price().unwrap(), - Some(oracle_price), + oracle_price, true, 0, 0, + 0, ) .unwrap(); @@ -323,10 +327,11 @@ mod determine_perp_fulfillment_methods { ], &market.amm, market.amm.reserve_price().unwrap(), - Some(oracle_price), + oracle_price, true, 0, 0, + 0, ) .unwrap(); @@ -390,10 +395,11 @@ mod determine_perp_fulfillment_methods { ], &market.amm, market.amm.reserve_price().unwrap(), - Some(oracle_price), + oracle_price, true, 0, 0, + 0, ) .unwrap(); @@ -459,10 +465,11 @@ mod determine_perp_fulfillment_methods { ], &market.amm, market.amm.reserve_price().unwrap(), - Some(oracle_price), + oracle_price, true, 0, 0, + 0, ) .unwrap(); @@ -527,10 +534,11 @@ mod determine_perp_fulfillment_methods { ], &market.amm, market.amm.reserve_price().unwrap(), - Some(oracle_price), + oracle_price, true, 0, 0, + 0, ) .unwrap(); @@ -594,10 +602,11 @@ mod determine_perp_fulfillment_methods { ], &market.amm, market.amm.reserve_price().unwrap(), - Some(oracle_price), + oracle_price, true, 0, 0, + 0, ) .unwrap(); @@ -663,10 +672,11 @@ mod determine_perp_fulfillment_methods { ], &market.amm, market.amm.reserve_price().unwrap(), - Some(oracle_price), + oracle_price, true, 0, 0, + 0, ) .unwrap(); @@ -723,10 +733,11 @@ mod determine_perp_fulfillment_methods { ], &market.amm, market.amm.reserve_price().unwrap(), - Some(oracle_price), + oracle_price, true, 0, 0, + 0, ) .unwrap(); From 8e84db0d7fcc65398c9f8e22fee070c0ed74520f Mon Sep 17 00:00:00 2001 From: Chris Heaney Date: Sat, 1 Jul 2023 14:30:34 -0400 Subject: [PATCH 09/18] Revert "initial stab at checking limit price before fill" This reverts commit 60e3076438091c9cd0d6e7127b1ae9be132e54a3. --- programs/drift/src/controller/orders.rs | 29 +--------------- .../src/controller/orders/amm_jit_tests.rs | 4 +-- .../src/controller/orders/amm_lp_jit_tests.rs | 14 ++++---- programs/drift/src/controller/orders/tests.rs | 2 -- programs/drift/src/math/fulfillment.rs | 24 ++------------ programs/drift/src/math/fulfillment/tests.rs | 33 +++++++------------ 6 files changed, 24 insertions(+), 82 deletions(-) diff --git a/programs/drift/src/controller/orders.rs b/programs/drift/src/controller/orders.rs index 479f283ac..c2dab02b2 100644 --- a/programs/drift/src/controller/orders.rs +++ b/programs/drift/src/controller/orders.rs @@ -1473,10 +1473,9 @@ fn fulfill_perp_order( maker_orders_info, &market.amm, reserve_price_before, - oracle_price, + Some(oracle_price), amm_is_available, slot, - market.margin_ratio_initial, min_auction_duration, )? }; @@ -3535,32 +3534,6 @@ fn fulfill_spot_order( let mut quote_market = spot_market_map.get_quote_spot_market_mut()?; let mut base_market = spot_market_map.get_ref_mut(&base_market_index)?; - let oracle_price = oracle_map.get_price_data(&base_market.oracle)?.price; - let taker_price = user.orders[user_order_index].get_limit_price( - Some(oracle_price), - None, - slot, - base_market.order_tick_size, - )?; - - if let Some(taker_price) = taker_price { - let may_breach_price_band = validate_fill_price_within_price_bands( - taker_price, - user.orders[user_order_index].direction, - oracle_price, - base_market - .historical_oracle_data - .last_oracle_price_twap_5min, - base_market.get_margin_ratio(&MarginRequirementType::Initial)?, - ) - .is_err(); - - if may_breach_price_band { - msg!("Cant fill because taker price may breach price band"); - return Ok((0, 0)); - } - } - let mut base_asset_amount = 0_u64; let mut quote_asset_amount = 0_u64; for fulfillment_method in fulfillment_methods.iter() { diff --git a/programs/drift/src/controller/orders/amm_jit_tests.rs b/programs/drift/src/controller/orders/amm_jit_tests.rs index ae71fb701..477991536 100644 --- a/programs/drift/src/controller/orders/amm_jit_tests.rs +++ b/programs/drift/src/controller/orders/amm_jit_tests.rs @@ -2400,7 +2400,7 @@ pub mod amm_jit { }, ..AMM::default() }, - margin_ratio_initial: 10000, + margin_ratio_initial: 1000, margin_ratio_maintenance: 500, status: MarketStatus::Initialized, ..PerpMarket::default_test() @@ -2680,7 +2680,7 @@ pub mod amm_jit { ..AMM::default() }, - margin_ratio_initial: 10000, + margin_ratio_initial: 1000, margin_ratio_maintenance: 500, status: MarketStatus::Initialized, ..PerpMarket::default_test() diff --git a/programs/drift/src/controller/orders/amm_lp_jit_tests.rs b/programs/drift/src/controller/orders/amm_lp_jit_tests.rs index e907f7039..2afb194e8 100644 --- a/programs/drift/src/controller/orders/amm_lp_jit_tests.rs +++ b/programs/drift/src/controller/orders/amm_lp_jit_tests.rs @@ -299,7 +299,7 @@ pub mod amm_lp_jit { let now = 0_i64; let slot = 0_u64; - let mut oracle_price = get_pyth_price(100, 6); + let mut oracle_price = get_pyth_price(21, 6); let oracle_price_key = Pubkey::from_str("J83w4HKfqxwcq3BEMMkPFSppX3gqekLyLJBexebFVkix").unwrap(); let pyth_program = crate::ids::pyth_program::id(); @@ -333,9 +333,9 @@ pub mod amm_lp_jit { long_spread: 20000, short_spread: 20000, 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, + last_oracle_price: (21 * PRICE_PRECISION) as i64, + last_oracle_price_twap: (21 * PRICE_PRECISION) as i64, + last_oracle_price_twap_5min: (21 * PRICE_PRECISION) as i64, ..HistoricalOracleData::default() }, @@ -343,7 +343,7 @@ pub mod amm_lp_jit { concentration_coef: CONCENTRATION_PRECISION + 1, ..AMM::default() }, - margin_ratio_initial: 10000, + margin_ratio_initial: 1000, margin_ratio_maintenance: 500, status: MarketStatus::Active, ..PerpMarket::default_test() @@ -2410,7 +2410,7 @@ pub mod amm_lp_jit { }, ..AMM::default() }, - margin_ratio_initial: 10000, + margin_ratio_initial: 1000, margin_ratio_maintenance: 500, status: MarketStatus::Initialized, ..PerpMarket::default_test() @@ -2690,7 +2690,7 @@ pub mod amm_lp_jit { ..AMM::default() }, - margin_ratio_initial: 10000, + margin_ratio_initial: 1000, margin_ratio_maintenance: 500, status: MarketStatus::Initialized, ..PerpMarket::default_test() diff --git a/programs/drift/src/controller/orders/tests.rs b/programs/drift/src/controller/orders/tests.rs index 9b5180bff..b6dcab292 100644 --- a/programs/drift/src/controller/orders/tests.rs +++ b/programs/drift/src/controller/orders/tests.rs @@ -3655,7 +3655,6 @@ pub mod fulfill_order { order_tick_size: 1, historical_oracle_data: HistoricalOracleData { last_oracle_price: 100 * PRICE_PRECISION_I64, - last_oracle_price_twap_5min: 100 * PRICE_PRECISION_I64, ..HistoricalOracleData::default() }, max_slippage_ratio: 50, @@ -3682,7 +3681,6 @@ pub mod fulfill_order { order_tick_size: 1, historical_oracle_data: HistoricalOracleData { last_oracle_price: 20000 * PRICE_PRECISION_I64, - last_oracle_price_twap_5min: 20000 * PRICE_PRECISION_I64, ..HistoricalOracleData::default() }, max_slippage_ratio: 50, diff --git a/programs/drift/src/math/fulfillment.rs b/programs/drift/src/math/fulfillment.rs index 176535d5d..b9fd43fbb 100644 --- a/programs/drift/src/math/fulfillment.rs +++ b/programs/drift/src/math/fulfillment.rs @@ -2,11 +2,9 @@ use crate::controller::position::PositionDirection; use crate::error::DriftResult; use crate::math::auction::is_amm_available_liquidity_source; use crate::math::matching::do_orders_cross; -use crate::math::orders::validate_fill_price_within_price_bands; use crate::state::fulfillment::{PerpFulfillmentMethod, SpotFulfillmentMethod}; use crate::state::perp_market::AMM; use crate::state::user::Order; -use solana_program::msg; use solana_program::pubkey::Pubkey; #[cfg(test)] @@ -17,35 +15,19 @@ pub fn determine_perp_fulfillment_methods( maker_orders_info: &[(Pubkey, usize, u64)], amm: &AMM, amm_reserve_price: u64, - oracle_price: i64, + valid_oracle_price: Option, amm_is_available: bool, slot: u64, - margin_ratio_initial: u32, min_auction_duration: u8, ) -> DriftResult> { let mut fulfillment_methods = Vec::with_capacity(8); let can_fill_with_amm = amm_is_available + && valid_oracle_price.is_some() && is_amm_available_liquidity_source(taker_order, min_auction_duration, slot)?; let taker_price = - taker_order.get_limit_price(Some(oracle_price), None, slot, amm.order_tick_size)?; - - if let Some(taker_price) = taker_price { - let may_breach_price_bands = validate_fill_price_within_price_bands( - taker_price, - taker_order.direction, - oracle_price, - amm.historical_oracle_data.last_oracle_price_twap_5min, - margin_ratio_initial, - ) - .is_err(); - - if may_breach_price_bands { - msg!("Cant fill order as limit price may breach price bands"); - return Ok(fulfillment_methods); - } - } + taker_order.get_limit_price(valid_oracle_price, None, slot, amm.order_tick_size)?; let maker_direction = taker_order.direction.opposite(); diff --git a/programs/drift/src/math/fulfillment/tests.rs b/programs/drift/src/math/fulfillment/tests.rs index 90d31d81b..7b0af30ce 100644 --- a/programs/drift/src/math/fulfillment/tests.rs +++ b/programs/drift/src/math/fulfillment/tests.rs @@ -58,11 +58,10 @@ mod determine_perp_fulfillment_methods { &[(Pubkey::default(), 0, 103 * PRICE_PRECISION_U64)], &market.amm, market.amm.reserve_price().unwrap(), - oracle_price, + Some(oracle_price), true, 0, 0, - 0, ) .unwrap(); @@ -116,11 +115,10 @@ mod determine_perp_fulfillment_methods { &[(Pubkey::default(), 0, 99 * PRICE_PRECISION_U64)], &market.amm, market.amm.reserve_price().unwrap(), - oracle_price, + Some(oracle_price), true, 0, 0, - 0, ) .unwrap(); @@ -186,11 +184,10 @@ mod determine_perp_fulfillment_methods { &[(Pubkey::default(), 0, 101 * PRICE_PRECISION_U64)], &market.amm, market.amm.reserve_price().unwrap(), - oracle_price, + Some(oracle_price), true, 0, 0, - 0, ) .unwrap(); @@ -254,11 +251,10 @@ mod determine_perp_fulfillment_methods { ], &market.amm, market.amm.reserve_price().unwrap(), - oracle_price, + Some(oracle_price), true, 0, 0, - 0, ) .unwrap(); @@ -327,11 +323,10 @@ mod determine_perp_fulfillment_methods { ], &market.amm, market.amm.reserve_price().unwrap(), - oracle_price, + Some(oracle_price), true, 0, 0, - 0, ) .unwrap(); @@ -395,11 +390,10 @@ mod determine_perp_fulfillment_methods { ], &market.amm, market.amm.reserve_price().unwrap(), - oracle_price, + Some(oracle_price), true, 0, 0, - 0, ) .unwrap(); @@ -465,11 +459,10 @@ mod determine_perp_fulfillment_methods { ], &market.amm, market.amm.reserve_price().unwrap(), - oracle_price, + Some(oracle_price), true, 0, 0, - 0, ) .unwrap(); @@ -534,11 +527,10 @@ mod determine_perp_fulfillment_methods { ], &market.amm, market.amm.reserve_price().unwrap(), - oracle_price, + Some(oracle_price), true, 0, 0, - 0, ) .unwrap(); @@ -602,11 +594,10 @@ mod determine_perp_fulfillment_methods { ], &market.amm, market.amm.reserve_price().unwrap(), - oracle_price, + Some(oracle_price), true, 0, 0, - 0, ) .unwrap(); @@ -672,11 +663,10 @@ mod determine_perp_fulfillment_methods { ], &market.amm, market.amm.reserve_price().unwrap(), - oracle_price, + Some(oracle_price), true, 0, 0, - 0, ) .unwrap(); @@ -733,11 +723,10 @@ mod determine_perp_fulfillment_methods { ], &market.amm, market.amm.reserve_price().unwrap(), - oracle_price, + Some(oracle_price), true, 0, 0, - 0, ) .unwrap(); From 3ab29a72e79c5f33c7836c472044474ea9ca31fb Mon Sep 17 00:00:00 2001 From: Chris Heaney Date: Sat, 1 Jul 2023 15:07:59 -0400 Subject: [PATCH 10/18] check twap and oracle divergence --- programs/drift/src/controller/orders.rs | 46 ++++-- programs/drift/src/controller/orders/tests.rs | 120 +------------- programs/drift/src/math/orders.rs | 29 +++- programs/drift/src/math/orders/tests.rs | 154 ++++++++++++++++++ 4 files changed, 220 insertions(+), 129 deletions(-) diff --git a/programs/drift/src/controller/orders.rs b/programs/drift/src/controller/orders.rs index c2dab02b2..6c6fe2756 100644 --- a/programs/drift/src/controller/orders.rs +++ b/programs/drift/src/controller/orders.rs @@ -1009,6 +1009,16 @@ pub fn fill_perp_order( slot, )?; + let oracle_too_divergent_with_twap_5min = + is_oracle_too_divergent_with_twap_5min(oracle_price, oracle_twap_5min)?; + if oracle_too_divergent_with_twap_5min { + // update filler last active so tx doesn't revert + if let Some(filler) = filler.as_deref_mut() { + filler.update_last_active_slot(slot); + } + return Ok(0); + } + let should_expire_order = should_expire_order(user, order_index, now)?; let position_index = @@ -3150,16 +3160,6 @@ pub fn fill_spot_order( } } - { - let mut base_market = spot_market_map.get_ref_mut(&order_market_index)?; - let oracle_price_data = oracle_map.get_price_data(&base_market.oracle)?; - update_spot_market_cumulative_interest(&mut base_market, Some(oracle_price_data), now)?; - - let mut quote_market = spot_market_map.get_quote_spot_market_mut()?; - let oracle_price_data = oracle_map.get_price_data("e_market.oracle)?; - update_spot_market_cumulative_interest(&mut quote_market, Some(oracle_price_data), now)?; - } - let is_filler_taker = user_key == filler_key; let is_filler_maker = maker.map_or(false, |maker| maker.key() == filler_key); let (mut filler, mut filler_stats) = if !is_filler_maker && !is_filler_taker { @@ -3190,6 +3190,32 @@ pub fn fill_spot_order( slot, )?; + { + let mut quote_market = spot_market_map.get_quote_spot_market_mut()?; + let oracle_price_data = oracle_map.get_price_data("e_market.oracle)?; + update_spot_market_cumulative_interest(&mut quote_market, Some(oracle_price_data), now)?; + + let mut base_market = spot_market_map.get_ref_mut(&order_market_index)?; + let oracle_price_data = oracle_map.get_price_data(&base_market.oracle)?; + update_spot_market_cumulative_interest(&mut base_market, Some(oracle_price_data), now)?; + + let oracle_too_divergent_with_twap_5min = is_oracle_too_divergent_with_twap_5min( + oracle_price_data.price, + base_market + .historical_oracle_data + .last_oracle_price_twap_5min, + )?; + + if oracle_too_divergent_with_twap_5min { + // update filler last active so tx doesn't revert + if let Some(filler) = filler.as_mut() { + filler.update_last_active_slot(slot); + } + + return Ok(0); + } + } + let should_expire_order = should_expire_order(user, order_index, now)?; let should_cancel_reduce_only = if user.orders[order_index].reduce_only { diff --git a/programs/drift/src/controller/orders/tests.rs b/programs/drift/src/controller/orders/tests.rs index b6dcab292..05038005f 100644 --- a/programs/drift/src/controller/orders/tests.rs +++ b/programs/drift/src/controller/orders/tests.rs @@ -4401,6 +4401,7 @@ pub mod fill_order { order_tick_size: 1, max_base_asset_reserve: 200 * AMM_RESERVE_PRECISION, min_base_asset_reserve: 50 * AMM_RESERVE_PRECISION, + historical_oracle_data: HistoricalOracleData::default_price(PRICE_PRECISION_I64), ..AMM::default() }, margin_ratio_initial: 1000, @@ -7589,6 +7590,7 @@ pub mod fill_spot_order { LAMPORTS_PER_SOL_I64, LAMPORTS_PER_SOL_U64, PRICE_PRECISION_I64, PRICE_PRECISION_U64, SPOT_BALANCE_PRECISION, SPOT_BALANCE_PRECISION_U64, }; + use crate::state::oracle::HistoricalOracleData; use crate::state::perp_market_map::PerpMarketMap; use crate::state::spot_fulfillment_params::TestFulfillmentParams; use crate::state::spot_market::{SpotBalanceType, SpotMarket}; @@ -7626,6 +7628,7 @@ pub mod fill_spot_order { let mut base_market = SpotMarket { deposit_balance: SPOT_BALANCE_PRECISION, + historical_oracle_data: HistoricalOracleData::default_price(PRICE_PRECISION_I64), ..SpotMarket::default_base_market() }; create_anchor_account_info!(base_market, SpotMarket, base_market_account_info); @@ -9326,120 +9329,3 @@ pub mod get_maker_orders_info { assert_eq!(maker_order_price_and_indexes.len(), 64); } } - -pub mod validate_fill_price_within_price_bands { - use crate::math::orders::validate_fill_price_within_price_bands; - use crate::{PositionDirection, MARGIN_PRECISION, PRICE_PRECISION_I64, PRICE_PRECISION_U64}; - - #[test] - fn valid_long() { - let oracle_price = 100 * PRICE_PRECISION_I64; - let twap = oracle_price; - let fill_price = 105 * PRICE_PRECISION_U64; - let direction = PositionDirection::Long; - let margin_ratio_initial = MARGIN_PRECISION / 10; - - assert!(validate_fill_price_within_price_bands( - fill_price, - direction, - oracle_price, - twap, - margin_ratio_initial, - ) - .is_ok()) - } - - #[test] - fn valid_short() { - let oracle_price = 100 * PRICE_PRECISION_I64; - let twap = oracle_price; - let fill_price = 95 * PRICE_PRECISION_U64; - let direction = PositionDirection::Short; - let margin_ratio_initial = MARGIN_PRECISION / 10; - - assert!(validate_fill_price_within_price_bands( - fill_price, - direction, - oracle_price, - twap, - margin_ratio_initial, - ) - .is_ok()) - } - - #[test] - fn invalid_long_breaches_oracle() { - let oracle_price = 100 * PRICE_PRECISION_I64; - let twap = oracle_price; - // 11% greater than oracle price - let fill_price = 111 * PRICE_PRECISION_U64; - let direction = PositionDirection::Long; - let margin_ratio_initial = MARGIN_PRECISION / 10; // 10x - - assert!(validate_fill_price_within_price_bands( - fill_price, - direction, - oracle_price, - twap, - margin_ratio_initial, - ) - .is_err()) - } - - #[test] - fn invalid_short_breaches_oracle() { - let oracle_price = 100 * PRICE_PRECISION_I64; - let twap = oracle_price; - // 11% less than oracle price - let fill_price = 89 * PRICE_PRECISION_U64; - let direction = PositionDirection::Short; - let margin_ratio_initial = MARGIN_PRECISION / 10; // 10x - - assert!(validate_fill_price_within_price_bands( - fill_price, - direction, - oracle_price, - twap, - margin_ratio_initial, - ) - .is_err()) - } - - #[test] - fn invalid_long_breaches_oracle_twap() { - let oracle_price = 150 * PRICE_PRECISION_I64; - let twap = 100 * PRICE_PRECISION_I64; - // 50% greater than twap - let fill_price = 150 * PRICE_PRECISION_U64; - let direction = PositionDirection::Long; - let margin_ratio_initial = MARGIN_PRECISION / 10; // 10x - - assert!(validate_fill_price_within_price_bands( - fill_price, - direction, - oracle_price, - twap, - margin_ratio_initial, - ) - .is_err()) - } - - #[test] - fn invalid_short_breaches_oracle_twap() { - let oracle_price = 50 * PRICE_PRECISION_I64; - let twap = 100 * PRICE_PRECISION_I64; - // 50% less than twap - let fill_price = 50 * PRICE_PRECISION_U64; - let direction = PositionDirection::Short; - let margin_ratio_initial = MARGIN_PRECISION / 10; // 10x - - assert!(validate_fill_price_within_price_bands( - fill_price, - direction, - oracle_price, - twap, - margin_ratio_initial, - ) - .is_err()) - } -} diff --git a/programs/drift/src/math/orders.rs b/programs/drift/src/math/orders.rs index 98a6a2004..0fe3343cd 100644 --- a/programs/drift/src/math/orders.rs +++ b/programs/drift/src/math/orders.rs @@ -10,8 +10,8 @@ use crate::math::amm::calculate_amm_available_liquidity; use crate::math::auction::is_auction_complete; use crate::math::casting::Cast; use crate::{ - math, BASE_PRECISION_I128, OPEN_ORDER_MARGIN_REQUIREMENT, PRICE_PRECISION_I128, - QUOTE_PRECISION_I128, SPOT_WEIGHT_PRECISION, + math, BASE_PRECISION_I128, OPEN_ORDER_MARGIN_REQUIREMENT, PERCENTAGE_PRECISION_U64, + PRICE_PRECISION_I128, QUOTE_PRECISION_I128, SPOT_WEIGHT_PRECISION, }; use crate::math::constants::MARGIN_PRECISION_U128; @@ -483,6 +483,31 @@ pub fn validate_fill_price_within_price_bands( Ok(()) } +pub fn is_oracle_too_divergent_with_twap_5min( + oracle_price: i64, + oracle_twap_5min: i64, +) -> DriftResult { + let precision = PERCENTAGE_PRECISION_U64.cast::()?; + let max_diff = precision / 2; // 50% + + let percent_diff = oracle_price + .safe_sub(oracle_twap_5min)? + .abs() + .safe_mul(precision)? + .safe_div(oracle_twap_5min.abs())?; + + let too_divergent = percent_diff >= max_diff; + if too_divergent { + msg!( + "Oracle Price Too Divergent from TWAP 5min. oracle: {} twap: {}", + oracle_price, + oracle_twap_5min + ); + } + + Ok(too_divergent) +} + pub fn order_satisfies_trigger_condition(order: &Order, oracle_price: u64) -> DriftResult { match order.trigger_condition { OrderTriggerCondition::Above => Ok(oracle_price > order.trigger_price), diff --git a/programs/drift/src/math/orders/tests.rs b/programs/drift/src/math/orders/tests.rs index 10a56b633..98a41065b 100644 --- a/programs/drift/src/math/orders/tests.rs +++ b/programs/drift/src/math/orders/tests.rs @@ -2337,3 +2337,157 @@ mod calculate_max_perp_order_size { assert_eq!(max_order_size, 999999999000); } } + +pub mod validate_fill_price_within_price_bands { + use crate::math::orders::validate_fill_price_within_price_bands; + use crate::{PositionDirection, MARGIN_PRECISION, PRICE_PRECISION_I64, PRICE_PRECISION_U64}; + + #[test] + fn valid_long() { + let oracle_price = 100 * PRICE_PRECISION_I64; + let twap = oracle_price; + let fill_price = 105 * PRICE_PRECISION_U64; + let direction = PositionDirection::Long; + let margin_ratio_initial = MARGIN_PRECISION / 10; + + assert!(validate_fill_price_within_price_bands( + fill_price, + direction, + oracle_price, + twap, + margin_ratio_initial, + ) + .is_ok()) + } + + #[test] + fn valid_short() { + let oracle_price = 100 * PRICE_PRECISION_I64; + let twap = oracle_price; + let fill_price = 95 * PRICE_PRECISION_U64; + let direction = PositionDirection::Short; + let margin_ratio_initial = MARGIN_PRECISION / 10; + + assert!(validate_fill_price_within_price_bands( + fill_price, + direction, + oracle_price, + twap, + margin_ratio_initial, + ) + .is_ok()) + } + + #[test] + fn invalid_long_breaches_oracle() { + let oracle_price = 100 * PRICE_PRECISION_I64; + let twap = oracle_price; + // 11% greater than oracle price + let fill_price = 111 * PRICE_PRECISION_U64; + let direction = PositionDirection::Long; + let margin_ratio_initial = MARGIN_PRECISION / 10; // 10x + + assert!(validate_fill_price_within_price_bands( + fill_price, + direction, + oracle_price, + twap, + margin_ratio_initial, + ) + .is_err()) + } + + #[test] + fn invalid_short_breaches_oracle() { + let oracle_price = 100 * PRICE_PRECISION_I64; + let twap = oracle_price; + // 11% less than oracle price + let fill_price = 89 * PRICE_PRECISION_U64; + let direction = PositionDirection::Short; + let margin_ratio_initial = MARGIN_PRECISION / 10; // 10x + + assert!(validate_fill_price_within_price_bands( + fill_price, + direction, + oracle_price, + twap, + margin_ratio_initial, + ) + .is_err()) + } + + #[test] + fn invalid_long_breaches_oracle_twap() { + let oracle_price = 150 * PRICE_PRECISION_I64; + let twap = 100 * PRICE_PRECISION_I64; + // 50% greater than twap + let fill_price = 150 * PRICE_PRECISION_U64; + let direction = PositionDirection::Long; + let margin_ratio_initial = MARGIN_PRECISION / 10; // 10x + + assert!(validate_fill_price_within_price_bands( + fill_price, + direction, + oracle_price, + twap, + margin_ratio_initial, + ) + .is_err()) + } + + #[test] + fn invalid_short_breaches_oracle_twap() { + let oracle_price = 50 * PRICE_PRECISION_I64; + let twap = 100 * PRICE_PRECISION_I64; + // 50% less than twap + let fill_price = 50 * PRICE_PRECISION_U64; + let direction = PositionDirection::Short; + let margin_ratio_initial = MARGIN_PRECISION / 10; // 10x + + assert!(validate_fill_price_within_price_bands( + fill_price, + direction, + oracle_price, + twap, + margin_ratio_initial, + ) + .is_err()) + } +} + +pub mod is_oracle_too_divergent_with_twap_5min { + use crate::math::orders::is_oracle_too_divergent_with_twap_5min; + use crate::PRICE_PRECISION_I64; + + #[test] + pub fn valid_above() { + let oracle_price = 149 * PRICE_PRECISION_I64; + let twap = 100 * PRICE_PRECISION_I64; + + assert!(!is_oracle_too_divergent_with_twap_5min(oracle_price, twap,).unwrap()) + } + + #[test] + pub fn invalid_above() { + let oracle_price = 151 * PRICE_PRECISION_I64; + let twap = 100 * PRICE_PRECISION_I64; + + assert!(is_oracle_too_divergent_with_twap_5min(oracle_price, twap,).unwrap()) + } + + #[test] + pub fn valid_below() { + let oracle_price = 51 * PRICE_PRECISION_I64; + let twap = 100 * PRICE_PRECISION_I64; + + assert!(!is_oracle_too_divergent_with_twap_5min(oracle_price, twap,).unwrap()) + } + + #[test] + pub fn invalid_below() { + let oracle_price = 49 * PRICE_PRECISION_I64; + let twap = 100 * PRICE_PRECISION_I64; + + assert!(is_oracle_too_divergent_with_twap_5min(oracle_price, twap,).unwrap()) + } +} From d6be9ef46612dbf20ec28c218d2f2d3958ac5c41 Mon Sep 17 00:00:00 2001 From: Chris Heaney Date: Mon, 3 Jul 2023 19:39:20 -0400 Subject: [PATCH 11/18] temp remove imbalance test --- test-scripts/run-anchor-tests.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/test-scripts/run-anchor-tests.sh b/test-scripts/run-anchor-tests.sh index cfa8fccc9..a369d0ef0 100644 --- a/test-scripts/run-anchor-tests.sh +++ b/test-scripts/run-anchor-tests.sh @@ -9,7 +9,6 @@ test_files=( maxLeverageOrderParams.ts multipleMakerOrders.ts postOnlyAmmFulfillment.ts - imbalancePerpPnl.ts delistMarket.ts delistMarketLiq.ts triggerSpotOrder.ts From 116b641172930e418524a91aa9ffab1e17c3eeb9 Mon Sep 17 00:00:00 2001 From: Chris Heaney Date: Mon, 3 Jul 2023 20:35:08 -0400 Subject: [PATCH 12/18] Revert "temp remove imbalance test" This reverts commit d6be9ef46612dbf20ec28c218d2f2d3958ac5c41. --- test-scripts/run-anchor-tests.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/test-scripts/run-anchor-tests.sh b/test-scripts/run-anchor-tests.sh index a369d0ef0..cfa8fccc9 100644 --- a/test-scripts/run-anchor-tests.sh +++ b/test-scripts/run-anchor-tests.sh @@ -9,6 +9,7 @@ test_files=( maxLeverageOrderParams.ts multipleMakerOrders.ts postOnlyAmmFulfillment.ts + imbalancePerpPnl.ts delistMarket.ts delistMarketLiq.ts triggerSpotOrder.ts From 78be5d16eac9251b459e4f0b80096a4385ce07ad Mon Sep 17 00:00:00 2001 From: Chris Heaney Date: Mon, 3 Jul 2023 22:04:38 -0400 Subject: [PATCH 13/18] updates to make oracle twap 5min divergence a state variable --- programs/drift/src/controller/lp/tests.rs | 7 +-- programs/drift/src/controller/orders.rs | 14 ++++- programs/drift/src/controller/orders/tests.rs | 13 ++-- .../drift/src/controller/pnl/delisting.rs | 59 ++++-------------- programs/drift/src/controller/pnl/tests.rs | 62 ++++--------------- programs/drift/src/controller/repeg/tests.rs | 16 ++--- programs/drift/src/math/amm.rs | 23 +++---- programs/drift/src/math/oracle/tests.rs | 4 +- programs/drift/src/math/orders.rs | 9 ++- programs/drift/src/math/orders/tests.rs | 18 ++++-- programs/drift/src/math/repeg/tests.rs | 4 +- programs/drift/src/state/state.rs | 29 ++++++--- sdk/src/idl/drift.json | 7 ++- sdk/src/math/oracles.ts | 14 ++--- sdk/src/types.ts | 4 +- sdk/tests/dlob/helpers.ts | 4 +- tests/admin.ts | 4 +- tests/delistMarket.ts | 7 +-- tests/imbalancePerpPnl.ts | 12 +++- tests/insuranceFundStake.ts | 7 +-- tests/liquidateBorrowForPerpPnl.ts | 4 +- tests/liquidatePerp.ts | 7 +-- tests/liquidatePerpAndLp.ts | 7 +-- tests/liquidatePerpPnlForDeposit.ts | 11 +++- tests/liquidityProvider.ts | 4 +- tests/maxLeverageOrderParams.ts | 5 +- tests/order.ts | 4 +- tests/perpLpJit.ts | 4 +- tests/phoenixTest.ts | 2 +- tests/serumTest.ts | 2 +- tests/tradingLP.ts | 4 +- tests/triggerOrders.ts | 5 +- tests/updateAMM.ts | 4 +- 33 files changed, 168 insertions(+), 212 deletions(-) diff --git a/programs/drift/src/controller/lp/tests.rs b/programs/drift/src/controller/lp/tests.rs index 98463c36a..14e64dd1f 100644 --- a/programs/drift/src/controller/lp/tests.rs +++ b/programs/drift/src/controller/lp/tests.rs @@ -25,7 +25,7 @@ use crate::state::perp_market::{MarketStatus, PerpMarket, PoolBalance}; use crate::state::perp_market_map::PerpMarketMap; use crate::state::spot_market::{SpotBalanceType, SpotMarket}; use crate::state::spot_market_map::SpotMarketMap; -use crate::state::state::{OracleGuardRails, PriceDivergenceGuardRails, State, ValidityGuardRails}; +use crate::state::state::{OracleGuardRails, State, ValidityGuardRails}; use crate::state::user::{SpotPosition, User}; use crate::test_utils::*; use crate::test_utils::{get_positions, get_pyth_price, get_spot_positions}; @@ -517,16 +517,13 @@ pub fn test_lp_settle_pnl() { let state = State { oracle_guard_rails: OracleGuardRails { - price_divergence: PriceDivergenceGuardRails { - mark_oracle_divergence_numerator: 1, - mark_oracle_divergence_denominator: 10, - }, validity: ValidityGuardRails { slots_before_stale_for_amm: 10, // 5s slots_before_stale_for_margin: 120, // 60s confidence_interval_max_size: 1000, too_volatile_ratio: 5, }, + ..OracleGuardRails::default() }, ..State::default() }; diff --git a/programs/drift/src/controller/orders.rs b/programs/drift/src/controller/orders.rs index 233e2ed77..92bc8d1a3 100644 --- a/programs/drift/src/controller/orders.rs +++ b/programs/drift/src/controller/orders.rs @@ -1009,8 +1009,14 @@ pub fn fill_perp_order( slot, )?; - let oracle_too_divergent_with_twap_5min = - is_oracle_too_divergent_with_twap_5min(oracle_price, oracle_twap_5min)?; + let oracle_too_divergent_with_twap_5min = is_oracle_too_divergent_with_twap_5min( + oracle_price, + oracle_twap_5min, + state + .oracle_guard_rails + .max_oracle_twap_5min_percent_divergence() + .cast()?, + )?; if oracle_too_divergent_with_twap_5min { // update filler last active so tx doesn't revert if let Some(filler) = filler.as_deref_mut() { @@ -3181,6 +3187,10 @@ pub fn fill_spot_order( base_market .historical_oracle_data .last_oracle_price_twap_5min, + state + .oracle_guard_rails + .max_oracle_twap_5min_percent_divergence() + .cast()?, )?; if oracle_too_divergent_with_twap_5min { diff --git a/programs/drift/src/controller/orders/tests.rs b/programs/drift/src/controller/orders/tests.rs index 05038005f..e3d9079fc 100644 --- a/programs/drift/src/controller/orders/tests.rs +++ b/programs/drift/src/controller/orders/tests.rs @@ -2365,7 +2365,6 @@ pub mod fulfill_order { use crate::controller::orders::{fulfill_perp_order, validate_market_within_price_band}; use crate::controller::position::PositionDirection; - use crate::create_account_info; use crate::create_anchor_account_info; use crate::get_orders; use crate::math::constants::{ @@ -2379,13 +2378,12 @@ pub mod fulfill_order { use crate::state::perp_market_map::PerpMarketMap; use crate::state::spot_market::{SpotBalanceType, SpotMarket}; use crate::state::spot_market_map::SpotMarketMap; - use crate::state::state::{ - OracleGuardRails, PriceDivergenceGuardRails, State, ValidityGuardRails, - }; + use crate::state::state::{OracleGuardRails, State, ValidityGuardRails}; use crate::state::user::{OrderStatus, OrderType, SpotPosition, User, UserStats}; use crate::state::user_map::{UserMap, UserStatsMap}; use crate::test_utils::*; use crate::test_utils::{get_orders, get_positions, get_pyth_price, get_spot_positions}; + use crate::{create_account_info, PERCENTAGE_PRECISION_U64}; use super::*; @@ -2429,16 +2427,13 @@ pub mod fulfill_order { let mut state = State { oracle_guard_rails: OracleGuardRails { - price_divergence: PriceDivergenceGuardRails { - mark_oracle_divergence_numerator: 1, - mark_oracle_divergence_denominator: 10, - }, validity: ValidityGuardRails { slots_before_stale_for_amm: 10, // 5s slots_before_stale_for_margin: 120, // 60s confidence_interval_max_size: 1000, too_volatile_ratio: 5, }, + ..OracleGuardRails::default() }, ..State::default() }; @@ -2457,7 +2452,7 @@ pub mod fulfill_order { state .oracle_guard_rails .price_divergence - .mark_oracle_divergence_numerator = 6; + .mark_oracle_percent_divergence = 6 * PERCENTAGE_PRECISION_U64 / 10; assert!(validate_market_within_price_band(&market, &state, true, None).unwrap()); // twap_5min $20 and mark $100 breaches 60% divergence -> failure diff --git a/programs/drift/src/controller/pnl/delisting.rs b/programs/drift/src/controller/pnl/delisting.rs index bd40391f4..e1618a995 100644 --- a/programs/drift/src/controller/pnl/delisting.rs +++ b/programs/drift/src/controller/pnl/delisting.rs @@ -45,9 +45,7 @@ pub mod delisting_test { use crate::state::perp_market_map::PerpMarketMap; use crate::state::spot_market::{SpotBalanceType, SpotMarket}; use crate::state::spot_market_map::SpotMarketMap; - use crate::state::state::{ - OracleGuardRails, PriceDivergenceGuardRails, State, ValidityGuardRails, - }; + use crate::state::state::{OracleGuardRails, State, ValidityGuardRails}; use crate::state::user::{OrderStatus, OrderType, SpotPosition, User, UserStats}; use crate::test_utils::*; use crate::test_utils::{get_orders, get_positions, get_pyth_price, get_spot_positions}; @@ -122,16 +120,13 @@ pub mod delisting_test { let state = State { oracle_guard_rails: OracleGuardRails { - price_divergence: PriceDivergenceGuardRails { - mark_oracle_divergence_numerator: 1, - mark_oracle_divergence_denominator: 10, - }, validity: ValidityGuardRails { slots_before_stale_for_amm: 10, // 5s slots_before_stale_for_margin: 120, // 60s confidence_interval_max_size: 1000, too_volatile_ratio: 5, }, + ..OracleGuardRails::default() }, ..State::default() }; @@ -246,16 +241,13 @@ pub mod delisting_test { let state = State { oracle_guard_rails: OracleGuardRails { - price_divergence: PriceDivergenceGuardRails { - mark_oracle_divergence_numerator: 1, - mark_oracle_divergence_denominator: 10, - }, validity: ValidityGuardRails { slots_before_stale_for_amm: 10, // 5s slots_before_stale_for_margin: 120, // 60s confidence_interval_max_size: 1000, too_volatile_ratio: 5, }, + ..OracleGuardRails::default() }, ..State::default() }; @@ -358,16 +350,13 @@ pub mod delisting_test { let state = State { oracle_guard_rails: OracleGuardRails { - price_divergence: PriceDivergenceGuardRails { - mark_oracle_divergence_numerator: 1, - mark_oracle_divergence_denominator: 10, - }, validity: ValidityGuardRails { slots_before_stale_for_amm: 10, // 5s slots_before_stale_for_margin: 120, // 60s confidence_interval_max_size: 1000, too_volatile_ratio: 5, }, + ..OracleGuardRails::default() }, ..State::default() }; @@ -474,16 +463,13 @@ pub mod delisting_test { let state = State { oracle_guard_rails: OracleGuardRails { - price_divergence: PriceDivergenceGuardRails { - mark_oracle_divergence_numerator: 1, - mark_oracle_divergence_denominator: 10, - }, validity: ValidityGuardRails { slots_before_stale_for_amm: 10, // 5s slots_before_stale_for_margin: 120, // 60s confidence_interval_max_size: 1000, too_volatile_ratio: 5, }, + ..OracleGuardRails::default() }, ..State::default() }; @@ -590,16 +576,13 @@ pub mod delisting_test { let state = State { oracle_guard_rails: OracleGuardRails { - price_divergence: PriceDivergenceGuardRails { - mark_oracle_divergence_numerator: 1, - mark_oracle_divergence_denominator: 10, - }, validity: ValidityGuardRails { slots_before_stale_for_amm: 10, // 5s slots_before_stale_for_margin: 120, // 60s confidence_interval_max_size: 1000, too_volatile_ratio: 5, }, + ..OracleGuardRails::default() }, ..State::default() }; @@ -748,16 +731,13 @@ pub mod delisting_test { let state = State { oracle_guard_rails: OracleGuardRails { - price_divergence: PriceDivergenceGuardRails { - mark_oracle_divergence_numerator: 1, - mark_oracle_divergence_denominator: 10, - }, validity: ValidityGuardRails { slots_before_stale_for_amm: 10, // 5s slots_before_stale_for_margin: 120, // 60s confidence_interval_max_size: 1000, too_volatile_ratio: 5, }, + ..OracleGuardRails::default() }, ..State::default() }; @@ -966,16 +946,13 @@ pub mod delisting_test { let state = State { oracle_guard_rails: OracleGuardRails { - price_divergence: PriceDivergenceGuardRails { - mark_oracle_divergence_numerator: 1, - mark_oracle_divergence_denominator: 10, - }, validity: ValidityGuardRails { slots_before_stale_for_amm: 10, // 5s slots_before_stale_for_margin: 120, // 60s confidence_interval_max_size: 1000, too_volatile_ratio: 5, }, + ..OracleGuardRails::default() }, ..State::default() }; @@ -1187,16 +1164,13 @@ pub mod delisting_test { let state = State { oracle_guard_rails: OracleGuardRails { - price_divergence: PriceDivergenceGuardRails { - mark_oracle_divergence_numerator: 1, - mark_oracle_divergence_denominator: 10, - }, validity: ValidityGuardRails { slots_before_stale_for_amm: 10, // 5s slots_before_stale_for_margin: 120, // 60s confidence_interval_max_size: 1000, too_volatile_ratio: 5, }, + ..OracleGuardRails::default() }, ..State::default() }; @@ -1469,16 +1443,13 @@ pub mod delisting_test { let state = State { oracle_guard_rails: OracleGuardRails { - price_divergence: PriceDivergenceGuardRails { - mark_oracle_divergence_numerator: 1, - mark_oracle_divergence_denominator: 10, - }, validity: ValidityGuardRails { slots_before_stale_for_amm: 10, // 5s slots_before_stale_for_margin: 120, // 60s confidence_interval_max_size: 1000, too_volatile_ratio: 5, }, + ..OracleGuardRails::default() }, ..State::default() }; @@ -1879,16 +1850,13 @@ pub mod delisting_test { let state = State { oracle_guard_rails: OracleGuardRails { - price_divergence: PriceDivergenceGuardRails { - mark_oracle_divergence_numerator: 1, - mark_oracle_divergence_denominator: 10, - }, validity: ValidityGuardRails { slots_before_stale_for_amm: 10, // 5s slots_before_stale_for_margin: 120, // 60s confidence_interval_max_size: 1000, too_volatile_ratio: 5, }, + ..OracleGuardRails::default() }, ..State::default() }; @@ -2270,16 +2238,13 @@ pub mod delisting_test { let state = State { oracle_guard_rails: OracleGuardRails { - price_divergence: PriceDivergenceGuardRails { - mark_oracle_divergence_numerator: 1, - mark_oracle_divergence_denominator: 10, - }, validity: ValidityGuardRails { slots_before_stale_for_amm: 10, // 5s slots_before_stale_for_margin: 120, // 60s confidence_interval_max_size: 1000, too_volatile_ratio: 5, }, + ..OracleGuardRails::default() }, initial_pct_to_liquidate: LIQUIDATION_PCT_PRECISION as u16, liquidation_duration: 150, diff --git a/programs/drift/src/controller/pnl/tests.rs b/programs/drift/src/controller/pnl/tests.rs index 923be732d..edfd35da5 100644 --- a/programs/drift/src/controller/pnl/tests.rs +++ b/programs/drift/src/controller/pnl/tests.rs @@ -20,7 +20,7 @@ use crate::state::perp_market::{MarketStatus, PerpMarket, PoolBalance, AMM}; use crate::state::perp_market_map::PerpMarketMap; use crate::state::spot_market::{SpotBalanceType, SpotMarket}; use crate::state::spot_market_map::SpotMarketMap; -use crate::state::state::{OracleGuardRails, PriceDivergenceGuardRails, State, ValidityGuardRails}; +use crate::state::state::{OracleGuardRails, State, ValidityGuardRails}; use crate::state::user::{PerpPosition, SpotPosition, User}; use crate::test_utils::*; use crate::test_utils::{get_positions, get_pyth_price, get_spot_positions}; @@ -32,16 +32,13 @@ pub fn user_no_position() { let state = State { oracle_guard_rails: OracleGuardRails { - price_divergence: PriceDivergenceGuardRails { - mark_oracle_divergence_numerator: 1, - mark_oracle_divergence_denominator: 10, - }, validity: ValidityGuardRails { slots_before_stale_for_amm: 10, // 5s slots_before_stale_for_margin: 120, // 60s confidence_interval_max_size: 1000, too_volatile_ratio: 5, }, + ..OracleGuardRails::default() }, ..State::default() }; @@ -146,16 +143,13 @@ pub fn user_does_not_meet_maintenance_requirement() { let state = State { oracle_guard_rails: OracleGuardRails { - price_divergence: PriceDivergenceGuardRails { - mark_oracle_divergence_numerator: 1, - mark_oracle_divergence_denominator: 10, - }, validity: ValidityGuardRails { slots_before_stale_for_amm: 10, // 5s slots_before_stale_for_margin: 120, // 60s confidence_interval_max_size: 1000, too_volatile_ratio: 5, }, + ..OracleGuardRails::default() }, ..State::default() }; @@ -266,16 +260,13 @@ pub fn user_unsettled_negative_pnl() { let slot = 0_u64; let state = State { oracle_guard_rails: OracleGuardRails { - price_divergence: PriceDivergenceGuardRails { - mark_oracle_divergence_numerator: 1, - mark_oracle_divergence_denominator: 10, - }, validity: ValidityGuardRails { slots_before_stale_for_amm: 10, // 5s slots_before_stale_for_margin: 120, // 60s confidence_interval_max_size: 1000, too_volatile_ratio: 5, }, + ..OracleGuardRails::default() }, ..State::default() }; @@ -399,16 +390,13 @@ pub fn user_unsettled_positive_pnl_more_than_pool() { let slot = 0_u64; let state = State { oracle_guard_rails: OracleGuardRails { - price_divergence: PriceDivergenceGuardRails { - mark_oracle_divergence_numerator: 1, - mark_oracle_divergence_denominator: 10, - }, validity: ValidityGuardRails { slots_before_stale_for_amm: 10, // 5s slots_before_stale_for_margin: 120, // 60s confidence_interval_max_size: 1000, too_volatile_ratio: 5, }, + ..OracleGuardRails::default() }, ..State::default() }; @@ -530,16 +518,13 @@ pub fn user_unsettled_positive_pnl_less_than_pool() { let slot = 0_u64; let state = State { oracle_guard_rails: OracleGuardRails { - price_divergence: PriceDivergenceGuardRails { - mark_oracle_divergence_numerator: 1, - mark_oracle_divergence_denominator: 10, - }, validity: ValidityGuardRails { slots_before_stale_for_amm: 10, // 5s slots_before_stale_for_margin: 120, // 60s confidence_interval_max_size: 1000, too_volatile_ratio: 5, }, + ..OracleGuardRails::default() }, ..State::default() }; @@ -663,16 +648,13 @@ pub fn market_fee_pool_receives_portion() { let slot = 0; let state = State { oracle_guard_rails: OracleGuardRails { - price_divergence: PriceDivergenceGuardRails { - mark_oracle_divergence_numerator: 1, - mark_oracle_divergence_denominator: 10, - }, validity: ValidityGuardRails { slots_before_stale_for_amm: 10, // 5s slots_before_stale_for_margin: 120, // 60s confidence_interval_max_size: 1000, too_volatile_ratio: 5, }, + ..OracleGuardRails::default() }, ..State::default() }; @@ -798,16 +780,13 @@ pub fn market_fee_pool_pays_back_to_pnl_pool() { let slot = 0_u64; let state = State { oracle_guard_rails: OracleGuardRails { - price_divergence: PriceDivergenceGuardRails { - mark_oracle_divergence_numerator: 1, - mark_oracle_divergence_denominator: 10, - }, validity: ValidityGuardRails { slots_before_stale_for_amm: 10, // 5s slots_before_stale_for_margin: 120, // 60s confidence_interval_max_size: 1000, too_volatile_ratio: 5, }, + ..OracleGuardRails::default() }, ..State::default() }; @@ -938,16 +917,13 @@ pub fn user_long_positive_unrealized_pnl_up_to_max_positive_pnl() { let slot = 0_u64; let state = State { oracle_guard_rails: OracleGuardRails { - price_divergence: PriceDivergenceGuardRails { - mark_oracle_divergence_numerator: 1, - mark_oracle_divergence_denominator: 10, - }, validity: ValidityGuardRails { slots_before_stale_for_amm: 10, // 5s slots_before_stale_for_margin: 120, // 60s confidence_interval_max_size: 1000, too_volatile_ratio: 5, }, + ..OracleGuardRails::default() }, ..State::default() }; @@ -1072,16 +1048,13 @@ pub fn user_long_positive_unrealized_pnl_up_to_max_positive_pnl_price_breached() let slot = 0_u64; let state = State { oracle_guard_rails: OracleGuardRails { - price_divergence: PriceDivergenceGuardRails { - mark_oracle_divergence_numerator: 1, - mark_oracle_divergence_denominator: 10, - }, validity: ValidityGuardRails { slots_before_stale_for_amm: 10, // 5s slots_before_stale_for_margin: 120, // 60s confidence_interval_max_size: 1000, too_volatile_ratio: 5, }, + ..OracleGuardRails::default() }, ..State::default() }; @@ -1203,16 +1176,13 @@ pub fn user_long_negative_unrealized_pnl() { let slot = 0_u64; let state = State { oracle_guard_rails: OracleGuardRails { - price_divergence: PriceDivergenceGuardRails { - mark_oracle_divergence_numerator: 1, - mark_oracle_divergence_denominator: 10, - }, validity: ValidityGuardRails { slots_before_stale_for_amm: 10, // 5s slots_before_stale_for_margin: 120, // 60s confidence_interval_max_size: 1000, too_volatile_ratio: 5, }, + ..OracleGuardRails::default() }, ..State::default() }; @@ -1337,16 +1307,13 @@ pub fn user_short_positive_unrealized_pnl_up_to_max_positive_pnl() { let slot = 0_u64; let state = State { oracle_guard_rails: OracleGuardRails { - price_divergence: PriceDivergenceGuardRails { - mark_oracle_divergence_numerator: 1, - mark_oracle_divergence_denominator: 10, - }, validity: ValidityGuardRails { slots_before_stale_for_amm: 10, // 5s slots_before_stale_for_margin: 120, // 60s confidence_interval_max_size: 1000, too_volatile_ratio: 5, }, + ..OracleGuardRails::default() }, ..State::default() }; @@ -1471,16 +1438,13 @@ pub fn user_short_negative_unrealized_pnl() { let slot = 0_u64; let state = State { oracle_guard_rails: OracleGuardRails { - price_divergence: PriceDivergenceGuardRails { - mark_oracle_divergence_numerator: 1, - mark_oracle_divergence_denominator: 10, - }, validity: ValidityGuardRails { slots_before_stale_for_amm: 10, // 5s slots_before_stale_for_margin: 120, // 60s confidence_interval_max_size: 1000, too_volatile_ratio: 5, }, + ..OracleGuardRails::default() }, ..State::default() }; diff --git a/programs/drift/src/controller/repeg/tests.rs b/programs/drift/src/controller/repeg/tests.rs index 3a5b332e6..5d00db312 100644 --- a/programs/drift/src/controller/repeg/tests.rs +++ b/programs/drift/src/controller/repeg/tests.rs @@ -52,8 +52,8 @@ pub fn update_amm_test() { let state = State { oracle_guard_rails: OracleGuardRails { price_divergence: PriceDivergenceGuardRails { - mark_oracle_divergence_numerator: 1, - mark_oracle_divergence_denominator: 10, + mark_oracle_percent_divergence: 1, + oracle_twap_5min_percent_divergence: 10, }, validity: ValidityGuardRails { slots_before_stale_for_amm: 10, // 5s @@ -194,8 +194,8 @@ pub fn update_amm_test_bad_oracle() { let state = State { oracle_guard_rails: OracleGuardRails { price_divergence: PriceDivergenceGuardRails { - mark_oracle_divergence_numerator: 1, - mark_oracle_divergence_denominator: 10, + mark_oracle_percent_divergence: 1, + oracle_twap_5min_percent_divergence: 10, }, validity: ValidityGuardRails { slots_before_stale_for_amm: 10, // 5s @@ -240,8 +240,8 @@ pub fn update_amm_larg_conf_test() { let state = State { oracle_guard_rails: OracleGuardRails { price_divergence: PriceDivergenceGuardRails { - mark_oracle_divergence_numerator: 1, - mark_oracle_divergence_denominator: 10, + mark_oracle_percent_divergence: 1, + oracle_twap_5min_percent_divergence: 10, }, validity: ValidityGuardRails { slots_before_stale_for_amm: 10, // 5s @@ -371,8 +371,8 @@ pub fn update_amm_larg_conf_w_neg_tfmd_test() { let state = State { oracle_guard_rails: OracleGuardRails { price_divergence: PriceDivergenceGuardRails { - mark_oracle_divergence_numerator: 1, - mark_oracle_divergence_denominator: 10, + mark_oracle_percent_divergence: 1, + oracle_twap_5min_percent_divergence: 10, }, validity: ValidityGuardRails { slots_before_stale_for_amm: 10, // 5s diff --git a/programs/drift/src/math/amm.rs b/programs/drift/src/math/amm.rs index 7ab9abbd6..1d89606d5 100644 --- a/programs/drift/src/math/amm.rs +++ b/programs/drift/src/math/amm.rs @@ -8,11 +8,10 @@ use crate::error::{DriftResult, ErrorCode}; use crate::math::bn::U192; use crate::math::casting::Cast; use crate::math::constants::{ - BID_ASK_SPREAD_PRECISION, BID_ASK_SPREAD_PRECISION_I128, BID_ASK_SPREAD_PRECISION_U128, - CONCENTRATION_PRECISION, DEFAULT_MAX_TWAP_UPDATE_PRICE_BAND_DENOMINATOR, FIVE_MINUTE, ONE_HOUR, - ONE_MINUTE, PRICE_TIMES_AMM_TO_QUOTE_PRECISION_RATIO, - PRICE_TIMES_AMM_TO_QUOTE_PRECISION_RATIO_I128, PRICE_TO_PEG_PRECISION_RATIO, - QUOTE_PRECISION_I64, + BID_ASK_SPREAD_PRECISION, BID_ASK_SPREAD_PRECISION_I128, CONCENTRATION_PRECISION, + DEFAULT_MAX_TWAP_UPDATE_PRICE_BAND_DENOMINATOR, FIVE_MINUTE, ONE_HOUR, ONE_MINUTE, + PRICE_TIMES_AMM_TO_QUOTE_PRECISION_RATIO, PRICE_TIMES_AMM_TO_QUOTE_PRECISION_RATIO_I128, + PRICE_TO_PEG_PRECISION_RATIO, QUOTE_PRECISION_I64, }; use crate::math::orders::standardize_base_asset_amount; use crate::math::quote_asset::reserve_to_asset_amount; @@ -20,7 +19,7 @@ use crate::math::stats::{calculate_new_twap, calculate_rolling_sum, calculate_we use crate::state::oracle::OraclePriceData; use crate::state::perp_market::AMM; use crate::state::state::PriceDivergenceGuardRails; -use crate::validate; +use crate::{validate, PERCENTAGE_PRECISION_U64}; use super::helpers::get_proportion_u128; use crate::math::safe_math::SafeMath; @@ -732,16 +731,8 @@ pub fn is_oracle_mark_too_divergent( oracle_guard_rails: &PriceDivergenceGuardRails, ) -> DriftResult { let max_divergence = oracle_guard_rails - .mark_oracle_divergence_numerator - .cast::()? - .safe_mul(BID_ASK_SPREAD_PRECISION_U128)? - .safe_div( - oracle_guard_rails - .mark_oracle_divergence_denominator - .cast()?, - )? - .cast::()?; - + .mark_oracle_percent_divergence + .max(PERCENTAGE_PRECISION_U64 / 10); Ok(price_spread_pct.unsigned_abs() > max_divergence) } diff --git a/programs/drift/src/math/oracle/tests.rs b/programs/drift/src/math/oracle/tests.rs index 09a7015ec..0233ae77a 100644 --- a/programs/drift/src/math/oracle/tests.rs +++ b/programs/drift/src/math/oracle/tests.rs @@ -38,8 +38,8 @@ fn calculate_oracle_valid() { let state = State { oracle_guard_rails: OracleGuardRails { price_divergence: PriceDivergenceGuardRails { - mark_oracle_divergence_numerator: 1, - mark_oracle_divergence_denominator: 10, + mark_oracle_percent_divergence: 1, + oracle_twap_5min_percent_divergence: 10, }, validity: ValidityGuardRails { slots_before_stale_for_amm: 10, // 5s diff --git a/programs/drift/src/math/orders.rs b/programs/drift/src/math/orders.rs index 0fe3343cd..3598c3fa5 100644 --- a/programs/drift/src/math/orders.rs +++ b/programs/drift/src/math/orders.rs @@ -486,18 +486,17 @@ pub fn validate_fill_price_within_price_bands( pub fn is_oracle_too_divergent_with_twap_5min( oracle_price: i64, oracle_twap_5min: i64, + max_divergence: i64, ) -> DriftResult { - let precision = PERCENTAGE_PRECISION_U64.cast::()?; - let max_diff = precision / 2; // 50% - let percent_diff = oracle_price .safe_sub(oracle_twap_5min)? .abs() - .safe_mul(precision)? + .safe_mul(PERCENTAGE_PRECISION_U64.cast::()?)? .safe_div(oracle_twap_5min.abs())?; - let too_divergent = percent_diff >= max_diff; + let too_divergent = percent_diff >= max_divergence; if too_divergent { + msg!("max divergence {}", max_divergence); msg!( "Oracle Price Too Divergent from TWAP 5min. oracle: {} twap: {}", oracle_price, diff --git a/programs/drift/src/math/orders/tests.rs b/programs/drift/src/math/orders/tests.rs index 98a41065b..88baee966 100644 --- a/programs/drift/src/math/orders/tests.rs +++ b/programs/drift/src/math/orders/tests.rs @@ -2457,37 +2457,45 @@ pub mod validate_fill_price_within_price_bands { pub mod is_oracle_too_divergent_with_twap_5min { use crate::math::orders::is_oracle_too_divergent_with_twap_5min; - use crate::PRICE_PRECISION_I64; + use crate::{PERCENTAGE_PRECISION_U64, PRICE_PRECISION_I64}; #[test] pub fn valid_above() { let oracle_price = 149 * PRICE_PRECISION_I64; let twap = 100 * PRICE_PRECISION_I64; + let max_divergence = PERCENTAGE_PRECISION_U64 as i64 / 2; - assert!(!is_oracle_too_divergent_with_twap_5min(oracle_price, twap,).unwrap()) + assert!( + !is_oracle_too_divergent_with_twap_5min(oracle_price, twap, max_divergence).unwrap() + ) } #[test] pub fn invalid_above() { let oracle_price = 151 * PRICE_PRECISION_I64; let twap = 100 * PRICE_PRECISION_I64; + let max_divergence = PERCENTAGE_PRECISION_U64 as i64 / 2; - assert!(is_oracle_too_divergent_with_twap_5min(oracle_price, twap,).unwrap()) + assert!(is_oracle_too_divergent_with_twap_5min(oracle_price, twap, max_divergence).unwrap()) } #[test] pub fn valid_below() { let oracle_price = 51 * PRICE_PRECISION_I64; let twap = 100 * PRICE_PRECISION_I64; + let max_divergence = PERCENTAGE_PRECISION_U64 as i64 / 2; - assert!(!is_oracle_too_divergent_with_twap_5min(oracle_price, twap,).unwrap()) + assert!( + !is_oracle_too_divergent_with_twap_5min(oracle_price, twap, max_divergence).unwrap() + ) } #[test] pub fn invalid_below() { let oracle_price = 49 * PRICE_PRECISION_I64; let twap = 100 * PRICE_PRECISION_I64; + let max_divergence = PERCENTAGE_PRECISION_U64 as i64 / 2; - assert!(is_oracle_too_divergent_with_twap_5min(oracle_price, twap,).unwrap()) + assert!(is_oracle_too_divergent_with_twap_5min(oracle_price, twap, max_divergence).unwrap()) } } diff --git a/programs/drift/src/math/repeg/tests.rs b/programs/drift/src/math/repeg/tests.rs index 18370e01b..575673e33 100644 --- a/programs/drift/src/math/repeg/tests.rs +++ b/programs/drift/src/math/repeg/tests.rs @@ -218,8 +218,8 @@ fn calculate_optimal_peg_and_budget_2_test() { let state = State { oracle_guard_rails: OracleGuardRails { price_divergence: PriceDivergenceGuardRails { - mark_oracle_divergence_numerator: 1, - mark_oracle_divergence_denominator: 10, + mark_oracle_percent_divergence: 1, + oracle_twap_5min_percent_divergence: 10, }, validity: ValidityGuardRails { slots_before_stale_for_amm: 10, // 5s diff --git a/programs/drift/src/state/state.rs b/programs/drift/src/state/state.rs index 871ee839c..8e33dbc93 100644 --- a/programs/drift/src/state/state.rs +++ b/programs/drift/src/state/state.rs @@ -7,6 +7,7 @@ use crate::math::constants::{ }; use crate::math::safe_unwrap::SafeUnwrap; use crate::state::traits::Size; +use crate::PERCENTAGE_PRECISION_U64; #[account] #[derive(Default)] @@ -87,10 +88,7 @@ pub struct OracleGuardRails { impl Default for OracleGuardRails { fn default() -> Self { OracleGuardRails { - price_divergence: PriceDivergenceGuardRails { - mark_oracle_divergence_numerator: 1, - mark_oracle_divergence_denominator: 10, - }, + price_divergence: PriceDivergenceGuardRails::default(), validity: ValidityGuardRails { slots_before_stale_for_amm: 10, // ~5 seconds slots_before_stale_for_margin: 120, // ~60 seconds @@ -101,10 +99,27 @@ impl Default for OracleGuardRails { } } -#[derive(Copy, AnchorSerialize, AnchorDeserialize, Clone, Default)] +impl OracleGuardRails { + pub fn max_oracle_twap_5min_percent_divergence(&self) -> u64 { + self.price_divergence + .oracle_twap_5min_percent_divergence + .max(PERCENTAGE_PRECISION_U64 / 2) + } +} + +#[derive(Copy, AnchorSerialize, AnchorDeserialize, Clone)] pub struct PriceDivergenceGuardRails { - pub mark_oracle_divergence_numerator: u64, - pub mark_oracle_divergence_denominator: u64, + pub mark_oracle_percent_divergence: u64, + pub oracle_twap_5min_percent_divergence: u64, +} + +impl Default for PriceDivergenceGuardRails { + fn default() -> Self { + PriceDivergenceGuardRails { + mark_oracle_percent_divergence: PERCENTAGE_PRECISION_U64 / 10, + oracle_twap_5min_percent_divergence: PERCENTAGE_PRECISION_U64 / 2, + } + } } #[derive(Copy, AnchorSerialize, AnchorDeserialize, Clone, Default)] diff --git a/sdk/src/idl/drift.json b/sdk/src/idl/drift.json index 98f139db8..8efe57b14 100644 --- a/sdk/src/idl/drift.json +++ b/sdk/src/idl/drift.json @@ -6900,11 +6900,11 @@ "kind": "struct", "fields": [ { - "name": "markOracleDivergenceNumerator", + "name": "markOraclePercentDivergence", "type": "u64" }, { - "name": "markOracleDivergenceDenominator", + "name": "oracleTwap5minPercentDivergence", "type": "u64" } ] @@ -7637,6 +7637,9 @@ { "name": "Initial" }, + { + "name": "Fill" + }, { "name": "Maintenance" } diff --git a/sdk/src/math/oracles.ts b/sdk/src/math/oracles.ts index b8feff5d1..d5bcedccb 100644 --- a/sdk/src/math/oracles.ts +++ b/sdk/src/math/oracles.ts @@ -7,6 +7,7 @@ import { ONE, ZERO, FIVE_MINUTE, + PERCENTAGE_PRECISION, } from '../constants/numericConstants'; import { BN, HistoricalOracleData, PerpMarketAccount } from '../index'; import { assert } from '../assert/assert'; @@ -78,13 +79,12 @@ export function isOracleTooDivergent( const oracleSpread = oracleTwap5min.sub(oraclePriceData.price); const oracleSpreadPct = oracleSpread.mul(PRICE_PRECISION).div(oracleTwap5min); - const tooDivergent = oracleSpreadPct - .abs() - .gte( - BID_ASK_SPREAD_PRECISION.mul( - oracleGuardRails.priceDivergence.markOracleDivergenceNumerator - ).div(oracleGuardRails.priceDivergence.markOracleDivergenceDenominator) - ); + const maxDivergence = BN.max( + oracleGuardRails.priceDivergence.markOraclePercentDivergence, + PERCENTAGE_PRECISION.div(new BN(10)) + ); + + const tooDivergent = oracleSpreadPct.abs().gte(maxDivergence); return tooDivergent; } diff --git a/sdk/src/types.ts b/sdk/src/types.ts index 136549403..acef252c1 100644 --- a/sdk/src/types.ts +++ b/sdk/src/types.ts @@ -1013,8 +1013,8 @@ export type OrderFillerRewardStructure = { export type OracleGuardRails = { priceDivergence: { - markOracleDivergenceNumerator: BN; - markOracleDivergenceDenominator: BN; + markOraclePercentDivergence: BN; + oracleTwap5MinPercentDivergence: BN; }; validity: { slotsBeforeStaleForAmm: BN; diff --git a/sdk/tests/dlob/helpers.ts b/sdk/tests/dlob/helpers.ts index 4cd22810b..9bddf6a90 100644 --- a/sdk/tests/dlob/helpers.ts +++ b/sdk/tests/dlob/helpers.ts @@ -511,8 +511,8 @@ export const mockStateAccount: StateAccount = { liquidationDuration: 0, oracleGuardRails: { priceDivergence: { - markOracleDivergenceNumerator: new BN(0), - markOracleDivergenceDenominator: new BN(0), + markOraclePercentDivergence: new BN(0), + oracleTwap5MinPercentDivergence: new BN(0), }, validity: { slotsBeforeStaleForAmm: new BN(0), diff --git a/tests/admin.ts b/tests/admin.ts index 9238200db..cb172e507 100644 --- a/tests/admin.ts +++ b/tests/admin.ts @@ -154,8 +154,8 @@ describe('admin', () => { it('Update oracle guard rails', async () => { const oracleGuardRails: OracleGuardRails = { priceDivergence: { - markOracleDivergenceNumerator: new BN(1), - markOracleDivergenceDenominator: new BN(1), + markOraclePercentDivergence: new BN(1000000), + oracleTwap5MinPercentDivergence: new BN(1000000), }, validity: { slotsBeforeStaleForAmm: new BN(1), diff --git a/tests/delistMarket.ts b/tests/delistMarket.ts index 35583c01d..c0975c5b5 100644 --- a/tests/delistMarket.ts +++ b/tests/delistMarket.ts @@ -34,7 +34,7 @@ import { getOraclePriceData, sleep, } from './testHelpers'; -import { BulkAccountLoader, isVariant } from '../sdk'; +import { BulkAccountLoader, isVariant, PERCENTAGE_PRECISION } from '../sdk'; import { Keypair } from '@solana/web3.js'; async function depositToFeePoolFromIF( @@ -305,8 +305,8 @@ describe('delist market', () => { const marketIndex = 0; const oracleGuardRails: OracleGuardRails = { priceDivergence: { - markOracleDivergenceNumerator: new BN(10), - markOracleDivergenceDenominator: new BN(1), + markOraclePercentDivergence: new BN(10).mul(PERCENTAGE_PRECISION), + oracleTwap5MinPercentDivergence: new BN(10).mul(PERCENTAGE_PRECISION), }, validity: { slotsBeforeStaleForAmm: new BN(100), @@ -314,7 +314,6 @@ describe('delist market', () => { confidenceIntervalMaxSize: new BN(100000), tooVolatileRatio: new BN(100000000), }, - useForLiquidations: false, }; await driftClient.updateOracleGuardRails(oracleGuardRails); diff --git a/tests/imbalancePerpPnl.ts b/tests/imbalancePerpPnl.ts index 166746f3e..64aa6543a 100644 --- a/tests/imbalancePerpPnl.ts +++ b/tests/imbalancePerpPnl.ts @@ -47,7 +47,7 @@ import { printTxLogs, sleep, } from './testHelpers'; -import { BulkAccountLoader, TWO } from '../sdk'; +import { BulkAccountLoader, PERCENTAGE_PRECISION, TWO } from '../sdk'; async function depositToFeePoolFromIF( amount: number, @@ -203,6 +203,12 @@ describe('imbalanced large perp pnl w/ borrow hitting limits', () => { await driftClient.initialize(usdcMint.publicKey, true); await driftClient.subscribe(); + const oracleGuardrails = driftClient.getStateAccount().oracleGuardRails; + oracleGuardrails.priceDivergence.oracleTwap5MinPercentDivergence = new BN( + 12 + ).mul(PERCENTAGE_PRECISION); + await driftClient.updateOracleGuardRails(oracleGuardrails); + try { await initializeQuoteSpotMarket(driftClient, usdcMint.publicKey); await initializeSolSpotMarket(driftClient, solOracle); @@ -837,8 +843,8 @@ describe('imbalanced large perp pnl w/ borrow hitting limits', () => { const oracleGuardRails: OracleGuardRails = { priceDivergence: { - markOracleDivergenceNumerator: new BN(12), - markOracleDivergenceDenominator: new BN(1), + markOraclePercentDivergence: new BN(12).mul(PERCENTAGE_PRECISION), + oracleTwap5MinPercentDivergence: new BN(100).mul(PERCENTAGE_PRECISION), }, validity: { slotsBeforeStaleForAmm: new BN(100), diff --git a/tests/insuranceFundStake.ts b/tests/insuranceFundStake.ts index ad7730fd3..61ea646e6 100644 --- a/tests/insuranceFundStake.ts +++ b/tests/insuranceFundStake.ts @@ -43,7 +43,7 @@ import { setFeedPrice, sleep, } from './testHelpers'; -import { BulkAccountLoader } from '../sdk'; +import { BulkAccountLoader, PERCENTAGE_PRECISION } from '../sdk'; describe('insurance fund stake', () => { const provider = anchor.AnchorProvider.local(undefined, { @@ -884,8 +884,8 @@ describe('insurance fund stake', () => { const prevTC = driftClientUser.getTotalCollateral(); const oracleGuardRails: OracleGuardRails = { priceDivergence: { - markOracleDivergenceNumerator: new BN(1), - markOracleDivergenceDenominator: new BN(1), + markOraclePercentDivergence: PERCENTAGE_PRECISION, + oracleTwap5MinPercentDivergence: PERCENTAGE_PRECISION, }, validity: { slotsBeforeStaleForAmm: new BN(100), @@ -893,7 +893,6 @@ describe('insurance fund stake', () => { confidenceIntervalMaxSize: new BN(100000), tooVolatileRatio: new BN(100000), }, - useForLiquidations: false, }; await driftClient.updateOracleGuardRails(oracleGuardRails); diff --git a/tests/liquidateBorrowForPerpPnl.ts b/tests/liquidateBorrowForPerpPnl.ts index c90264ffa..95ac211fe 100644 --- a/tests/liquidateBorrowForPerpPnl.ts +++ b/tests/liquidateBorrowForPerpPnl.ts @@ -132,8 +132,8 @@ describe('liquidate borrow for perp pnl', () => { const oracleGuardRails: OracleGuardRails = { priceDivergence: { - markOracleDivergenceNumerator: new BN(1), - markOracleDivergenceDenominator: new BN(1), + markOraclePercentDivergence: new BN(1000000), + oracleTwap5MinPercentDivergence: new BN(1000000), }, validity: { slotsBeforeStaleForAmm: new BN(100), diff --git a/tests/liquidatePerp.ts b/tests/liquidatePerp.ts index ec53b789e..d5ebd04bd 100644 --- a/tests/liquidatePerp.ts +++ b/tests/liquidatePerp.ts @@ -31,7 +31,7 @@ import { printTxLogs, sleep, } from './testHelpers'; -import { BulkAccountLoader } from '../sdk'; +import { BulkAccountLoader, PERCENTAGE_PRECISION } from '../sdk'; describe('liquidate perp (no open orders)', () => { const provider = anchor.AnchorProvider.local(undefined, { @@ -287,8 +287,8 @@ describe('liquidate perp (no open orders)', () => { const oracleGuardRails: OracleGuardRails = { priceDivergence: { - markOracleDivergenceNumerator: new BN(1), - markOracleDivergenceDenominator: new BN(10), + markOraclePercentDivergence: PERCENTAGE_PRECISION, + oracleTwap5MinPercentDivergence: PERCENTAGE_PRECISION.div(new BN(10)), }, validity: { slotsBeforeStaleForAmm: new BN(100), @@ -296,7 +296,6 @@ describe('liquidate perp (no open orders)', () => { confidenceIntervalMaxSize: new BN(100000), tooVolatileRatio: new BN(11), // allow 11x change }, - useForLiquidations: false, }; await driftClient.updateOracleGuardRails(oracleGuardRails); diff --git a/tests/liquidatePerpAndLp.ts b/tests/liquidatePerpAndLp.ts index 03048cfc4..f05cbd3ff 100644 --- a/tests/liquidatePerpAndLp.ts +++ b/tests/liquidatePerpAndLp.ts @@ -32,7 +32,7 @@ import { printTxLogs, sleep, } from './testHelpers'; -import { BulkAccountLoader } from '../sdk'; +import { BulkAccountLoader, PERCENTAGE_PRECISION } from '../sdk'; describe('liquidate perp and lp', () => { const provider = anchor.AnchorProvider.local(undefined, { @@ -292,8 +292,8 @@ describe('liquidate perp and lp', () => { const oracleGuardRails: OracleGuardRails = { priceDivergence: { - markOracleDivergenceNumerator: new BN(1), - markOracleDivergenceDenominator: new BN(10), + markOraclePercentDivergence: PERCENTAGE_PRECISION, + oracleTwap5MinPercentDivergence: PERCENTAGE_PRECISION.div(new BN(10)), }, validity: { slotsBeforeStaleForAmm: new BN(100), @@ -301,7 +301,6 @@ describe('liquidate perp and lp', () => { confidenceIntervalMaxSize: new BN(100000), tooVolatileRatio: new BN(11), // allow 11x change }, - useForLiquidations: false, }; await driftClient.updateOracleGuardRails(oracleGuardRails); diff --git a/tests/liquidatePerpPnlForDeposit.ts b/tests/liquidatePerpPnlForDeposit.ts index b11f14671..e235b39e8 100644 --- a/tests/liquidatePerpPnlForDeposit.ts +++ b/tests/liquidatePerpPnlForDeposit.ts @@ -31,7 +31,12 @@ import { initializeSolSpotMarket, printTxLogs, } from './testHelpers'; -import { BulkAccountLoader, isVariant, QUOTE_PRECISION } from '../sdk'; +import { + BulkAccountLoader, + isVariant, + PERCENTAGE_PRECISION, + QUOTE_PRECISION, +} from '../sdk'; describe('liquidate perp pnl for deposit', () => { const provider = anchor.AnchorProvider.local(undefined, { @@ -132,8 +137,8 @@ describe('liquidate perp pnl for deposit', () => { const oracleGuardRails: OracleGuardRails = { priceDivergence: { - markOracleDivergenceNumerator: new BN(100), - markOracleDivergenceDenominator: new BN(10), + markOraclePercentDivergence: new BN(10).mul(PERCENTAGE_PRECISION), + oracleTwap5MinPercentDivergence: new BN(10).mul(PERCENTAGE_PRECISION), }, validity: { slotsBeforeStaleForAmm: new BN(100), diff --git a/tests/liquidityProvider.ts b/tests/liquidityProvider.ts index 24a4c38f3..ad3ccad0c 100644 --- a/tests/liquidityProvider.ts +++ b/tests/liquidityProvider.ts @@ -228,8 +228,8 @@ describe('liquidity providing', () => { const oracleGuardRails: OracleGuardRails = { priceDivergence: { - markOracleDivergenceNumerator: new BN(1), - markOracleDivergenceDenominator: new BN(1), + markOraclePercentDivergence: new BN(1000000), + oracleTwap5MinPercentDivergence: new BN(1000000), }, validity: { slotsBeforeStaleForAmm: new BN(10), diff --git a/tests/maxLeverageOrderParams.ts b/tests/maxLeverageOrderParams.ts index 65875706b..46923a95c 100644 --- a/tests/maxLeverageOrderParams.ts +++ b/tests/maxLeverageOrderParams.ts @@ -29,6 +29,7 @@ import { BulkAccountLoader, getMarketOrderParams, MAX_LEVERAGE_ORDER_SIZE, + PERCENTAGE_PRECISION, } from '../sdk'; describe('max leverage order params', () => { @@ -125,8 +126,8 @@ describe('max leverage order params', () => { const oracleGuardRails: OracleGuardRails = { priceDivergence: { - markOracleDivergenceNumerator: new BN(1), - markOracleDivergenceDenominator: new BN(10), + markOraclePercentDivergence: PERCENTAGE_PRECISION.div(new BN(10)), + oracleTwap5MinPercentDivergence: PERCENTAGE_PRECISION, }, validity: { slotsBeforeStaleForAmm: new BN(100), diff --git a/tests/order.ts b/tests/order.ts index c0f78f1ef..17e47f03c 100644 --- a/tests/order.ts +++ b/tests/order.ts @@ -1376,8 +1376,8 @@ describe('orders', () => { it('PlaceAndTake LONG Order 100% filled', async () => { const oracleGuardRails: OracleGuardRails = { priceDivergence: { - markOracleDivergenceNumerator: new BN(1), - markOracleDivergenceDenominator: new BN(1), + markOraclePercentDivergence: new BN(1000000), + oracleTwap5MinPercentDivergence: new BN(1000000), }, validity: { slotsBeforeStaleForAmm: new BN(100), diff --git a/tests/perpLpJit.ts b/tests/perpLpJit.ts index a15d1da65..6e24d47e8 100644 --- a/tests/perpLpJit.ts +++ b/tests/perpLpJit.ts @@ -220,8 +220,8 @@ describe('lp jit', () => { const oracleGuardRails: OracleGuardRails = { priceDivergence: { - markOracleDivergenceNumerator: new BN(1), - markOracleDivergenceDenominator: new BN(1), + markOraclePercentDivergence: new BN(1000000), + oracleTwap5MinPercentDivergence: new BN(1000000), }, validity: { slotsBeforeStaleForAmm: new BN(10), diff --git a/tests/phoenixTest.ts b/tests/phoenixTest.ts index b21193900..5ac27a2f7 100644 --- a/tests/phoenixTest.ts +++ b/tests/phoenixTest.ts @@ -236,7 +236,7 @@ describe('phoenix spot market', () => { assert(phoenixMarket.data.header.authority.equals(god.publicKey)); assert(phoenixMarket.data.traders.has(god.publicKey.toBase58())); - solOracle = await mockOracle(30); + solOracle = await mockOracle(100); marketIndexes = []; spotMarketIndexes = [0, 1]; oracleInfos = [{ publicKey: solOracle, source: OracleSource.PYTH }]; diff --git a/tests/serumTest.ts b/tests/serumTest.ts index eccc25f19..b713edcb8 100644 --- a/tests/serumTest.ts +++ b/tests/serumTest.ts @@ -85,7 +85,7 @@ describe('serum spot market', () => { solAmount ); - solOracle = await mockOracle(30); + solOracle = await mockOracle(100); marketIndexes = []; spotMarketIndexes = [0, 1]; diff --git a/tests/tradingLP.ts b/tests/tradingLP.ts index 8e8277330..5c1f8ce5a 100644 --- a/tests/tradingLP.ts +++ b/tests/tradingLP.ts @@ -162,8 +162,8 @@ describe('trading liquidity providing', () => { ); const oracleGuardRails: OracleGuardRails = { priceDivergence: { - markOracleDivergenceNumerator: new BN(1), - markOracleDivergenceDenominator: new BN(1), + markOraclePercentDivergence: new BN(1000000), + oracleTwap5MinPercentDivergence: new BN(1000000), }, validity: { slotsBeforeStaleForAmm: new BN(10), diff --git a/tests/triggerOrders.ts b/tests/triggerOrders.ts index 2af8081b5..89129c7bd 100644 --- a/tests/triggerOrders.ts +++ b/tests/triggerOrders.ts @@ -31,6 +31,7 @@ import { BulkAccountLoader, convertToNumber, OracleSource, + PERCENTAGE_PRECISION, QUOTE_PRECISION, ZERO, } from '../sdk'; @@ -107,8 +108,8 @@ describe('trigger orders', () => { const oracleGuardRails: OracleGuardRails = { priceDivergence: { - markOracleDivergenceNumerator: new BN(100), - markOracleDivergenceDenominator: new BN(10), + markOraclePercentDivergence: PERCENTAGE_PRECISION.mul(new BN(10)), + oracleTwap5MinPercentDivergence: PERCENTAGE_PRECISION.mul(new BN(10)), }, validity: { slotsBeforeStaleForAmm: new BN(100), diff --git a/tests/updateAMM.ts b/tests/updateAMM.ts index 3ca5ad432..162e125d4 100644 --- a/tests/updateAMM.ts +++ b/tests/updateAMM.ts @@ -500,8 +500,8 @@ describe('update amm', () => { it('Many market balanced prepegs, long position', async () => { const oracleGuardRails: OracleGuardRails = { priceDivergence: { - markOracleDivergenceNumerator: new BN(1), - markOracleDivergenceDenominator: new BN(1), + markOraclePercentDivergence: new BN(1000000), + oracleTwap5MinPercentDivergence: new BN(1000000), }, validity: { slotsBeforeStaleForAmm: new BN(100), From 9acee0b3122ab066b3ff576ae46b04500f65cf6c Mon Sep 17 00:00:00 2001 From: Chris Heaney Date: Tue, 4 Jul 2023 15:31:08 -0400 Subject: [PATCH 14/18] add test --- test-scripts/run-anchor-tests.sh | 1 + tests/oracleFillPriceGuardrails.ts | 241 +++++++++++++++++++++++++++++ 2 files changed, 242 insertions(+) create mode 100644 tests/oracleFillPriceGuardrails.ts diff --git a/test-scripts/run-anchor-tests.sh b/test-scripts/run-anchor-tests.sh index cfa8fccc9..9ed0d5c28 100644 --- a/test-scripts/run-anchor-tests.sh +++ b/test-scripts/run-anchor-tests.sh @@ -4,6 +4,7 @@ if [ "$1" != "--skip-build" ]; then fi test_files=( + oracleFillPriceGuardrails.ts perpLpJit.ts spotSwap.ts maxLeverageOrderParams.ts diff --git a/tests/oracleFillPriceGuardrails.ts b/tests/oracleFillPriceGuardrails.ts new file mode 100644 index 000000000..ef8eefa26 --- /dev/null +++ b/tests/oracleFillPriceGuardrails.ts @@ -0,0 +1,241 @@ +import * as anchor from '@coral-xyz/anchor'; +import { assert } from 'chai'; + +import { Program } from '@coral-xyz/anchor'; + +import { + TestClient, + BN, + PRICE_PRECISION, + PositionDirection, + EventSubscriber, + MarketStatus, + BASE_PRECISION, + isVariant, + OracleSource, + PEG_PRECISION, + BulkAccountLoader, +} from '../sdk/src'; + +import { + createUserWithUSDCAccount, + initializeQuoteSpotMarket, + mockOracle, + mockUSDCMint, + mockUserUSDCAccount, + printTxLogs, + setFeedPrice, +} from './testHelpers'; +import { MARGIN_PRECISION, OrderType, PostOnlyParams } from '../sdk'; + +describe('multiple maker orders', () => { + const provider = anchor.AnchorProvider.local(undefined, { + commitment: 'confirmed', + preflightCommitment: 'confirmed', + }); + const connection = provider.connection; + anchor.setProvider(provider); + const chProgram = anchor.workspace.Drift as Program; + + let fillerDriftClient: TestClient; + const eventSubscriber = new EventSubscriber(connection, chProgram, { + commitment: 'recent', + }); + eventSubscriber.subscribe(); + + const bulkAccountLoader = new BulkAccountLoader(connection, 'confirmed', 1); + + let usdcMint; + let userUSDCAccount; + + // ammInvariant == k == x * y + const mantissaSqrtScale = new BN(100000); + const ammInitialQuoteAssetReserve = new anchor.BN(5 * 10 ** 13).mul( + mantissaSqrtScale + ); + const ammInitialBaseAssetReserve = new anchor.BN(5 * 10 ** 13).mul( + mantissaSqrtScale + ); + + const usdcAmount = new BN(100000 * 10 ** 6); + + let solUsd; + let marketIndexes; + let spotMarketIndexes; + let oracleInfos; + + before(async () => { + usdcMint = await mockUSDCMint(provider); + userUSDCAccount = await mockUserUSDCAccount(usdcMint, usdcAmount, provider); + + solUsd = await mockOracle(20); + + marketIndexes = [0, 1]; + spotMarketIndexes = [0]; + oracleInfos = [{ publicKey: solUsd, source: OracleSource.PYTH }]; + + fillerDriftClient = new TestClient({ + connection, + wallet: provider.wallet, + programID: chProgram.programId, + opts: { + commitment: 'confirmed', + }, + activeSubAccountId: 0, + perpMarketIndexes: marketIndexes, + spotMarketIndexes: spotMarketIndexes, + oracleInfos, + accountSubscription: { + type: 'polling', + accountLoader: bulkAccountLoader, + }, + }); + await fillerDriftClient.initialize(usdcMint.publicKey, true); + await fillerDriftClient.subscribe(); + await initializeQuoteSpotMarket(fillerDriftClient, usdcMint.publicKey); + // dont fill against the vamm + await fillerDriftClient.updatePerpAuctionDuration(new BN(100)); + + const periodicity = new BN(60 * 60); // 1 HOUR + + await fillerDriftClient.initializePerpMarket( + 0, + solUsd, + ammInitialBaseAssetReserve, + ammInitialQuoteAssetReserve, + periodicity, + new BN(20 * PEG_PRECISION.toNumber()) + ); + await fillerDriftClient.updatePerpMarketStatus(0, MarketStatus.ACTIVE); + + await fillerDriftClient.updatePerpMarketBaseSpread( + 0, + PRICE_PRECISION.toNumber() / 8 + ); + + await fillerDriftClient.updatePerpMarketMarginRatio( + 0, + MARGIN_PRECISION.toNumber() / 2, + MARGIN_PRECISION.toNumber() / 3 + ); + + await fillerDriftClient.updatePerpMarketMaxSpread( + 0, + PRICE_PRECISION.toNumber() / 5 + ); + + await fillerDriftClient.initializeUserAccountAndDepositCollateral( + usdcAmount, + userUSDCAccount.publicKey + ); + }); + + beforeEach(async () => { + await fillerDriftClient.moveAmmPrice( + 0, + ammInitialBaseAssetReserve, + ammInitialQuoteAssetReserve + ); + }); + + after(async () => { + await fillerDriftClient.unsubscribe(); + await eventSubscriber.unsubscribe(); + }); + + it('taker long solUsd', async () => { + const [takerDriftClient, takerUSDCAccount] = + await createUserWithUSDCAccount( + provider, + usdcMint, + chProgram, + usdcAmount, + marketIndexes, + spotMarketIndexes, + oracleInfos, + bulkAccountLoader + ); + + await takerDriftClient.deposit(usdcAmount, 0, takerUSDCAccount); + + const [makerDriftClient, makerUSDCAccount] = + await createUserWithUSDCAccount( + provider, + usdcMint, + chProgram, + usdcAmount, + marketIndexes, + spotMarketIndexes, + oracleInfos, + bulkAccountLoader + ); + + await makerDriftClient.deposit(usdcAmount, 0, makerUSDCAccount); + + await makerDriftClient.placePerpOrder({ + marketIndex: 0, + direction: PositionDirection.SHORT, + price: new BN(21).mul(PRICE_PRECISION), + orderType: OrderType.LIMIT, + baseAssetAmount: BASE_PRECISION, + }); + + await takerDriftClient.placePerpOrder({ + marketIndex: 0, + orderType: OrderType.LIMIT, + price: new BN(100).mul(PRICE_PRECISION), + direction: PositionDirection.LONG, + baseAssetAmount: BASE_PRECISION, + }); + + // move price to $30 + await setFeedPrice(anchor.workspace.Pyth, 30, solUsd); + + const makerInfo = [ + { + maker: await makerDriftClient.getUserAccountPublicKey(), + makerUserAccount: makerDriftClient.getUserAccount(), + makerStats: await makerDriftClient.getUserStatsAccountPublicKey(), + }, + ]; + const firstFillTxSig = await fillerDriftClient.fillPerpOrder( + await takerDriftClient.getUserAccountPublicKey(), + takerDriftClient.getUserAccount(), + takerDriftClient.getOrder(1), + makerInfo + ); + await printTxLogs(connection, firstFillTxSig); + + // assert that the + const orderActionRecord = + eventSubscriber.getEventsArray('OrderActionRecord')[0]; + assert(isVariant(orderActionRecord.action, 'cancel')); + + await makerDriftClient.placePerpOrder({ + marketIndex: 0, + direction: PositionDirection.SHORT, + price: new BN(31).mul(PRICE_PRECISION), + orderType: OrderType.LIMIT, + baseAssetAmount: BASE_PRECISION, + postOnly: PostOnlyParams.MUST_POST_ONLY, + }); + + let error = false; + try { + await fillerDriftClient.fillPerpOrder( + await takerDriftClient.getUserAccountPublicKey(), + takerDriftClient.getUserAccount(), + takerDriftClient.getOrder(1), + makerInfo + ); + } catch (e) { + error = true; + assert(e.message.includes('0x1787')); + } + + assert(error); + + await takerDriftClient.unsubscribe(); + await makerDriftClient.unsubscribe(); + }); +}); From f6bf28231ced69f2f5b22f02370bf72eccb16a1d Mon Sep 17 00:00:00 2001 From: 0xbigz <83473873+0xbigz@users.noreply.github.com> Date: Wed, 5 Jul 2023 13:08:31 -0400 Subject: [PATCH 15/18] make changes to fix tests/delistMarket.ts --- programs/drift/src/controller/orders.rs | 8 ++++++++ programs/drift/src/math/orders.rs | 23 ++++++++++++++------- programs/drift/src/math/orders/tests.rs | 6 ++++++ tests/delistMarket.ts | 27 +++++++++++++++++++++---- 4 files changed, 53 insertions(+), 11 deletions(-) diff --git a/programs/drift/src/controller/orders.rs b/programs/drift/src/controller/orders.rs index 92bc8d1a3..c38b334b4 100644 --- a/programs/drift/src/controller/orders.rs +++ b/programs/drift/src/controller/orders.rs @@ -1103,6 +1103,10 @@ pub fn fill_perp_order( oracle_price, oracle_twap_5min, perp_market_map.get_ref(&market_index)?.margin_ratio_initial, + state + .oracle_guard_rails + .price_divergence + .oracle_twap_5min_percent_divergence, )?; } @@ -3289,6 +3293,10 @@ pub fn fill_spot_order( oracle_price, oracle_twap_5min, spot_market.get_margin_ratio(&MarginRequirementType::Initial)?, + state + .oracle_guard_rails + .price_divergence + .oracle_twap_5min_percent_divergence, )?; } diff --git a/programs/drift/src/math/orders.rs b/programs/drift/src/math/orders.rs index 3598c3fa5..ecae760fb 100644 --- a/programs/drift/src/math/orders.rs +++ b/programs/drift/src/math/orders.rs @@ -407,19 +407,20 @@ pub fn validate_fill_price_within_price_bands( oracle_price: i64, oracle_twap_5min: i64, margin_ratio_initial: u32, + oracle_twap_5min_percent_divergence: u64, ) -> DriftResult { let oracle_price = oracle_price.unsigned_abs(); let oracle_twap_5min = oracle_twap_5min.unsigned_abs(); let max_oracle_diff = margin_ratio_initial.cast::()?; - let max_oracle_twap_diff = MARGIN_PRECISION_U128 / 2; // 50% + let max_oracle_twap_diff = oracle_twap_5min_percent_divergence.cast::()?; // 50% if direction == PositionDirection::Long { if fill_price < oracle_price && fill_price < oracle_twap_5min { return Ok(()); } - let percent_diff = fill_price + let percent_diff: u128 = fill_price .saturating_sub(oracle_price) .cast::()? .safe_mul(MARGIN_PRECISION_U128)? @@ -428,7 +429,9 @@ pub fn validate_fill_price_within_price_bands( validate!( percent_diff < max_oracle_diff, ErrorCode::PriceBandsBreached, - "Fill Price Breaches Oracle Price Bands: {} >= {}", + "Fill Price Breaches Oracle Price Bands: {} % <= {} % (fill: {} >= oracle: {})", + max_oracle_diff, + percent_diff, fill_price, oracle_price )?; @@ -442,7 +445,9 @@ pub fn validate_fill_price_within_price_bands( validate!( percent_diff < max_oracle_twap_diff, ErrorCode::PriceBandsBreached, - "Fill Price Breaches Oracle TWAP Price Bands: {} >= {}", + "Fill Price Breaches Oracle TWAP Price Bands: {} % <= {} % (fill: {} >= twap: {})", + max_oracle_twap_diff, + percent_diff, fill_price, oracle_twap_5min )?; @@ -451,7 +456,7 @@ pub fn validate_fill_price_within_price_bands( return Ok(()); } - let percent_diff = oracle_price + let percent_diff: u128 = oracle_price .saturating_sub(fill_price) .cast::()? .safe_mul(MARGIN_PRECISION_U128)? @@ -460,7 +465,9 @@ pub fn validate_fill_price_within_price_bands( validate!( percent_diff < max_oracle_diff, ErrorCode::PriceBandsBreached, - "Fill Price Breaches Oracle Price Bands: {} <= {}", + "Fill Price Breaches Oracle Price Bands: {} % <= {} % (fill: {} <= oracle: {})", + max_oracle_diff, + percent_diff, fill_price, oracle_price )?; @@ -474,7 +481,9 @@ pub fn validate_fill_price_within_price_bands( validate!( percent_diff < max_oracle_twap_diff, ErrorCode::PriceBandsBreached, - "Fill Price Breaches Oracle TWAP Price Bands: {} <= {}", + "Fill Price Breaches Oracle TWAP Price Bands: {} % <= {} % (fill: {} <= twap: {})", + max_oracle_twap_diff, + percent_diff, fill_price, oracle_twap_5min )?; diff --git a/programs/drift/src/math/orders/tests.rs b/programs/drift/src/math/orders/tests.rs index 88baee966..33feb7ceb 100644 --- a/programs/drift/src/math/orders/tests.rs +++ b/programs/drift/src/math/orders/tests.rs @@ -2356,6 +2356,7 @@ pub mod validate_fill_price_within_price_bands { oracle_price, twap, margin_ratio_initial, + (MARGIN_PRECISION / 2) as u64, ) .is_ok()) } @@ -2374,6 +2375,7 @@ pub mod validate_fill_price_within_price_bands { oracle_price, twap, margin_ratio_initial, + (MARGIN_PRECISION / 2) as u64, ) .is_ok()) } @@ -2393,6 +2395,7 @@ pub mod validate_fill_price_within_price_bands { oracle_price, twap, margin_ratio_initial, + (MARGIN_PRECISION / 2) as u64, ) .is_err()) } @@ -2412,6 +2415,7 @@ pub mod validate_fill_price_within_price_bands { oracle_price, twap, margin_ratio_initial, + (MARGIN_PRECISION / 2) as u64, ) .is_err()) } @@ -2431,6 +2435,7 @@ pub mod validate_fill_price_within_price_bands { oracle_price, twap, margin_ratio_initial, + (MARGIN_PRECISION / 2) as u64, ) .is_err()) } @@ -2450,6 +2455,7 @@ pub mod validate_fill_price_within_price_bands { oracle_price, twap, margin_ratio_initial, + (MARGIN_PRECISION / 2) as u64, ) .is_err()) } diff --git a/tests/delistMarket.ts b/tests/delistMarket.ts index c0975c5b5..b53788005 100644 --- a/tests/delistMarket.ts +++ b/tests/delistMarket.ts @@ -170,7 +170,7 @@ describe('delist market', () => { // await driftClient.updatePerpMarketCurveUpdateIntensity(new BN(0), 100); await driftClient.updatePerpMarketStepSizeAndTickSize( 0, - new BN(1), + new BN(10), new BN(1) ); await driftClient.updatePerpMarketMinOrderSize(0, new BN(1)); @@ -354,7 +354,12 @@ describe('delist market', () => { marketIndex, perpMarket.amm.sqrtK.mul(new BN(9912345)).div(new BN(10012345)) ); - await liquidatorDriftClient.closePosition(marketIndex); + + console.log( + 'liquidatorDriftClient perps:', + liquidatorDriftClient.getUserAccount().perpPositions[0] + ); + // await liquidatorDriftClient.closePosition(marketIndex); // sol tanks 90% await driftClient.moveAmmToPrice( @@ -363,6 +368,7 @@ describe('delist market', () => { ); await setFeedPrice(anchor.workspace.Pyth, 43.1337 / 10, solOracle); }); + // return 0; it('put market in reduce only mode', async () => { const marketIndex = 0; @@ -622,11 +628,24 @@ describe('delist market', () => { it('put settle market pools to revenue pool', async () => { const marketIndex = 0; + const marketBefore = driftClient.getPerpMarketAccount(marketIndex); + const userCostBasisBefore = marketBefore.amm.quoteAssetAmount; + + console.log('userCostBasisBefore:', userCostBasisBefore.toString()); + assert(userCostBasisBefore.eq(new BN(-1))); // from LP burn + + await liquidatorDriftClient.settlePNL( + await liquidatorDriftClient.getUserAccountPublicKey(), + liquidatorDriftClient.getUserAccount(), + marketIndex + ); + + await driftClient.fetchAccounts(); const market = driftClient.getPerpMarketAccount(marketIndex); const userCostBasis = market.amm.quoteAssetAmount; - console.log('userCostBasis:', userCostBasis.toString()); - assert(userCostBasis.eq(ZERO)); + assert(userCostBasis.eq(ZERO)); // ready to settle expiration + try { await driftClient.settleExpiredMarketPoolsToRevenuePool(marketIndex); } catch (e) { From c8db5cf913e372f2e5be82ca2af6b6c9711d43a2 Mon Sep 17 00:00:00 2001 From: Chris Heaney Date: Wed, 5 Jul 2023 14:40:43 -0400 Subject: [PATCH 16/18] fix how max twap diff is pulled --- programs/drift/src/controller/orders.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/programs/drift/src/controller/orders.rs b/programs/drift/src/controller/orders.rs index c38b334b4..5a4a05674 100644 --- a/programs/drift/src/controller/orders.rs +++ b/programs/drift/src/controller/orders.rs @@ -1105,8 +1105,7 @@ pub fn fill_perp_order( perp_market_map.get_ref(&market_index)?.margin_ratio_initial, state .oracle_guard_rails - .price_divergence - .oracle_twap_5min_percent_divergence, + .max_oracle_twap_5min_percent_divergence(), )?; } @@ -3295,8 +3294,7 @@ pub fn fill_spot_order( spot_market.get_margin_ratio(&MarginRequirementType::Initial)?, state .oracle_guard_rails - .price_divergence - .oracle_twap_5min_percent_divergence, + .max_oracle_twap_5min_percent_divergence(), )?; } From a75531c50f78a431283b248e3173ef31f4b4f986 Mon Sep 17 00:00:00 2001 From: Chris Heaney Date: Wed, 5 Jul 2023 15:39:40 -0400 Subject: [PATCH 17/18] fix tests --- programs/drift/src/math/orders.rs | 8 ++++---- programs/drift/src/math/orders/tests.rs | 17 ++++++++++------- tests/oracleFillPriceGuardrails.ts | 6 ++++-- tests/placeAndMakeSpotOrder.ts | 9 ++++++++- tests/repegAndSpread.ts | 5 +++-- tests/tradingLP.ts | 15 +++++++++++++-- 6 files changed, 42 insertions(+), 18 deletions(-) diff --git a/programs/drift/src/math/orders.rs b/programs/drift/src/math/orders.rs index ecae760fb..ea5b6eef2 100644 --- a/programs/drift/src/math/orders.rs +++ b/programs/drift/src/math/orders.rs @@ -10,8 +10,8 @@ use crate::math::amm::calculate_amm_available_liquidity; use crate::math::auction::is_auction_complete; use crate::math::casting::Cast; use crate::{ - math, BASE_PRECISION_I128, OPEN_ORDER_MARGIN_REQUIREMENT, PERCENTAGE_PRECISION_U64, - PRICE_PRECISION_I128, QUOTE_PRECISION_I128, SPOT_WEIGHT_PRECISION, + math, BASE_PRECISION_I128, OPEN_ORDER_MARGIN_REQUIREMENT, PERCENTAGE_PRECISION, + PERCENTAGE_PRECISION_U64, PRICE_PRECISION_I128, QUOTE_PRECISION_I128, SPOT_WEIGHT_PRECISION, }; use crate::math::constants::MARGIN_PRECISION_U128; @@ -439,7 +439,7 @@ pub fn validate_fill_price_within_price_bands( let percent_diff = fill_price .saturating_sub(oracle_twap_5min) .cast::()? - .safe_mul(MARGIN_PRECISION_U128)? + .safe_mul(PERCENTAGE_PRECISION)? .safe_div(oracle_twap_5min.cast()?)?; validate!( @@ -475,7 +475,7 @@ pub fn validate_fill_price_within_price_bands( let percent_diff = oracle_twap_5min .saturating_sub(fill_price) .cast::()? - .safe_mul(MARGIN_PRECISION_U128)? + .safe_mul(PERCENTAGE_PRECISION)? .safe_div(oracle_twap_5min.cast()?)?; validate!( diff --git a/programs/drift/src/math/orders/tests.rs b/programs/drift/src/math/orders/tests.rs index 33feb7ceb..83378ed43 100644 --- a/programs/drift/src/math/orders/tests.rs +++ b/programs/drift/src/math/orders/tests.rs @@ -2340,7 +2340,10 @@ mod calculate_max_perp_order_size { pub mod validate_fill_price_within_price_bands { use crate::math::orders::validate_fill_price_within_price_bands; - use crate::{PositionDirection, MARGIN_PRECISION, PRICE_PRECISION_I64, PRICE_PRECISION_U64}; + use crate::{ + PositionDirection, MARGIN_PRECISION, PERCENTAGE_PRECISION, PRICE_PRECISION_I64, + PRICE_PRECISION_U64, + }; #[test] fn valid_long() { @@ -2356,7 +2359,7 @@ pub mod validate_fill_price_within_price_bands { oracle_price, twap, margin_ratio_initial, - (MARGIN_PRECISION / 2) as u64, + (PERCENTAGE_PRECISION / 2) as u64, ) .is_ok()) } @@ -2375,7 +2378,7 @@ pub mod validate_fill_price_within_price_bands { oracle_price, twap, margin_ratio_initial, - (MARGIN_PRECISION / 2) as u64, + (PERCENTAGE_PRECISION / 2) as u64, ) .is_ok()) } @@ -2395,7 +2398,7 @@ pub mod validate_fill_price_within_price_bands { oracle_price, twap, margin_ratio_initial, - (MARGIN_PRECISION / 2) as u64, + (PERCENTAGE_PRECISION / 2) as u64, ) .is_err()) } @@ -2415,7 +2418,7 @@ pub mod validate_fill_price_within_price_bands { oracle_price, twap, margin_ratio_initial, - (MARGIN_PRECISION / 2) as u64, + (PERCENTAGE_PRECISION / 2) as u64, ) .is_err()) } @@ -2435,7 +2438,7 @@ pub mod validate_fill_price_within_price_bands { oracle_price, twap, margin_ratio_initial, - (MARGIN_PRECISION / 2) as u64, + (PERCENTAGE_PRECISION / 2) as u64, ) .is_err()) } @@ -2455,7 +2458,7 @@ pub mod validate_fill_price_within_price_bands { oracle_price, twap, margin_ratio_initial, - (MARGIN_PRECISION / 2) as u64, + (PERCENTAGE_PRECISION / 2) as u64, ) .is_err()) } diff --git a/tests/oracleFillPriceGuardrails.ts b/tests/oracleFillPriceGuardrails.ts index ef8eefa26..36866bba2 100644 --- a/tests/oracleFillPriceGuardrails.ts +++ b/tests/oracleFillPriceGuardrails.ts @@ -28,7 +28,7 @@ import { } from './testHelpers'; import { MARGIN_PRECISION, OrderType, PostOnlyParams } from '../sdk'; -describe('multiple maker orders', () => { +describe('oracle fill guardrails', () => { const provider = anchor.AnchorProvider.local(undefined, { commitment: 'confirmed', preflightCommitment: 'confirmed', @@ -222,12 +222,14 @@ describe('multiple maker orders', () => { let error = false; try { - await fillerDriftClient.fillPerpOrder( + const txSig = await fillerDriftClient.fillPerpOrder( await takerDriftClient.getUserAccountPublicKey(), takerDriftClient.getUserAccount(), takerDriftClient.getOrder(1), makerInfo ); + + await printTxLogs(connection, txSig); } catch (e) { error = true; assert(e.message.includes('0x1787')); diff --git a/tests/placeAndMakeSpotOrder.ts b/tests/placeAndMakeSpotOrder.ts index c8501dae8..b10233879 100644 --- a/tests/placeAndMakeSpotOrder.ts +++ b/tests/placeAndMakeSpotOrder.ts @@ -27,7 +27,7 @@ import { printTxLogs, sleep, } from './testHelpers'; -import { BulkAccountLoader, PostOnlyParams } from '../sdk'; +import { BulkAccountLoader, MARGIN_PRECISION, PostOnlyParams } from '../sdk'; describe('place and make spot order', () => { const provider = anchor.AnchorProvider.local(undefined, { @@ -88,6 +88,13 @@ describe('place and make spot order', () => { await initializeQuoteSpotMarket(makerDriftClient, usdcMint.publicKey); await initializeSolSpotMarket(makerDriftClient, solUsd); await makerDriftClient.updatePerpAuctionDuration(new BN(0)); + await makerDriftClient.updateSpotMarketMarginWeights( + 1, + MARGIN_PRECISION.toNumber() * 0.75, + MARGIN_PRECISION.toNumber() * 0.8, + MARGIN_PRECISION.toNumber() * 1.25, + MARGIN_PRECISION.toNumber() * 1.2 + ); await makerDriftClient.initializeUserAccountAndDepositCollateral( usdcAmount, diff --git a/tests/repegAndSpread.ts b/tests/repegAndSpread.ts index bd60e6389..e9a9eb1f2 100644 --- a/tests/repegAndSpread.ts +++ b/tests/repegAndSpread.ts @@ -17,6 +17,7 @@ import { OracleGuardRails, BASE_PRECISION, BulkAccountLoader, + PERCENTAGE_PRECISION, } from '../sdk'; import { Keypair } from '@solana/web3.js'; import { Program } from '@coral-xyz/anchor'; @@ -259,8 +260,8 @@ describe('repeg and spread amm', () => { it('BTC market massive spread', async () => { const oracleGuardRails: OracleGuardRails = { priceDivergence: { - markOracleDivergenceNumerator: new BN(1), - markOracleDivergenceDenominator: new BN(1), + markOraclePercentDivergence: PERCENTAGE_PRECISION, + oracleTwap5MinPercentDivergence: PERCENTAGE_PRECISION, }, validity: { slotsBeforeStaleForAmm: new BN(100), diff --git a/tests/tradingLP.ts b/tests/tradingLP.ts index 5c1f8ce5a..69b8ba90d 100644 --- a/tests/tradingLP.ts +++ b/tests/tradingLP.ts @@ -1,6 +1,13 @@ import * as anchor from '@coral-xyz/anchor'; import { assert } from 'chai'; -import { BN, User, OracleSource, Wallet, BulkAccountLoader } from '../sdk'; +import { + BN, + User, + OracleSource, + Wallet, + BulkAccountLoader, + MARGIN_PRECISION, +} from '../sdk'; import { Program } from '@coral-xyz/anchor'; @@ -171,7 +178,6 @@ describe('trading liquidity providing', () => { confidenceIntervalMaxSize: new BN(100), tooVolatileRatio: new BN(100), }, - useForLiquidations: true, }; await driftClient.updateOracleGuardRails(oracleGuardRails); @@ -184,6 +190,11 @@ describe('trading liquidity providing', () => { new BN(0) ); await driftClient.updatePerpAuctionDuration(new BN(0)); + await driftClient.updatePerpMarketMarginRatio( + 0, + MARGIN_PRECISION.toNumber() / 2, + MARGIN_PRECISION.toNumber() / 4 + ); [traderDriftClient, traderDriftClientUser] = await createNewUser( chProgram, From 77e390fc3134a5a325384c5bd0f326036be2fd92 Mon Sep 17 00:00:00 2001 From: Chris Heaney Date: Wed, 5 Jul 2023 17:00:31 -0400 Subject: [PATCH 18/18] CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 97e72a9f5..0e67f0754 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Features - program: new margin type for when orders are being filled (([#518](https://github.com/drift-labs/protocol-v2/pull/518))) +- program: new fill price bands (([#516](https://github.com/drift-labs/protocol-v2/pull/516))) ### Fixes