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
2 changes: 1 addition & 1 deletion deploy-scripts/build-devnet.sh
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
#!/bin/sh
anchor build -- --no-default-features
anchor build -- --no-default-features --features no-entrypoint
2 changes: 1 addition & 1 deletion programs/drift/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ no-entrypoint = []
cpi = ["no-entrypoint"]
mainnet-beta=[]
anchor-test= []
default=["mainnet-beta"]
default=["mainnet-beta", "no-entrypoint"]
drift-rs=[]

[dependencies]
Expand Down
4 changes: 3 additions & 1 deletion programs/drift/src/controller/funding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,11 +188,13 @@ pub fn update_funding_rate(
if valid_funding_update {
let oracle_price_data = oracle_map.get_price_data(&market.oracle_id())?;
let sanitize_clamp_denominator = market.get_sanitize_clamp_denominator()?;
let mm_oracle_price_data =
market.get_mm_oracle_price_data(*oracle_price_data, slot, &guard_rails.validity)?;

let oracle_price_twap = amm::update_oracle_price_twap(
&mut market.amm,
now,
oracle_price_data,
&mm_oracle_price_data,
Some(reserve_price),
sanitize_clamp_denominator,
)?;
Expand Down
14 changes: 12 additions & 2 deletions programs/drift/src/controller/liquidation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,10 +203,15 @@ pub fn liquidate_perp(

let mut market = perp_market_map.get_ref_mut(&market_index)?;
let oracle_price_data = oracle_map.get_price_data(&market.oracle_id())?;
let mm_oracle_price_data = market.get_mm_oracle_price_data(
*oracle_price_data,
slot,
&state.oracle_guard_rails.validity,
)?;

update_amm_and_check_validity(
&mut market,
oracle_price_data,
&mm_oracle_price_data,
state,
now,
slot,
Expand Down Expand Up @@ -846,10 +851,15 @@ pub fn liquidate_perp_with_fill(

let mut market = perp_market_map.get_ref_mut(&market_index)?;
let oracle_price_data = oracle_map.get_price_data(&market.oracle_id())?;
let mm_oracle_price_data = market.get_mm_oracle_price_data(
*oracle_price_data,
slot,
&state.oracle_guard_rails.validity,
)?;

update_amm_and_check_validity(
&mut market,
oracle_price_data,
&mm_oracle_price_data,
state,
now,
slot,
Expand Down
40 changes: 31 additions & 9 deletions programs/drift/src/controller/orders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ use crate::math::matching::{
are_orders_same_market_but_different_sides, calculate_fill_for_matched_orders,
calculate_filler_multiplier_for_matched_orders, do_orders_cross, is_maker_for_taker,
};
use crate::math::oracle::{is_oracle_valid_for_action, DriftAction, OracleValidity};
use crate::math::oracle::{
self, is_oracle_valid_for_action, oracle_validity, DriftAction, OracleValidity,
};
use crate::math::safe_math::SafeMath;
use crate::math::safe_unwrap::SafeUnwrap;
use crate::math::spot_balance::{get_signed_token_amount, get_token_amount};
Expand Down Expand Up @@ -1048,7 +1050,7 @@ pub fn fill_perp_order(
}

let reserve_price_before: u64;
let oracle_validity: OracleValidity;
let safe_oracle_validity: OracleValidity;
let oracle_price: i64;
let oracle_twap_5min: i64;
let perp_market_index: u16;
Expand All @@ -1068,19 +1070,29 @@ pub fn fill_perp_order(
"Market is in settlement mode",
)?;

let (oracle_price_data, _oracle_validity) = oracle_map.get_price_data_and_validity(
let oracle_price_data = oracle_map.get_price_data(&market.oracle_id())?;
let mm_oracle_price_data = market.get_mm_oracle_price_data(
*oracle_price_data,
slot,
&state.oracle_guard_rails.validity,
)?;
let safe_oracle_price_data = mm_oracle_price_data.get_safe_oracle_price_data();
safe_oracle_validity = oracle_validity(
MarketType::Perp,
market.market_index,
&market.oracle_id(),
market.amm.historical_oracle_data.last_oracle_price_twap,
&safe_oracle_price_data,
&state.oracle_guard_rails.validity,
market.get_max_confidence_interval_multiplier()?,
&market.amm.oracle_source,
oracle::LogMode::SafeMMOracle,
market.amm.oracle_slot_delay_override,
)?;

oracle_valid_for_amm_fill =
is_oracle_valid_for_action(_oracle_validity, Some(DriftAction::FillOrderAmm))?;
is_oracle_valid_for_action(safe_oracle_validity, Some(DriftAction::FillOrderAmm))?;

oracle_stale_for_margin = oracle_price_data.delay
oracle_stale_for_margin = mm_oracle_price_data.get_delay()
> state
.oracle_guard_rails
.validity
Expand All @@ -1090,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 All @@ -1100,12 +1123,11 @@ pub fn fill_perp_order(
user_can_skip_duration = user.can_skip_auction_duration(user_stats)?;

reserve_price_before = market.amm.reserve_price()?;
oracle_price = oracle_price_data.price;
oracle_price = mm_oracle_price_data.get_price();
oracle_twap_5min = market
.amm
.historical_oracle_data
.last_oracle_price_twap_5min;
oracle_validity = _oracle_validity;
perp_market_index = market.market_index;

min_auction_duration =
Expand All @@ -1114,7 +1136,7 @@ pub fn fill_perp_order(

// allow oracle price to be used to calculate limit price if it's valid or stale for amm
let valid_oracle_price =
if is_oracle_valid_for_action(oracle_validity, Some(DriftAction::OracleOrderPrice))? {
if is_oracle_valid_for_action(safe_oracle_validity, Some(DriftAction::OracleOrderPrice))? {
Some(oracle_price)
} else {
msg!("Perp market = {} oracle deemed invalid", perp_market_index);
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
Loading
Loading