diff --git a/CHANGELOG.md b/CHANGELOG.md index f10d42f8e..8ba6f58e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Features +- program: include usdc oracle ([#397](https://github.com/drift-labs/protocol-v2/pull/397)) + ### Fixes ### Breaking diff --git a/programs/drift/src/controller/liquidation.rs b/programs/drift/src/controller/liquidation.rs index a69996e49..cc7c09b64 100644 --- a/programs/drift/src/controller/liquidation.rs +++ b/programs/drift/src/controller/liquidation.rs @@ -305,6 +305,8 @@ pub fn liquidate_perp( let market = perp_market_map.get_ref(&market_index)?; let liquidation_fee = market.liquidator_fee; let if_liquidation_fee = market.if_liquidation_fee; + let quote_spot_market = spot_market_map.get_ref(&market.quote_spot_market_index)?; + let quote_oracle_price = oracle_map.get_price_data("e_spot_market.oracle)?.price; let base_asset_amount_to_cover_margin_shortage = standardize_base_asset_amount_ceil( calculate_base_asset_amount_to_cover_margin_shortage( margin_shortage, @@ -312,10 +314,12 @@ pub fn liquidate_perp( liquidation_fee, if_liquidation_fee, oracle_price, + quote_oracle_price, )?, market.amm.order_step_size, )?; drop(market); + drop(quote_spot_market); let max_pct_allowed = calculate_max_pct_to_liquidate( user, @@ -1174,10 +1178,11 @@ pub fn liquidate_borrow_for_perp_pnl( "Perp position must have position pnl" )?; - let quote_price = oracle_map.quote_asset_price_data.price; - let market = perp_market_map.get_ref(&perp_market_index)?; + let quote_spot_market = spot_market_map.get_ref(&market.quote_spot_market_index)?; + let quote_price = oracle_map.get_price_data("e_spot_market.oracle)?.price; + let pnl_asset_weight = market.get_unrealized_asset_weight(pnl, MarginRequirementType::Maintenance)?; @@ -1694,10 +1699,11 @@ pub fn liquidate_perp_pnl_for_deposit( "Perp position must have negative pnl" )?; - let quote_price = oracle_map.quote_asset_price_data.price; - let market = perp_market_map.get_ref(&perp_market_index)?; + let quote_spot_market = spot_market_map.get_ref(&market.quote_spot_market_index)?; + let quote_price = oracle_map.get_price_data("e_spot_market.oracle)?.price; + ( unsettled_pnl.unsigned_abs(), quote_price, diff --git a/programs/drift/src/controller/liquidation/tests.rs b/programs/drift/src/controller/liquidation/tests.rs index 09e547699..f7f2617ae 100644 --- a/programs/drift/src/controller/liquidation/tests.rs +++ b/programs/drift/src/controller/liquidation/tests.rs @@ -8,7 +8,6 @@ pub mod liquidate_perp { use crate::controller::liquidation::liquidate_perp; use crate::controller::position::PositionDirection; - use crate::create_account_info; use crate::create_anchor_account_info; use crate::error::ErrorCode; use crate::math::constants::{ @@ -34,6 +33,7 @@ pub mod liquidate_perp { }; use crate::test_utils::*; use crate::test_utils::{get_orders, get_positions, get_pyth_price, get_spot_positions}; + use crate::{create_account_info, PRICE_PRECISION_I64}; #[test] pub fn successful_liquidation_long_perp() { @@ -88,6 +88,11 @@ pub mod liquidate_perp { cumulative_deposit_interest: SPOT_CUMULATIVE_INTEREST_PRECISION, decimals: 6, initial_asset_weight: SPOT_WEIGHT_PRECISION, + historical_oracle_data: HistoricalOracleData { + last_oracle_price_twap: PRICE_PRECISION_I64, + last_oracle_price_twap_5min: PRICE_PRECISION_I64, + ..HistoricalOracleData::default() + }, ..SpotMarket::default() }; create_anchor_account_info!(spot_market, SpotMarket, spot_market_account_info); @@ -233,6 +238,11 @@ pub mod liquidate_perp { cumulative_deposit_interest: SPOT_CUMULATIVE_INTEREST_PRECISION, decimals: 6, initial_asset_weight: SPOT_WEIGHT_PRECISION, + historical_oracle_data: HistoricalOracleData { + last_oracle_price_twap: PRICE_PRECISION_I64, + last_oracle_price_twap_5min: PRICE_PRECISION_I64, + ..HistoricalOracleData::default() + }, ..SpotMarket::default() }; create_anchor_account_info!(spot_market, SpotMarket, spot_market_account_info); @@ -378,6 +388,7 @@ pub mod liquidate_perp { 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); @@ -511,6 +522,11 @@ pub mod liquidate_perp { cumulative_deposit_interest: SPOT_CUMULATIVE_INTEREST_PRECISION, decimals: 6, initial_asset_weight: SPOT_WEIGHT_PRECISION, + historical_oracle_data: HistoricalOracleData { + last_oracle_price_twap: PRICE_PRECISION_I64, + last_oracle_price_twap_5min: PRICE_PRECISION_I64, + ..HistoricalOracleData::default() + }, ..SpotMarket::default() }; create_anchor_account_info!(spot_market, SpotMarket, spot_market_account_info); @@ -656,6 +672,7 @@ pub mod liquidate_perp { 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); @@ -829,6 +846,11 @@ pub mod liquidate_perp { cumulative_deposit_interest: SPOT_CUMULATIVE_INTEREST_PRECISION, decimals: 6, initial_asset_weight: SPOT_WEIGHT_PRECISION, + historical_oracle_data: HistoricalOracleData { + last_oracle_price_twap: PRICE_PRECISION_I64, + last_oracle_price_twap_5min: PRICE_PRECISION_I64, + ..HistoricalOracleData::default() + }, ..SpotMarket::default() }; create_anchor_account_info!(spot_market, SpotMarket, spot_market_account_info); @@ -1019,6 +1041,11 @@ pub mod liquidate_perp { cumulative_deposit_interest: SPOT_CUMULATIVE_INTEREST_PRECISION, decimals: 6, initial_asset_weight: SPOT_WEIGHT_PRECISION, + historical_oracle_data: HistoricalOracleData { + last_oracle_price_twap: PRICE_PRECISION_I64, + last_oracle_price_twap_5min: PRICE_PRECISION_I64, + ..HistoricalOracleData::default() + }, ..SpotMarket::default() }; create_anchor_account_info!(spot_market, SpotMarket, spot_market_account_info); @@ -1146,6 +1173,11 @@ pub mod liquidate_perp { cumulative_deposit_interest: SPOT_CUMULATIVE_INTEREST_PRECISION, decimals: 6, initial_asset_weight: SPOT_WEIGHT_PRECISION, + historical_oracle_data: HistoricalOracleData { + last_oracle_price_twap: PRICE_PRECISION_I64, + last_oracle_price_twap_5min: PRICE_PRECISION_I64, + ..HistoricalOracleData::default() + }, ..SpotMarket::default() }; create_anchor_account_info!(spot_market, SpotMarket, spot_market_account_info); @@ -1273,6 +1305,11 @@ pub mod liquidate_perp { cumulative_deposit_interest: SPOT_CUMULATIVE_INTEREST_PRECISION, decimals: 6, initial_asset_weight: SPOT_WEIGHT_PRECISION, + historical_oracle_data: HistoricalOracleData { + last_oracle_price_twap: PRICE_PRECISION_I64, + last_oracle_price_twap_5min: PRICE_PRECISION_I64, + ..HistoricalOracleData::default() + }, ..SpotMarket::default() }; create_anchor_account_info!(spot_market, SpotMarket, spot_market_account_info); @@ -1415,6 +1452,7 @@ pub mod liquidate_perp { 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); @@ -1687,6 +1725,7 @@ pub mod liquidate_perp { 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); @@ -1764,7 +1803,6 @@ pub mod liquidate_spot { use solana_program::pubkey::Pubkey; use crate::controller::liquidation::liquidate_spot; - use crate::create_account_info; use crate::create_anchor_account_info; use crate::error::ErrorCode; use crate::math::constants::{ @@ -1786,6 +1824,7 @@ pub mod liquidate_spot { use crate::state::user::{Order, PerpPosition, SpotPosition, User}; use crate::test_utils::*; use crate::test_utils::{get_pyth_price, get_spot_positions}; + use crate::{create_account_info, QUOTE_PRECISION_I64}; #[test] pub fn successful_liquidation_liability_transfer_implied_by_asset_amount() { @@ -1815,6 +1854,11 @@ pub mod liquidate_spot { maintenance_asset_weight: SPOT_WEIGHT_PRECISION, deposit_balance: 200 * SPOT_BALANCE_PRECISION, liquidator_fee: 0, + historical_oracle_data: HistoricalOracleData { + last_oracle_price_twap: QUOTE_PRECISION_I64, + last_oracle_price_twap_5min: QUOTE_PRECISION_I64, + ..HistoricalOracleData::default() + }, ..SpotMarket::default() }; create_anchor_account_info!(usdc_market, SpotMarket, usdc_spot_market_account_info); @@ -1942,6 +1986,11 @@ pub mod liquidate_spot { maintenance_asset_weight: SPOT_WEIGHT_PRECISION, deposit_balance: 200 * SPOT_BALANCE_PRECISION, liquidator_fee: 0, + historical_oracle_data: HistoricalOracleData { + last_oracle_price_twap: QUOTE_PRECISION_I64, + last_oracle_price_twap_5min: QUOTE_PRECISION_I64, + ..HistoricalOracleData::default() + }, ..SpotMarket::default() }; create_anchor_account_info!(usdc_market, SpotMarket, usdc_spot_market_account_info); @@ -2098,6 +2147,11 @@ pub mod liquidate_spot { maintenance_asset_weight: SPOT_WEIGHT_PRECISION, deposit_balance: 200 * SPOT_BALANCE_PRECISION, liquidator_fee: 0, + historical_oracle_data: HistoricalOracleData { + last_oracle_price_twap: QUOTE_PRECISION_I64, + last_oracle_price_twap_5min: QUOTE_PRECISION_I64, + ..HistoricalOracleData::default() + }, ..SpotMarket::default() }; create_anchor_account_info!(usdc_market, SpotMarket, usdc_spot_market_account_info); @@ -2301,6 +2355,11 @@ pub mod liquidate_spot { maintenance_asset_weight: SPOT_WEIGHT_PRECISION, deposit_balance: 200 * SPOT_BALANCE_PRECISION, liquidator_fee: 0, + historical_oracle_data: HistoricalOracleData { + last_oracle_price_twap: QUOTE_PRECISION_I64, + last_oracle_price_twap_5min: QUOTE_PRECISION_I64, + ..HistoricalOracleData::default() + }, ..SpotMarket::default() }; create_anchor_account_info!(usdc_market, SpotMarket, usdc_spot_market_account_info); @@ -2416,6 +2475,11 @@ pub mod liquidate_spot { maintenance_asset_weight: SPOT_WEIGHT_PRECISION, deposit_balance: 200 * SPOT_BALANCE_PRECISION, liquidator_fee: 0, + historical_oracle_data: HistoricalOracleData { + last_oracle_price_twap: QUOTE_PRECISION_I64, + last_oracle_price_twap_5min: QUOTE_PRECISION_I64, + ..HistoricalOracleData::default() + }, ..SpotMarket::default() }; create_anchor_account_info!(usdc_market, SpotMarket, usdc_spot_market_account_info); @@ -2531,6 +2595,11 @@ pub mod liquidate_spot { maintenance_asset_weight: SPOT_WEIGHT_PRECISION, deposit_balance: 200 * SPOT_BALANCE_PRECISION, liquidator_fee: 0, + historical_oracle_data: HistoricalOracleData { + last_oracle_price_twap: QUOTE_PRECISION_I64, + last_oracle_price_twap_5min: QUOTE_PRECISION_I64, + ..HistoricalOracleData::default() + }, ..SpotMarket::default() }; create_anchor_account_info!(usdc_market, SpotMarket, usdc_spot_market_account_info); @@ -2653,6 +2722,11 @@ pub mod liquidate_spot { maintenance_asset_weight: SPOT_WEIGHT_PRECISION, deposit_balance: 200 * SPOT_BALANCE_PRECISION, liquidator_fee: 0, + historical_oracle_data: HistoricalOracleData { + last_oracle_price_twap: QUOTE_PRECISION_I64, + last_oracle_price_twap_5min: QUOTE_PRECISION_I64, + ..HistoricalOracleData::default() + }, ..SpotMarket::default() }; create_anchor_account_info!(usdc_market, SpotMarket, usdc_spot_market_account_info); @@ -2924,6 +2998,11 @@ pub mod liquidate_borrow_for_perp_pnl { maintenance_asset_weight: SPOT_WEIGHT_PRECISION, deposit_balance: 200 * SPOT_BALANCE_PRECISION, liquidator_fee: 0, + historical_oracle_data: HistoricalOracleData { + last_oracle_price_twap: QUOTE_PRECISION_I64, + last_oracle_price_twap_5min: QUOTE_PRECISION_I64, + ..HistoricalOracleData::default() + }, ..SpotMarket::default() }; create_anchor_account_info!(usdc_market, SpotMarket, usdc_spot_market_account_info); @@ -3073,6 +3152,11 @@ pub mod liquidate_borrow_for_perp_pnl { maintenance_asset_weight: SPOT_WEIGHT_PRECISION, deposit_balance: 200 * SPOT_BALANCE_PRECISION, liquidator_fee: 0, + historical_oracle_data: HistoricalOracleData { + last_oracle_price_twap: QUOTE_PRECISION_I64, + last_oracle_price_twap_5min: QUOTE_PRECISION_I64, + ..HistoricalOracleData::default() + }, ..SpotMarket::default() }; create_anchor_account_info!(usdc_market, SpotMarket, usdc_spot_market_account_info); @@ -3262,6 +3346,11 @@ pub mod liquidate_borrow_for_perp_pnl { maintenance_asset_weight: SPOT_WEIGHT_PRECISION, deposit_balance: 200 * SPOT_BALANCE_PRECISION, liquidator_fee: 0, + historical_oracle_data: HistoricalOracleData { + last_oracle_price_twap: QUOTE_PRECISION_I64, + last_oracle_price_twap_5min: QUOTE_PRECISION_I64, + ..HistoricalOracleData::default() + }, ..SpotMarket::default() }; create_anchor_account_info!(usdc_market, SpotMarket, usdc_spot_market_account_info); @@ -3411,6 +3500,11 @@ pub mod liquidate_borrow_for_perp_pnl { maintenance_asset_weight: SPOT_WEIGHT_PRECISION, deposit_balance: 200 * SPOT_BALANCE_PRECISION, liquidator_fee: 0, + historical_oracle_data: HistoricalOracleData { + last_oracle_price_twap: QUOTE_PRECISION_I64, + last_oracle_price_twap_5min: QUOTE_PRECISION_I64, + ..HistoricalOracleData::default() + }, ..SpotMarket::default() }; create_anchor_account_info!(usdc_market, SpotMarket, usdc_spot_market_account_info); @@ -3552,6 +3646,11 @@ pub mod liquidate_borrow_for_perp_pnl { maintenance_asset_weight: SPOT_WEIGHT_PRECISION, deposit_balance: 200 * SPOT_BALANCE_PRECISION, liquidator_fee: 0, + historical_oracle_data: HistoricalOracleData { + last_oracle_price_twap: QUOTE_PRECISION_I64, + last_oracle_price_twap_5min: QUOTE_PRECISION_I64, + ..HistoricalOracleData::default() + }, ..SpotMarket::default() }; create_anchor_account_info!(usdc_market, SpotMarket, usdc_spot_market_account_info); @@ -3693,6 +3792,11 @@ pub mod liquidate_borrow_for_perp_pnl { maintenance_asset_weight: SPOT_WEIGHT_PRECISION, deposit_balance: 200 * SPOT_BALANCE_PRECISION, liquidator_fee: 0, + historical_oracle_data: HistoricalOracleData { + last_oracle_price_twap: QUOTE_PRECISION_I64, + last_oracle_price_twap_5min: QUOTE_PRECISION_I64, + ..HistoricalOracleData::default() + }, ..SpotMarket::default() }; create_anchor_account_info!(usdc_market, SpotMarket, usdc_spot_market_account_info); @@ -3844,6 +3948,11 @@ pub mod liquidate_borrow_for_perp_pnl { maintenance_asset_weight: SPOT_WEIGHT_PRECISION, deposit_balance: 200 * SPOT_BALANCE_PRECISION, liquidator_fee: 0, + historical_oracle_data: HistoricalOracleData { + last_oracle_price_twap: QUOTE_PRECISION_I64, + last_oracle_price_twap_5min: QUOTE_PRECISION_I64, + ..HistoricalOracleData::default() + }, ..SpotMarket::default() }; create_anchor_account_info!(usdc_market, SpotMarket, usdc_spot_market_account_info); @@ -4108,6 +4217,11 @@ pub mod liquidate_perp_pnl_for_deposit { maintenance_asset_weight: SPOT_WEIGHT_PRECISION, deposit_balance: 200 * SPOT_BALANCE_PRECISION, liquidator_fee: 0, + historical_oracle_data: HistoricalOracleData { + last_oracle_price_twap: QUOTE_PRECISION_I64, + last_oracle_price_twap_5min: QUOTE_PRECISION_I64, + ..HistoricalOracleData::default() + }, ..SpotMarket::default() }; create_anchor_account_info!(usdc_market, SpotMarket, usdc_spot_market_account_info); @@ -4258,6 +4372,11 @@ pub mod liquidate_perp_pnl_for_deposit { maintenance_asset_weight: SPOT_WEIGHT_PRECISION, deposit_balance: 200 * SPOT_BALANCE_PRECISION, liquidator_fee: 0, + historical_oracle_data: HistoricalOracleData { + last_oracle_price_twap: QUOTE_PRECISION_I64, + last_oracle_price_twap_5min: QUOTE_PRECISION_I64, + ..HistoricalOracleData::default() + }, ..SpotMarket::default() }; create_anchor_account_info!(usdc_market, SpotMarket, usdc_spot_market_account_info); @@ -4410,6 +4529,11 @@ pub mod liquidate_perp_pnl_for_deposit { maintenance_asset_weight: SPOT_WEIGHT_PRECISION, deposit_balance: 200 * SPOT_BALANCE_PRECISION, liquidator_fee: 0, + historical_oracle_data: HistoricalOracleData { + last_oracle_price_twap: QUOTE_PRECISION_I64, + last_oracle_price_twap_5min: QUOTE_PRECISION_I64, + ..HistoricalOracleData::default() + }, ..SpotMarket::default() }; create_anchor_account_info!(usdc_market, SpotMarket, usdc_spot_market_account_info); @@ -4559,6 +4683,11 @@ pub mod liquidate_perp_pnl_for_deposit { maintenance_asset_weight: SPOT_WEIGHT_PRECISION, deposit_balance: 200 * SPOT_BALANCE_PRECISION, liquidator_fee: 0, + historical_oracle_data: HistoricalOracleData { + last_oracle_price_twap: QUOTE_PRECISION_I64, + last_oracle_price_twap_5min: QUOTE_PRECISION_I64, + ..HistoricalOracleData::default() + }, ..SpotMarket::default() }; create_anchor_account_info!(usdc_market, SpotMarket, usdc_spot_market_account_info); @@ -4700,6 +4829,11 @@ pub mod liquidate_perp_pnl_for_deposit { maintenance_asset_weight: SPOT_WEIGHT_PRECISION, deposit_balance: 200 * SPOT_BALANCE_PRECISION, liquidator_fee: 0, + historical_oracle_data: HistoricalOracleData { + last_oracle_price_twap: QUOTE_PRECISION_I64, + last_oracle_price_twap_5min: QUOTE_PRECISION_I64, + ..HistoricalOracleData::default() + }, ..SpotMarket::default() }; create_anchor_account_info!(usdc_market, SpotMarket, usdc_spot_market_account_info); @@ -4842,6 +4976,11 @@ pub mod liquidate_perp_pnl_for_deposit { maintenance_asset_weight: SPOT_WEIGHT_PRECISION, deposit_balance: 200 * SPOT_BALANCE_PRECISION, liquidator_fee: 0, + historical_oracle_data: HistoricalOracleData { + last_oracle_price_twap: QUOTE_PRECISION_I64, + last_oracle_price_twap_5min: QUOTE_PRECISION_I64, + ..HistoricalOracleData::default() + }, ..SpotMarket::default() }; create_anchor_account_info!(usdc_market, SpotMarket, usdc_spot_market_account_info); @@ -4992,6 +5131,11 @@ pub mod liquidate_perp_pnl_for_deposit { maintenance_asset_weight: SPOT_WEIGHT_PRECISION, deposit_balance: 200 * SPOT_BALANCE_PRECISION, liquidator_fee: 0, + historical_oracle_data: HistoricalOracleData { + last_oracle_price_twap: QUOTE_PRECISION_I64, + last_oracle_price_twap_5min: QUOTE_PRECISION_I64, + ..HistoricalOracleData::default() + }, ..SpotMarket::default() }; create_anchor_account_info!(usdc_market, SpotMarket, usdc_spot_market_account_info); @@ -5486,6 +5630,11 @@ pub mod liquidate_perp_pnl_for_deposit { maintenance_asset_weight: SPOT_WEIGHT_PRECISION, deposit_balance: 200 * SPOT_BALANCE_PRECISION, liquidator_fee: 0, + historical_oracle_data: HistoricalOracleData { + last_oracle_price_twap: QUOTE_PRECISION_I64, + last_oracle_price_twap_5min: QUOTE_PRECISION_I64, + ..HistoricalOracleData::default() + }, ..SpotMarket::default() }; create_anchor_account_info!(usdc_market, SpotMarket, usdc_spot_market_account_info); @@ -5644,7 +5793,6 @@ pub mod resolve_perp_bankruptcy { use crate::controller::funding::settle_funding_payment; use crate::controller::liquidation::resolve_perp_bankruptcy; use crate::controller::position::PositionDirection; - use crate::create_account_info; use crate::create_anchor_account_info; use crate::math::constants::{ AMM_RESERVE_PRECISION, BASE_PRECISION_I128, BASE_PRECISION_I64, BASE_PRECISION_U64, @@ -5653,7 +5801,7 @@ pub mod resolve_perp_bankruptcy { SPOT_BALANCE_PRECISION, SPOT_BALANCE_PRECISION_U64, SPOT_CUMULATIVE_INTEREST_PRECISION, SPOT_WEIGHT_PRECISION, }; - use crate::state::oracle::OracleSource; + use crate::state::oracle::{HistoricalOracleData, OracleSource}; use crate::state::oracle_map::OracleMap; use crate::state::perp_market::{MarketStatus, PerpMarket, PoolBalance, AMM}; use crate::state::perp_market_map::PerpMarketMap; @@ -5664,6 +5812,7 @@ pub mod resolve_perp_bankruptcy { }; use crate::test_utils::*; use crate::test_utils::{get_orders, get_positions, get_pyth_price, get_spot_positions}; + use crate::{create_account_info, PRICE_PRECISION_I64}; #[test] pub fn successful_resolve_perp_bankruptcy() { @@ -5720,6 +5869,11 @@ pub mod resolve_perp_bankruptcy { cumulative_deposit_interest: SPOT_CUMULATIVE_INTEREST_PRECISION, decimals: 6, initial_asset_weight: SPOT_WEIGHT_PRECISION, + historical_oracle_data: HistoricalOracleData { + last_oracle_price_twap: PRICE_PRECISION_I64, + last_oracle_price_twap_5min: PRICE_PRECISION_I64, + ..HistoricalOracleData::default() + }, ..SpotMarket::default() }; create_anchor_account_info!(spot_market, SpotMarket, spot_market_account_info); @@ -6101,7 +6255,7 @@ pub mod resolve_spot_bankruptcy { SPOT_BALANCE_PRECISION_U64, SPOT_CUMULATIVE_INTEREST_PRECISION, SPOT_WEIGHT_PRECISION, }; use crate::math::spot_balance::get_token_amount; - use crate::state::oracle::OracleSource; + use crate::state::oracle::{HistoricalOracleData, OracleSource}; use crate::state::oracle_map::OracleMap; use crate::state::perp_market::{MarketStatus, PerpMarket, AMM}; use crate::state::perp_market_map::PerpMarketMap; @@ -6170,6 +6324,7 @@ pub mod resolve_spot_bankruptcy { initial_asset_weight: SPOT_WEIGHT_PRECISION, deposit_balance: 1000 * SPOT_BALANCE_PRECISION, borrow_balance: 100 * SPOT_BALANCE_PRECISION, + historical_oracle_data: HistoricalOracleData::default_price(QUOTE_PRECISION_I64), ..SpotMarket::default() }; create_anchor_account_info!(spot_market, SpotMarket, spot_market_account_info); diff --git a/programs/drift/src/controller/orders.rs b/programs/drift/src/controller/orders.rs index 7be0a46aa..33d0b808b 100644 --- a/programs/drift/src/controller/orders.rs +++ b/programs/drift/src/controller/orders.rs @@ -2684,7 +2684,7 @@ pub fn place_spot_order( let oracle_price_data = *oracle_map.get_price_data(&spot_market.oracle)?; let (worst_case_token_amount_before, _) = user.spot_positions[spot_position_index] - .get_worst_case_token_amounts(spot_market, &oracle_price_data, None, None)?; + .get_worst_case_token_amount(spot_market, &oracle_price_data, None, None)?; let balance_type = user.spot_positions[spot_position_index].balance_type; let token_amount = user.spot_positions[spot_position_index].get_token_amount(spot_market)?; @@ -2807,7 +2807,7 @@ pub fn place_spot_order( } let (worst_case_token_amount_after, _) = user.spot_positions[spot_position_index] - .get_worst_case_token_amounts(spot_market, &oracle_price_data, None, None)?; + .get_worst_case_token_amount(spot_market, &oracle_price_data, None, None)?; let order_risk_decreasing = is_spot_order_risk_decreasing(&user.orders[new_order_index], &balance_type, token_amount)?; diff --git a/programs/drift/src/controller/orders/amm_jit_tests.rs b/programs/drift/src/controller/orders/amm_jit_tests.rs index ecc00090a..6dc773df1 100644 --- a/programs/drift/src/controller/orders/amm_jit_tests.rs +++ b/programs/drift/src/controller/orders/amm_jit_tests.rs @@ -203,6 +203,7 @@ pub mod amm_jit { 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); @@ -384,6 +385,7 @@ pub mod amm_jit { 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); @@ -579,6 +581,7 @@ pub mod amm_jit { 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); @@ -773,6 +776,7 @@ pub mod amm_jit { 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); @@ -984,6 +988,7 @@ pub mod amm_jit { 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); @@ -1187,6 +1192,7 @@ pub mod amm_jit { 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); @@ -1394,6 +1400,7 @@ pub mod amm_jit { 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); @@ -1575,6 +1582,7 @@ pub mod amm_jit { 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); @@ -1772,6 +1780,7 @@ pub mod amm_jit { 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); @@ -1982,6 +1991,7 @@ pub mod amm_jit { 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); @@ -2261,6 +2271,7 @@ pub mod amm_jit { 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); @@ -2524,6 +2535,7 @@ pub mod amm_jit { 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); diff --git a/programs/drift/src/controller/orders/tests.rs b/programs/drift/src/controller/orders/tests.rs index 667ab8e7e..970e6425e 100644 --- a/programs/drift/src/controller/orders/tests.rs +++ b/programs/drift/src/controller/orders/tests.rs @@ -2554,6 +2554,7 @@ pub mod fulfill_order { 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); @@ -2784,6 +2785,7 @@ pub mod fulfill_order { 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); @@ -2985,6 +2987,7 @@ pub mod fulfill_order { 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); @@ -3189,6 +3192,7 @@ pub mod fulfill_order { 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); @@ -3393,6 +3397,7 @@ pub mod fulfill_order { 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); @@ -3702,6 +3707,7 @@ pub mod fulfill_order { 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); @@ -3945,7 +3951,6 @@ pub mod fill_order { use crate::controller::orders::fill_perp_order; use crate::controller::position::PositionDirection; - use crate::create_account_info; use crate::create_anchor_account_info; use crate::math::constants::{ AMM_RESERVE_PRECISION, BASE_PRECISION_I64, BASE_PRECISION_U64, PEG_PRECISION, @@ -3964,6 +3969,7 @@ pub mod fill_order { use crate::test_utils::{ create_account_info, get_orders, get_positions, get_pyth_price, get_spot_positions, }; + use crate::{create_account_info, QUOTE_PRECISION_I64}; use super::*; use crate::error::ErrorCode; @@ -4049,6 +4055,7 @@ pub mod fill_order { 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); @@ -4252,6 +4259,7 @@ pub mod fill_order { 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); @@ -4590,6 +4598,7 @@ pub mod fill_order { 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); @@ -8047,7 +8056,6 @@ pub mod get_maker_orders_info { use crate::controller::orders::get_maker_orders_info; use crate::controller::position::PositionDirection; - use crate::create_anchor_account_info; use crate::math::constants::{ AMM_RESERVE_PRECISION, BASE_PRECISION_I64, BASE_PRECISION_U64, PEG_PRECISION, PRICE_PRECISION_I64, PRICE_PRECISION_U64, SPOT_BALANCE_PRECISION_U64, @@ -8066,6 +8074,7 @@ pub mod get_maker_orders_info { create_account_info, get_orders, get_positions, get_pyth_price, get_spot_positions, }; use crate::{create_account_info, get_orders}; + use crate::{create_anchor_account_info, QUOTE_PRECISION_I64}; use super::*; @@ -8150,6 +8159,7 @@ pub mod get_maker_orders_info { 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); @@ -8343,6 +8353,7 @@ pub mod get_maker_orders_info { 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); @@ -8537,6 +8548,7 @@ pub mod get_maker_orders_info { 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); @@ -8717,6 +8729,7 @@ pub mod get_maker_orders_info { 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); @@ -8971,6 +8984,7 @@ pub mod get_maker_orders_info { 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); @@ -9167,6 +9181,7 @@ pub mod get_maker_orders_info { 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); diff --git a/programs/drift/src/controller/pnl/delisting.rs b/programs/drift/src/controller/pnl/delisting.rs index 580bfddad..bd40391f4 100644 --- a/programs/drift/src/controller/pnl/delisting.rs +++ b/programs/drift/src/controller/pnl/delisting.rs @@ -707,6 +707,7 @@ pub mod delisting_test { maintenance_liability_weight: SPOT_WEIGHT_PRECISION, deposit_balance: 10000 * SPOT_BALANCE_PRECISION, borrow_balance: 100 * SPOT_BALANCE_PRECISION, + historical_oracle_data: HistoricalOracleData::default_price(QUOTE_PRECISION_I64), ..SpotMarket::default() }; create_anchor_account_info!(spot_market, SpotMarket, spot_market_account_info); @@ -924,6 +925,7 @@ pub mod delisting_test { maintenance_liability_weight: SPOT_WEIGHT_PRECISION, deposit_balance: 10000 * SPOT_BALANCE_PRECISION, borrow_balance: 100 * SPOT_BALANCE_PRECISION, + historical_oracle_data: HistoricalOracleData::default_price(QUOTE_PRECISION_I64), ..SpotMarket::default() }; create_anchor_account_info!(spot_market, SpotMarket, spot_market_account_info); @@ -1144,6 +1146,7 @@ pub mod delisting_test { maintenance_liability_weight: SPOT_WEIGHT_PRECISION, deposit_balance: 10000 * SPOT_BALANCE_PRECISION, borrow_balance: 100 * SPOT_BALANCE_PRECISION, + historical_oracle_data: HistoricalOracleData::default_price(QUOTE_PRECISION_I64), ..SpotMarket::default() }; create_anchor_account_info!(spot_market, SpotMarket, spot_market_account_info); @@ -1348,6 +1351,7 @@ pub mod delisting_test { maintenance_liability_weight: SPOT_WEIGHT_PRECISION, deposit_balance: 300000 * SPOT_BALANCE_PRECISION, borrow_balance: 100 * SPOT_BALANCE_PRECISION, + historical_oracle_data: HistoricalOracleData::default_price(QUOTE_PRECISION_I64), ..SpotMarket::default() }; create_anchor_account_info!(spot_market, SpotMarket, spot_market_account_info); @@ -1771,6 +1775,7 @@ pub mod delisting_test { maintenance_liability_weight: SPOT_WEIGHT_PRECISION, deposit_balance: 10000 * SPOT_BALANCE_PRECISION, borrow_balance: 100 * SPOT_BALANCE_PRECISION, + historical_oracle_data: HistoricalOracleData::default_price(QUOTE_PRECISION_I64), ..SpotMarket::default() }; create_anchor_account_info!(spot_market, SpotMarket, spot_market_account_info); @@ -2150,6 +2155,7 @@ pub mod delisting_test { maintenance_liability_weight: SPOT_WEIGHT_PRECISION, deposit_balance: 40000 * SPOT_BALANCE_PRECISION, borrow_balance: 100 * SPOT_BALANCE_PRECISION, + historical_oracle_data: HistoricalOracleData::default_price(QUOTE_PRECISION_I64), ..SpotMarket::default() }; create_anchor_account_info!(spot_market, SpotMarket, spot_market_account_info); @@ -2348,9 +2354,12 @@ pub mod delisting_test { &shorter.perp_positions[0], &market, oracle_price_data, + QUOTE_PRECISION_I64, + QUOTE_PRECISION_I64, MarginRequirementType::Initial, 0, false, + false, ) .unwrap(); @@ -2426,9 +2435,12 @@ pub mod delisting_test { &shorter.perp_positions[0], &market, oracle_price_data, + QUOTE_PRECISION_I64, + QUOTE_PRECISION_I64, MarginRequirementType::Initial, 0, false, + false, ) .unwrap(); @@ -2511,9 +2523,12 @@ pub mod delisting_test { &shorter.perp_positions[0], &market, oracle_price_data, + QUOTE_PRECISION_I64, + QUOTE_PRECISION_I64, MarginRequirementType::Initial, 0, false, + false, ) .unwrap(); @@ -2600,9 +2615,12 @@ pub mod delisting_test { &shorter.perp_positions[0], &market, oracle_price_data, + QUOTE_PRECISION_I64, + QUOTE_PRECISION_I64, MarginRequirementType::Initial, 0, false, + false, ) .unwrap(); diff --git a/programs/drift/src/controller/pnl/tests.rs b/programs/drift/src/controller/pnl/tests.rs index 67b1febb2..0d40c803d 100644 --- a/programs/drift/src/controller/pnl/tests.rs +++ b/programs/drift/src/controller/pnl/tests.rs @@ -219,6 +219,7 @@ pub fn user_does_not_meet_maintenance_requirement() { initial_asset_weight: SPOT_WEIGHT_PRECISION, maintenance_asset_weight: SPOT_WEIGHT_PRECISION, deposit_balance: 100 * SPOT_BALANCE_PRECISION, + historical_oracle_data: HistoricalOracleData::default_price(QUOTE_PRECISION_I64), ..SpotMarket::default() }; create_anchor_account_info!(spot_market, SpotMarket, spot_market_account_info); @@ -337,6 +338,7 @@ pub fn user_unsettled_negative_pnl() { initial_asset_weight: SPOT_WEIGHT_PRECISION, maintenance_asset_weight: SPOT_WEIGHT_PRECISION, deposit_balance: 100 * SPOT_BALANCE_PRECISION, + historical_oracle_data: HistoricalOracleData::default_price(QUOTE_PRECISION_I64), ..SpotMarket::default() }; create_anchor_account_info!(spot_market, SpotMarket, spot_market_account_info); @@ -467,6 +469,7 @@ pub fn user_unsettled_positive_pnl_more_than_pool() { initial_asset_weight: SPOT_WEIGHT_PRECISION, maintenance_asset_weight: SPOT_WEIGHT_PRECISION, deposit_balance: 100 * SPOT_BALANCE_PRECISION, + historical_oracle_data: HistoricalOracleData::default_price(QUOTE_PRECISION_I64), ..SpotMarket::default() }; create_anchor_account_info!(spot_market, SpotMarket, spot_market_account_info); @@ -597,6 +600,7 @@ pub fn user_unsettled_positive_pnl_less_than_pool() { initial_asset_weight: SPOT_WEIGHT_PRECISION, maintenance_asset_weight: SPOT_WEIGHT_PRECISION, deposit_balance: 100 * SPOT_BALANCE_PRECISION, + historical_oracle_data: HistoricalOracleData::default_price(QUOTE_PRECISION_I64), ..SpotMarket::default() }; create_anchor_account_info!(spot_market, SpotMarket, spot_market_account_info); @@ -729,6 +733,7 @@ pub fn market_fee_pool_receives_portion() { initial_asset_weight: SPOT_WEIGHT_PRECISION, maintenance_asset_weight: SPOT_WEIGHT_PRECISION, deposit_balance: 100 * SPOT_BALANCE_PRECISION, + historical_oracle_data: HistoricalOracleData::default_price(QUOTE_PRECISION_I64), ..SpotMarket::default() }; create_anchor_account_info!(spot_market, SpotMarket, spot_market_account_info); @@ -867,6 +872,7 @@ pub fn market_fee_pool_pays_back_to_pnl_pool() { initial_asset_weight: SPOT_WEIGHT_PRECISION, maintenance_asset_weight: SPOT_WEIGHT_PRECISION, deposit_balance: 100 * SPOT_BALANCE_PRECISION, + historical_oracle_data: HistoricalOracleData::default_price(QUOTE_PRECISION_I64), ..SpotMarket::default() }; create_anchor_account_info!(spot_market, SpotMarket, spot_market_account_info); @@ -998,6 +1004,7 @@ pub fn user_long_positive_unrealized_pnl_up_to_max_positive_pnl() { initial_asset_weight: SPOT_WEIGHT_PRECISION, maintenance_asset_weight: SPOT_WEIGHT_PRECISION, deposit_balance: 100 * SPOT_BALANCE_PRECISION, + historical_oracle_data: HistoricalOracleData::default_price(QUOTE_PRECISION_I64), ..SpotMarket::default() }; create_anchor_account_info!(spot_market, SpotMarket, spot_market_account_info); @@ -1130,6 +1137,7 @@ pub fn user_long_positive_unrealized_pnl_up_to_max_positive_pnl_price_breached() initial_asset_weight: SPOT_WEIGHT_PRECISION, maintenance_asset_weight: SPOT_WEIGHT_PRECISION, deposit_balance: 100 * SPOT_BALANCE_PRECISION, + historical_oracle_data: HistoricalOracleData::default_price(QUOTE_PRECISION_I64), ..SpotMarket::default() }; create_anchor_account_info!(spot_market, SpotMarket, spot_market_account_info); @@ -1259,6 +1267,7 @@ pub fn user_long_negative_unrealized_pnl() { initial_asset_weight: SPOT_WEIGHT_PRECISION, maintenance_asset_weight: SPOT_WEIGHT_PRECISION, deposit_balance: 100 * SPOT_BALANCE_PRECISION, + historical_oracle_data: HistoricalOracleData::default_price(QUOTE_PRECISION_I64), ..SpotMarket::default() }; create_anchor_account_info!(spot_market, SpotMarket, spot_market_account_info); @@ -1391,6 +1400,7 @@ pub fn user_short_positive_unrealized_pnl_up_to_max_positive_pnl() { initial_asset_weight: SPOT_WEIGHT_PRECISION, maintenance_asset_weight: SPOT_WEIGHT_PRECISION, deposit_balance: 100 * SPOT_BALANCE_PRECISION, + historical_oracle_data: HistoricalOracleData::default_price(QUOTE_PRECISION_I64), ..SpotMarket::default() }; create_anchor_account_info!(spot_market, SpotMarket, spot_market_account_info); @@ -1523,6 +1533,7 @@ pub fn user_short_negative_unrealized_pnl() { initial_asset_weight: SPOT_WEIGHT_PRECISION, maintenance_asset_weight: SPOT_WEIGHT_PRECISION, deposit_balance: 100 * SPOT_BALANCE_PRECISION, + historical_oracle_data: HistoricalOracleData::default_price(QUOTE_PRECISION_I64), ..SpotMarket::default() }; create_anchor_account_info!(spot_market, SpotMarket, spot_market_account_info); diff --git a/programs/drift/src/controller/spot_balance/tests.rs b/programs/drift/src/controller/spot_balance/tests.rs index 1093d4f7e..921b39945 100644 --- a/programs/drift/src/controller/spot_balance/tests.rs +++ b/programs/drift/src/controller/spot_balance/tests.rs @@ -10,10 +10,10 @@ use crate::create_account_info; use crate::create_anchor_account_info; use crate::math::constants::{ AMM_RESERVE_PRECISION, BASE_PRECISION_I128, BASE_PRECISION_I64, LIQUIDATION_FEE_PRECISION, - PEG_PRECISION, QUOTE_PRECISION, QUOTE_PRECISION_I128, QUOTE_PRECISION_I64, QUOTE_PRECISION_U64, - SPOT_BALANCE_PRECISION, SPOT_BALANCE_PRECISION_U64, SPOT_CUMULATIVE_INTEREST_PRECISION, - SPOT_RATE_PRECISION_U32, SPOT_UTILIZATION_PRECISION, SPOT_UTILIZATION_PRECISION_U32, - SPOT_WEIGHT_PRECISION, + PEG_PRECISION, PRICE_PRECISION_I64, QUOTE_PRECISION, QUOTE_PRECISION_I128, QUOTE_PRECISION_I64, + QUOTE_PRECISION_U64, SPOT_BALANCE_PRECISION, SPOT_BALANCE_PRECISION_U64, + SPOT_CUMULATIVE_INTEREST_PRECISION, SPOT_RATE_PRECISION_U32, SPOT_UTILIZATION_PRECISION, + SPOT_UTILIZATION_PRECISION_U32, SPOT_WEIGHT_PRECISION, }; use crate::math::margin::{ calculate_margin_requirement_and_total_collateral, MarginRequirementType, @@ -21,6 +21,7 @@ use crate::math::margin::{ use crate::math::spot_withdraw::{ calculate_max_borrow_token_amount, calculate_min_deposit_token, check_withdraw_limits, }; +use crate::math::stats::calculate_weighted_average; use crate::state::oracle::{HistoricalOracleData, OracleSource}; use crate::state::oracle_map::OracleMap; use crate::state::perp_market::{MarketStatus, PerpMarket, AMM}; @@ -1184,6 +1185,7 @@ fn attempt_borrow_with_massive_upnl() { deposit_balance: 100_000_000 * SPOT_BALANCE_PRECISION, //$100M usdc borrow_balance: 0, deposit_token_twap: QUOTE_PRECISION_U64 / 2, + historical_oracle_data: HistoricalOracleData::default_quote_oracle(), status: MarketStatus::Active, ..SpotMarket::default() @@ -1283,3 +1285,86 @@ fn attempt_borrow_with_massive_upnl() { assert_eq!(margin_requirement, 10_000_000_000); assert_eq!(total_collateral, 8_100_000_000); //100 * 100 *.8 + 100 (cap of upnl for initial margin) } + +#[test] +fn check_usdc_spot_market_twap() { + let mut now = 30_i64; + let _slot = 0_u64; + + let _oracle_price = get_pyth_price(1, 6); + // let oracle_price_key = + // Pubkey::from_str("J83w4HKfqxwcq3BEMMkPFSppX3gqekLyLJBexebFVkix").unwrap(); + + // usdc market + let mut 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: 100_000_000 * SPOT_BALANCE_PRECISION, //$100M usdc + borrow_balance: 0, + deposit_token_twap: QUOTE_PRECISION_U64 / 2, + historical_oracle_data: HistoricalOracleData::default_quote_oracle(), + status: MarketStatus::Active, + ..SpotMarket::default() + }; + // create_anchor_account_info!(spot_market, SpotMarket, spot_market_account_info); + let oracle_price_data = OraclePriceData { + price: PRICE_PRECISION_I64, + confidence: 1, + delay: 0, + has_sufficient_number_of_data_points: true, + }; + + update_spot_market_twap_stats(&mut spot_market, Some(&oracle_price_data), now).unwrap(); + assert_eq!(spot_market.historical_oracle_data.last_oracle_delay, 0); + assert_eq!( + spot_market.historical_oracle_data.last_oracle_price_twap, + 1000001 + ); + assert_eq!( + spot_market + .historical_oracle_data + .last_oracle_price_twap_5min, + 1000001 + ); + let cur_time = 1679940002; + now += cur_time; + update_spot_market_twap_stats(&mut spot_market, Some(&oracle_price_data), now).unwrap(); + assert_eq!( + spot_market + .historical_oracle_data + .last_oracle_price_twap_5min, + 1000000 + ); + + while now < cur_time + 1000 { + now += 1; + update_spot_market_twap_stats(&mut spot_market, Some(&oracle_price_data), now).unwrap(); + update_spot_market_twap_stats(&mut spot_market, Some(&oracle_price_data), now).unwrap(); + } + + // twap gets distorted with multiple stat update calls in same clock.unix_timestamp + assert_eq!( + spot_market + .historical_oracle_data + .last_oracle_price_twap_5min, + 1000001 + ); + assert_eq!( + spot_market.historical_oracle_data.last_oracle_price_twap, + 1000001 + ); + + let wa_res = + calculate_weighted_average(PRICE_PRECISION_I64, PRICE_PRECISION_I64, 0, ONE_HOUR).unwrap(); + + assert_eq!(wa_res, PRICE_PRECISION_I64); + let wa_res2 = + calculate_weighted_average(PRICE_PRECISION_I64, PRICE_PRECISION_I64 + 1, 0, ONE_HOUR) + .unwrap(); + assert_eq!(wa_res2, PRICE_PRECISION_I64 + 1); +} diff --git a/programs/drift/src/ids.rs b/programs/drift/src/ids.rs index a4a246bca..0e627da3b 100644 --- a/programs/drift/src/ids.rs +++ b/programs/drift/src/ids.rs @@ -14,6 +14,14 @@ pub mod bonk_oracle { declare_id!("6bquU99ktV1VRiHDr8gMhDFt3kMfhCQo5nfNrg2Urvsn"); } +pub mod usdc_oracle { + use solana_program::declare_id; + #[cfg(feature = "mainnet-beta")] + declare_id!("Gnt27xtC473ZT2Mw5u8wZ68Z3gULkSTb5DuxJy7eJotD"); + #[cfg(not(feature = "mainnet-beta"))] + declare_id!("5SSkXsEKQepHHAewytPVwdej4epN1nxgLVM84L4KXgy7"); +} + pub mod serum_program { use solana_program::declare_id; #[cfg(feature = "mainnet-beta")] diff --git a/programs/drift/src/instructions/admin.rs b/programs/drift/src/instructions/admin.rs index 80b90e204..e2fde215d 100644 --- a/programs/drift/src/instructions/admin.rs +++ b/programs/drift/src/instructions/admin.rs @@ -7,7 +7,6 @@ use bytemuck::cast_slice; use serum_dex::state::ToAlignedBytes; use solana_program::msg; -use crate::controller; use crate::error::ErrorCode; use crate::get_then_update_id; use crate::instructions::constraints::*; @@ -51,6 +50,7 @@ use crate::validation::fee_structure::validate_fee_structure; use crate::validation::margin::{validate_margin, validate_margin_weights}; use crate::validation::perp_market::validate_perp_market; use crate::validation::spot_market::validate_borrow_rate; +use crate::{controller, QUOTE_PRECISION_I64}; use crate::{math, safe_increment}; pub fn handle_initialize(ctx: Context) -> Result<()> { @@ -513,6 +513,14 @@ pub fn handle_initialize_perp_market( .get_pyth_twap(&ctx.accounts.oracle, 1000000)?; (oracle_price, oracle_delay, last_oracle_price_twap) } + OracleSource::PythStableCoin => { + let OraclePriceData { + price: oracle_price, + delay: oracle_delay, + .. + } = get_pyth_price(&ctx.accounts.oracle, clock_slot, 1)?; + (oracle_price, oracle_delay, QUOTE_PRECISION_I64) + } OracleSource::Switchboard => { msg!("Switchboard oracle cant be used for perp market"); return Err(ErrorCode::InvalidOracle.into()); @@ -573,7 +581,9 @@ pub fn handle_initialize_perp_market( unrealized_pnl_max_imbalance: 0, liquidator_fee, if_liquidation_fee: LIQUIDATION_FEE_PRECISION / 100, // 1% - padding: [0; 51], + padding1: false, + quote_spot_market_index: 0, + padding: [0; 48], amm: AMM { oracle: *ctx.accounts.oracle.key, oracle_source, diff --git a/programs/drift/src/math/liquidation.rs b/programs/drift/src/math/liquidation.rs index 6daee692c..afd6dd07a 100644 --- a/programs/drift/src/math/liquidation.rs +++ b/programs/drift/src/math/liquidation.rs @@ -30,6 +30,7 @@ pub fn calculate_base_asset_amount_to_cover_margin_shortage( liquidation_fee: u32, if_liquidation_fee: u32, oracle_price: i64, + quote_oracle_price: i64, ) -> DriftResult { let margin_ratio = margin_ratio.safe_mul(LIQUIDATION_FEE_TO_MARGIN_PRECISION_RATIO)?; @@ -42,6 +43,8 @@ pub fn calculate_base_asset_amount_to_cover_margin_shortage( .safe_div( oracle_price .cast::()? + .safe_mul(quote_oracle_price.cast()?)? + .safe_div(PRICE_PRECISION)? .safe_mul(margin_ratio.safe_sub(liquidation_fee)?.cast()?)? .safe_div(LIQUIDATION_FEE_PRECISION_U128)? .safe_sub( diff --git a/programs/drift/src/math/liquidation/tests.rs b/programs/drift/src/math/liquidation/tests.rs index f55ca5ef4..2f1344b95 100644 --- a/programs/drift/src/math/liquidation/tests.rs +++ b/programs/drift/src/math/liquidation/tests.rs @@ -18,12 +18,46 @@ mod calculate_base_asset_amount_to_cover_margin_shortage { liquidation_fee, 0, oracle_price, + PRICE_PRECISION_I64, ) .unwrap(); assert_eq!(base_asset_amount, BASE_PRECISION_U64); // must lose 1 base } + #[test] + pub fn usdc_not_one() { + let margin_shortage = 10 * QUOTE_PRECISION; // $10 shortage + let margin_ratio = MARGIN_PRECISION as u32 / 10; // 10x leverage + let liquidation_fee = 0; // 0 percent + let oracle_price = 100 * PRICE_PRECISION_I64; // $100 / base + let quote_oracle_price = 99 * 10000; + let base_asset_amount = calculate_base_asset_amount_to_cover_margin_shortage( + margin_shortage, + margin_ratio, + liquidation_fee, + 0, + oracle_price, + quote_oracle_price, + ) + .unwrap(); + + assert_eq!(base_asset_amount, 1010101010); + + let quote_oracle_price = 101 * 10000; + let base_asset_amount = calculate_base_asset_amount_to_cover_margin_shortage( + margin_shortage, + margin_ratio, + liquidation_fee, + 0, + oracle_price, + quote_oracle_price, + ) + .unwrap(); + + assert_eq!(base_asset_amount, 990099009); + } + #[test] pub fn one_percent_liquidation_fee() { let margin_shortage = 10 * QUOTE_PRECISION; // $10 shortage @@ -36,6 +70,7 @@ mod calculate_base_asset_amount_to_cover_margin_shortage { liquidation_fee, 0, oracle_price, + PRICE_PRECISION_I64, ) .unwrap(); @@ -69,6 +104,7 @@ mod calculate_base_asset_amount_to_cover_margin_shortage { liquidation_fee, if_liquidation_fee, oracle_price, + PRICE_PRECISION_I64, ) .unwrap(); diff --git a/programs/drift/src/math/margin.rs b/programs/drift/src/math/margin.rs index ac6c6f694..e26e692c1 100644 --- a/programs/drift/src/math/margin.rs +++ b/programs/drift/src/math/margin.rs @@ -9,8 +9,8 @@ use crate::math::position::{ calculate_base_asset_value_with_oracle_price, }; -use crate::validate; use crate::validation; +use crate::{validate, PRICE_PRECISION_I128}; use crate::math::casting::Cast; use crate::math::funding::calculate_funding_payment; @@ -131,9 +131,12 @@ pub fn calculate_perp_position_value_and_pnl( market_position: &PerpPosition, market: &PerpMarket, oracle_price_data: &OraclePriceData, + quote_oracle_price: i64, + quote_oracle_twap: i64, margin_requirement_type: MarginRequirementType, user_custom_margin_ratio: u32, with_bounds: bool, + strict: bool, ) -> DriftResult<(u128, i128, u128)> { let unrealized_funding = calculate_funding_payment( if market_position.base_asset_amount > 0 { @@ -204,6 +207,17 @@ pub fn calculate_perp_position_value_and_pnl( valuation_price, )?; + // for calculating the perps value, since it's a liability, use the large of twap and quote oracle price + let quote_price = if strict { + quote_oracle_price.max(quote_oracle_twap) + } else { + quote_oracle_price + }; + + let worse_case_base_asset_value = worse_case_base_asset_value + .safe_mul(quote_price.cast()?)? + .safe_div(PRICE_PRECISION)?; + let margin_ratio = user_custom_margin_ratio.max(market.get_margin_ratio( worst_case_base_asset_amount.unsigned_abs(), margin_requirement_type, @@ -224,9 +238,19 @@ pub fn calculate_perp_position_value_and_pnl( let unrealized_asset_weight = market.get_unrealized_asset_weight(total_unrealized_pnl, margin_requirement_type)?; + let quote_price = if strict && unrealized_pnl > 0 { + quote_oracle_price.min(quote_oracle_twap) + } else if strict && unrealized_pnl < 0 { + quote_oracle_price.max(quote_oracle_twap) + } else { + quote_oracle_price + }; + let mut weighted_unrealized_pnl = total_unrealized_pnl - .safe_mul(unrealized_asset_weight as i128)? - .safe_div(SPOT_WEIGHT_PRECISION as i128)?; + .safe_mul(quote_price.cast()?)? + .safe_div(PRICE_PRECISION_I128)? + .safe_mul(unrealized_asset_weight.cast()?)? + .safe_div(SPOT_WEIGHT_PRECISION.cast()?)?; if with_bounds && margin_requirement_type == MarginRequirementType::Initial { // safety guard for dangerously configured perp market @@ -306,7 +330,7 @@ pub fn calculate_margin_requirement_and_total_collateral_and_liability_info( is_oracle_valid_for_action(oracle_validity, Some(DriftAction::MarginCalc))?; if spot_market.market_index == 0 { - let token_amount = spot_position.get_token_amount(&spot_market)?; + let token_amount = spot_position.get_signed_token_amount(&spot_market)?; if token_amount == 0 { validate!( spot_position.scaled_balance == 0, @@ -316,18 +340,33 @@ pub fn calculate_margin_requirement_and_total_collateral_and_liability_info( token_amount, )?; } + + let token_value = if strict { + get_strict_token_value( + token_amount, + spot_market.decimals, + oracle_price_data, + spot_market + .historical_oracle_data + .last_oracle_price_twap_5min, + )? + } else { + get_token_value(token_amount, spot_market.decimals, oracle_price_data.price)? + }; + match spot_position.balance_type { SpotBalanceType::Deposit => { - total_collateral = total_collateral.safe_add(token_amount.cast::()?)? + total_collateral = total_collateral.safe_add(token_value)? } SpotBalanceType::Borrow => { + let token_value = token_value.unsigned_abs(); let liability_weight = user_custom_margin_ratio.max(SPOT_WEIGHT_PRECISION); - let weighted_token_value = token_amount + let weighted_token_value = token_value .safe_mul(liability_weight.cast()?)? .safe_div(SPOT_WEIGHT_PRECISION_U128)?; validate!( - weighted_token_value >= token_amount, + weighted_token_value >= token_value, ErrorCode::InvalidMarginRatio, "weighted_token_value={} < abs(token_amount={}) in spot market_index={}", weighted_token_value, @@ -350,7 +389,7 @@ pub fn calculate_margin_requirement_and_total_collateral_and_liability_info( margin_requirement_plus_buffer = margin_requirement_plus_buffer.safe_add( calculate_margin_requirement_with_buffer( weighted_token_value, - token_amount, + token_value, margin_buffer_ratio, )?, )?; @@ -359,8 +398,8 @@ pub fn calculate_margin_requirement_and_total_collateral_and_liability_info( } } else { let signed_token_amount = spot_position.get_signed_token_amount(&spot_market)?; - let (worst_case_token_amount, worst_cast_quote_token_amount): (i128, i128) = - spot_position.get_worst_case_token_amounts( + let (worst_case_token_amount, worst_case_orders_value): (i128, i128) = spot_position + .get_worst_case_token_amount( &spot_market, oracle_price_data, if strict { @@ -404,7 +443,7 @@ pub fn calculate_margin_requirement_and_total_collateral_and_liability_info( // the worst case token value is the deposit/borrow amount * oracle + worst case order size * oracle let worst_case_token_value = - signed_token_value.safe_add(worst_cast_quote_token_amount.neg())?; + signed_token_value.safe_add(worst_case_orders_value.neg())?; margin_requirement = margin_requirement.safe_add(spot_position.margin_requirement_for_open_orders()?)?; @@ -475,14 +514,14 @@ pub fn calculate_margin_requirement_and_total_collateral_and_liability_info( } } - match worst_cast_quote_token_amount.cmp(&0) { + match worst_case_orders_value.cmp(&0) { Ordering::Greater => { total_collateral = - total_collateral.safe_add(worst_cast_quote_token_amount.cast::()?)? + total_collateral.safe_add(worst_case_orders_value.cast::()?)? } Ordering::Less => { let liability_weight = user_custom_margin_ratio.max(SPOT_WEIGHT_PRECISION); - let weighted_token_value = worst_cast_quote_token_amount + let weighted_token_value = worst_case_orders_value .unsigned_abs() .safe_mul(liability_weight.cast()?)? .safe_div(SPOT_WEIGHT_PRECISION_U128)?; @@ -493,7 +532,7 @@ pub fn calculate_margin_requirement_and_total_collateral_and_liability_info( margin_requirement_plus_buffer = margin_requirement_plus_buffer.safe_add( calculate_margin_requirement_with_buffer( weighted_token_value, - worst_cast_quote_token_amount.unsigned_abs(), + worst_case_orders_value.unsigned_abs(), margin_buffer_ratio, )?, )?; @@ -511,6 +550,27 @@ pub fn calculate_margin_requirement_and_total_collateral_and_liability_info( let market = &perp_market_map.get_ref(&market_position.market_index)?; + let (quote_oracle_price, quote_oracle_twap) = { + let quote_spot_market = spot_market_map.get_ref(&market.quote_spot_market_index)?; + let (quote_oracle_price_data, quote_oracle_validity) = oracle_map + .get_price_data_and_validity( + "e_spot_market.oracle, + quote_spot_market + .historical_oracle_data + .last_oracle_price_twap, + )?; + + all_oracles_valid &= + is_oracle_valid_for_action(quote_oracle_validity, Some(DriftAction::MarginCalc))?; + + ( + quote_oracle_price_data.price, + quote_spot_market + .historical_oracle_data + .last_oracle_price_twap_5min, + ) + }; + let (oracle_price_data, oracle_validity) = oracle_map.get_price_data_and_validity( &market.amm.oracle, market.amm.historical_oracle_data.last_oracle_price_twap, @@ -523,9 +583,12 @@ pub fn calculate_margin_requirement_and_total_collateral_and_liability_info( market_position, market, oracle_price_data, + quote_oracle_price, + quote_oracle_twap, margin_requirement_type, user_custom_margin_ratio, true, + strict, )?; margin_requirement = margin_requirement.safe_add(perp_margin_requirement)?; diff --git a/programs/drift/src/math/margin/tests.rs b/programs/drift/src/math/margin/tests.rs index 1b92b8f7a..128d06402 100644 --- a/programs/drift/src/math/margin/tests.rs +++ b/programs/drift/src/math/margin/tests.rs @@ -194,9 +194,12 @@ mod test { &market_position, &market, &oracle_price_data, + QUOTE_PRECISION_I64, + QUOTE_PRECISION_I64, MarginRequirementType::Initial, 0, false, + false, ) .unwrap(); @@ -308,9 +311,12 @@ mod test { &market_position, &market, &oracle_price_data, + QUOTE_PRECISION_I64, + QUOTE_PRECISION_I64, MarginRequirementType::Initial, 0, false, + false, ) .unwrap(); @@ -384,9 +390,12 @@ mod test { &market_position, &market, &oracle_price_data, + QUOTE_PRECISION_I64, + QUOTE_PRECISION_I64, MarginRequirementType::Initial, 0, false, + false, ) .unwrap(); @@ -446,9 +455,12 @@ mod test { &position, &market, &oracle_price_data, + QUOTE_PRECISION_I64, + QUOTE_PRECISION_I64, MarginRequirementType::Initial, 0, false, + false, ) .unwrap(); @@ -469,9 +481,12 @@ mod test { &position, &market, &oracle_price_data, + QUOTE_PRECISION_I64, + QUOTE_PRECISION_I64, MarginRequirementType::Initial, 0, false, + false, ) .unwrap(); @@ -515,9 +530,12 @@ mod test { &position, &market, &oracle_price_data, + QUOTE_PRECISION_I64, + QUOTE_PRECISION_I64, MarginRequirementType::Initial, 0, false, + false, ) .unwrap(); @@ -537,9 +555,12 @@ mod test { &position, &market, &oracle_price_data, + QUOTE_PRECISION_I64, + QUOTE_PRECISION_I64, MarginRequirementType::Initial, 0, false, + false, ) .unwrap(); @@ -565,7 +586,7 @@ mod calculate_margin_requirement_and_total_collateral { use crate::math::margin::{ calculate_margin_requirement_and_total_collateral, MarginRequirementType, }; - use crate::state::oracle::OracleSource; + use crate::state::oracle::{HistoricalOracleData, OracleSource}; use crate::state::oracle_map::OracleMap; use crate::state::perp_market::{MarketStatus, PerpMarket, AMM}; use crate::state::perp_market_map::PerpMarketMap; @@ -602,6 +623,7 @@ mod calculate_margin_requirement_and_total_collateral { 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); @@ -690,6 +712,7 @@ mod calculate_margin_requirement_and_total_collateral { 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); @@ -778,6 +801,7 @@ mod calculate_margin_requirement_and_total_collateral { 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); @@ -887,6 +911,7 @@ mod calculate_margin_requirement_and_total_collateral { 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); @@ -1051,6 +1076,7 @@ mod calculate_margin_requirement_and_total_collateral { 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); @@ -1168,6 +1194,7 @@ mod calculate_margin_requirement_and_total_collateral { 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); @@ -1288,8 +1315,6 @@ mod calculate_margin_requirement_and_total_collateral_and_liability_info { use anchor_lang::Owner; use solana_program::pubkey::Pubkey; - use crate::create_account_info; - use crate::create_anchor_account_info; use crate::math::constants::{ AMM_RESERVE_PRECISION, LIQUIDATION_FEE_PRECISION, PEG_PRECISION, QUOTE_PRECISION, SPOT_BALANCE_PRECISION, SPOT_BALANCE_PRECISION_U64, SPOT_CUMULATIVE_INTEREST_PRECISION, @@ -1298,7 +1323,7 @@ mod calculate_margin_requirement_and_total_collateral_and_liability_info { use crate::math::margin::{ calculate_margin_requirement_and_total_collateral_and_liability_info, MarginRequirementType, }; - use crate::state::oracle::OracleSource; + use crate::state::oracle::{HistoricalOracleData, OracleSource}; use crate::state::oracle_map::OracleMap; use crate::state::perp_market::{MarketStatus, PerpMarket, AMM}; use crate::state::perp_market_map::PerpMarketMap; @@ -1307,6 +1332,8 @@ mod calculate_margin_requirement_and_total_collateral_and_liability_info { use crate::state::user::{Order, OrderType, PerpPosition, SpotPosition, User}; use crate::test_utils::*; use crate::test_utils::{get_positions, get_pyth_price}; + use crate::{create_account_info, PRICE_PRECISION_I64}; + use crate::{create_anchor_account_info, BASE_PRECISION_I64}; #[test] fn no_perp_position_but_trigger_order() { @@ -1355,6 +1382,7 @@ mod calculate_margin_requirement_and_total_collateral_and_liability_info { 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); @@ -1466,6 +1494,7 @@ mod calculate_margin_requirement_and_total_collateral_and_liability_info { 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); @@ -1530,6 +1559,458 @@ mod calculate_margin_requirement_and_total_collateral_and_liability_info { assert_eq!(margin_requirement, QUOTE_PRECISION / 100); assert_eq!(num_of_liabilities, 1); } + + #[test] + pub fn usdc_less_than_1_with_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, + sol_oracle_account_info + ); + + let mut usdc_oracle_price = get_hardcoded_pyth_price(99 * 10000, 6); // $.99 + let usdc_oracle_price_key = + Pubkey::from_str("J83w4HKfqxwcq3BEMMkPFSppX3gqekLyLJBexebFVkix").unwrap(); + let pyth_program = crate::ids::pyth_program::id(); + create_account_info!( + usdc_oracle_price, + &usdc_oracle_price_key, + &pyth_program, + usdc_oracle_account_info + ); + let oracle_account_infos = Vec::from([sol_oracle_account_info, usdc_oracle_account_info]); + let mut oracle_map = + OracleMap::load(&mut oracle_account_infos.iter().peekable(), slot, None).unwrap(); + + let market_map = PerpMarketMap::empty(); + + let mut usdc_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, + deposit_balance: 10000 * SPOT_BALANCE_PRECISION, + liquidator_fee: 0, + historical_oracle_data: HistoricalOracleData::default_quote_oracle(), + oracle: usdc_oracle_price_key, + ..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: usdc_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::Deposit, + scaled_balance: SPOT_BALANCE_PRECISION_U64, + ..SpotPosition::default() + }; + let user = User { + orders: [Order::default(); 32], + perp_positions: [PerpPosition::default(); 8], + spot_positions, + ..User::default() + }; + + let (margin_requirement, total_collateral, _, _, _, _) = + calculate_margin_requirement_and_total_collateral_and_liability_info( + &user, + &market_map, + MarginRequirementType::Initial, + &spot_market_map, + &mut oracle_map, + None, + true, + ) + .unwrap(); + + assert_eq!(margin_requirement, 0); + assert_eq!(total_collateral, 990000); + + let mut spot_market = spot_market_map.get_ref_mut(&0).unwrap(); + spot_market.historical_oracle_data = HistoricalOracleData { + last_oracle_price_twap_5min: 95 * PRICE_PRECISION_I64 / 100, + ..HistoricalOracleData::default() + }; + drop(spot_market); + + let (margin_requirement, total_collateral, _, _, _, _) = + calculate_margin_requirement_and_total_collateral_and_liability_info( + &user, + &market_map, + MarginRequirementType::Initial, + &spot_market_map, + &mut oracle_map, + None, + true, + ) + .unwrap(); + + assert_eq!(margin_requirement, 0); + assert_eq!(total_collateral, 950000); + + let mut spot_market = spot_market_map.get_ref_mut(&0).unwrap(); + spot_market.historical_oracle_data = HistoricalOracleData { + last_oracle_price_twap_5min: 101 * PRICE_PRECISION_I64 / 100, + ..HistoricalOracleData::default() + }; + drop(spot_market); + + let (margin_requirement, total_collateral, _, _, _, _) = + calculate_margin_requirement_and_total_collateral_and_liability_info( + &user, + &market_map, + MarginRequirementType::Initial, + &spot_market_map, + &mut oracle_map, + None, + true, + ) + .unwrap(); + + assert_eq!(margin_requirement, 0); + // twap is 1.01, but oracle is .99, so we use oracle + assert_eq!(total_collateral, 990000); + } + + #[test] + pub fn usdc_more_than_1_with_borrow() { + 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, + sol_oracle_account_info + ); + + let mut usdc_oracle_price = get_hardcoded_pyth_price(101 * 10000, 6); // $1.01 + let usdc_oracle_price_key = + Pubkey::from_str("J83w4HKfqxwcq3BEMMkPFSppX3gqekLyLJBexebFVkix").unwrap(); + let pyth_program = crate::ids::pyth_program::id(); + create_account_info!( + usdc_oracle_price, + &usdc_oracle_price_key, + &pyth_program, + usdc_oracle_account_info + ); + let oracle_account_infos = Vec::from([sol_oracle_account_info, usdc_oracle_account_info]); + let mut oracle_map = + OracleMap::load(&mut oracle_account_infos.iter().peekable(), slot, None).unwrap(); + + let market_map = PerpMarketMap::empty(); + + 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, + borrow_balance: 1000 * SPOT_BALANCE_PRECISION, + liquidator_fee: 0, + historical_oracle_data: HistoricalOracleData::default_quote_oracle(), + oracle: usdc_oracle_price_key, + ..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: usdc_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: SPOT_BALANCE_PRECISION_U64, + ..SpotPosition::default() + }; + let user = User { + orders: [Order::default(); 32], + perp_positions: [PerpPosition::default(); 8], + spot_positions, + ..User::default() + }; + + let (margin_requirement, total_collateral, _, _, _, _) = + calculate_margin_requirement_and_total_collateral_and_liability_info( + &user, + &market_map, + MarginRequirementType::Initial, + &spot_market_map, + &mut oracle_map, + None, + true, + ) + .unwrap(); + + assert_eq!(margin_requirement, 1010000); + assert_eq!(total_collateral, 0); + + let mut spot_market = spot_market_map.get_ref_mut(&0).unwrap(); + spot_market.historical_oracle_data = HistoricalOracleData { + last_oracle_price_twap_5min: 102 * PRICE_PRECISION_I64 / 100, + ..HistoricalOracleData::default() + }; + drop(spot_market); + + let (margin_requirement, total_collateral, _, _, _, _) = + calculate_margin_requirement_and_total_collateral_and_liability_info( + &user, + &market_map, + MarginRequirementType::Initial, + &spot_market_map, + &mut oracle_map, + None, + true, + ) + .unwrap(); + + assert_eq!(margin_requirement, 1020000); + assert_eq!(total_collateral, 0); + + let mut spot_market = spot_market_map.get_ref_mut(&0).unwrap(); + spot_market.historical_oracle_data = HistoricalOracleData { + last_oracle_price_twap_5min: 99 * PRICE_PRECISION_I64 / 100, + ..HistoricalOracleData::default() + }; + drop(spot_market); + + let (margin_requirement, total_collateral, _, _, _, _) = + calculate_margin_requirement_and_total_collateral_and_liability_info( + &user, + &market_map, + MarginRequirementType::Initial, + &spot_market_map, + &mut oracle_map, + None, + true, + ) + .unwrap(); + + assert_eq!(total_collateral, 0); + // twap is .99, but oracle is 1.01, so we use oracle + assert_eq!(margin_requirement, 1010000); + } + + #[test] + pub fn usdc_not_1_with_perp_position() { + 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, + sol_oracle_account_info + ); + + let usdc_price = 101 * 10000; // $1.01 + let mut usdc_oracle_price = get_hardcoded_pyth_price(usdc_price, 6); + let usdc_oracle_price_key = + Pubkey::from_str("J83w4HKfqxwcq3BEMMkPFSppX3gqekLyLJBexebFVkiF").unwrap(); + let pyth_program = crate::ids::pyth_program::id(); + create_account_info!( + usdc_oracle_price, + &usdc_oracle_price_key, + &pyth_program, + usdc_oracle_account_info + ); + let oracle_account_infos = Vec::from([sol_oracle_account_info, usdc_oracle_account_info]); + let mut oracle_map = + OracleMap::load(&mut oracle_account_infos.iter().peekable(), slot, None).unwrap(); + + let mut usdc_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, + deposit_balance: 10000 * SPOT_BALANCE_PRECISION, + liquidator_fee: 0, + historical_oracle_data: HistoricalOracleData::default_price(usdc_price), + oracle: usdc_oracle_price_key, + ..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: usdc_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 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 spot_positions = [SpotPosition::default(); 8]; + spot_positions[0] = SpotPosition { + market_index: 0, + balance_type: SpotBalanceType::Deposit, + scaled_balance: 10 * SPOT_BALANCE_PRECISION_U64, + ..SpotPosition::default() + }; + let user = User { + orders: [Order::default(); 32], + perp_positions: get_positions(PerpPosition { + market_index: 0, + base_asset_amount: BASE_PRECISION_I64, + ..PerpPosition::default() + }), + spot_positions, + ..User::default() + }; + + let (margin_requirement, total_collateral, _, _, _, _) = + calculate_margin_requirement_and_total_collateral_and_liability_info( + &user, + &perp_market_map, + MarginRequirementType::Initial, + &spot_market_map, + &mut oracle_map, + None, + true, + ) + .unwrap(); + + assert_eq!(margin_requirement, 10100000); + assert_eq!(total_collateral, 10100000); + + let mut spot_market = spot_market_map.get_ref_mut(&0).unwrap(); + spot_market.historical_oracle_data = HistoricalOracleData { + last_oracle_price_twap_5min: 105 * PRICE_PRECISION_I64 / 100, + ..HistoricalOracleData::default() + }; + drop(spot_market); + + let (margin_requirement, total_collateral, _, _, _, _) = + calculate_margin_requirement_and_total_collateral_and_liability_info( + &user, + &perp_market_map, + MarginRequirementType::Initial, + &spot_market_map, + &mut oracle_map, + None, + true, + ) + .unwrap(); + + assert_eq!(margin_requirement, 10500000); + assert_eq!(total_collateral, 10100000); + + let mut spot_market = spot_market_map.get_ref_mut(&0).unwrap(); + spot_market.historical_oracle_data = HistoricalOracleData { + last_oracle_price_twap_5min: 95 * PRICE_PRECISION_I64 / 100, + ..HistoricalOracleData::default() + }; + drop(spot_market); + + let (margin_requirement, total_collateral, _, _, _, _) = + calculate_margin_requirement_and_total_collateral_and_liability_info( + &user, + &perp_market_map, + MarginRequirementType::Initial, + &spot_market_map, + &mut oracle_map, + None, + true, + ) + .unwrap(); + + assert_eq!(margin_requirement, 10100000); + assert_eq!(total_collateral, 9500000); + } } #[cfg(test)] @@ -1546,7 +2027,7 @@ mod calculate_max_withdrawable_amount { SPOT_CUMULATIVE_INTEREST_PRECISION, SPOT_WEIGHT_PRECISION, }; use crate::math::margin::calculate_max_withdrawable_amount; - use crate::state::oracle::OracleSource; + use crate::state::oracle::{HistoricalOracleData, OracleSource}; use crate::state::oracle_map::OracleMap; use crate::state::perp_market_map::PerpMarketMap; use crate::state::spot_market::{SpotBalanceType, SpotMarket}; @@ -1582,6 +2063,7 @@ mod calculate_max_withdrawable_amount { 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); @@ -1670,6 +2152,7 @@ mod calculate_max_withdrawable_amount { initial_liability_weight: SPOT_WEIGHT_PRECISION, maintenance_liability_weight: SPOT_WEIGHT_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); @@ -1757,6 +2240,7 @@ mod calculate_max_withdrawable_amount { initial_liability_weight: SPOT_WEIGHT_PRECISION, maintenance_liability_weight: SPOT_WEIGHT_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); diff --git a/programs/drift/src/math/orders.rs b/programs/drift/src/math/orders.rs index 0d48b5e54..52917c2ce 100644 --- a/programs/drift/src/math/orders.rs +++ b/programs/drift/src/math/orders.rs @@ -693,7 +693,18 @@ pub fn calculate_max_perp_order_size( let perp_market = perp_market_map.get_ref(&market_index)?; - let oracle_price_data = oracle_map.get_price_data(&perp_market.amm.oracle)?; + let oracle_price_data_price = oracle_map.get_price_data(&perp_market.amm.oracle)?.price; + + let quote_spot_market = spot_market_map.get_ref(&perp_market.quote_spot_market_index)?; + let quote_oracle_price = oracle_map + .get_price_data("e_spot_market.oracle)? + .price + .max( + quote_spot_market + .historical_oracle_data + .last_oracle_price_twap_5min, + ); + drop(quote_spot_market); let perp_position: &PerpPosition = &user.perp_positions[position_index]; let base_asset_amount = perp_position.base_asset_amount; @@ -737,7 +748,9 @@ pub fn calculate_max_perp_order_size( .safe_mul(MARGIN_PRECISION_U128.cast()?)? .safe_div(margin_ratio.cast()?)? .safe_mul(PRICE_PRECISION_I128)? - .safe_div(oracle_price_data.price.cast()?)? + .safe_div(oracle_price_data_price.cast()?)? + .safe_mul(PRICE_PRECISION_I128)? + .safe_div(quote_oracle_price.cast()?)? .cast::()?; let updated_margin_ratio = perp_market.get_margin_ratio( @@ -754,7 +767,9 @@ pub fn calculate_max_perp_order_size( .safe_mul(MARGIN_PRECISION_U128.cast()?)? .safe_div(updated_margin_ratio.cast()?)? .safe_mul(PRICE_PRECISION_I128)? - .safe_div(oracle_price_data.price.cast()?)? + .safe_div(oracle_price_data_price.cast()?)? + .safe_mul(PRICE_PRECISION_I128)? + .safe_div(quote_oracle_price.cast()?)? .cast::()?; } @@ -797,8 +812,8 @@ pub fn calculate_max_spot_order_size( let spot_position = user.get_spot_position(market_index)?; let signed_token_amount = spot_position.get_signed_token_amount(&spot_market)?; - let (worst_case_token_amount, worst_case_quote_amount) = spot_position - .get_worst_case_token_amounts( + let (worst_case_token_amount, worst_case_orders_value) = spot_position + .get_worst_case_token_amount( &spot_market, oracle_price_data, Some(twap), @@ -813,7 +828,7 @@ pub fn calculate_max_spot_order_size( )?; let worst_case_token_value_before = - token_value_before.safe_add(worst_case_quote_amount.neg())?; + token_value_before.safe_add(worst_case_orders_value.neg())?; // account for order flipping worst case base asset amount if worst_case_token_amount < 0 && direction == PositionDirection::Long { @@ -823,7 +838,7 @@ pub fn calculate_max_spot_order_size( &MarginRequirementType::Initial, )?; - let free_collateral_consumption_before = worst_case_quote_amount.safe_add( + let free_collateral_consumption_before = worst_case_orders_value.safe_add( worst_case_token_value_before .safe_mul(liability_weight.cast()?)? .safe_div(SPOT_WEIGHT_PRECISION.cast()?)?, @@ -873,7 +888,7 @@ pub fn calculate_max_spot_order_size( let free_collateral_contribution_before = worst_case_token_value_before .safe_mul(asset_weight.cast()?)? .safe_div(SPOT_WEIGHT_PRECISION.cast()?)? - .safe_add(worst_case_quote_amount)?; + .safe_add(worst_case_orders_value)?; let asks_to_flip = worst_case_token_amount .neg() diff --git a/programs/drift/src/math/orders/tests.rs b/programs/drift/src/math/orders/tests.rs index 6da52954e..10a56b633 100644 --- a/programs/drift/src/math/orders/tests.rs +++ b/programs/drift/src/math/orders/tests.rs @@ -1489,6 +1489,7 @@ mod calculate_max_spot_order_size { 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); @@ -1594,6 +1595,7 @@ mod calculate_max_spot_order_size { 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); @@ -1683,6 +1685,7 @@ mod calculate_max_spot_order_size { 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); @@ -1788,6 +1791,7 @@ mod calculate_max_spot_order_size { 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); @@ -1875,7 +1879,7 @@ mod calculate_max_perp_order_size { use crate::state::user::{Order, PerpPosition, SpotPosition, User}; use crate::test_utils::get_pyth_price; use crate::test_utils::*; - use crate::{create_account_info, PositionDirection}; + use crate::{create_account_info, PositionDirection, PRICE_PRECISION_I64}; use crate::{ create_anchor_account_info, MarketStatus, AMM_RESERVE_PRECISION, PEG_PRECISION, PRICE_PRECISION, @@ -1942,6 +1946,11 @@ mod calculate_max_perp_order_size { maintenance_asset_weight: SPOT_WEIGHT_PRECISION, deposit_balance: 10000 * SPOT_BALANCE_PRECISION, liquidator_fee: 0, + historical_oracle_data: HistoricalOracleData { + last_oracle_price_twap: PRICE_PRECISION_I64, + last_oracle_price_twap_5min: PRICE_PRECISION_I64, + ..HistoricalOracleData::default() + }, ..SpotMarket::default() }; create_anchor_account_info!(usdc_spot_market, SpotMarket, usdc_spot_market_account_info); @@ -2058,6 +2067,11 @@ mod calculate_max_perp_order_size { maintenance_asset_weight: SPOT_WEIGHT_PRECISION, deposit_balance: 10000 * SPOT_BALANCE_PRECISION, liquidator_fee: 0, + historical_oracle_data: HistoricalOracleData { + last_oracle_price_twap: PRICE_PRECISION_I64, + last_oracle_price_twap_5min: PRICE_PRECISION_I64, + ..HistoricalOracleData::default() + }, ..SpotMarket::default() }; create_anchor_account_info!(usdc_spot_market, SpotMarket, usdc_spot_market_account_info); @@ -2158,6 +2172,11 @@ mod calculate_max_perp_order_size { maintenance_asset_weight: SPOT_WEIGHT_PRECISION, deposit_balance: 10000 * SPOT_BALANCE_PRECISION, liquidator_fee: 0, + historical_oracle_data: HistoricalOracleData { + last_oracle_price_twap: PRICE_PRECISION_I64, + last_oracle_price_twap_5min: PRICE_PRECISION_I64, + ..HistoricalOracleData::default() + }, ..SpotMarket::default() }; create_anchor_account_info!(usdc_spot_market, SpotMarket, usdc_spot_market_account_info); @@ -2274,6 +2293,11 @@ mod calculate_max_perp_order_size { maintenance_asset_weight: SPOT_WEIGHT_PRECISION, deposit_balance: 10000 * SPOT_BALANCE_PRECISION, liquidator_fee: 0, + historical_oracle_data: HistoricalOracleData { + last_oracle_price_twap: PRICE_PRECISION_I64, + last_oracle_price_twap_5min: PRICE_PRECISION_I64, + ..HistoricalOracleData::default() + }, ..SpotMarket::default() }; create_anchor_account_info!(usdc_spot_market, SpotMarket, usdc_spot_market_account_info); diff --git a/programs/drift/src/math/stats.rs b/programs/drift/src/math/stats.rs index 628a8dd61..57210e4b1 100644 --- a/programs/drift/src/math/stats.rs +++ b/programs/drift/src/math/stats.rs @@ -28,6 +28,14 @@ pub fn calculate_weighted_average( let prev_twap_99 = data1.cast::()?.safe_mul(weight1.cast()?)?; let latest_price_01 = data2.cast::()?.safe_mul(weight2.cast()?)?; + if weight1 == 0 { + return Ok(data2); + } + + if weight2 == 0 { + return Ok(data1); + } + let bias: i64 = if weight2 > 1 { if latest_price_01 < prev_twap_99 { -1 diff --git a/programs/drift/src/state/oracle.rs b/programs/drift/src/state/oracle.rs index c93faf259..83f9662e3 100644 --- a/programs/drift/src/state/oracle.rs +++ b/programs/drift/src/state/oracle.rs @@ -96,6 +96,7 @@ pub enum OracleSource { QuoteAsset, Pyth1K, Pyth1M, + PythStableCoin, } impl Default for OracleSource { @@ -133,6 +134,7 @@ pub fn get_oracle_price( OracleSource::Pyth => get_pyth_price(price_oracle, clock_slot, 1), OracleSource::Pyth1K => get_pyth_price(price_oracle, clock_slot, 1000), OracleSource::Pyth1M => get_pyth_price(price_oracle, clock_slot, 1000000), + OracleSource::PythStableCoin => get_pyth_stable_coin_price(price_oracle, clock_slot), OracleSource::Switchboard => { msg!("Switchboard oracle not yet supported"); Err(crate::error::ErrorCode::InvalidOracle) @@ -201,6 +203,23 @@ pub fn get_pyth_price( }) } +pub fn get_pyth_stable_coin_price( + price_oracle: &AccountInfo, + clock_slot: u64, +) -> DriftResult { + let mut oracle_price_data = get_pyth_price(price_oracle, clock_slot, 1)?; + + let price = oracle_price_data.price; + let confidence = oracle_price_data.confidence; + let five_bps = 500_i64; + + if price.safe_sub(PRICE_PRECISION_I64)?.abs() <= five_bps.min(confidence.cast()?) { + oracle_price_data.price = PRICE_PRECISION_I64; + } + + Ok(oracle_price_data) +} + // pub fn get_switchboard_price( // _price_oracle: &AccountInfo, // _clock_slot: u64, diff --git a/programs/drift/src/state/oracle_map.rs b/programs/drift/src/state/oracle_map.rs index 09e8a3727..27b97bd35 100644 --- a/programs/drift/src/state/oracle_map.rs +++ b/programs/drift/src/state/oracle_map.rs @@ -1,5 +1,5 @@ use crate::error::{DriftResult, ErrorCode}; -use crate::ids::{bonk_oracle, pyth_program}; +use crate::ids::{bonk_oracle, pyth_program, usdc_oracle}; use crate::math::constants::PRICE_PRECISION_I64; use crate::math::oracle::{oracle_validity, OracleValidity}; use crate::state::oracle::{get_oracle_price, OraclePriceData, OracleSource}; @@ -42,8 +42,18 @@ impl<'a> OracleMap<'a> { .clone()) } + /// When switching to use usdc oracle, will start to enforce it on a specific slot + fn should_get_quote_asset_price_data(&self, pubkey: &Pubkey) -> bool { + #[cfg(feature = "mainnet-beta")] + return (self.slot < 187188369 && pubkey == &usdc_oracle::id()) + || pubkey == &Pubkey::default(); + #[cfg(not(feature = "mainnet-beta"))] + return (self.slot < 205430247 && pubkey == &usdc_oracle::id()) + || pubkey == &Pubkey::default(); + } + pub fn get_price_data(&mut self, pubkey: &Pubkey) -> DriftResult<&OraclePriceData> { - if pubkey == &Pubkey::default() { + if self.should_get_quote_asset_price_data(pubkey) { return Ok(&self.quote_asset_price_data); } @@ -74,7 +84,7 @@ impl<'a> OracleMap<'a> { pubkey: &Pubkey, last_oracle_price_twap: i64, ) -> DriftResult<(&OraclePriceData, OracleValidity)> { - if pubkey == &Pubkey::default() { + if self.should_get_quote_asset_price_data(pubkey) { return Ok((&self.quote_asset_price_data, OracleValidity::Valid)); } @@ -117,7 +127,7 @@ impl<'a> OracleMap<'a> { &mut self, pubkey: &Pubkey, ) -> DriftResult<(&OraclePriceData, &ValidityGuardRails)> { - if pubkey == &Pubkey::default() { + if self.should_get_quote_asset_price_data(pubkey) { let validity_guard_rails = &self.oracle_guard_rails.validity; return Ok((&self.quote_asset_price_data, validity_guard_rails)); } @@ -164,6 +174,8 @@ impl<'a> OracleMap<'a> { let oracle_source = if pubkey == bonk_oracle::id() { OracleSource::Pyth1M + } else if pubkey == usdc_oracle::id() { + OracleSource::PythStableCoin } else { OracleSource::Pyth }; @@ -213,6 +225,8 @@ impl<'a> OracleMap<'a> { let pubkey = account_info.key(); let oracle_source = if pubkey == bonk_oracle::id() { OracleSource::Pyth1M + } else if pubkey == usdc_oracle::id() { + OracleSource::PythStableCoin } else { OracleSource::Pyth }; diff --git a/programs/drift/src/state/perp_market.rs b/programs/drift/src/state/perp_market.rs index 6a5b611bf..3f0540a17 100644 --- a/programs/drift/src/state/perp_market.rs +++ b/programs/drift/src/state/perp_market.rs @@ -119,7 +119,9 @@ pub struct PerpMarket { pub status: MarketStatus, pub contract_type: ContractType, pub contract_tier: ContractTier, - pub padding: [u8; 51], + pub padding1: bool, + pub quote_spot_market_index: u16, + pub padding: [u8; 48], } impl Default for PerpMarket { @@ -150,7 +152,9 @@ impl Default for PerpMarket { status: MarketStatus::default(), contract_type: ContractType::default(), contract_tier: ContractTier::default(), - padding: [0; 51], + padding1: false, + quote_spot_market_index: 0, + padding: [0; 48], } } } @@ -566,7 +570,9 @@ impl AMM { pub fn get_oracle_twap(&self, price_oracle: &AccountInfo) -> DriftResult> { match self.oracle_source { - OracleSource::Pyth => Ok(Some(self.get_pyth_twap(price_oracle, 1)?)), + OracleSource::Pyth | OracleSource::PythStableCoin => { + Ok(Some(self.get_pyth_twap(price_oracle, 1)?)) + } OracleSource::Pyth1K => Ok(Some(self.get_pyth_twap(price_oracle, 1000)?)), OracleSource::Pyth1M => Ok(Some(self.get_pyth_twap(price_oracle, 1000000)?)), OracleSource::Switchboard => Ok(None), diff --git a/programs/drift/src/state/spot_market.rs b/programs/drift/src/state/spot_market.rs index 59c3735fa..044e2354a 100644 --- a/programs/drift/src/state/spot_market.rs +++ b/programs/drift/src/state/spot_market.rs @@ -6,9 +6,9 @@ use borsh::{BorshDeserialize, BorshSerialize}; use crate::error::DriftResult; use crate::instructions::SpotFulfillmentType; -#[cfg(test)] -use crate::math::constants::SPOT_CUMULATIVE_INTEREST_PRECISION; use crate::math::constants::{AMM_RESERVE_PRECISION, MARGIN_PRECISION, SPOT_WEIGHT_PRECISION_U128}; +#[cfg(test)] +use crate::math::constants::{PRICE_PRECISION_I64, SPOT_CUMULATIVE_INTEREST_PRECISION}; use crate::math::margin::{ calculate_size_discount_asset_weight, calculate_size_premium_liability_weight, MarginRequirementType, @@ -277,6 +277,11 @@ impl SpotMarket { maintenance_asset_weight: 10000, order_tick_size: 1, status: MarketStatus::Active, + historical_oracle_data: HistoricalOracleData { + last_oracle_price_twap: PRICE_PRECISION_I64, + last_oracle_price_twap_5min: PRICE_PRECISION_I64, + ..HistoricalOracleData::default() + }, ..SpotMarket::default() } } diff --git a/programs/drift/src/state/user.rs b/programs/drift/src/state/user.rs index 19f9b474b..9aa4608df 100644 --- a/programs/drift/src/state/user.rs +++ b/programs/drift/src/state/user.rs @@ -369,7 +369,7 @@ impl SpotPosition { ) } - pub fn get_worst_case_token_amounts( + pub fn get_worst_case_token_amount( &self, spot_market: &SpotMarket, oracle_price_data: &OraclePriceData, @@ -391,13 +391,13 @@ impl SpotPosition { }; if token_amount_all_bids_fill.abs() > token_amount_all_asks_fill.abs() { - let worst_case_quote_token_amount = + let worst_case_orders_value = get_token_value(-self.open_bids as i128, spot_market.decimals, oracle_price)?; - Ok((token_amount_all_bids_fill, worst_case_quote_token_amount)) + Ok((token_amount_all_bids_fill, worst_case_orders_value)) } else { - let worst_case_quote_token_amount = + let worst_case_orders_value = get_token_value(-self.open_asks as i128, spot_market.decimals, oracle_price)?; - Ok((token_amount_all_asks_fill, worst_case_quote_token_amount)) + Ok((token_amount_all_asks_fill, worst_case_orders_value)) } } } diff --git a/programs/drift/src/state/user/tests.rs b/programs/drift/src/state/user/tests.rs index 65d62c591..d38e01d49 100644 --- a/programs/drift/src/state/user/tests.rs +++ b/programs/drift/src/state/user/tests.rs @@ -593,7 +593,7 @@ mod get_claimable_pnl { } } -mod get_worst_case_token_amounts { +mod get_worst_case_token_amount { use crate::math::constants::{ PRICE_PRECISION_I64, QUOTE_PRECISION_I128, SPOT_BALANCE_PRECISION_U64, SPOT_CUMULATIVE_INTEREST_PRECISION, @@ -629,12 +629,12 @@ mod get_worst_case_token_amounts { has_sufficient_number_of_data_points: true, }; - let (worst_case_token_amount, worst_case_quote_token_amount) = spot_position - .get_worst_case_token_amounts(&spot_market, &oracle_price_data, None, None) + let (worst_case_token_amount, worst_case_orders_value) = spot_position + .get_worst_case_token_amount(&spot_market, &oracle_price_data, None, None) .unwrap(); assert_eq!(worst_case_token_amount, 10_i128.pow(9)); - assert_eq!(worst_case_quote_token_amount, -100 * QUOTE_PRECISION_I128); + assert_eq!(worst_case_orders_value, -100 * QUOTE_PRECISION_I128); } #[test] @@ -664,12 +664,12 @@ mod get_worst_case_token_amounts { has_sufficient_number_of_data_points: true, }; - let (worst_case_token_amount, worst_case_quote_token_amount) = spot_position - .get_worst_case_token_amounts(&spot_market, &oracle_price_data, None, None) + let (worst_case_token_amount, worst_case_orders_value) = spot_position + .get_worst_case_token_amount(&spot_market, &oracle_price_data, None, None) .unwrap(); assert_eq!(worst_case_token_amount, -(10_i128.pow(9))); - assert_eq!(worst_case_quote_token_amount, 100 * QUOTE_PRECISION_I128); + assert_eq!(worst_case_orders_value, 100 * QUOTE_PRECISION_I128); } #[test] @@ -699,12 +699,12 @@ mod get_worst_case_token_amounts { has_sufficient_number_of_data_points: true, }; - let (worst_case_token_amount, worst_case_quote_token_amount) = spot_position - .get_worst_case_token_amounts(&spot_market, &oracle_price_data, None, None) + let (worst_case_token_amount, worst_case_orders_value) = spot_position + .get_worst_case_token_amount(&spot_market, &oracle_price_data, None, None) .unwrap(); assert_eq!(worst_case_token_amount, 2 * 10_i128.pow(9)); - assert_eq!(worst_case_quote_token_amount, 0); + assert_eq!(worst_case_orders_value, 0); } #[test] @@ -734,12 +734,12 @@ mod get_worst_case_token_amounts { has_sufficient_number_of_data_points: true, }; - let (worst_case_token_amount, worst_case_quote_token_amount) = spot_position - .get_worst_case_token_amounts(&spot_market, &oracle_price_data, None, None) + let (worst_case_token_amount, worst_case_orders_value) = spot_position + .get_worst_case_token_amount(&spot_market, &oracle_price_data, None, None) .unwrap(); assert_eq!(worst_case_token_amount, -(10_i128.pow(9))); - assert_eq!(worst_case_quote_token_amount, 200 * QUOTE_PRECISION_I128); + assert_eq!(worst_case_orders_value, 200 * QUOTE_PRECISION_I128); } #[test] @@ -769,12 +769,12 @@ mod get_worst_case_token_amounts { has_sufficient_number_of_data_points: true, }; - let (worst_case_token_amount, worst_case_quote_token_amount) = spot_position - .get_worst_case_token_amounts(&spot_market, &oracle_price_data, None, None) + let (worst_case_token_amount, worst_case_orders_value) = spot_position + .get_worst_case_token_amount(&spot_market, &oracle_price_data, None, None) .unwrap(); assert_eq!(worst_case_token_amount, 3 * 10_i128.pow(9)); - assert_eq!(worst_case_quote_token_amount, -100 * QUOTE_PRECISION_I128); + assert_eq!(worst_case_orders_value, -100 * QUOTE_PRECISION_I128); } #[test] @@ -805,12 +805,12 @@ mod get_worst_case_token_amounts { has_sufficient_number_of_data_points: true, }; - let (worst_case_token_amount, worst_case_quote_token_amount) = spot_position - .get_worst_case_token_amounts(&spot_market, &oracle_price_data, None, None) + let (worst_case_token_amount, worst_case_orders_value) = spot_position + .get_worst_case_token_amount(&spot_market, &oracle_price_data, None, None) .unwrap(); assert_eq!(worst_case_token_amount, -2 * 10_i128.pow(9)); - assert_eq!(worst_case_quote_token_amount, 0); + assert_eq!(worst_case_orders_value, 0); } #[test] @@ -841,12 +841,12 @@ mod get_worst_case_token_amounts { has_sufficient_number_of_data_points: true, }; - let (worst_case_token_amount, worst_case_quote_token_amount) = spot_position - .get_worst_case_token_amounts(&spot_market, &oracle_price_data, None, None) + let (worst_case_token_amount, worst_case_orders_value) = spot_position + .get_worst_case_token_amount(&spot_market, &oracle_price_data, None, None) .unwrap(); assert_eq!(worst_case_token_amount, 3 * 10_i128.pow(9)); - assert_eq!(worst_case_quote_token_amount, -500 * QUOTE_PRECISION_I128); + assert_eq!(worst_case_orders_value, -500 * QUOTE_PRECISION_I128); } #[test] @@ -877,12 +877,12 @@ mod get_worst_case_token_amounts { has_sufficient_number_of_data_points: true, }; - let (worst_case_token_amount, worst_case_quote_token_amount) = spot_position - .get_worst_case_token_amounts(&spot_market, &oracle_price_data, None, None) + let (worst_case_token_amount, worst_case_orders_value) = spot_position + .get_worst_case_token_amount(&spot_market, &oracle_price_data, None, None) .unwrap(); assert_eq!(worst_case_token_amount, -3 * 10_i128.pow(9)); - assert_eq!(worst_case_quote_token_amount, 100 * QUOTE_PRECISION_I128); + assert_eq!(worst_case_orders_value, 100 * QUOTE_PRECISION_I128); } } diff --git a/sdk/src/driftClient.ts b/sdk/src/driftClient.ts index d8cfb9ba6..11fdcdef5 100644 --- a/sdk/src/driftClient.ts +++ b/sdk/src/driftClient.ts @@ -953,17 +953,32 @@ export class DriftClient { // if cache has more recent slot than user positions account slot, add market to remaining accounts // otherwise remove from slot if (slot > lastUserSlot) { - const marketAccount = this.getPerpMarketAccount(marketIndex); + const perpMarketAccount = this.getPerpMarketAccount(marketIndex); perpMarketAccountMap.set(marketIndex, { - pubkey: marketAccount.pubkey, + pubkey: perpMarketAccount.pubkey, isSigner: false, isWritable: false, }); - oracleAccountMap.set(marketAccount.amm.oracle.toString(), { - pubkey: marketAccount.amm.oracle, + oracleAccountMap.set(perpMarketAccount.amm.oracle.toString(), { + pubkey: perpMarketAccount.amm.oracle, isSigner: false, isWritable: false, }); + const spotMarketAccount = this.getSpotMarketAccount( + perpMarketAccount.quoteSpotMarketIndex + ); + spotMarketAccountMap.set(perpMarketAccount.quoteSpotMarketIndex, { + pubkey: spotMarketAccount.pubkey, + isSigner: false, + isWritable: false, + }); + if (!spotMarketAccount.oracle.equals(PublicKey.default)) { + oracleAccountMap.set(spotMarketAccount.oracle.toString(), { + pubkey: spotMarketAccount.oracle, + isSigner: false, + isWritable: false, + }); + } } else { this.perpMarketLastSlotCache.delete(marketIndex); } @@ -976,15 +991,15 @@ export class DriftClient { // if cache has more recent slot than user positions account slot, add market to remaining accounts // otherwise remove from slot if (slot > lastUserSlot) { - const marketAccount = this.getSpotMarketAccount(marketIndex); + const spotMarketAccount = this.getSpotMarketAccount(marketIndex); spotMarketAccountMap.set(marketIndex, { - pubkey: marketAccount.pubkey, + pubkey: spotMarketAccount.pubkey, isSigner: false, isWritable: false, }); - if (!marketAccount.oracle.equals(PublicKey.default)) { - oracleAccountMap.set(marketAccount.oracle.toString(), { - pubkey: marketAccount.oracle, + if (!spotMarketAccount.oracle.equals(PublicKey.default)) { + oracleAccountMap.set(spotMarketAccount.oracle.toString(), { + pubkey: spotMarketAccount.oracle, isSigner: false, isWritable: false, }); @@ -996,50 +1011,80 @@ export class DriftClient { } if (params.readablePerpMarketIndex !== undefined) { - const marketAccount = this.getPerpMarketAccount( + const perpMarketAccount = this.getPerpMarketAccount( params.readablePerpMarketIndex ); perpMarketAccountMap.set(params.readablePerpMarketIndex, { - pubkey: marketAccount.pubkey, + pubkey: perpMarketAccount.pubkey, isSigner: false, isWritable: false, }); - oracleAccountMap.set(marketAccount.amm.oracle.toString(), { - pubkey: marketAccount.amm.oracle, + oracleAccountMap.set(perpMarketAccount.amm.oracle.toString(), { + pubkey: perpMarketAccount.amm.oracle, isSigner: false, isWritable: false, }); + const spotMarketAccount = this.getSpotMarketAccount( + perpMarketAccount.quoteSpotMarketIndex + ); + spotMarketAccountMap.set(perpMarketAccount.quoteSpotMarketIndex, { + pubkey: spotMarketAccount.pubkey, + isSigner: false, + isWritable: false, + }); + if (!spotMarketAccount.oracle.equals(PublicKey.default)) { + oracleAccountMap.set(spotMarketAccount.oracle.toString(), { + pubkey: spotMarketAccount.oracle, + isSigner: false, + isWritable: false, + }); + } + } + + if (params.readableSpotMarketIndexes !== undefined) { + for (const readableSpotMarketIndex of params.readableSpotMarketIndexes) { + const spotMarketAccount = this.getSpotMarketAccount( + readableSpotMarketIndex + ); + spotMarketAccountMap.set(readableSpotMarketIndex, { + pubkey: spotMarketAccount.pubkey, + isSigner: false, + isWritable: false, + }); + if (!spotMarketAccount.oracle.equals(PublicKey.default)) { + oracleAccountMap.set(spotMarketAccount.oracle.toString(), { + pubkey: spotMarketAccount.oracle, + isSigner: false, + isWritable: false, + }); + } + } } if (params.writablePerpMarketIndexes !== undefined) { for (const writablePerpMarketIndex of params.writablePerpMarketIndexes) { - const marketAccount = this.getPerpMarketAccount( + const perpMarketAccount = this.getPerpMarketAccount( writablePerpMarketIndex ); perpMarketAccountMap.set(writablePerpMarketIndex, { - pubkey: marketAccount.pubkey, + pubkey: perpMarketAccount.pubkey, isSigner: false, isWritable: true, }); - oracleAccountMap.set(marketAccount.amm.oracle.toString(), { - pubkey: marketAccount.amm.oracle, + oracleAccountMap.set(perpMarketAccount.amm.oracle.toString(), { + pubkey: perpMarketAccount.amm.oracle, isSigner: false, isWritable: false, }); - } - } - - if (params.readableSpotMarketIndexes !== undefined) { - for (const readableSpotMarketIndex of params.readableSpotMarketIndexes) { const spotMarketAccount = this.getSpotMarketAccount( - readableSpotMarketIndex + perpMarketAccount.quoteSpotMarketIndex ); - spotMarketAccountMap.set(readableSpotMarketIndex, { + spotMarketAccountMap.set(perpMarketAccount.quoteSpotMarketIndex, { pubkey: spotMarketAccount.pubkey, isSigner: false, isWritable: false, }); - if (spotMarketAccount.marketIndex !== 0) { + if (!spotMarketAccount.oracle.equals(PublicKey.default)) { oracleAccountMap.set(spotMarketAccount.oracle.toString(), { pubkey: spotMarketAccount.oracle, isSigner: false, @@ -1109,27 +1154,52 @@ export class DriftClient { !spotPosition.openAsks.eq(ZERO) || !spotPosition.openBids.eq(ZERO) ) { + const quoteSpotMarket = this.getQuoteSpotMarketAccount(); spotMarketAccountMap.set(QUOTE_SPOT_MARKET_INDEX, { - pubkey: this.getQuoteSpotMarketAccount().pubkey, + pubkey: quoteSpotMarket.pubkey, isSigner: false, isWritable: false, }); + if (!quoteSpotMarket.oracle.equals(PublicKey.default)) { + oracleAccountMap.set(quoteSpotMarket.oracle.toString(), { + pubkey: quoteSpotMarket.oracle, + isSigner: false, + isWritable: false, + }); + } } } } for (const position of userAccount.perpPositions) { if (!positionIsAvailable(position)) { - const market = this.getPerpMarketAccount(position.marketIndex); + const perpMarketAccount = this.getPerpMarketAccount( + position.marketIndex + ); perpMarketAccountMap.set(position.marketIndex, { - pubkey: market.pubkey, + pubkey: perpMarketAccount.pubkey, isWritable: false, isSigner: false, }); - oracleAccountMap.set(market.amm.oracle.toString(), { - pubkey: market.amm.oracle, + oracleAccountMap.set(perpMarketAccount.amm.oracle.toString(), { + pubkey: perpMarketAccount.amm.oracle, isWritable: false, isSigner: false, }); + const spotMarketAccount = this.getSpotMarketAccount( + perpMarketAccount.quoteSpotMarketIndex + ); + spotMarketAccountMap.set(perpMarketAccount.quoteSpotMarketIndex, { + pubkey: spotMarketAccount.pubkey, + isSigner: false, + isWritable: false, + }); + if (!spotMarketAccount.oracle.equals(PublicKey.default)) { + oracleAccountMap.set(spotMarketAccount.oracle.toString(), { + pubkey: spotMarketAccount.oracle, + isSigner: false, + isWritable: false, + }); + } } } } diff --git a/sdk/src/factory/oracleClient.ts b/sdk/src/factory/oracleClient.ts index 84e2e40f9..7b5feae67 100644 --- a/sdk/src/factory/oracleClient.ts +++ b/sdk/src/factory/oracleClient.ts @@ -22,6 +22,10 @@ export function getOracleClient( return new PythClient(connection, new BN(1000000)); } + if (isVariant(oracleSource, 'pythStableCoin')) { + return new PythClient(connection, undefined, true); + } + // if (isVariant(oracleSource, 'switchboard')) { // return new SwitchboardClient(connection); // } diff --git a/sdk/src/idl/drift.json b/sdk/src/idl/drift.json index 4e3c04cf7..5bb688fd1 100644 --- a/sdk/src/idl/drift.json +++ b/sdk/src/idl/drift.json @@ -4081,12 +4081,20 @@ "defined": "ContractTier" } }, + { + "name": "padding1", + "type": "bool" + }, + { + "name": "quoteSpotMarketIndex", + "type": "u16" + }, { "name": "padding", "type": { "array": [ "u8", - 51 + 48 ] } } @@ -6383,6 +6391,9 @@ }, { "name": "Pyth1M" + }, + { + "name": "PythStableCoin" } ] } diff --git a/sdk/src/oracles/pythClient.ts b/sdk/src/oracles/pythClient.ts index a98c3e60e..e41792bb9 100644 --- a/sdk/src/oracles/pythClient.ts +++ b/sdk/src/oracles/pythClient.ts @@ -2,15 +2,26 @@ import { parsePriceData } from '@pythnetwork/client'; import { Connection, PublicKey } from '@solana/web3.js'; import { OracleClient, OraclePriceData } from './types'; import { BN } from '@project-serum/anchor'; -import { ONE, PRICE_PRECISION, TEN } from '../constants/numericConstants'; +import { + ONE, + PRICE_PRECISION, + QUOTE_PRECISION, + TEN, +} from '../constants/numericConstants'; export class PythClient implements OracleClient { private connection: Connection; private multiple: BN; + private stableCoin: boolean; - public constructor(connection: Connection, multiple = ONE) { + public constructor( + connection: Connection, + multiple = ONE, + stableCoin = false + ) { this.connection = connection; this.multiple = multiple; + this.stableCoin = stableCoin; } public async getOraclePriceData( @@ -22,18 +33,24 @@ export class PythClient implements OracleClient { public getOraclePriceDataFromBuffer(buffer: Buffer): OraclePriceData { const priceData = parsePriceData(buffer); + const confidence = convertPythPrice( + priceData.confidence, + priceData.exponent, + this.multiple + ); + let price = convertPythPrice( + priceData.aggregate.price, + priceData.exponent, + this.multiple + ); + if (this.stableCoin) { + price = getStableCoinPrice(price, confidence); + } + return { - price: convertPythPrice( - priceData.aggregate.price, - priceData.exponent, - this.multiple - ), + price, slot: new BN(priceData.lastSlot.toString()), - confidence: convertPythPrice( - priceData.confidence, - priceData.exponent, - this.multiple - ), + confidence, twap: convertPythPrice( priceData.twap.value, priceData.exponent, @@ -60,3 +77,12 @@ export function convertPythPrice( .mul(PRICE_PRECISION) .div(pythPrecision); } + +const fiveBPS = new BN(500); +function getStableCoinPrice(price: BN, confidence: BN): BN { + if (price.sub(QUOTE_PRECISION).abs().lt(BN.min(confidence, fiveBPS))) { + return QUOTE_PRECISION; + } else { + return price; + } +} diff --git a/sdk/src/types.ts b/sdk/src/types.ts index 4c8971dec..11e2cea37 100644 --- a/sdk/src/types.ts +++ b/sdk/src/types.ts @@ -80,6 +80,7 @@ export class OracleSource { static readonly PYTH_1M = { pyth1M: {} }; // static readonly SWITCHBOARD = { switchboard: {} }; static readonly QUOTE_ASSET = { quoteAsset: {} }; + static readonly PYTH_STABLE_COIN = { pythStableCoin: {} }; } export class OrderType { @@ -560,6 +561,7 @@ export type PerpMarketAccount = { quoteSettledInsurance: BN; quoteMaxInsurance: BN; }; + quoteSpotMarketIndex: number; }; export type HistoricalOracleData = { diff --git a/sdk/src/user.ts b/sdk/src/user.ts index 979ebe373..b54355916 100644 --- a/sdk/src/user.ts +++ b/sdk/src/user.ts @@ -424,7 +424,8 @@ export class User { return this.getTotalPerpPositionValue( marginCategory, liquidationBuffer, - true + true, + strict ).add( this.getSpotMarketLiabilityValue( undefined, @@ -467,9 +468,9 @@ export class User { public getUnrealizedPNL( withFunding?: boolean, marketIndex?: number, - withWeightMarginCategory?: MarginCategory + withWeightMarginCategory?: MarginCategory, + strict = false ): BN { - const quoteSpotMarket = this.driftClient.getQuoteSpotMarketAccount(); return this.getActivePerpPositions() .filter((pos) => (marketIndex ? pos.marketIndex === marketIndex : true)) .reduce((unrealizedPnl, perpPosition) => { @@ -480,6 +481,13 @@ export class User { market.marketIndex ); + const quoteSpotMarket = this.driftClient.getSpotMarketAccount( + market.quoteSpotMarketIndex + ); + const quoteOraclePriceData = this.getOracleDataForSpotMarket( + market.quoteSpotMarketIndex + ); + if (perpPosition.lpShares.gt(ZERO)) { perpPosition = this.getSettledLPPosition(perpPosition.marketIndex)[0]; } @@ -491,6 +499,25 @@ export class User { oraclePriceData ); + let quotePrice; + if (strict && positionUnrealizedPnl.gt(ZERO)) { + quotePrice = BN.min( + quoteOraclePriceData.price, + quoteSpotMarket.historicalOracleData.lastOraclePriceTwap5Min + ); + } else if (strict && positionUnrealizedPnl.lt(ZERO)) { + quotePrice = BN.max( + quoteOraclePriceData.price, + quoteSpotMarket.historicalOracleData.lastOraclePriceTwap5Min + ); + } else { + quotePrice = quoteOraclePriceData.price; + } + + positionUnrealizedPnl = positionUnrealizedPnl + .mul(quotePrice) + .div(new BN(PRICE_PRECISION)); + if (withWeightMarginCategory !== undefined) { if (positionUnrealizedPnl.gt(ZERO)) { positionUnrealizedPnl = positionUnrealizedPnl @@ -558,55 +585,60 @@ export class User { const spotMarketAccount: SpotMarketAccount = this.driftClient.getSpotMarketAccount(spotPosition.marketIndex); + const oraclePriceData = this.getOracleDataForSpotMarket( + spotPosition.marketIndex + ); + if ( spotPosition.marketIndex === QUOTE_SPOT_MARKET_INDEX && countForQuote ) { - if (isVariant(spotPosition.balanceType, 'borrow')) { - const tokenAmount = getTokenAmount( + const tokenAmount = getSignedTokenAmount( + getTokenAmount( spotPosition.scaledBalance, spotMarketAccount, spotPosition.balanceType - ); - - let weight = SPOT_MARKET_WEIGHT_PRECISION; - if (marginCategory === 'Initial') { - weight = BN.max( - weight, - new BN(this.getUserAccount().maxMarginRatio) - ); - } + ), + spotPosition.balanceType + ); - const weightedTokenValue = tokenAmount - .mul(weight) - .div(SPOT_MARKET_WEIGHT_PRECISION); + if (isVariant(spotPosition.balanceType, 'borrow')) { + const weightedTokenValue = this.getSpotLiabilityValue( + tokenAmount, + oraclePriceData, + spotMarketAccount, + marginCategory, + liquidationBuffer, + strict, + now + ).abs(); netQuoteValue = netQuoteValue.sub(weightedTokenValue); - - continue; } else { - const tokenAmount = getTokenAmount( - spotPosition.scaledBalance, + const weightedTokenValue = this.getSpotAssetValue( + tokenAmount, + oraclePriceData, spotMarketAccount, - spotPosition.balanceType + marginCategory, + strict, + now ); - netQuoteValue = netQuoteValue.add(tokenAmount); - - continue; + netQuoteValue = netQuoteValue.add(weightedTokenValue); } - } - const oraclePriceData = this.getOracleDataForSpotMarket( - spotPosition.marketIndex - ); + continue; + } if (!includeOpenOrders && countForBase) { if (isVariant(spotPosition.balanceType, 'borrow')) { - const tokenAmount = getTokenAmount( - spotPosition.scaledBalance, - spotMarketAccount, - spotPosition.balanceType + const tokenAmount = getSignedTokenAmount( + getTokenAmount( + spotPosition.scaledBalance, + spotMarketAccount, + spotPosition.balanceType + ), + SpotBalanceType.BORROW ); const liabilityValue = this.getSpotLiabilityValue( tokenAmount, @@ -616,7 +648,7 @@ export class User { liquidationBuffer, strict, now - ); + ).abs(); totalLiabilityValue = totalLiabilityValue.add(liabilityValue); continue; @@ -662,14 +694,14 @@ export class User { if (worstCaseTokenAmount.lt(ZERO) && countForBase) { const baseLiabilityValue = this.getSpotLiabilityValue( - worstCaseTokenAmount.abs(), + worstCaseTokenAmount, oraclePriceData, spotMarketAccount, marginCategory, liquidationBuffer, strict, now - ); + ).abs(); totalLiabilityValue = totalLiabilityValue.add(baseLiabilityValue); } @@ -738,7 +770,7 @@ export class User { ): BN { let liabilityValue = null; - if (strict && spotMarketAccount.marketIndex != QUOTE_SPOT_MARKET_INDEX) { + if (strict) { const estOracleTwap = calculateLiveOracleTwap( spotMarketAccount.historicalOracleData, oraclePriceData, @@ -809,7 +841,7 @@ export class User { now?: BN ): BN { let assetValue = null; - if (strict && spotMarketAccount.marketIndex != QUOTE_SPOT_MARKET_INDEX) { + if (strict) { const estOracleTwap = calculateLiveOracleTwap( spotMarketAccount.historicalOracleData, oraclePriceData, @@ -886,10 +918,16 @@ export class User { * calculates TotalCollateral: collateral + unrealized pnl * @returns : Precision QUOTE_PRECISION */ - public getTotalCollateral(marginCategory: MarginCategory = 'Initial'): BN { - return this.getSpotMarketAssetValue(undefined, marginCategory, true).add( - this.getUnrealizedPNL(true, undefined, marginCategory) - ); + public getTotalCollateral( + marginCategory: MarginCategory = 'Initial', + strict = false + ): BN { + return this.getSpotMarketAssetValue( + undefined, + marginCategory, + true, + strict + ).add(this.getUnrealizedPNL(true, undefined, marginCategory, strict)); } /** @@ -941,7 +979,8 @@ export class User { getTotalPerpPositionValue( marginCategory?: MarginCategory, liquidationBuffer?: BN, - includeOpenOrders?: boolean + includeOpenOrders?: boolean, + strict = false ): BN { return this.getActivePerpPositions().reduce( (totalPerpValue, perpPosition) => { @@ -1011,7 +1050,27 @@ export class User { marginRatio = ZERO; } + const quoteSpotMarket = this.driftClient.getSpotMarketAccount( + market.quoteSpotMarketIndex + ); + const quoteOraclePriceData = + this.driftClient.getOraclePriceDataAndSlot( + quoteSpotMarket.oracle + ).data; + + let quotePrice; + if (strict) { + quotePrice = BN.max( + quoteOraclePriceData.price, + quoteSpotMarket.historicalOracleData.lastOraclePriceTwap5Min + ); + } else { + quotePrice = quoteOraclePriceData.price; + } + baseAssetValue = baseAssetValue + .mul(quotePrice) + .div(PRICE_PRECISION) .mul(marginRatio) .div(MARGIN_PRECISION); diff --git a/sdk/tests/dlob/helpers.ts b/sdk/tests/dlob/helpers.ts index 83e7c4728..eee3ae550 100644 --- a/sdk/tests/dlob/helpers.ts +++ b/sdk/tests/dlob/helpers.ts @@ -172,6 +172,7 @@ export const mockPerpMarkets: Array = [ quoteSettledInsurance: new BN(0), quoteMaxInsurance: new BN(0), }, + quoteSpotMarketIndex: 0, }, { status: MarketStatus.INITIALIZED, @@ -208,6 +209,7 @@ export const mockPerpMarkets: Array = [ quoteSettledInsurance: new BN(0), quoteMaxInsurance: new BN(0), }, + quoteSpotMarketIndex: 0, }, { status: MarketStatus.INITIALIZED, @@ -244,6 +246,7 @@ export const mockPerpMarkets: Array = [ quoteSettledInsurance: new BN(0), quoteMaxInsurance: new BN(0), }, + quoteSpotMarketIndex: 0, }, ]; diff --git a/tests/imbalancePerpPnl.ts b/tests/imbalancePerpPnl.ts index c20ab4a65..9545bac3b 100644 --- a/tests/imbalancePerpPnl.ts +++ b/tests/imbalancePerpPnl.ts @@ -411,7 +411,7 @@ describe('imbalanced large perp pnl w/ borrow hitting limits', () => { ); console.log('pnlimbalance00:', imbalance00.toString()); - assert(imbalance00.eq(new BN(-9821952))); + assert(imbalance00.eq(new BN(-1009821952))); const bank0Value1p5 = driftClientLoserUser.getSpotMarketAssetValue(0); console.log('uL.bank0Value1p5:', bank0Value1p5.toString()); @@ -566,7 +566,7 @@ describe('imbalanced large perp pnl w/ borrow hitting limits', () => { ); console.log('pnlimbalance:', imbalance.toString()); - assert(imbalance.eq(new BN(44462178048))); //44k! :o + assert(imbalance.eq(new BN(43462178048))); //44k! :o console.log( 'lastOraclePrice:', @@ -607,7 +607,7 @@ describe('imbalanced large perp pnl w/ borrow hitting limits', () => { ); console.log('pnlimbalance:', imbalance.toString()); - assert(imbalance.eq(new BN(44462178048))); //44k! :o + assert(imbalance.eq(new BN(43462178048))); //44k! :o console.log( 'lastOraclePrice:', @@ -686,7 +686,7 @@ describe('imbalanced large perp pnl w/ borrow hitting limits', () => { ); console.log('pnlimbalance:', imbalance.toString()); - assert(imbalance.eq(new BN(44_462_178_048))); //44k still :o + assert(imbalance.eq(new BN(43462178048))); //44k still :o assert(perpMarket.insuranceClaim.revenueWithdrawSinceLastSettle.eq(ZERO)); console.log('pnlimbalance:', imbalance.toString()); @@ -883,8 +883,8 @@ describe('imbalanced large perp pnl w/ borrow hitting limits', () => { ); console.log('pnlimbalance:', imbalance.toString()); - assert(imbalance.lt(new BN(44454544927 + 20000))); //44k still :o - assert(imbalance.gt(new BN(44454544927 - 20000))); //44k still :o + assert(imbalance.lt(new BN(43454544927 + 20000))); //44k still :o + assert(imbalance.gt(new BN(43454544927 - 20000))); //44k still :o console.log( 'revenueWithdrawSinceLastSettle:', diff --git a/tests/liquidityProvider.ts b/tests/liquidityProvider.ts index f66b8bcb0..47a43e287 100644 --- a/tests/liquidityProvider.ts +++ b/tests/liquidityProvider.ts @@ -358,7 +358,7 @@ describe('liquidity providing', () => { const newInitMarginReq = driftClientUser.getInitialMarginRequirement(); console.log(initMarginReq.toString(), '->', newInitMarginReq.toString()); - assert(newInitMarginReq.eq(new BN(8283999))); + assert(newInitMarginReq.eq(new BN(8284008))); // ensure margin calcs didnt modify user position const _position = driftClientUser.getPerpPosition(0); diff --git a/tests/maxLeverageOrderParams.ts b/tests/maxLeverageOrderParams.ts index c88bbb506..57e33d3f2 100644 --- a/tests/maxLeverageOrderParams.ts +++ b/tests/maxLeverageOrderParams.ts @@ -192,7 +192,7 @@ describe('max leverage order params', () => { ); let leverage = driftClient.getUser().getLeverage().toNumber() / 10000; - assert(leverage === 4.995); + assert(leverage === 4.9949); await driftClient.cancelOrderByUserId(1); @@ -207,7 +207,7 @@ describe('max leverage order params', () => { ); leverage = driftClient.getUser().getLeverage().toNumber() / 10000; - assert(leverage === 4.995); + assert(leverage === 4.9949); await driftClient.cancelOrderByUserId(1); });