Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions programs/drift/src/controller/orders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ use crate::math::matching::{
calculate_filler_multiplier_for_matched_orders, do_orders_cross, is_maker_for_taker,
};
use crate::math::oracle::{
is_oracle_valid_for_action, oracle_validity, DriftAction, OracleValidity,
self, is_oracle_valid_for_action, oracle_validity, DriftAction, OracleValidity,
};
use crate::math::safe_math::SafeMath;
use crate::math::safe_unwrap::SafeUnwrap;
Expand Down Expand Up @@ -1085,7 +1085,7 @@ pub fn fill_perp_order(
&state.oracle_guard_rails.validity,
market.get_max_confidence_interval_multiplier()?,
&market.amm.oracle_source,
true,
oracle::LogMode::SafeMMOracle,
market.amm.oracle_slot_delay_override,
)?;

Expand All @@ -1102,6 +1102,17 @@ pub fn fill_perp_order(
amm_is_available &= !market.is_operation_paused(PerpOperation::AmmFill);
amm_is_available &= !market.has_too_much_drawdown()?;

// We are already using safe oracle data from MM oracle.
// But AMM isnt available if we could have used MM oracle but fell back due to price diff
let amm_available_mm_oracle_recent_but_volatile =
if mm_oracle_price_data.is_enabled() && mm_oracle_price_data.is_mm_oracle_as_recent() {
let amm_available = !mm_oracle_price_data.is_mm_exchange_diff_bps_high();
amm_available
} else {
true
};
amm_is_available &= amm_available_mm_oracle_recent_but_volatile;

let amm_wants_to_jit_make = market.amm.amm_wants_to_jit_make(order_direction)?;
amm_lp_allowed_to_jit_make = market
.amm
Expand Down
180 changes: 179 additions & 1 deletion programs/drift/src/controller/orders/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2973,7 +2973,9 @@ pub mod fulfill_order {
use std::str::FromStr;
use std::u64;

use crate::controller::orders::{fulfill_perp_order, validate_market_within_price_band};
use crate::controller::orders::{
fill_perp_order, fulfill_perp_order, validate_market_within_price_band,
};
use crate::controller::position::PositionDirection;
use crate::create_anchor_account_info;
use crate::error::ErrorCode;
Expand Down Expand Up @@ -4845,6 +4847,182 @@ pub mod fulfill_order {
assert_eq!(ask_price, 100069968); // ~ 100.1 * (0.9997)
}

#[test]
fn amm_unavailable_from_volatile_mm_oracle() {
use anchor_lang::prelude::{AccountLoader, Clock};

let slot = 56_u64;
let clock = Clock {
slot,
epoch_start_timestamp: 0,
epoch: 0,
leader_schedule_epoch: 0,
unix_timestamp: 0,
};

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();
create_account_info!(
oracle_price,
&oracle_price_key,
&pyth_program,
oracle_account_info
);
let mut oracle_map = OracleMap::load_one(&oracle_account_info, slot, None).unwrap();

let mut market = PerpMarket {
amm: AMM {
base_asset_reserve: 100 * AMM_RESERVE_PRECISION,
quote_asset_reserve: 100 * AMM_RESERVE_PRECISION,
bid_base_asset_reserve: 100 * AMM_RESERVE_PRECISION,
bid_quote_asset_reserve: 100 * AMM_RESERVE_PRECISION,
ask_base_asset_reserve: 100 * AMM_RESERVE_PRECISION,
ask_quote_asset_reserve: 100 * AMM_RESERVE_PRECISION,
terminal_quote_asset_reserve: 100 * AMM_RESERVE_PRECISION,
sqrt_k: 100 * AMM_RESERVE_PRECISION,
peg_multiplier: 100 * PEG_PRECISION,
max_slippage_ratio: 50,
max_fill_reserve_fraction: 100,
order_step_size: 1000,
order_tick_size: 1,
oracle: oracle_price_key,
base_spread: 0, // 1 basis point
mm_oracle_price: 102 * PRICE_PRECISION_I64,
mm_oracle_slot: slot,
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,

..HistoricalOracleData::default()
},
..AMM::default()
},
margin_ratio_initial: 1000,
margin_ratio_maintenance: 500,
status: MarketStatus::Initialized,
..PerpMarket::default_test()
};
market.amm.max_base_asset_reserve = u128::MAX;
market.amm.min_base_asset_reserve = 0;
market.status = MarketStatus::Active;

create_anchor_account_info!(market, PerpMarket, market_account_info);
let market_map = PerpMarketMap::load_one(&market_account_info, true).unwrap();

let mut spot_market = SpotMarket {
market_index: 0,
oracle_source: OracleSource::QuoteAsset,
cumulative_deposit_interest: SPOT_CUMULATIVE_INTEREST_PRECISION,
decimals: 6,
initial_asset_weight: SPOT_WEIGHT_PRECISION,
maintenance_asset_weight: SPOT_WEIGHT_PRECISION,
historical_oracle_data: HistoricalOracleData::default_price(QUOTE_PRECISION_I64),
..SpotMarket::default()
};
create_anchor_account_info!(spot_market, SpotMarket, spot_market_account_info);
let spot_market_map = SpotMarketMap::load_one(&spot_market_account_info, true).unwrap();

let mut taker = User {
orders: get_orders(Order {
market_index: 0,
status: OrderStatus::Open,
order_type: OrderType::Market,
direction: PositionDirection::Long,
base_asset_amount: BASE_PRECISION_U64,
slot: 0,
auction_start_price: 0,
auction_end_price: 100 * PRICE_PRECISION_I64,
auction_duration: 0,
price: 150 * PRICE_PRECISION_U64,
order_id: 1,
..Order::default()
}),
perp_positions: get_positions(PerpPosition {
market_index: 0,
open_orders: 1,
open_bids: BASE_PRECISION_I64,
..PerpPosition::default()
}),
spot_positions: get_spot_positions(SpotPosition {
market_index: 0,
balance_type: SpotBalanceType::Deposit,
scaled_balance: 100 * SPOT_BALANCE_PRECISION_U64,
..SpotPosition::default()
}),
..User::default()
};
create_anchor_account_info!(taker, User, user_account_info);
let user_account_loader: AccountLoader<User> =
AccountLoader::try_from(&user_account_info).unwrap();
create_anchor_account_info!(UserStats::default(), UserStats, user_stats_account_info);
let user_stats_account_loader: AccountLoader<UserStats> =
AccountLoader::try_from(&user_stats_account_info).unwrap();

let filler_key = Pubkey::from_str("My11111111111111111111111111111111111111111").unwrap();
create_anchor_account_info!(User::default(), &filler_key, User, user_account_info);
let filler_account_loader: AccountLoader<User> =
AccountLoader::try_from(&user_account_info).unwrap();

create_anchor_account_info!(UserStats::default(), UserStats, filler_stats_account_info);
let filler_stats_account_loader: AccountLoader<UserStats> =
AccountLoader::try_from(&filler_stats_account_info).unwrap();

let state = State {
min_perp_auction_duration: 1,
default_market_order_time_in_force: 10,
..State::default()
};

let (base_asset_amount, _) = fill_perp_order(
1,
&state,
&user_account_loader,
&user_stats_account_loader,
&spot_market_map,
&market_map,
&mut oracle_map,
&filler_account_loader,
&filler_stats_account_loader,
&UserMap::empty(),
&UserStatsMap::empty(),
None,
&clock,
FillMode::Fill,
)
.unwrap();

assert_eq!(base_asset_amount, 0);

// Will fill if MM oracle price is not too volatile at mm oracle price
market.amm.mm_oracle_price = 101 * PRICE_PRECISION_I64;
create_anchor_account_info!(market, PerpMarket, market_account_info);
let market_map = PerpMarketMap::load_one(&market_account_info, true).unwrap();

let (base_asset_amount, quote_asset_amount) = fill_perp_order(
1,
&state,
&user_account_loader,
&user_stats_account_loader,
&spot_market_map,
&market_map,
&mut oracle_map,
&filler_account_loader,
&filler_stats_account_loader,
&UserMap::empty(),
&UserStatsMap::empty(),
None,
&clock,
FillMode::Fill,
)
.unwrap();

assert_eq!(base_asset_amount, BASE_PRECISION_U64);
assert_eq!(quote_asset_amount, 101010102);
}

// Add back if we check free collateral in fill again
// #[test]
// fn fulfill_with_negative_free_collateral() {
Expand Down
16 changes: 8 additions & 8 deletions programs/drift/src/controller/position/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1250,7 +1250,7 @@ fn amm_perp_ref_offset() {
crate::validation::perp_market::validate_perp_market(&perp_market).unwrap();

// Update MM oracle and reference price offset stays the same and is applied to the MM oracle
perp_market.amm.mm_oracle_price = 7200000;
perp_market.amm.mm_oracle_price = oracle_price_data.price * 1005 / 1000;
perp_market.amm.mm_oracle_slot = clock_slot;
let mm_oracle_price_data = perp_market
.get_mm_oracle_price_data(
Expand All @@ -1272,13 +1272,13 @@ fn amm_perp_ref_offset() {
.amm
.bid_ask_price(reserve_price_mm_offset)
.unwrap();
assert_eq!(perp_market.amm.reference_price_offset, 132);
assert_eq!(reserve_price_mm_offset, 7199999);
assert_eq!(b2, 7180271);
assert_eq!(a2, 7221656);
assert_eq!(perp_market.amm.reference_price_offset, 133);
assert_eq!(reserve_price_mm_offset, 7137107);
assert_eq!(b2, 7101549);
assert_eq!(a2, 7174591);

// Uses the original oracle if the slot is old, ignoring MM oracle
perp_market.amm.mm_oracle_price = 7200000;
perp_market.amm.mm_oracle_price = mm_oracle_price_data.get_price() * 995 / 1000;
perp_market.amm.mm_oracle_slot = clock_slot - 100;
let mut mm_oracle_price = perp_market
.get_mm_oracle_price_data(
Expand All @@ -1301,8 +1301,8 @@ fn amm_perp_ref_offset() {
.bid_ask_price(reserve_price_mm_offset_3)
.unwrap();
assert_eq!(reserve_price_mm_offset_3, r);
assert_eq!(b3, 7082154);
assert_eq!(a3, 7122974);
assert_eq!(b3, 7066225);
assert_eq!(a3, 7138903);
}

#[test]
Expand Down
5 changes: 3 additions & 2 deletions programs/drift/src/controller/repeg.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::cmp::min;

use crate::math::oracle::LogMode;
use crate::msg;
use crate::state::oracle::MMOraclePriceData;
use crate::state::oracle::OraclePriceData;
Expand Down Expand Up @@ -174,7 +175,7 @@ pub fn _update_amm(
&state.oracle_guard_rails.validity,
market.get_max_confidence_interval_multiplier()?,
&market.amm.oracle_source,
true,
oracle::LogMode::SafeMMOracle,
0,
)?;

Expand Down Expand Up @@ -263,7 +264,7 @@ pub fn update_amm_and_check_validity(
&state.oracle_guard_rails.validity,
market.get_max_confidence_interval_multiplier()?,
&market.amm.oracle_source,
false,
LogMode::SafeMMOracle,
0,
)?;

Expand Down
4 changes: 2 additions & 2 deletions programs/drift/src/controller/repeg/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ pub fn update_amm_test() {
&state.oracle_guard_rails.validity,
market.get_max_confidence_interval_multiplier().unwrap(),
&market.amm.oracle_source,
false,
LogMode::ExchangeOracle,
0,
)
.unwrap()
Expand Down Expand Up @@ -245,7 +245,7 @@ pub fn update_amm_test_bad_oracle() {
&state.oracle_guard_rails.validity,
market.get_max_confidence_interval_multiplier().unwrap(),
&market.amm.oracle_source,
false,
LogMode::None,
0,
)
.unwrap()
Expand Down
4 changes: 2 additions & 2 deletions programs/drift/src/controller/spot_balance.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::math::oracle::oracle_validity;
use crate::math::oracle::{oracle_validity, LogMode};
use crate::state::state::ValidityGuardRails;
use std::cmp::max; //, OracleValidity};

Expand Down Expand Up @@ -403,7 +403,7 @@ pub fn update_spot_market_and_check_validity(
validity_guard_rails,
spot_market.get_max_confidence_interval_multiplier()?,
&spot_market.oracle_source,
false,
LogMode::ExchangeOracle,
0,
)?;

Expand Down
1 change: 0 additions & 1 deletion programs/drift/src/instructions/admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4873,7 +4873,6 @@ pub fn handle_update_mm_oracle_native(accounts: &[AccountInfo], data: &[u8]) ->
let clock_data = clock_account.data.borrow();

perp_market[832..840].copy_from_slice(&clock_data[0..8]);
// perp_market[832..840].copy_from_slice(Clock::get()?.slot.to_le_bytes().as_ref());
perp_market[912..920].copy_from_slice(&data[0..8]);
perp_market[936..944].copy_from_slice(&data[8..16]);
}
Expand Down
18 changes: 12 additions & 6 deletions programs/drift/src/math/amm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,11 @@ pub fn update_oracle_price_twap(
None => amm.reserve_price()?,
};
let oracle_confidence = mm_oracle_price_data.get_confidence();
let oracle_price = normalise_oracle_price(amm, mm_oracle_price_data, Some(reserve_price))?;
let oracle_price = normalise_oracle_price(
amm,
&mm_oracle_price_data.get_exchange_oracle_price_data(),
Some(reserve_price),
)?;

let capped_oracle_update_price = sanitize_new_price(
oracle_price,
Expand All @@ -435,14 +439,16 @@ pub fn update_oracle_price_twap(
)?;

amm.last_oracle_normalised_price = capped_oracle_update_price;
amm.historical_oracle_data.last_oracle_price = mm_oracle_price_data.get_price();
amm.historical_oracle_data.last_oracle_price =
mm_oracle_price_data.get_exchange_oracle_price_data().price;

// Adjust confidence if the mm oracle and oracle price data are different by 5bps or more
// use decayed last_oracle_conf_pct as lower bound
amm.last_oracle_conf_pct =
amm.get_new_oracle_conf_pct(oracle_confidence, reserve_price, now)?;

amm.historical_oracle_data.last_oracle_delay = mm_oracle_price_data.get_delay();
amm.historical_oracle_data.last_oracle_delay =
mm_oracle_price_data.get_exchange_oracle_price_data().delay;
amm.last_oracle_reserve_price_spread_pct = calculate_oracle_reserve_price_spread_pct(
amm,
mm_oracle_price_data,
Expand Down Expand Up @@ -733,10 +739,10 @@ pub fn calculate_oracle_reserve_price_spread(

pub fn normalise_oracle_price(
amm: &AMM,
mm_oracle_price_data: &MMOraclePriceData,
oracle_price_data: &OraclePriceData,
precomputed_reserve_price: Option<u64>,
) -> DriftResult<i64> {
let oracle_price = mm_oracle_price_data.get_price();
let oracle_price = oracle_price_data.price;

let reserve_price = match precomputed_reserve_price {
Some(reserve_price) => reserve_price.cast::<i64>()?,
Expand All @@ -745,7 +751,7 @@ pub fn normalise_oracle_price(

// 2.5 bps of the mark price
let reserve_price_2p5_bps = reserve_price.safe_div(4000)?;
let conf_int = mm_oracle_price_data.get_confidence().cast::<i64>()?;
let conf_int = oracle_price_data.confidence.cast::<i64>()?;

// normalises oracle toward mark price based on the oracle’s confidence interval
// if mark above oracle: use oracle+conf unless it exceeds .99975 * mark price
Expand Down
Loading