Skip to content

Commit adcae56

Browse files
authored
mm oracle improvements (#1771)
* v1 safety improvements * isolate funding from MM oracle * add cargo tests for amm availability * change oracle validity log bool to enum * address comment
1 parent c66099e commit adcae56

File tree

15 files changed

+309
-69
lines changed

15 files changed

+309
-69
lines changed

programs/drift/src/controller/orders.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ use crate::math::matching::{
4343
calculate_filler_multiplier_for_matched_orders, do_orders_cross, is_maker_for_taker,
4444
};
4545
use crate::math::oracle::{
46-
is_oracle_valid_for_action, oracle_validity, DriftAction, OracleValidity,
46+
self, is_oracle_valid_for_action, oracle_validity, DriftAction, OracleValidity,
4747
};
4848
use crate::math::safe_math::SafeMath;
4949
use crate::math::safe_unwrap::SafeUnwrap;
@@ -1085,7 +1085,7 @@ pub fn fill_perp_order(
10851085
&state.oracle_guard_rails.validity,
10861086
market.get_max_confidence_interval_multiplier()?,
10871087
&market.amm.oracle_source,
1088-
true,
1088+
oracle::LogMode::SafeMMOracle,
10891089
market.amm.oracle_slot_delay_override,
10901090
)?;
10911091

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

1105+
// We are already using safe oracle data from MM oracle.
1106+
// But AMM isnt available if we could have used MM oracle but fell back due to price diff
1107+
let amm_available_mm_oracle_recent_but_volatile =
1108+
if mm_oracle_price_data.is_enabled() && mm_oracle_price_data.is_mm_oracle_as_recent() {
1109+
let amm_available = !mm_oracle_price_data.is_mm_exchange_diff_bps_high();
1110+
amm_available
1111+
} else {
1112+
true
1113+
};
1114+
amm_is_available &= amm_available_mm_oracle_recent_but_volatile;
1115+
11051116
let amm_wants_to_jit_make = market.amm.amm_wants_to_jit_make(order_direction)?;
11061117
amm_lp_allowed_to_jit_make = market
11071118
.amm

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

Lines changed: 179 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2973,7 +2973,9 @@ pub mod fulfill_order {
29732973
use std::str::FromStr;
29742974
use std::u64;
29752975

2976-
use crate::controller::orders::{fulfill_perp_order, validate_market_within_price_band};
2976+
use crate::controller::orders::{
2977+
fill_perp_order, fulfill_perp_order, validate_market_within_price_band,
2978+
};
29772979
use crate::controller::position::PositionDirection;
29782980
use crate::create_anchor_account_info;
29792981
use crate::error::ErrorCode;
@@ -4845,6 +4847,182 @@ pub mod fulfill_order {
48454847
assert_eq!(ask_price, 100069968); // ~ 100.1 * (0.9997)
48464848
}
48474849

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

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

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1250,7 +1250,7 @@ fn amm_perp_ref_offset() {
12501250
crate::validation::perp_market::validate_perp_market(&perp_market).unwrap();
12511251

12521252
// Update MM oracle and reference price offset stays the same and is applied to the MM oracle
1253-
perp_market.amm.mm_oracle_price = 7200000;
1253+
perp_market.amm.mm_oracle_price = oracle_price_data.price * 1005 / 1000;
12541254
perp_market.amm.mm_oracle_slot = clock_slot;
12551255
let mm_oracle_price_data = perp_market
12561256
.get_mm_oracle_price_data(
@@ -1272,13 +1272,13 @@ fn amm_perp_ref_offset() {
12721272
.amm
12731273
.bid_ask_price(reserve_price_mm_offset)
12741274
.unwrap();
1275-
assert_eq!(perp_market.amm.reference_price_offset, 132);
1276-
assert_eq!(reserve_price_mm_offset, 7199999);
1277-
assert_eq!(b2, 7180271);
1278-
assert_eq!(a2, 7221656);
1275+
assert_eq!(perp_market.amm.reference_price_offset, 133);
1276+
assert_eq!(reserve_price_mm_offset, 7137107);
1277+
assert_eq!(b2, 7101549);
1278+
assert_eq!(a2, 7174591);
12791279

12801280
// Uses the original oracle if the slot is old, ignoring MM oracle
1281-
perp_market.amm.mm_oracle_price = 7200000;
1281+
perp_market.amm.mm_oracle_price = mm_oracle_price_data.get_price() * 995 / 1000;
12821282
perp_market.amm.mm_oracle_slot = clock_slot - 100;
12831283
let mut mm_oracle_price = perp_market
12841284
.get_mm_oracle_price_data(
@@ -1301,8 +1301,8 @@ fn amm_perp_ref_offset() {
13011301
.bid_ask_price(reserve_price_mm_offset_3)
13021302
.unwrap();
13031303
assert_eq!(reserve_price_mm_offset_3, r);
1304-
assert_eq!(b3, 7082154);
1305-
assert_eq!(a3, 7122974);
1304+
assert_eq!(b3, 7066225);
1305+
assert_eq!(a3, 7138903);
13061306
}
13071307

13081308
#[test]

programs/drift/src/controller/repeg.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::cmp::min;
22

3+
use crate::math::oracle::LogMode;
34
use crate::msg;
45
use crate::state::oracle::MMOraclePriceData;
56
use crate::state::oracle::OraclePriceData;
@@ -174,7 +175,7 @@ pub fn _update_amm(
174175
&state.oracle_guard_rails.validity,
175176
market.get_max_confidence_interval_multiplier()?,
176177
&market.amm.oracle_source,
177-
true,
178+
oracle::LogMode::SafeMMOracle,
178179
0,
179180
)?;
180181

@@ -263,7 +264,7 @@ pub fn update_amm_and_check_validity(
263264
&state.oracle_guard_rails.validity,
264265
market.get_max_confidence_interval_multiplier()?,
265266
&market.amm.oracle_source,
266-
false,
267+
LogMode::SafeMMOracle,
267268
0,
268269
)?;
269270

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ pub fn update_amm_test() {
112112
&state.oracle_guard_rails.validity,
113113
market.get_max_confidence_interval_multiplier().unwrap(),
114114
&market.amm.oracle_source,
115-
false,
115+
LogMode::ExchangeOracle,
116116
0,
117117
)
118118
.unwrap()
@@ -245,7 +245,7 @@ pub fn update_amm_test_bad_oracle() {
245245
&state.oracle_guard_rails.validity,
246246
market.get_max_confidence_interval_multiplier().unwrap(),
247247
&market.amm.oracle_source,
248-
false,
248+
LogMode::None,
249249
0,
250250
)
251251
.unwrap()

programs/drift/src/controller/spot_balance.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::math::oracle::oracle_validity;
1+
use crate::math::oracle::{oracle_validity, LogMode};
22
use crate::state::state::ValidityGuardRails;
33
use std::cmp::max; //, OracleValidity};
44

@@ -403,7 +403,7 @@ pub fn update_spot_market_and_check_validity(
403403
validity_guard_rails,
404404
spot_market.get_max_confidence_interval_multiplier()?,
405405
&spot_market.oracle_source,
406-
false,
406+
LogMode::ExchangeOracle,
407407
0,
408408
)?;
409409

programs/drift/src/instructions/admin.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4873,7 +4873,6 @@ pub fn handle_update_mm_oracle_native(accounts: &[AccountInfo], data: &[u8]) ->
48734873
let clock_data = clock_account.data.borrow();
48744874

48754875
perp_market[832..840].copy_from_slice(&clock_data[0..8]);
4876-
// perp_market[832..840].copy_from_slice(Clock::get()?.slot.to_le_bytes().as_ref());
48774876
perp_market[912..920].copy_from_slice(&data[0..8]);
48784877
perp_market[936..944].copy_from_slice(&data[8..16]);
48794878
}

programs/drift/src/math/amm.rs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -409,7 +409,11 @@ pub fn update_oracle_price_twap(
409409
None => amm.reserve_price()?,
410410
};
411411
let oracle_confidence = mm_oracle_price_data.get_confidence();
412-
let oracle_price = normalise_oracle_price(amm, mm_oracle_price_data, Some(reserve_price))?;
412+
let oracle_price = normalise_oracle_price(
413+
amm,
414+
&mm_oracle_price_data.get_exchange_oracle_price_data(),
415+
Some(reserve_price),
416+
)?;
413417

414418
let capped_oracle_update_price = sanitize_new_price(
415419
oracle_price,
@@ -435,14 +439,16 @@ pub fn update_oracle_price_twap(
435439
)?;
436440

437441
amm.last_oracle_normalised_price = capped_oracle_update_price;
438-
amm.historical_oracle_data.last_oracle_price = mm_oracle_price_data.get_price();
442+
amm.historical_oracle_data.last_oracle_price =
443+
mm_oracle_price_data.get_exchange_oracle_price_data().price;
439444

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

445-
amm.historical_oracle_data.last_oracle_delay = mm_oracle_price_data.get_delay();
450+
amm.historical_oracle_data.last_oracle_delay =
451+
mm_oracle_price_data.get_exchange_oracle_price_data().delay;
446452
amm.last_oracle_reserve_price_spread_pct = calculate_oracle_reserve_price_spread_pct(
447453
amm,
448454
mm_oracle_price_data,
@@ -733,10 +739,10 @@ pub fn calculate_oracle_reserve_price_spread(
733739

734740
pub fn normalise_oracle_price(
735741
amm: &AMM,
736-
mm_oracle_price_data: &MMOraclePriceData,
742+
oracle_price_data: &OraclePriceData,
737743
precomputed_reserve_price: Option<u64>,
738744
) -> DriftResult<i64> {
739-
let oracle_price = mm_oracle_price_data.get_price();
745+
let oracle_price = oracle_price_data.price;
740746

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

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

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

0 commit comments

Comments
 (0)