diff --git a/programs/mango-v4/src/health/account_retriever.rs b/programs/mango-v4/src/health/account_retriever.rs index 7ffd66919..31b47cdb0 100644 --- a/programs/mango-v4/src/health/account_retriever.rs +++ b/programs/mango-v4/src/health/account_retriever.rs @@ -47,6 +47,7 @@ pub trait AccountRetriever { /// 3. PerpMarket accounts, in the order of account.perps.iter_active_accounts() /// 4. PerpMarket oracle accounts, in the order of the perp market accounts /// 5. serum3 OpenOrders accounts, in the order of account.serum3.iter_active() +/// 6. fallback oracle accounts, order and existence of accounts is not guaranteed pub struct FixedOrderAccountRetriever { pub ais: Vec, pub n_banks: usize, @@ -54,6 +55,7 @@ pub struct FixedOrderAccountRetriever { pub begin_perp: usize, pub begin_serum3: usize, pub staleness_slot: Option, + pub fallback_oracle_ais: Vec, } pub fn new_fixed_order_account_retriever<'a, 'info>( @@ -66,19 +68,22 @@ pub fn new_fixed_order_account_retriever<'a, 'info>( let expected_ais = active_token_len * 2 // banks + oracles + active_perp_len * 2 // PerpMarkets + Oracles + active_serum3_len; // open_orders - require_msg_typed!(ais.len() == expected_ais, MangoError::InvalidHealthAccountCount, + require_msg_typed!(ais.len() >= expected_ais, MangoError::InvalidHealthAccountCount, "received {} accounts but expected {} ({} banks, {} bank oracles, {} perp markets, {} perp oracles, {} serum3 oos)", ais.len(), expected_ais, active_token_len, active_token_len, active_perp_len, active_perp_len, active_serum3_len ); + let fixed_ais = AccountInfoRef::borrow_slice(&ais[..expected_ais])?; + let fallback_oracle_ais = AccountInfoRef::borrow_slice(&ais[expected_ais..])?; Ok(FixedOrderAccountRetriever { - ais: AccountInfoRef::borrow_slice(ais)?, + ais: fixed_ais, n_banks: active_token_len, n_perps: active_perp_len, begin_perp: active_token_len * 2, begin_serum3: active_token_len * 2 + active_perp_len * 2, staleness_slot: Some(Clock::get()?.slot), + fallback_oracle_ais, }) } @@ -103,11 +108,6 @@ impl FixedOrderAccountRetriever { Ok(market) } - fn oracle_price_bank(&self, account_index: usize, bank: &Bank) -> Result { - let oracle = &self.ais[account_index]; - bank.oracle_price(oracle, self.staleness_slot) - } - fn oracle_price_perp(&self, account_index: usize, perp_market: &PerpMarket) -> Result { let oracle = &self.ais[account_index]; perp_market.oracle_price(oracle, self.staleness_slot) @@ -134,7 +134,14 @@ impl AccountRetriever for FixedOrderAccountRetriever { })?; let oracle_index = self.n_banks + active_token_position_index; - let oracle_price = self.oracle_price_bank(oracle_index, bank).with_context(|| { + let oracle = &self.ais[oracle_index]; + let fallback_opt = self + .fallback_oracle_ais + .iter() + .find(|ai| ai.key() == &bank.fallback_oracle); + let oracle_price_result = + bank.oracle_price_with_fallback(oracle, fallback_opt, self.staleness_slot); + let oracle_price = oracle_price_result.with_context(|| { format!( "getting oracle for bank with health account index {} and token index {}, passed account {}", bank_account_index, diff --git a/programs/mango-v4/src/instructions/stub_oracle_create.rs b/programs/mango-v4/src/instructions/stub_oracle_create.rs index 211c4108a..05f5ab0af 100644 --- a/programs/mango-v4/src/instructions/stub_oracle_create.rs +++ b/programs/mango-v4/src/instructions/stub_oracle_create.rs @@ -3,10 +3,7 @@ use fixed::types::I80F48; use crate::accounts_ix::*; -pub fn stub_oracle_create( - ctx: Context, - price: I80F48 -) -> Result<()> { +pub fn stub_oracle_create(ctx: Context, price: I80F48) -> Result<()> { let mut oracle = ctx.accounts.oracle.load_init()?; oracle.group = ctx.accounts.group.key(); oracle.mint = ctx.accounts.mint.key(); diff --git a/programs/mango-v4/src/state/bank.rs b/programs/mango-v4/src/state/bank.rs index 334c8944f..8c7196b94 100644 --- a/programs/mango-v4/src/state/bank.rs +++ b/programs/mango-v4/src/state/bank.rs @@ -949,6 +949,14 @@ impl Bank { staleness_slot: Option, ) -> Result { require_keys_eq!(self.oracle, *oracle_acc.key()); + self.oracle_price_inner(oracle_acc, staleness_slot) + } + + fn oracle_price_inner( + &self, + oracle_acc: &impl KeyedAccountReader, + staleness_slot: Option, + ) -> Result { let state = oracle::oracle_state_unchecked(oracle_acc, self.mint_decimals)?; state.check_confidence_and_maybe_staleness( &self.oracle, @@ -958,6 +966,23 @@ impl Bank { Ok(state.price) } + /// Tries to return the primary oracle price, and if there is a confidence or staleness issue returns the fallback oracle price. + pub fn oracle_price_with_fallback( + &self, + oracle_acc: &impl KeyedAccountReader, + fallback_oracle_acc_opt: Option<&impl KeyedAccountReader>, + staleness_slot: Option, + ) -> Result { + let primary_price = self.oracle_price(oracle_acc, staleness_slot); + if primary_price.is_ok() || fallback_oracle_acc_opt.is_none() { + primary_price + } else { + let fallback_oracle_acc = fallback_oracle_acc_opt.unwrap(); + require_keys_eq!(self.fallback_oracle, *fallback_oracle_acc.key()); + self.oracle_price_inner(fallback_oracle_acc, staleness_slot) + } + } + pub fn stable_price(&self) -> I80F48 { I80F48::from_num(self.stable_price_model.stable_price) }