Skip to content

Commit 2d158ee

Browse files
crispheaneymoosecat2jordy25519
authored
Nour/mm oracle 2 (#1767)
* program: new amm oracle (#1738) * zero unused amm fields * cargo fmt * bare bones ix * minimal anchor mm oracle impl * update test file * only do admin validate when not anchor test * updates * generalize native entry * fix weird function name chop off * make it compile for --feature cpi (#1748) Co-authored-by: jordy25519 <[email protected]> * more efficeint clock and state bit flags check * vamm uses mm oracle (#1747) * add offset * working tests * refactor to use MM oracle as its own type * remove weird preface * sdk updates * bankrun tests all pass * fix test * changes and fixes * widen confidence if mm oracle too diff * sdk side for confidence adjust * changelog * fix lint * fix cargo tests * address comments * add conf check * remove anchor ix and cache oracle confidence * only state admin can reenable mm oracle kill switch * cargo fmt --------- Co-authored-by: jordy25519 <[email protected]> * fix tests (#1764) * Nour/move ixs around (#1766) * move around ixs * remove message * add devnet oracle crank wallet * refactored mm oracle * sdk changes + cargo fmt * fix tests * validate price bands with fill fix * normalize fill within price bands * add sdk warning * updated type * undefined guard so anchor tests pass * accept vec for update amm and view amm * adjust test to work with new price bands * Revert "adjust test to work with new price bands" This reverts commit ee40ac8. * remove price bands logic * add zero ix for mm oracle for reset * 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 --------- Co-authored-by: moosecat <[email protected]> Co-authored-by: jordy25519 <[email protected]>
1 parent 753db33 commit 2d158ee

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1667
-273
lines changed

deploy-scripts/build-devnet.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
#!/bin/sh
2-
anchor build -- --no-default-features
2+
anchor build -- --no-default-features --features no-entrypoint

programs/drift/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ no-entrypoint = []
1414
cpi = ["no-entrypoint"]
1515
mainnet-beta=[]
1616
anchor-test= []
17-
default=["mainnet-beta"]
17+
default=["mainnet-beta", "no-entrypoint"]
1818
drift-rs=[]
1919

2020
[dependencies]

programs/drift/src/controller/funding.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,11 +188,13 @@ pub fn update_funding_rate(
188188
if valid_funding_update {
189189
let oracle_price_data = oracle_map.get_price_data(&market.oracle_id())?;
190190
let sanitize_clamp_denominator = market.get_sanitize_clamp_denominator()?;
191+
let mm_oracle_price_data =
192+
market.get_mm_oracle_price_data(*oracle_price_data, slot, &guard_rails.validity)?;
191193

192194
let oracle_price_twap = amm::update_oracle_price_twap(
193195
&mut market.amm,
194196
now,
195-
oracle_price_data,
197+
&mm_oracle_price_data,
196198
Some(reserve_price),
197199
sanitize_clamp_denominator,
198200
)?;

programs/drift/src/controller/liquidation.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,10 +203,15 @@ pub fn liquidate_perp(
203203

204204
let mut market = perp_market_map.get_ref_mut(&market_index)?;
205205
let oracle_price_data = oracle_map.get_price_data(&market.oracle_id())?;
206+
let mm_oracle_price_data = market.get_mm_oracle_price_data(
207+
*oracle_price_data,
208+
slot,
209+
&state.oracle_guard_rails.validity,
210+
)?;
206211

207212
update_amm_and_check_validity(
208213
&mut market,
209-
oracle_price_data,
214+
&mm_oracle_price_data,
210215
state,
211216
now,
212217
slot,
@@ -848,10 +853,15 @@ pub fn liquidate_perp_with_fill(
848853

849854
let mut market = perp_market_map.get_ref_mut(&market_index)?;
850855
let oracle_price_data = oracle_map.get_price_data(&market.oracle_id())?;
856+
let mm_oracle_price_data = market.get_mm_oracle_price_data(
857+
*oracle_price_data,
858+
slot,
859+
&state.oracle_guard_rails.validity,
860+
)?;
851861

852862
update_amm_and_check_validity(
853863
&mut market,
854-
oracle_price_data,
864+
&mm_oracle_price_data,
855865
state,
856866
now,
857867
slot,

programs/drift/src/controller/orders.rs

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@ use crate::math::matching::{
4242
are_orders_same_market_but_different_sides, calculate_fill_for_matched_orders,
4343
calculate_filler_multiplier_for_matched_orders, do_orders_cross, is_maker_for_taker,
4444
};
45-
use crate::math::oracle::{is_oracle_valid_for_action, DriftAction, OracleValidity};
45+
use crate::math::oracle::{
46+
self, is_oracle_valid_for_action, oracle_validity, DriftAction, OracleValidity,
47+
};
4648
use crate::math::safe_math::SafeMath;
4749
use crate::math::safe_unwrap::SafeUnwrap;
4850
use crate::math::spot_balance::{get_signed_token_amount, get_token_amount};
@@ -1048,7 +1050,7 @@ pub fn fill_perp_order(
10481050
}
10491051

10501052
let reserve_price_before: u64;
1051-
let oracle_validity: OracleValidity;
1053+
let safe_oracle_validity: OracleValidity;
10521054
let oracle_price: i64;
10531055
let oracle_twap_5min: i64;
10541056
let perp_market_index: u16;
@@ -1068,19 +1070,29 @@ pub fn fill_perp_order(
10681070
"Market is in settlement mode",
10691071
)?;
10701072

1071-
let (oracle_price_data, _oracle_validity) = oracle_map.get_price_data_and_validity(
1073+
let oracle_price_data = oracle_map.get_price_data(&market.oracle_id())?;
1074+
let mm_oracle_price_data = market.get_mm_oracle_price_data(
1075+
*oracle_price_data,
1076+
slot,
1077+
&state.oracle_guard_rails.validity,
1078+
)?;
1079+
let safe_oracle_price_data = mm_oracle_price_data.get_safe_oracle_price_data();
1080+
safe_oracle_validity = oracle_validity(
10721081
MarketType::Perp,
10731082
market.market_index,
1074-
&market.oracle_id(),
10751083
market.amm.historical_oracle_data.last_oracle_price_twap,
1084+
&safe_oracle_price_data,
1085+
&state.oracle_guard_rails.validity,
10761086
market.get_max_confidence_interval_multiplier()?,
1087+
&market.amm.oracle_source,
1088+
oracle::LogMode::SafeMMOracle,
10771089
market.amm.oracle_slot_delay_override,
10781090
)?;
10791091

10801092
oracle_valid_for_amm_fill =
1081-
is_oracle_valid_for_action(_oracle_validity, Some(DriftAction::FillOrderAmm))?;
1093+
is_oracle_valid_for_action(safe_oracle_validity, Some(DriftAction::FillOrderAmm))?;
10821094

1083-
oracle_stale_for_margin = oracle_price_data.delay
1095+
oracle_stale_for_margin = mm_oracle_price_data.get_delay()
10841096
> state
10851097
.oracle_guard_rails
10861098
.validity
@@ -1090,6 +1102,17 @@ pub fn fill_perp_order(
10901102
amm_is_available &= !market.is_operation_paused(PerpOperation::AmmFill);
10911103
amm_is_available &= !market.has_too_much_drawdown()?;
10921104

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+
10931116
let amm_wants_to_jit_make = market.amm.amm_wants_to_jit_make(order_direction)?;
10941117
amm_lp_allowed_to_jit_make = market
10951118
.amm
@@ -1100,12 +1123,11 @@ pub fn fill_perp_order(
11001123
user_can_skip_duration = user.can_skip_auction_duration(user_stats)?;
11011124

11021125
reserve_price_before = market.amm.reserve_price()?;
1103-
oracle_price = oracle_price_data.price;
1126+
oracle_price = mm_oracle_price_data.get_price();
11041127
oracle_twap_5min = market
11051128
.amm
11061129
.historical_oracle_data
11071130
.last_oracle_price_twap_5min;
1108-
oracle_validity = _oracle_validity;
11091131
perp_market_index = market.market_index;
11101132

11111133
min_auction_duration =
@@ -1114,7 +1136,7 @@ pub fn fill_perp_order(
11141136

11151137
// allow oracle price to be used to calculate limit price if it's valid or stale for amm
11161138
let valid_oracle_price =
1117-
if is_oracle_valid_for_action(oracle_validity, Some(DriftAction::OracleOrderPrice))? {
1139+
if is_oracle_valid_for_action(safe_oracle_validity, Some(DriftAction::OracleOrderPrice))? {
11181140
Some(oracle_price)
11191141
} else {
11201142
msg!("Perp market = {} oracle deemed invalid", perp_market_index);

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() {

0 commit comments

Comments
 (0)