diff --git a/CHANGELOG.md b/CHANGELOG.md index e0f52e230..10ced41a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Features +- program: more lenient w invalid deposit oracles ([#1324](https://github.com/drift-labs/protocol-v2/pull/1324)) + ### Fixes - sdk: getBestBids/Asks only considers price/time priority ([#1322](https://github.com/drift-labs/protocol-v2/pull/1322)) diff --git a/programs/drift/src/controller/repeg.rs b/programs/drift/src/controller/repeg.rs index 1615de297..de886db4c 100644 --- a/programs/drift/src/controller/repeg.rs +++ b/programs/drift/src/controller/repeg.rs @@ -159,6 +159,7 @@ pub fn _update_amm( oracle_price_data, &state.oracle_guard_rails.validity, market.get_max_confidence_interval_multiplier()?, + &market.amm.oracle_source, true, )?; @@ -246,6 +247,7 @@ pub fn update_amm_and_check_validity( oracle_price_data, &state.oracle_guard_rails.validity, market.get_max_confidence_interval_multiplier()?, + &market.amm.oracle_source, false, )?; diff --git a/programs/drift/src/controller/repeg/tests.rs b/programs/drift/src/controller/repeg/tests.rs index d27c7f97a..c8463e07a 100644 --- a/programs/drift/src/controller/repeg/tests.rs +++ b/programs/drift/src/controller/repeg/tests.rs @@ -105,6 +105,7 @@ pub fn update_amm_test() { &oracle_price_data, &state.oracle_guard_rails.validity, market.get_max_confidence_interval_multiplier().unwrap(), + &market.amm.oracle_source, false, ) .unwrap() @@ -232,6 +233,7 @@ pub fn update_amm_test_bad_oracle() { &oracle_price_data, &state.oracle_guard_rails.validity, market.get_max_confidence_interval_multiplier().unwrap(), + &market.amm.oracle_source, false, ) .unwrap() diff --git a/programs/drift/src/controller/spot_balance.rs b/programs/drift/src/controller/spot_balance.rs index 9503a6c08..756d56c24 100644 --- a/programs/drift/src/controller/spot_balance.rs +++ b/programs/drift/src/controller/spot_balance.rs @@ -402,6 +402,7 @@ pub fn update_spot_market_and_check_validity( oracle_price_data, validity_guard_rails, spot_market.get_max_confidence_interval_multiplier()?, + &spot_market.oracle_source, false, )?; diff --git a/programs/drift/src/math/margin.rs b/programs/drift/src/math/margin.rs index 5533e2f2a..8c6618759 100644 --- a/programs/drift/src/math/margin.rs +++ b/programs/drift/src/math/margin.rs @@ -265,10 +265,7 @@ pub fn calculate_margin_requirement_and_total_collateral_and_liability_info( spot_market.get_max_confidence_interval_multiplier()?, )?; - calculation.update_all_oracles_valid(is_oracle_valid_for_action( - oracle_validity, - Some(DriftAction::MarginCalc), - )?); + let oracle_valid = is_oracle_valid_for_action(oracle_validity, Some(DriftAction::MarginCalc))?; let strict_oracle_price = StrictOraclePrice::new( oracle_price_data.price, @@ -293,13 +290,20 @@ pub fn calculate_margin_requirement_and_total_collateral_and_liability_info( calculation.update_fuel_spot_bonus(&spot_market, token_amount, &strict_oracle_price)?; - let token_value = + let mut token_value = get_strict_token_value(token_amount, spot_market.decimals, &strict_oracle_price)?; match spot_position.balance_type { SpotBalanceType::Deposit => { + if calculation.context.ignore_invalid_deposit_oracles && !oracle_valid { + msg!("token_value set to 0 for market_index={}", spot_market.market_index); + token_value = 0; + } + calculation.add_total_collateral(token_value)?; + calculation.update_all_deposit_oracles_valid(oracle_valid); + #[cfg(feature = "drift-rs")] calculation.add_spot_asset_value(token_value)?; } @@ -322,6 +326,8 @@ pub fn calculate_margin_requirement_and_total_collateral_and_liability_info( calculation.add_spot_liability()?; + calculation.update_all_liability_oracles_valid(oracle_valid); + #[cfg(feature = "drift-rs")] calculation.add_spot_liability_value(token_value)?; } @@ -337,9 +343,9 @@ pub fn calculate_margin_requirement_and_total_collateral_and_liability_info( let OrderFillSimulation { token_amount: worst_case_token_amount, - orders_value: worst_case_orders_value, + orders_value: mut worst_case_orders_value, token_value: worst_case_token_value, - weighted_token_value: worst_case_weighted_token_value, + weighted_token_value: mut worst_case_weighted_token_value, .. } = spot_position .get_worst_case_fill_simulation( @@ -372,9 +378,16 @@ pub fn calculate_margin_requirement_and_total_collateral_and_liability_info( match worst_case_token_value.cmp(&0) { Ordering::Greater => { + if calculation.context.ignore_invalid_deposit_oracles && !oracle_valid { + msg!("worst_case_weighted_token_value set to 0 for market_index={}", spot_market.market_index); + worst_case_weighted_token_value = 0; + } + calculation .add_total_collateral(worst_case_weighted_token_value.cast::()?)?; + calculation.update_all_deposit_oracles_valid(oracle_valid); + #[cfg(feature = "drift-rs")] calculation.add_spot_asset_value(worst_case_token_value)?; } @@ -405,12 +418,15 @@ pub fn calculate_margin_requirement_and_total_collateral_and_liability_info( spot_market.asset_tier == AssetTier::Isolated, ); + calculation.update_all_liability_oracles_valid(oracle_valid); + #[cfg(feature = "drift-rs")] calculation.add_spot_liability_value(worst_case_token_value.unsigned_abs())?; } Ordering::Equal => { if spot_position.has_open_order() { calculation.add_spot_liability()?; + calculation.update_all_liability_oracles_valid(oracle_valid); calculation.update_with_spot_isolated_liability( spot_market.asset_tier == AssetTier::Isolated, ); @@ -420,6 +436,11 @@ pub fn calculate_margin_requirement_and_total_collateral_and_liability_info( match worst_case_orders_value.cmp(&0) { Ordering::Greater => { + if calculation.context.ignore_invalid_deposit_oracles && !oracle_valid { + msg!("worst_case_orders_value set to 0 for market_index={}", spot_market.market_index); + worst_case_orders_value = 0; + } + calculation.add_total_collateral(worst_case_orders_value.cast::()?)?; #[cfg(feature = "drift-rs")] @@ -459,11 +480,6 @@ pub fn calculate_margin_requirement_and_total_collateral_and_liability_info( quote_spot_market.get_max_confidence_interval_multiplier()?, )?; - calculation.update_all_oracles_valid(is_oracle_valid_for_action( - quote_oracle_validity, - Some(DriftAction::MarginCalc), - )?); - let strict_quote_price = StrictOraclePrice::new( quote_oracle_price_data.price, quote_spot_market @@ -535,7 +551,11 @@ pub fn calculate_margin_requirement_and_total_collateral_and_liability_info( } if has_perp_liability || calculation.context.margin_type != MarginRequirementType::Initial { - calculation.update_all_oracles_valid(is_oracle_valid_for_action( + calculation.update_all_liability_oracles_valid(is_oracle_valid_for_action( + quote_oracle_validity, + Some(DriftAction::MarginCalc), + )?); + calculation.update_all_liability_oracles_valid(is_oracle_valid_for_action( oracle_validity, Some(DriftAction::MarginCalc), )?); @@ -594,7 +614,9 @@ pub fn meets_withdraw_margin_requirement( margin_requirement_type: MarginRequirementType, ) -> DriftResult { let strict = margin_requirement_type == MarginRequirementType::Initial; - let context = MarginContext::standard(margin_requirement_type).strict(strict); + let context = MarginContext::standard(margin_requirement_type) + .strict(strict) + .ignore_invalid_deposit_oracles(true); let calculation = calculate_margin_requirement_and_total_collateral_and_liability_info( user, @@ -606,9 +628,9 @@ pub fn meets_withdraw_margin_requirement( if calculation.margin_requirement > 0 || calculation.get_num_of_liabilities()? > 0 { validate!( - calculation.all_oracles_valid, + calculation.all_liability_oracles_valid, ErrorCode::InvalidOracle, - "User attempting to withdraw with outstanding liabilities when an oracle is invalid" + "User attempting to withdraw with outstanding liabilities when a liability oracle is invalid" )?; } diff --git a/programs/drift/src/math/margin/tests.rs b/programs/drift/src/math/margin/tests.rs index 50cd0c798..e961adea3 100644 --- a/programs/drift/src/math/margin/tests.rs +++ b/programs/drift/src/math/margin/tests.rs @@ -1136,7 +1136,8 @@ mod calculate_margin_requirement_and_total_collateral { let MarginCalculation { total_collateral, margin_requirement, - all_oracles_valid: oracles_valid, + all_deposit_oracles_valid: deposit_oracles_valid, + all_liability_oracles_valid: liability_oracles_valid, .. } = calculate_margin_requirement_and_total_collateral_and_liability_info( &user, @@ -1161,7 +1162,8 @@ mod calculate_margin_requirement_and_total_collateral { 10 ); - assert_eq!(oracles_valid, false); + assert_eq!(deposit_oracles_valid, false); + assert_eq!(liability_oracles_valid, true); assert_eq!(total_collateral, 0); // todo not 0 assert_eq!(margin_requirement, 0); } @@ -1257,7 +1259,8 @@ mod calculate_margin_requirement_and_total_collateral { let MarginCalculation { total_collateral, margin_requirement, - all_oracles_valid: oracles_valid, + all_deposit_oracles_valid: deposit_oracles_valid, + all_liability_oracles_valid: liability_oracles_valid, .. } = calculate_margin_requirement_and_total_collateral_and_liability_info( &user, @@ -1282,7 +1285,8 @@ mod calculate_margin_requirement_and_total_collateral { 1 ); - assert_eq!(oracles_valid, false); + assert_eq!(deposit_oracles_valid, true); + assert_eq!(liability_oracles_valid, false); assert_eq!(total_collateral, 0); // todo not 0 assert_eq!(margin_requirement, 3); @@ -1302,7 +1306,8 @@ mod calculate_margin_requirement_and_total_collateral { let MarginCalculation { total_collateral, margin_requirement, - all_oracles_valid: oracles_valid, + all_deposit_oracles_valid: deposit_oracles_valid, + all_liability_oracles_valid: liability_oracles_valid, .. } = calculate_margin_requirement_and_total_collateral_and_liability_info( &user, @@ -1327,7 +1332,8 @@ mod calculate_margin_requirement_and_total_collateral { 1 ); - assert_eq!(oracles_valid, false); + assert_eq!(deposit_oracles_valid, true); + assert_eq!(liability_oracles_valid, false); assert_eq!(total_collateral, 0); // todo not 0 assert_eq!(margin_requirement, 3); } @@ -1428,6 +1434,345 @@ mod calculate_margin_requirement_and_total_collateral { assert_eq!(total_collateral, 9000000000); assert_eq!(margin_requirement, 12100000000); } + + #[test] + pub fn invalid_oracle_for_deposit() { + let slot = 0_u64; + + let mut sol_oracle_price = get_pyth_price(100, 6); + let sol_oracle_price_key = + Pubkey::from_str("J83w4HKfqxwcq3BEMMkPFSppX3gqekLyLJBexebFVkix").unwrap(); + let pyth_program = crate::ids::pyth_program::id(); + create_account_info!( + sol_oracle_price, + &sol_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: 101 * AMM_RESERVE_PRECISION, + bid_quote_asset_reserve: 99 * AMM_RESERVE_PRECISION, + ask_base_asset_reserve: 99 * AMM_RESERVE_PRECISION, + ask_quote_asset_reserve: 101 * AMM_RESERVE_PRECISION, + sqrt_k: 100 * AMM_RESERVE_PRECISION, + peg_multiplier: 100 * PEG_PRECISION, + order_step_size: 10000000, + oracle: sol_oracle_price_key, + ..AMM::default() + }, + margin_ratio_initial: 1000, + margin_ratio_maintenance: 500, + status: MarketStatus::Initialized, + ..PerpMarket::default() + }; + create_anchor_account_info!(market, PerpMarket, market_account_info); + let perp_market_map = PerpMarketMap::load_one(&market_account_info, true).unwrap(); + + let mut usdc_spot_market = SpotMarket { + market_index: 0, + oracle_source: OracleSource::QuoteAsset, + cumulative_deposit_interest: SPOT_CUMULATIVE_INTEREST_PRECISION, + cumulative_borrow_interest: SPOT_CUMULATIVE_INTEREST_PRECISION, + decimals: 6, + initial_asset_weight: SPOT_WEIGHT_PRECISION, + maintenance_asset_weight: SPOT_WEIGHT_PRECISION, + deposit_balance: 10000 * SPOT_BALANCE_PRECISION, + liquidator_fee: 0, + historical_oracle_data: HistoricalOracleData::default_quote_oracle(), + ..SpotMarket::default() + }; + create_anchor_account_info!(usdc_spot_market, SpotMarket, usdc_spot_market_account_info); + let mut sol_spot_market = SpotMarket { + market_index: 1, + oracle_source: OracleSource::Pyth, + oracle: sol_oracle_price_key, + cumulative_deposit_interest: SPOT_CUMULATIVE_INTEREST_PRECISION, + cumulative_borrow_interest: SPOT_CUMULATIVE_INTEREST_PRECISION, + decimals: 9, + initial_asset_weight: 8 * SPOT_WEIGHT_PRECISION / 10, + maintenance_asset_weight: 9 * SPOT_WEIGHT_PRECISION / 10, + initial_liability_weight: 12 * SPOT_WEIGHT_PRECISION / 10, + maintenance_liability_weight: 11 * SPOT_WEIGHT_PRECISION / 10, + liquidator_fee: LIQUIDATION_FEE_PRECISION / 1000, + ..SpotMarket::default() + }; + create_anchor_account_info!(sol_spot_market, SpotMarket, sol_spot_market_account_info); + let spot_market_account_infos = Vec::from([ + &usdc_spot_market_account_info, + &sol_spot_market_account_info, + ]); + let spot_market_map = + SpotMarketMap::load_multiple(spot_market_account_infos, true).unwrap(); + + let mut spot_positions = [SpotPosition::default(); 8]; + spot_positions[0] = SpotPosition { + market_index: 0, + balance_type: SpotBalanceType::Borrow, + scaled_balance: 100 * SPOT_BALANCE_PRECISION_U64, + ..SpotPosition::default() + }; + spot_positions[1] = SpotPosition { + market_index: 1, + balance_type: SpotBalanceType::Deposit, + scaled_balance: 100 * SPOT_BALANCE_PRECISION_U64, + ..SpotPosition::default() + }; + + let user = User { + orders: [Order::default(); 32], + spot_positions, + max_margin_ratio: 2 * MARGIN_PRECISION, // .5x leverage + ..User::default() + }; + + let MarginCalculation { + total_collateral, + margin_requirement, + all_deposit_oracles_valid: deposit_oracles_valid, + all_liability_oracles_valid: liability_oracles_valid, + .. + } = calculate_margin_requirement_and_total_collateral_and_liability_info( + &user, + &perp_market_map, + &spot_market_map, + &mut oracle_map, + MarginContext::standard(MarginRequirementType::Initial).ignore_invalid_deposit_oracles(true), + ) + .unwrap(); + + assert_eq!(deposit_oracles_valid, false); + assert_eq!(liability_oracles_valid, true); + assert_eq!(total_collateral, 0); + assert_ne!(margin_requirement, 0); + } + + #[test] + pub fn invalid_oracle_for_bid() { + let slot = 0_u64; + + let mut sol_oracle_price = get_pyth_price(100, 6); + let sol_oracle_price_key = + Pubkey::from_str("J83w4HKfqxwcq3BEMMkPFSppX3gqekLyLJBexebFVkix").unwrap(); + let pyth_program = crate::ids::pyth_program::id(); + create_account_info!( + sol_oracle_price, + &sol_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: 101 * AMM_RESERVE_PRECISION, + bid_quote_asset_reserve: 99 * AMM_RESERVE_PRECISION, + ask_base_asset_reserve: 99 * AMM_RESERVE_PRECISION, + ask_quote_asset_reserve: 101 * AMM_RESERVE_PRECISION, + sqrt_k: 100 * AMM_RESERVE_PRECISION, + peg_multiplier: 100 * PEG_PRECISION, + order_step_size: 10000000, + oracle: sol_oracle_price_key, + ..AMM::default() + }, + margin_ratio_initial: 1000, + margin_ratio_maintenance: 500, + status: MarketStatus::Initialized, + ..PerpMarket::default() + }; + create_anchor_account_info!(market, PerpMarket, market_account_info); + let perp_market_map = PerpMarketMap::load_one(&market_account_info, true).unwrap(); + + let mut usdc_spot_market = SpotMarket { + market_index: 0, + oracle_source: OracleSource::QuoteAsset, + cumulative_deposit_interest: SPOT_CUMULATIVE_INTEREST_PRECISION, + cumulative_borrow_interest: SPOT_CUMULATIVE_INTEREST_PRECISION, + decimals: 6, + initial_asset_weight: SPOT_WEIGHT_PRECISION, + maintenance_asset_weight: SPOT_WEIGHT_PRECISION, + deposit_balance: 10000 * SPOT_BALANCE_PRECISION, + liquidator_fee: 0, + historical_oracle_data: HistoricalOracleData::default_quote_oracle(), + ..SpotMarket::default() + }; + create_anchor_account_info!(usdc_spot_market, SpotMarket, usdc_spot_market_account_info); + let mut sol_spot_market = SpotMarket { + market_index: 1, + oracle_source: OracleSource::Pyth, + oracle: sol_oracle_price_key, + cumulative_deposit_interest: SPOT_CUMULATIVE_INTEREST_PRECISION, + cumulative_borrow_interest: SPOT_CUMULATIVE_INTEREST_PRECISION, + decimals: 9, + initial_asset_weight: 8 * SPOT_WEIGHT_PRECISION / 10, + maintenance_asset_weight: 9 * SPOT_WEIGHT_PRECISION / 10, + initial_liability_weight: 12 * SPOT_WEIGHT_PRECISION / 10, + maintenance_liability_weight: 11 * SPOT_WEIGHT_PRECISION / 10, + liquidator_fee: LIQUIDATION_FEE_PRECISION / 1000, + ..SpotMarket::default() + }; + create_anchor_account_info!(sol_spot_market, SpotMarket, sol_spot_market_account_info); + let spot_market_account_infos = Vec::from([ + &usdc_spot_market_account_info, + &sol_spot_market_account_info, + ]); + let spot_market_map = + SpotMarketMap::load_multiple(spot_market_account_infos, true).unwrap(); + + let mut spot_positions = [SpotPosition::default(); 8]; + spot_positions[1] = SpotPosition { + market_index: 1, + balance_type: SpotBalanceType::Deposit, + open_orders: 1, + open_bids: 100 * BASE_PRECISION_I64, + ..SpotPosition::default() + }; + + let user = User { + orders: [Order::default(); 32], + spot_positions, + max_margin_ratio: 2 * MARGIN_PRECISION, // .5x leverage + ..User::default() + }; + + let MarginCalculation { + total_collateral, + margin_requirement, + all_deposit_oracles_valid: deposit_oracles_valid, + all_liability_oracles_valid: liability_oracles_valid, + .. + } = calculate_margin_requirement_and_total_collateral_and_liability_info( + &user, + &perp_market_map, + &spot_market_map, + &mut oracle_map, + MarginContext::standard(MarginRequirementType::Initial).ignore_invalid_deposit_oracles(true), + ) + .unwrap(); + + assert_eq!(deposit_oracles_valid, false); + assert_eq!(liability_oracles_valid, true); + assert_eq!(total_collateral, 0); + assert_ne!(margin_requirement, 0); + } + + #[test] + pub fn invalid_oracle_for_ask() { + let slot = 0_u64; + + let mut sol_oracle_price = get_pyth_price(100, 6); + let sol_oracle_price_key = + Pubkey::from_str("J83w4HKfqxwcq3BEMMkPFSppX3gqekLyLJBexebFVkix").unwrap(); + let pyth_program = crate::ids::pyth_program::id(); + create_account_info!( + sol_oracle_price, + &sol_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: 101 * AMM_RESERVE_PRECISION, + bid_quote_asset_reserve: 99 * AMM_RESERVE_PRECISION, + ask_base_asset_reserve: 99 * AMM_RESERVE_PRECISION, + ask_quote_asset_reserve: 101 * AMM_RESERVE_PRECISION, + sqrt_k: 100 * AMM_RESERVE_PRECISION, + peg_multiplier: 100 * PEG_PRECISION, + order_step_size: 10000000, + oracle: sol_oracle_price_key, + ..AMM::default() + }, + margin_ratio_initial: 1000, + margin_ratio_maintenance: 500, + status: MarketStatus::Initialized, + ..PerpMarket::default() + }; + create_anchor_account_info!(market, PerpMarket, market_account_info); + let perp_market_map = PerpMarketMap::load_one(&market_account_info, true).unwrap(); + + let mut usdc_spot_market = SpotMarket { + market_index: 0, + oracle_source: OracleSource::QuoteAsset, + cumulative_deposit_interest: SPOT_CUMULATIVE_INTEREST_PRECISION, + cumulative_borrow_interest: SPOT_CUMULATIVE_INTEREST_PRECISION, + decimals: 6, + initial_asset_weight: SPOT_WEIGHT_PRECISION, + maintenance_asset_weight: SPOT_WEIGHT_PRECISION, + deposit_balance: 10000 * SPOT_BALANCE_PRECISION, + liquidator_fee: 0, + historical_oracle_data: HistoricalOracleData::default_quote_oracle(), + ..SpotMarket::default() + }; + create_anchor_account_info!(usdc_spot_market, SpotMarket, usdc_spot_market_account_info); + let mut sol_spot_market = SpotMarket { + market_index: 1, + oracle_source: OracleSource::Pyth, + oracle: sol_oracle_price_key, + cumulative_deposit_interest: SPOT_CUMULATIVE_INTEREST_PRECISION, + cumulative_borrow_interest: SPOT_CUMULATIVE_INTEREST_PRECISION, + decimals: 9, + initial_asset_weight: 8 * SPOT_WEIGHT_PRECISION / 10, + maintenance_asset_weight: 9 * SPOT_WEIGHT_PRECISION / 10, + initial_liability_weight: 12 * SPOT_WEIGHT_PRECISION / 10, + maintenance_liability_weight: 11 * SPOT_WEIGHT_PRECISION / 10, + liquidator_fee: LIQUIDATION_FEE_PRECISION / 1000, + ..SpotMarket::default() + }; + create_anchor_account_info!(sol_spot_market, SpotMarket, sol_spot_market_account_info); + let spot_market_account_infos = Vec::from([ + &usdc_spot_market_account_info, + &sol_spot_market_account_info, + ]); + let spot_market_map = + SpotMarketMap::load_multiple(spot_market_account_infos, true).unwrap(); + + let mut spot_positions = [SpotPosition::default(); 8]; + spot_positions[1] = SpotPosition { + market_index: 1, + balance_type: SpotBalanceType::Deposit, + open_orders: 1, + open_asks: -100 * BASE_PRECISION_I64, + ..SpotPosition::default() + }; + + let user = User { + orders: [Order::default(); 32], + spot_positions, + max_margin_ratio: 2 * MARGIN_PRECISION, // .5x leverage + ..User::default() + }; + + let MarginCalculation { + total_collateral, + margin_requirement, + all_deposit_oracles_valid: deposit_oracles_valid, + all_liability_oracles_valid: liability_oracles_valid, + .. + } = calculate_margin_requirement_and_total_collateral_and_liability_info( + &user, + &perp_market_map, + &spot_market_map, + &mut oracle_map, + MarginContext::standard(MarginRequirementType::Initial).ignore_invalid_deposit_oracles(true), + ) + .unwrap(); + + assert_eq!(deposit_oracles_valid, true); + assert_eq!(liability_oracles_valid, false); + assert_eq!(total_collateral, 0); + assert_ne!(margin_requirement, 0); + } + } #[cfg(test)] diff --git a/programs/drift/src/math/oracle.rs b/programs/drift/src/math/oracle.rs index 2c480a837..8c883b16a 100644 --- a/programs/drift/src/math/oracle.rs +++ b/programs/drift/src/math/oracle.rs @@ -9,7 +9,7 @@ use crate::math::casting::Cast; use crate::math::constants::BID_ASK_SPREAD_PRECISION; use crate::math::safe_math::SafeMath; -use crate::state::oracle::OraclePriceData; +use crate::state::oracle::{OraclePriceData, OracleSource}; use crate::state::paused_operations::PerpOperation; use crate::state::perp_market::PerpMarket; use crate::state::state::{OracleGuardRails, ValidityGuardRails}; @@ -189,6 +189,7 @@ pub fn get_oracle_status( oracle_price_data, &guard_rails.validity, market.get_max_confidence_interval_multiplier()?, + &market.amm.oracle_source, false, )?; let oracle_reserve_price_spread_pct = @@ -213,6 +214,7 @@ pub fn oracle_validity( oracle_price_data: &OraclePriceData, valid_oracle_guard_rails: &ValidityGuardRails, max_confidence_interval_multiplier: u64, + oracle_source: &OracleSource, log_validity: bool, ) -> DriftResult { let OraclePriceData { @@ -239,8 +241,13 @@ pub fn oracle_validity( .safe_mul(max_confidence_interval_multiplier)?); let is_stale_for_amm = oracle_delay.gt(&valid_oracle_guard_rails.slots_before_stale_for_amm); - let is_stale_for_margin = - oracle_delay.gt(&valid_oracle_guard_rails.slots_before_stale_for_margin); + + let is_stale_for_margin = if matches!(oracle_source, OracleSource::PythStableCoinPull | OracleSource::PythStableCoin) + { + oracle_delay.gt(&(valid_oracle_guard_rails.slots_before_stale_for_margin.saturating_mul(3))) + } else { + oracle_delay.gt(&valid_oracle_guard_rails.slots_before_stale_for_margin) + }; let oracle_validity = if is_oracle_price_nonpositive { OracleValidity::NonPositive diff --git a/programs/drift/src/math/repeg.rs b/programs/drift/src/math/repeg.rs index 3abf693d0..55371c2f1 100644 --- a/programs/drift/src/math/repeg.rs +++ b/programs/drift/src/math/repeg.rs @@ -43,6 +43,7 @@ pub fn calculate_repeg_validity_from_oracle_account( &oracle_price_data, &oracle_guard_rails.validity, market.get_max_confidence_interval_multiplier()?, + &market.amm.oracle_source, true, )? == OracleValidity::Valid; diff --git a/programs/drift/src/state/margin_calculation.rs b/programs/drift/src/state/margin_calculation.rs index cad6f9de8..103d87808 100644 --- a/programs/drift/src/state/margin_calculation.rs +++ b/programs/drift/src/state/margin_calculation.rs @@ -26,6 +26,7 @@ pub struct MarginContext { pub margin_type: MarginRequirementType, pub mode: MarginCalculationMode, pub strict: bool, + pub ignore_invalid_deposit_oracles: bool, pub margin_buffer: u128, pub fuel_bonus_numerator: i64, pub fuel_bonus: u64, @@ -63,6 +64,7 @@ impl MarginContext { track_open_orders_fraction: false, }, strict: false, + ignore_invalid_deposit_oracles: false, margin_buffer: 0, fuel_bonus_numerator: 0, fuel_bonus: 0, @@ -76,6 +78,11 @@ impl MarginContext { self } + pub fn ignore_invalid_deposit_oracles(mut self, ignore: bool) -> Self { + self.ignore_invalid_deposit_oracles = ignore; + self + } + pub fn margin_buffer(mut self, margin_buffer: u32) -> Self { self.margin_buffer = margin_buffer as u128; self @@ -126,6 +133,7 @@ impl MarginContext { }, margin_buffer: margin_buffer as u128, strict: false, + ignore_invalid_deposit_oracles: false, fuel_bonus_numerator: 0, fuel_bonus: 0, fuel_perp_delta: None, @@ -164,7 +172,8 @@ pub struct MarginCalculation { pub margin_requirement_plus_buffer: u128, pub num_spot_liabilities: u8, pub num_perp_liabilities: u8, - pub all_oracles_valid: bool, + pub all_deposit_oracles_valid: bool, + pub all_liability_oracles_valid: bool, pub with_perp_isolated_liability: bool, pub with_spot_isolated_liability: bool, pub total_spot_asset_value: i128, @@ -187,7 +196,8 @@ impl MarginCalculation { margin_requirement_plus_buffer: 0, num_spot_liabilities: 0, num_perp_liabilities: 0, - all_oracles_valid: true, + all_deposit_oracles_valid: true, + all_liability_oracles_valid: true, with_perp_isolated_liability: false, with_spot_isolated_liability: false, total_spot_asset_value: 0, @@ -280,8 +290,12 @@ impl MarginCalculation { Ok(()) } - pub fn update_all_oracles_valid(&mut self, valid: bool) { - self.all_oracles_valid &= valid; + pub fn update_all_deposit_oracles_valid(&mut self, valid: bool) { + self.all_deposit_oracles_valid &= valid; + } + + pub fn update_all_liability_oracles_valid(&mut self, valid: bool) { + self.all_liability_oracles_valid &= valid; } pub fn update_with_spot_isolated_liability(&mut self, isolated: bool) { diff --git a/programs/drift/src/state/oracle_map.rs b/programs/drift/src/state/oracle_map.rs index 67692ef98..e541d5471 100644 --- a/programs/drift/src/state/oracle_map.rs +++ b/programs/drift/src/state/oracle_map.rs @@ -93,6 +93,14 @@ impl<'a> OracleMap<'a> { self.price_data.get(pubkey).safe_unwrap() } + pub fn get_oracle_source(&self, pubkey: &Pubkey) -> DriftResult { + Ok(self + .oracles + .get(pubkey) + .ok_or(ErrorCode::OracleNotFound)? + .oracle_source) + } + pub fn get_price_data_and_validity( &mut self, market_type: MarketType, @@ -111,6 +119,7 @@ impl<'a> OracleMap<'a> { let oracle_validity = if let Some(oracle_validity) = self.validity.get(pubkey) { *oracle_validity } else { + let oracle_source = self.get_oracle_source(pubkey)?; let oracle_validity = oracle_validity( market_type, market_index, @@ -118,6 +127,7 @@ impl<'a> OracleMap<'a> { oracle_price_data, &self.oracle_guard_rails.validity, max_confidence_interval_multiplier, + &oracle_source, true, )?; self.validity.insert(*pubkey, oracle_validity); @@ -149,6 +159,7 @@ impl<'a> OracleMap<'a> { oracle_price_data, &self.oracle_guard_rails.validity, max_confidence_interval_multiplier, + &oracle_source, true, )?; self.validity.insert(*pubkey, oracle_validity); diff --git a/programs/drift/src/state/user.rs b/programs/drift/src/state/user.rs index fd95a3b41..3e437ccc8 100644 --- a/programs/drift/src/state/user.rs +++ b/programs/drift/src/state/user.rs @@ -505,6 +505,7 @@ impl User { let strict = margin_requirement_type == MarginRequirementType::Initial; let context = MarginContext::standard(margin_requirement_type) .strict(strict) + .ignore_invalid_deposit_oracles(true) .fuel_spot_delta(withdraw_market_index, withdraw_amount.cast::()?) .fuel_numerator(self, now); @@ -518,7 +519,7 @@ impl User { if calculation.margin_requirement > 0 || calculation.get_num_of_liabilities()? > 0 { validate!( - calculation.all_oracles_valid, + calculation.all_liability_oracles_valid, ErrorCode::InvalidOracle, "User attempting to withdraw with outstanding liabilities when an oracle is invalid" )?;