Merge pull request #790 from blockworks-foundation/lou/fallback-oracle

Add fallback oracles
This commit is contained in:
Lou-Kamades 2023-12-01 12:59:38 -06:00 committed by GitHub
commit 838df8cd7c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 775 additions and 128 deletions

View File

@ -373,7 +373,7 @@ pub async fn init(
{
if unchecked_oracle_state
.check_confidence_and_maybe_staleness(
&oracle_pk,
&mkt.1.name,
&oracle_config.to_oracle_config(),
None, // force this to always return a price no matter how stale
)

View File

@ -34,6 +34,7 @@ pub async fn new(
begin_perp: active_token_len * 2,
begin_serum3: active_token_len * 2 + active_perp_len * 2,
staleness_slot: None,
begin_fallback_oracles: metas.len(), // TODO: add support for fallback oracle accounts
};
let now_ts = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();
mango_v4::health::new_health_cache(&account.borrow(), &retriever, now_ts)
@ -67,6 +68,7 @@ pub fn new_sync(
begin_perp: active_token_len * 2,
begin_serum3: active_token_len * 2 + active_perp_len * 2,
staleness_slot: None,
begin_fallback_oracles: metas.len(), // TODO: add support for fallback oracle accounts
};
let now_ts = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();
mango_v4::health::new_health_cache(&account.borrow(), &retriever, now_ts)

View File

@ -12,8 +12,6 @@ pub struct StubOracleCreate<'info> {
#[account(
init,
seeds = [b"StubOracle".as_ref(), group.key().as_ref(), mint.key().as_ref()],
bump,
payer = payer,
space = 8 + std::mem::size_of::<StubOracle>(),
)]

View File

@ -21,4 +21,9 @@ pub struct TokenEdit<'info> {
///
/// CHECK: The oracle can be one of several different account types
pub oracle: UncheckedAccount<'info>,
/// The fallback oracle account is optional and only used when set_fallback_oracle is true.
///
/// CHECK: The fallback oracle can be one of several different account types
pub fallback_oracle: UncheckedAccount<'info>,
}

View File

@ -51,6 +51,9 @@ pub struct TokenRegister<'info> {
/// CHECK: The oracle can be one of several different account types
pub oracle: UncheckedAccount<'info>,
/// CHECK: The oracle can be one of several different account types
pub fallback_oracle: UncheckedAccount<'info>,
#[account(mut)]
pub payer: Signer<'info>,

View File

@ -51,6 +51,9 @@ pub struct TokenRegisterTrustless<'info> {
/// CHECK: The oracle can be one of several different account types
pub oracle: UncheckedAccount<'info>,
/// CHECK: The oracle can be one of several different account types
pub fallback_oracle: UncheckedAccount<'info>,
#[account(mut)]
pub payer: Signer<'info>,

View File

@ -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<T: KeyedAccountReader> {
pub ais: Vec<T>,
pub n_banks: usize,
@ -54,6 +55,7 @@ pub struct FixedOrderAccountRetriever<T: KeyedAccountReader> {
pub begin_perp: usize,
pub begin_serum3: usize,
pub staleness_slot: Option<u64>,
pub begin_fallback_oracles: usize,
}
pub fn new_fixed_order_account_retriever<'a, 'info>(
@ -66,7 +68,7 @@ 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
@ -79,6 +81,7 @@ pub fn new_fixed_order_account_retriever<'a, 'info>(
begin_perp: active_token_len * 2,
begin_serum3: active_token_len * 2 + active_perp_len * 2,
staleness_slot: Some(Clock::get()?.slot),
begin_fallback_oracles: expected_ais,
})
}
@ -103,11 +106,6 @@ impl<T: KeyedAccountReader> FixedOrderAccountRetriever<T> {
Ok(market)
}
fn oracle_price_bank(&self, account_index: usize, bank: &Bank) -> Result<I80F48> {
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<I80F48> {
let oracle = &self.ais[account_index];
perp_market.oracle_price(oracle, self.staleness_slot)
@ -134,7 +132,13 @@ impl<T: KeyedAccountReader> AccountRetriever for FixedOrderAccountRetriever<T> {
})?;
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.ais[self.begin_fallback_oracles..]
.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,
@ -196,6 +200,7 @@ impl<T: KeyedAccountReader> AccountRetriever for FixedOrderAccountRetriever<T> {
pub struct ScannedBanksAndOracles<'a, 'info> {
banks: Vec<AccountInfoRefMut<'a, 'info>>,
oracles: Vec<AccountInfoRef<'a, 'info>>,
fallback_oracles: Vec<AccountInfoRef<'a, 'info>>,
index_map: HashMap<TokenIndex, usize>,
staleness_slot: Option<u64>,
}
@ -222,7 +227,10 @@ impl<'a, 'info> ScannedBanksAndOracles<'a, 'info> {
let index = self.bank_index(token_index1)?;
let bank = self.banks[index].load_mut_fully_unchecked::<Bank>()?;
let oracle = &self.oracles[index];
let price = bank.oracle_price(oracle, self.staleness_slot)?;
let fallback_oracle_opt =
fetch_fallback_oracle(&self.fallback_oracles, &bank.fallback_oracle);
let price =
bank.oracle_price_with_fallback(oracle, fallback_oracle_opt, self.staleness_slot)?;
return Ok((bank, price, None));
}
let index1 = self.bank_index(token_index1)?;
@ -240,8 +248,14 @@ impl<'a, 'info> ScannedBanksAndOracles<'a, 'info> {
let bank2 = second_bank_part[second - (first + 1)].load_mut_fully_unchecked::<Bank>()?;
let oracle1 = &self.oracles[first];
let oracle2 = &self.oracles[second];
let price1 = bank1.oracle_price(oracle1, self.staleness_slot)?;
let price2 = bank2.oracle_price(oracle2, self.staleness_slot)?;
let fallback_oracle_opt1 =
fetch_fallback_oracle(&self.fallback_oracles, &bank1.fallback_oracle);
let fallback_oracle_opt2 =
fetch_fallback_oracle(&self.fallback_oracles, &bank2.fallback_oracle);
let price1 =
bank1.oracle_price_with_fallback(oracle1, fallback_oracle_opt1, self.staleness_slot)?;
let price2 =
bank2.oracle_price_with_fallback(oracle2, fallback_oracle_opt2, self.staleness_slot)?;
if swap {
Ok((bank2, price2, Some((bank1, price1))))
} else {
@ -254,7 +268,11 @@ impl<'a, 'info> ScannedBanksAndOracles<'a, 'info> {
// The account was already loaded successfully during construction
let bank = self.banks[index].load_fully_unchecked::<Bank>()?;
let oracle = &self.oracles[index];
let price = bank.oracle_price(oracle, self.staleness_slot)?;
let fallback_oracle_opt =
fetch_fallback_oracle(&self.fallback_oracles, &bank.fallback_oracle);
let price =
bank.oracle_price_with_fallback(oracle, fallback_oracle_opt, self.staleness_slot)?;
Ok((bank, price))
}
}
@ -265,6 +283,7 @@ impl<'a, 'info> ScannedBanksAndOracles<'a, 'info> {
/// - an unknown number of PerpMarket accounts
/// - the same number of oracles in the same order as the perp markets
/// - an unknown number of serum3 OpenOrders accounts
/// - an unknown number of fallback oracle accounts
/// and retrieves accounts needed for the health computation by doing a linear
/// scan for each request.
pub struct ScanningAccountRetriever<'a, 'info> {
@ -350,17 +369,26 @@ impl<'a, 'info> ScanningAccountRetriever<'a, 'info> {
let n_perps = perp_index_map.len();
let perp_oracles_start = perps_start + n_perps;
let serum3_start = perp_oracles_start + n_perps;
let n_serum3 = ais[serum3_start..]
.iter()
.take_while(|x| {
x.data_len() == std::mem::size_of::<serum_dex::state::OpenOrders>() + 12
&& serum3_cpi::has_serum_header(&x.data.borrow())
})
.count();
let fallback_oracles_start = serum3_start + n_serum3;
Ok(Self {
banks_and_oracles: ScannedBanksAndOracles {
banks: AccountInfoRefMut::borrow_slice(&ais[..n_banks])?,
oracles: AccountInfoRef::borrow_slice(&ais[n_banks..perps_start])?,
fallback_oracles: AccountInfoRef::borrow_slice(&ais[fallback_oracles_start..])?,
index_map: token_index_map,
staleness_slot,
},
perp_markets: AccountInfoRef::borrow_slice(&ais[perps_start..perp_oracles_start])?,
perp_oracles: AccountInfoRef::borrow_slice(&ais[perp_oracles_start..serum3_start])?,
serum3_oos: AccountInfoRef::borrow_slice(&ais[serum3_start..])?,
serum3_oos: AccountInfoRef::borrow_slice(&ais[serum3_start..fallback_oracles_start])?,
perp_index_map,
})
}
@ -437,6 +465,14 @@ impl<'a, 'info> AccountRetriever for ScanningAccountRetriever<'a, 'info> {
}
}
#[inline(always)]
fn fetch_fallback_oracle<'a, 'info>(
fallback_oracles: &'a Vec<AccountInfoRef<'a, 'info>>,
fallback_key: &Pubkey,
) -> Option<&'a AccountInfoRef<'a, 'info>> {
fallback_oracles.iter().find(|ai| ai.key() == fallback_key)
}
#[cfg(test)]
mod tests {
use super::super::test::*;

View File

@ -8,7 +8,7 @@ use crate::error::MangoError;
use crate::state::*;
use crate::accounts_ix::*;
use crate::logs::{emit_stack, TokenMetaDataLog};
use crate::logs::{emit_stack, TokenMetaDataLogV2};
use crate::util::fill_from_str;
#[allow(unused_variables)]
@ -49,6 +49,7 @@ pub fn token_edit(
maint_weight_shift_asset_target_opt: Option<f32>,
maint_weight_shift_liab_target_opt: Option<f32>,
maint_weight_shift_abort: bool,
set_fallback_oracle: bool,
) -> Result<()> {
let group = ctx.accounts.group.load()?;
@ -76,6 +77,16 @@ pub fn token_edit(
mint_info.oracle = oracle;
require_group_admin = true;
}
if set_fallback_oracle {
msg!(
"Fallback oracle old {:?}, new {:?}",
bank.fallback_oracle,
ctx.accounts.fallback_oracle.key()
);
bank.fallback_oracle = ctx.accounts.fallback_oracle.key();
mint_info.fallback_oracle = ctx.accounts.fallback_oracle.key();
require_group_admin = true;
}
if reset_stable_price {
msg!("Stable price reset");
require_keys_eq!(bank.oracle, ctx.accounts.oracle.key());
@ -456,12 +467,13 @@ pub fn token_edit(
let bank = ctx.remaining_accounts.first().unwrap().load_mut::<Bank>()?;
bank.verify()?;
emit_stack(TokenMetaDataLog {
emit_stack(TokenMetaDataLogV2 {
mango_group: ctx.accounts.group.key(),
mint: mint_info.mint.key(),
token_index: bank.token_index,
mint_decimals: bank.mint_decimals,
oracle: mint_info.oracle.key(),
fallback_oracle: ctx.accounts.fallback_oracle.key(),
mint_info: ctx.accounts.mint_info.key(),
});

View File

@ -6,7 +6,7 @@ use crate::error::*;
use crate::state::*;
use crate::util::fill_from_str;
use crate::logs::{emit_stack, TokenMetaDataLog};
use crate::logs::{emit_stack, TokenMetaDataLogV2};
pub const INDEX_START: I80F48 = I80F48::from_bits(1_000_000 * I80F48::ONE.to_bits());
@ -119,7 +119,8 @@ pub fn token_register(
maint_weight_shift_duration_inv: I80F48::ZERO,
maint_weight_shift_asset_target: I80F48::ZERO,
maint_weight_shift_liab_target: I80F48::ZERO,
reserved: [0; 2008],
fallback_oracle: ctx.accounts.fallback_oracle.key(),
reserved: [0; 1976],
};
if let Ok(oracle_price) =
@ -143,19 +144,21 @@ pub fn token_register(
banks: Default::default(),
vaults: Default::default(),
oracle: ctx.accounts.oracle.key(),
fallback_oracle: ctx.accounts.fallback_oracle.key(),
registration_time: Clock::get()?.unix_timestamp.try_into().unwrap(),
reserved: [0; 2560],
reserved: [0; 2528],
};
mint_info.banks[0] = ctx.accounts.bank.key();
mint_info.vaults[0] = ctx.accounts.vault.key();
emit_stack(TokenMetaDataLog {
emit_stack(TokenMetaDataLogV2 {
mango_group: ctx.accounts.group.key(),
mint: ctx.accounts.mint.key(),
token_index,
mint_decimals: ctx.accounts.mint.decimals,
oracle: ctx.accounts.oracle.key(),
fallback_oracle: ctx.accounts.fallback_oracle.key(),
mint_info: ctx.accounts.mint_info.key(),
});

View File

@ -7,7 +7,7 @@ use crate::instructions::INDEX_START;
use crate::state::*;
use crate::util::fill_from_str;
use crate::logs::{emit_stack, TokenMetaDataLog};
use crate::logs::{emit_stack, TokenMetaDataLogV2};
use crate::accounts_ix::*;
@ -102,7 +102,8 @@ pub fn token_register_trustless(
maint_weight_shift_duration_inv: I80F48::ZERO,
maint_weight_shift_asset_target: I80F48::ZERO,
maint_weight_shift_liab_target: I80F48::ZERO,
reserved: [0; 2008],
fallback_oracle: ctx.accounts.fallback_oracle.key(),
reserved: [0; 1976],
};
if let Ok(oracle_price) =
@ -126,19 +127,21 @@ pub fn token_register_trustless(
banks: Default::default(),
vaults: Default::default(),
oracle: ctx.accounts.oracle.key(),
fallback_oracle: ctx.accounts.fallback_oracle.key(),
registration_time: Clock::get()?.unix_timestamp.try_into().unwrap(),
reserved: [0; 2560],
reserved: [0; 2528],
};
mint_info.banks[0] = ctx.accounts.bank.key();
mint_info.vaults[0] = ctx.accounts.vault.key();
emit_stack(TokenMetaDataLog {
emit_stack(TokenMetaDataLogV2 {
mango_group: ctx.accounts.group.key(),
mint: ctx.accounts.mint.key(),
token_index,
mint_decimals: ctx.accounts.mint.decimals,
oracle: ctx.accounts.oracle.key(),
fallback_oracle: ctx.accounts.fallback_oracle.key(),
mint_info: ctx.accounts.mint_info.key(),
});

View File

@ -181,11 +181,12 @@ pub fn token_withdraw(ctx: Context<TokenWithdraw>, amount: u64, allow_borrow: bo
// When borrowing the price has be trustworthy, so we can do a reasonable
// net borrow check.
unsafe_oracle_state.check_confidence_and_maybe_staleness(
&bank.oracle,
&bank.oracle_config,
Some(Clock::get()?.slot),
)?;
let slot_opt = Some(Clock::get()?.slot);
unsafe_oracle_state
.check_confidence_and_maybe_staleness(&bank.name(), &bank.oracle_config, slot_opt)
.with_context(|| {
oracle_log_context(&unsafe_oracle_state, &bank.oracle_config, slot_opt)
})?;
bank.check_net_borrows(unsafe_oracle_state.price)?;
}

View File

@ -234,6 +234,7 @@ pub mod mango_v4 {
maint_weight_shift_asset_target_opt: Option<f32>,
maint_weight_shift_liab_target_opt: Option<f32>,
maint_weight_shift_abort: bool,
set_fallback_oracle: bool,
) -> Result<()> {
#[cfg(feature = "enable-gpl")]
instructions::token_edit(
@ -272,6 +273,7 @@ pub mod mango_v4 {
maint_weight_shift_asset_target_opt,
maint_weight_shift_liab_target_opt,
maint_weight_shift_abort,
set_fallback_oracle,
)?;
Ok(())
}

View File

@ -450,6 +450,17 @@ pub struct TokenMetaDataLog {
pub mint_info: Pubkey,
}
#[event]
pub struct TokenMetaDataLogV2 {
pub mango_group: Pubkey,
pub mint: Pubkey,
pub token_index: u16,
pub mint_decimals: u8,
pub oracle: Pubkey,
pub fallback_oracle: Pubkey,
pub mint_info: Pubkey,
}
#[event]
pub struct PerpMarketMetaDataLog {
pub mango_group: Pubkey,

View File

@ -1,5 +1,5 @@
use anchor_lang::prelude::*;
use serum_dex::state::{OpenOrders, ToAlignedBytes};
use serum_dex::state::{OpenOrders, ToAlignedBytes, ACCOUNT_HEAD_PADDING};
use std::cell::{Ref, RefMut};
use std::cmp::min;
@ -49,6 +49,14 @@ fn strip_data_header_mut<H: bytemuck::Pod, D: bytemuck::Pod>(
}))
}
pub fn has_serum_header(data: &[u8]) -> bool {
if data.len() < 5 {
return false;
}
let head = &data[..5];
head == ACCOUNT_HEAD_PADDING
}
pub fn load_market_state<'a>(
market_account: &'a AccountInfo,
program_id: &Pubkey,

View File

@ -9,6 +9,7 @@ use anchor_lang::prelude::*;
use anchor_spl::token::TokenAccount;
use derivative::Derivative;
use fixed::types::I80F48;
use oracle::oracle_log_context;
use static_assertions::const_assert_eq;
use std::mem::size_of;
@ -167,8 +168,10 @@ pub struct Bank {
pub maint_weight_shift_asset_target: I80F48,
pub maint_weight_shift_liab_target: I80F48,
pub fallback_oracle: Pubkey,
#[derivative(Debug = "ignore")]
pub reserved: [u8; 2008],
pub reserved: [u8; 1976],
}
const_assert_eq!(
size_of::<Bank>(),
@ -292,7 +295,8 @@ impl Bank {
maint_weight_shift_duration_inv: existing_bank.maint_weight_shift_duration_inv,
maint_weight_shift_asset_target: existing_bank.maint_weight_shift_asset_target,
maint_weight_shift_liab_target: existing_bank.maint_weight_shift_liab_target,
reserved: [0; 2008],
fallback_oracle: existing_bank.oracle,
reserved: [0; 1976],
}
}
@ -947,12 +951,50 @@ impl Bank {
) -> Result<I80F48> {
require_keys_eq!(self.oracle, *oracle_acc.key());
let state = oracle::oracle_state_unchecked(oracle_acc, self.mint_decimals)?;
state.check_confidence_and_maybe_staleness(
&self.oracle,
state
.check_confidence_and_maybe_staleness(&self.name(), &self.oracle_config, staleness_slot)
.with_context(|| oracle_log_context(&state, &self.oracle_config, staleness_slot))?;
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<u64>,
) -> Result<I80F48> {
require_keys_eq!(self.oracle, *oracle_acc.key());
let primary_state = oracle::oracle_state_unchecked(oracle_acc, self.mint_decimals)?;
let primary_ok = primary_state.check_confidence_and_maybe_staleness(
&self.name(),
&self.oracle_config,
staleness_slot,
)?;
Ok(state.price)
);
if primary_ok.is_oracle_error() && fallback_oracle_acc_opt.is_some() {
let fallback_oracle_acc = fallback_oracle_acc_opt.unwrap();
require_keys_eq!(self.fallback_oracle, *fallback_oracle_acc.key());
let fallback_state =
oracle::oracle_state_unchecked(fallback_oracle_acc, self.mint_decimals)?;
let fallback_ok = fallback_state.check_confidence_and_maybe_staleness(
&self.name(),
&self.oracle_config,
staleness_slot,
);
fallback_ok.with_context(|| {
format!(
"{} {}",
oracle_log_context(&primary_state, &self.oracle_config, staleness_slot),
oracle_log_context(&fallback_state, &self.oracle_config, staleness_slot)
)
})?;
Ok(fallback_state.price)
} else {
primary_ok.with_context(|| {
oracle_log_context(&primary_state, &self.oracle_config, staleness_slot)
})?;
Ok(primary_state.price)
}
}
pub fn stable_price(&self) -> I80F48 {

View File

@ -33,8 +33,10 @@ pub struct MintInfo {
pub registration_time: u64,
pub fallback_oracle: Pubkey,
#[derivative(Debug = "ignore")]
pub reserved: [u8; 2560],
pub reserved: [u8; 2528],
}
const_assert_eq!(
size_of::<MintInfo>(),

View File

@ -105,19 +105,19 @@ impl OracleState {
#[inline]
pub fn check_confidence_and_maybe_staleness(
&self,
oracle_pk: &Pubkey,
oracle_name: &str,
config: &OracleConfig,
staleness_slot: Option<u64>,
) -> Result<()> {
if let Some(now_slot) = staleness_slot {
self.check_staleness(oracle_pk, config, now_slot)?;
self.check_staleness(oracle_name, config, now_slot)?;
}
self.check_confidence(oracle_pk, config)
self.check_confidence(oracle_name, config)
}
pub fn check_staleness(
&self,
oracle_pk: &Pubkey,
oracle_name: &str,
config: &OracleConfig,
now_slot: u64,
) -> Result<()> {
@ -127,27 +127,15 @@ impl OracleState {
.saturating_add(config.max_staleness_slots as u64)
< now_slot
{
msg!(
"Oracle is stale; pubkey {}, price: {}, last_update_slot: {}, now_slot: {}",
oracle_pk,
self.price.to_num::<f64>(),
self.last_update_slot,
now_slot,
);
msg!("Oracle is stale: {}", oracle_name);
return Err(MangoError::OracleStale.into());
}
Ok(())
}
pub fn check_confidence(&self, oracle_pk: &Pubkey, config: &OracleConfig) -> Result<()> {
pub fn check_confidence(&self, oracle_name: &str, config: &OracleConfig) -> Result<()> {
if self.deviation > config.conf_filter * self.price {
msg!(
"Oracle confidence not good enough: pubkey {}, price: {}, deviation: {}, conf_filter: {}",
oracle_pk,
self.price.to_num::<f64>(),
self.deviation.to_num::<f64>(),
config.conf_filter.to_num::<f32>(),
);
msg!("Oracle confidence not good enough: {}", oracle_name);
return Err(MangoError::OracleConfidence.into());
}
Ok(())
@ -228,7 +216,7 @@ fn pyth_get_price(
/// Returns the price of one native base token, in native quote tokens
///
/// Example: The for SOL at 40 USDC/SOL it would return 0.04 (the unit is USDC-native/SOL-native)
/// Example: The price for SOL at 40 USDC/SOL it would return 0.04 (the unit is USDC-native/SOL-native)
///
/// This currently assumes that quote decimals (i.e. decimals for USD) is 6, like for USDC.
///
@ -332,6 +320,21 @@ pub fn oracle_state_unchecked(
})
}
pub fn oracle_log_context(
state: &OracleState,
oracle_config: &OracleConfig,
staleness_slot: Option<u64>,
) -> String {
format!(
"price: {}, deviation: {}, last_update_slot: {}, now_slot: {}, conf_filter: {:#?}",
state.price.to_num::<f64>(),
state.deviation.to_num::<f64>(),
state.last_update_slot,
staleness_slot.unwrap_or_else(|| u64::MAX),
oracle_config.conf_filter.to_num::<f32>(),
)
}
#[cfg(test)]
mod tests {
use super::*;

View File

@ -4,10 +4,11 @@ use anchor_lang::prelude::*;
use derivative::Derivative;
use fixed::types::I80F48;
use oracle::oracle_log_context;
use static_assertions::const_assert_eq;
use crate::accounts_zerocopy::KeyedAccountReader;
use crate::error::MangoError;
use crate::error::{Contextable, MangoError};
use crate::logs::{emit_stack, PerpUpdateFundingLogV2};
use crate::state::orderbook::Side;
use crate::state::{oracle, TokenIndex};
@ -275,11 +276,9 @@ impl PerpMarket {
) -> Result<OracleState> {
require_keys_eq!(self.oracle, *oracle_acc.key());
let state = oracle::oracle_state_unchecked(oracle_acc, self.base_decimals)?;
state.check_confidence_and_maybe_staleness(
&self.oracle,
&self.oracle_config,
staleness_slot,
)?;
state
.check_confidence_and_maybe_staleness(&self.name(), &self.oracle_config, staleness_slot)
.with_context(|| oracle_log_context(&state, &self.oracle_config, staleness_slot))?;
Ok(state)
}

View File

@ -286,6 +286,7 @@ async fn test_basic() -> Result<(), TransportError> {
send_tx(
solana,
StubOracleCloseInstruction {
oracle: tokens[0].oracle,
group,
mint: bank_data.mint,
admin,
@ -459,6 +460,7 @@ async fn test_bank_maint_weight_shift() -> Result<(), TransportError> {
group,
admin,
mint: mints[0].pubkey,
fallback_oracle: Pubkey::default(),
options: mango_v4::instruction::TokenEdit {
maint_weight_shift_start_opt: Some(start_time + 1000),
maint_weight_shift_end_opt: Some(start_time + 2000),
@ -492,6 +494,7 @@ async fn test_bank_maint_weight_shift() -> Result<(), TransportError> {
group,
admin,
mint: mints[0].pubkey,
fallback_oracle: Pubkey::default(),
options: mango_v4::instruction::TokenEdit {
maint_weight_shift_abort: true,
..token_edit_instruction_default()

View File

@ -1,4 +1,5 @@
use super::*;
use anchor_lang::prelude::AccountMeta;
use mango_v4::accounts_ix::{Serum3OrderType, Serum3SelfTradeBehavior, Serum3Side};
async fn deposit_cu_datapoint(
@ -24,6 +25,31 @@ async fn deposit_cu_datapoint(
result.metadata.unwrap().compute_units_consumed
}
async fn deposit_cu_fallbacks_datapoint(
solana: &SolanaCookie,
account: Pubkey,
owner: TestKeypair,
token_account: Pubkey,
remaining_accounts: Vec<AccountMeta>,
) -> u64 {
let result = send_tx_with_extra_accounts(
solana,
TokenDepositInstruction {
amount: 10,
reduce_only: false,
account,
owner,
token_account,
token_authority: owner,
bank_index: 0,
},
remaining_accounts,
)
.await
.unwrap();
result.metadata.unwrap().compute_units_consumed
}
// Try to reach compute limits in health checks by having many different tokens in an account
#[tokio::test]
async fn test_health_compute_tokens() -> Result<(), TransportError> {
@ -68,7 +94,7 @@ async fn test_health_compute_tokens() -> Result<(), TransportError> {
let avg_cu_increase = cu_measurements.windows(2).map(|p| p[1] - p[0]).sum::<u64>()
/ (cu_measurements.len() - 1) as u64;
println!("average cu increase: {avg_cu_increase}");
assert!(avg_cu_increase < 3200);
assert!(avg_cu_increase < 3350);
Ok(())
}
@ -107,6 +133,7 @@ async fn test_health_compute_tokens_during_maint_weight_shift() -> Result<(), Tr
group,
admin,
mint: mint.pubkey,
fallback_oracle: Pubkey::default(),
options: mango_v4::instruction::TokenEdit {
maint_weight_shift_start_opt: Some(now - 1000),
maint_weight_shift_end_opt: Some(now + 1000),
@ -137,7 +164,178 @@ async fn test_health_compute_tokens_during_maint_weight_shift() -> Result<(), Tr
let avg_cu_increase = cu_measurements.windows(2).map(|p| p[1] - p[0]).sum::<u64>()
/ (cu_measurements.len() - 1) as u64;
println!("average cu increase: {avg_cu_increase}");
assert!(avg_cu_increase < 4200);
assert!(avg_cu_increase < 4300);
Ok(())
}
// Try to reach compute limits in health checks by having many different tokens in an account and using fallback oracles for them
#[tokio::test]
async fn test_health_compute_tokens_fallback_oracles() -> Result<(), TransportError> {
let mut test_builder = TestContextBuilder::new();
test_builder.test().set_compute_max_units(450_000);
let context = test_builder.start_default().await;
let solana = &context.solana.clone();
let num_tokens = 8;
let admin = TestKeypair::new();
let owner = context.users[0].key;
let payer = context.users[1].key;
let mints = &context.mints[0..num_tokens];
let mut fallback_oracle_kps = Vec::with_capacity(num_tokens);
for _ in 0..num_tokens {
fallback_oracle_kps.push(TestKeypair::new());
}
let success_metas: Vec<AccountMeta> = fallback_oracle_kps
.iter()
.map(|x| AccountMeta {
pubkey: x.pubkey(),
is_signer: false,
is_writable: false,
})
.collect();
let failure_metas = vec![];
//
// SETUP: Create a group and an account
//
let GroupWithTokens { group, tokens, .. } = GroupWithTokensConfig {
admin,
payer,
mints: mints.to_vec(),
..GroupWithTokensConfig::default()
}
.create(solana)
.await;
let account =
create_funded_account(&solana, group, owner, 0, &context.users[1], &[], 1000, 0).await;
let mut success_measurements = vec![];
let mut failure_measurements = vec![];
for token_account in &context.users[0].token_accounts[..mints.len()] {
deposit_cu_datapoint(solana, account, owner, *token_account).await;
}
//
// SETUP: Create and register fallback oracles for each token
//
for (i, _token_account) in context.users[0].token_accounts[..mints.len()]
.iter()
.enumerate()
{
send_tx(
solana,
StubOracleCreate {
oracle: fallback_oracle_kps[i],
group,
mint: mints[i].pubkey,
admin,
payer,
},
)
.await
.unwrap();
send_tx(
solana,
TokenEdit {
group,
admin,
mint: mints[i].pubkey,
fallback_oracle: fallback_oracle_kps[i].pubkey(),
options: mango_v4::instruction::TokenEdit {
set_fallback_oracle: true,
..token_edit_instruction_default()
},
},
)
.await
.unwrap();
}
//
// TEST: Progressively make each oracle invalid so that the fallback is used
//
for (i, token_account) in context.users[0].token_accounts[..mints.len()]
.iter()
.enumerate()
{
send_tx(
solana,
StubOracleSetTestInstruction {
oracle: tokens[i].oracle,
group,
mint: mints[i].pubkey,
admin,
price: 1.0,
last_update_slot: 0,
deviation: 100.0,
},
)
.await
.unwrap();
success_measurements.push(
deposit_cu_fallbacks_datapoint(
solana,
account,
owner,
*token_account,
success_metas.clone(),
)
.await,
);
failure_measurements.push(
deposit_cu_fallbacks_datapoint(
solana,
account,
owner,
*token_account,
failure_metas.clone(),
)
.await,
);
}
println!("successful fallbacks:");
for (i, pair) in success_measurements.windows(2).enumerate() {
println!(
"after adding token {}: {} (+{})",
i,
pair[1],
pair[1] - pair[0]
);
}
println!("failed fallbacks:");
for (i, pair) in failure_measurements.windows(2).enumerate() {
println!(
"after adding token {}: {} (+{})",
i,
pair[1],
pair[1] - pair[0]
);
}
let avg_success_increase = success_measurements
.windows(2)
.map(|p| p[1] - p[0])
.sum::<u64>()
/ (success_measurements.len() - 1) as u64;
let avg_failure_increase = failure_measurements
.windows(2)
.map(|p| p[1] - p[0])
.sum::<u64>()
/ (failure_measurements.len() - 1) as u64;
println!("average success increase: {avg_success_increase}");
println!("average failure increase: {avg_failure_increase}");
assert!(avg_success_increase < 2_050);
assert!(avg_success_increase < 18_500);
Ok(())
}

View File

@ -244,7 +244,7 @@ async fn test_margin_trade() -> Result<(), BanksClientError> {
#[tokio::test]
async fn test_flash_loan_swap_fee() -> Result<(), BanksClientError> {
let mut test_builder = TestContextBuilder::new();
test_builder.test().set_compute_max_units(100_000);
test_builder.test().set_compute_max_units(150_000);
let context = test_builder.start_default().await;
let solana = &context.solana.clone();
@ -278,6 +278,7 @@ async fn test_flash_loan_swap_fee() -> Result<(), BanksClientError> {
group,
admin,
mint: tokens[1].mint.pubkey,
fallback_oracle: Pubkey::default(),
options: mango_v4::instruction::TokenEdit {
flash_loan_swap_fee_rate_opt: Some(swap_fee_rate as f32),
..token_edit_instruction_default()

View File

@ -975,6 +975,7 @@ async fn test_perp_pnl_settle_limit() -> Result<(), TransportError> {
send_tx(
solana,
StubOracleSetInstruction {
oracle: tokens[1].oracle,
group,
admin,
mint: mints[1].pubkey,

View File

@ -1,6 +1,7 @@
#![allow(dead_code)]
use super::*;
use anchor_lang::prelude::AccountMeta;
use mango_v4::accounts_ix::{Serum3OrderType, Serum3SelfTradeBehavior, Serum3Side};
use mango_v4::serum3_cpi::{load_open_orders_bytes, OpenOrdersSlim};
use std::sync::Arc;
@ -1503,6 +1504,182 @@ async fn test_serum_compute() -> Result<(), TransportError> {
Ok(())
}
#[tokio::test]
async fn test_fallback_oracle_serum() -> Result<(), TransportError> {
let mut test_builder = TestContextBuilder::new();
test_builder.test().set_compute_max_units(150_000);
let context = test_builder.start_default().await;
let solana = &context.solana.clone();
let fallback_oracle_kp = TestKeypair::new();
let fallback_oracle = fallback_oracle_kp.pubkey();
let owner = context.users[0].key;
let payer = context.users[1].key;
let payer_token_accounts = &context.users[1].token_accounts[0..3];
//
// SETUP: Create a group and an account
//
let deposit_amount = 1_000;
let CommonSetup {
group_with_tokens,
quote_token,
base_token,
mut order_placer,
..
} = common_setup(&context, deposit_amount).await;
let GroupWithTokens {
group,
admin,
tokens,
..
} = group_with_tokens;
//
// SETUP: Create a fallback oracle
//
send_tx(
solana,
StubOracleCreate {
oracle: fallback_oracle_kp,
group,
mint: tokens[2].mint.pubkey,
admin,
payer,
},
)
.await
.unwrap();
//
// SETUP: Add a fallback oracle
//
send_tx(
solana,
TokenEdit {
group,
admin,
mint: tokens[2].mint.pubkey,
fallback_oracle,
options: mango_v4::instruction::TokenEdit {
set_fallback_oracle: true,
..token_edit_instruction_default()
},
},
)
.await
.unwrap();
let bank_data: Bank = solana.get_account(tokens[2].bank).await;
assert!(bank_data.fallback_oracle == fallback_oracle);
// Create some token1 borrows
send_tx(
solana,
TokenWithdrawInstruction {
amount: 1_500,
allow_borrow: true,
account: order_placer.account,
owner,
token_account: payer_token_accounts[2],
bank_index: 0,
},
)
.await
.unwrap();
// Make oracle invalid by increasing deviation
send_tx(
solana,
StubOracleSetTestInstruction {
oracle: tokens[2].oracle,
group,
mint: tokens[2].mint.pubkey,
admin,
price: 1.0,
last_update_slot: 0,
deviation: 100.0,
},
)
.await
.unwrap();
//
// TEST: Place a failing order
//
let limit_price = 1.0;
let max_base = 100;
let order_fut = order_placer.try_bid(limit_price, max_base, false).await;
assert_mango_error(
&order_fut,
6023,
"an oracle does not reach the confidence threshold".to_string(),
);
// now send txn with a fallback oracle in the remaining accounts
let fallback_oracle_meta = AccountMeta {
pubkey: fallback_oracle,
is_writable: false,
is_signer: false,
};
let client_order_id = order_placer.inc_client_order_id();
let place_ix = Serum3PlaceOrderInstruction {
side: Serum3Side::Bid,
limit_price: (limit_price * 100.0 / 10.0) as u64, // in quote_lot (10) per base lot (100)
max_base_qty: max_base / 100, // in base lot (100)
// 4 bps taker fees added in
max_native_quote_qty_including_fees: (limit_price * (max_base as f64) * (1.0)).ceil()
as u64,
self_trade_behavior: Serum3SelfTradeBehavior::AbortTransaction,
order_type: Serum3OrderType::Limit,
client_order_id,
limit: 10,
account: order_placer.account,
owner: order_placer.owner,
serum_market: order_placer.serum_market,
};
let result = send_tx_with_extra_accounts(solana, place_ix, vec![fallback_oracle_meta])
.await
.unwrap();
result.result.unwrap();
let account_data = get_mango_account(solana, order_placer.account).await;
assert_eq!(
account_data
.token_position_by_raw_index(0)
.unwrap()
.in_use_count,
1
);
assert_eq!(
account_data
.token_position_by_raw_index(1)
.unwrap()
.in_use_count,
1
);
assert_eq!(
account_data
.token_position_by_raw_index(2)
.unwrap()
.in_use_count,
0
);
let serum_orders = account_data.serum3_orders_by_raw_index(0).unwrap();
assert_eq!(serum_orders.base_borrows_without_fee, 0);
assert_eq!(serum_orders.quote_borrows_without_fee, 0);
assert_eq!(serum_orders.base_deposits_reserved, 0);
assert_eq!(serum_orders.quote_deposits_reserved, 100);
let base_bank = solana.get_account::<Bank>(base_token.bank).await;
assert_eq!(base_bank.deposits_in_serum, 0);
let quote_bank = solana.get_account::<Bank>(quote_token.bank).await;
assert_eq!(quote_bank.deposits_in_serum, 100);
Ok(())
}
struct CommonSetup {
group_with_tokens: GroupWithTokens,
serum_market_cookie: SpotMarketCookie,

View File

@ -1,9 +1,10 @@
use super::*;
use anchor_lang::prelude::AccountMeta;
#[tokio::test]
async fn test_stale_oracle_deposit_withdraw() -> Result<(), TransportError> {
let mut test_builder = TestContextBuilder::new();
test_builder.test().set_compute_max_units(100_000); // bad oracles log a lot
test_builder.test().set_compute_max_units(150_000); // bad oracles log a lot
let context = test_builder.start_default().await;
let solana = &context.solana.clone();
@ -17,7 +18,7 @@ async fn test_stale_oracle_deposit_withdraw() -> Result<(), TransportError> {
// SETUP: Create a group, account, register tokens
//
let mango_setup::GroupWithTokens { group, .. } = mango_setup::GroupWithTokensConfig {
let mango_setup::GroupWithTokens { group, tokens, .. } = mango_setup::GroupWithTokensConfig {
admin,
payer,
mints: mints.to_vec(),
@ -71,6 +72,7 @@ async fn test_stale_oracle_deposit_withdraw() -> Result<(), TransportError> {
send_tx(
solana,
StubOracleSetTestInstruction {
oracle: tokens[0].oracle,
group,
mint: mints[0].pubkey,
admin,
@ -84,6 +86,7 @@ async fn test_stale_oracle_deposit_withdraw() -> Result<(), TransportError> {
send_tx(
solana,
StubOracleSetTestInstruction {
oracle: tokens[1].oracle,
group,
mint: mints[1].pubkey,
admin,
@ -97,6 +100,7 @@ async fn test_stale_oracle_deposit_withdraw() -> Result<(), TransportError> {
send_tx(
solana,
StubOracleSetTestInstruction {
oracle: tokens[2].oracle,
group,
mint: mints[2].pubkey,
admin,
@ -172,3 +176,143 @@ async fn test_stale_oracle_deposit_withdraw() -> Result<(), TransportError> {
Ok(())
}
#[tokio::test]
async fn test_fallback_oracle_withdraw() -> Result<(), TransportError> {
let mut test_builder = TestContextBuilder::new();
test_builder.test().set_compute_max_units(150_000); // bad oracles log a lot
let context = test_builder.start_default().await;
let solana = &context.solana.clone();
let fallback_oracle_kp = TestKeypair::new();
let fallback_oracle = fallback_oracle_kp.pubkey();
let admin = TestKeypair::new();
let owner = context.users[0].key;
let payer = context.users[1].key;
let mints = &context.mints[0..3];
let payer_token_accounts = &context.users[1].token_accounts[0..3];
let mango_setup::GroupWithTokens { group, tokens, .. } = mango_setup::GroupWithTokensConfig {
admin,
payer,
mints: mints.to_vec(),
..mango_setup::GroupWithTokensConfig::default()
}
.create(solana)
.await;
// setup fallback_oracle
send_tx(
solana,
StubOracleCreate {
oracle: fallback_oracle_kp,
group,
mint: mints[2].pubkey,
admin,
payer,
},
)
.await
.unwrap();
// add a fallback oracle
send_tx(
solana,
TokenEdit {
group,
admin,
mint: mints[2].pubkey,
fallback_oracle,
options: mango_v4::instruction::TokenEdit {
set_fallback_oracle: true,
..token_edit_instruction_default()
},
},
)
.await
.unwrap();
let bank_data: Bank = solana.get_account(tokens[2].bank).await;
assert!(bank_data.fallback_oracle == fallback_oracle);
// fill vaults, so we can borrow
let _vault_account = create_funded_account(
&solana,
group,
owner,
2,
&context.users[1],
mints,
100_000,
0,
)
.await;
// Create account with token3 of deposits
let account = create_funded_account(
&solana,
group,
owner,
0,
&context.users[1],
&[mints[2]],
1_000_000,
0,
)
.await;
// Create some token1 borrows
send_tx(
solana,
TokenWithdrawInstruction {
amount: 1,
allow_borrow: true,
account,
owner,
token_account: payer_token_accounts[1],
bank_index: 0,
},
)
.await
.unwrap();
// Make oracle invalid by increasing deviation
send_tx(
solana,
StubOracleSetTestInstruction {
oracle: tokens[2].oracle,
group,
mint: mints[2].pubkey,
admin,
price: 1.0,
last_update_slot: 0,
deviation: 100.0,
},
)
.await
.unwrap();
let token_withdraw_ix = TokenWithdrawInstruction {
amount: 1,
allow_borrow: true,
account,
owner,
token_account: payer_token_accounts[2],
bank_index: 0,
};
// Verify that withdrawing collateral won't work
assert!(send_tx(solana, token_withdraw_ix.clone(),).await.is_err());
// now send txn with a fallback oracle in the remaining accounts
let fallback_oracle_meta = AccountMeta {
pubkey: fallback_oracle,
is_writable: false,
is_signer: false,
};
send_tx_with_extra_accounts(solana, token_withdraw_ix, vec![fallback_oracle_meta])
.await
.unwrap();
Ok(())
}

View File

@ -71,6 +71,7 @@ async fn test_token_conditional_swap_basic() -> Result<(), TransportError> {
group,
admin,
mint: quote_token.mint.pubkey,
fallback_oracle: Pubkey::default(),
options: mango_v4::instruction::TokenEdit {
token_conditional_swap_taker_fee_rate_opt: Some(0.05),
token_conditional_swap_maker_fee_rate_opt: Some(0.1),
@ -388,6 +389,7 @@ async fn test_token_conditional_swap_linear_auction() -> Result<(), TransportErr
group,
admin,
mint: quote_token.mint.pubkey,
fallback_oracle: Pubkey::default(),
options: mango_v4::instruction::TokenEdit {
token_conditional_swap_taker_fee_rate_opt: Some(0.05),
token_conditional_swap_maker_fee_rate_opt: Some(0.1),
@ -648,6 +650,7 @@ async fn test_token_conditional_swap_premium_auction() -> Result<(), TransportEr
group,
admin,
mint: quote_token.mint.pubkey,
fallback_oracle: Pubkey::default(),
options: mango_v4::instruction::TokenEdit {
token_conditional_swap_taker_fee_rate_opt: Some(0.05),
token_conditional_swap_maker_fee_rate_opt: Some(0.1),

View File

@ -41,14 +41,15 @@ impl ClientAccountLoader for &SolanaCookie {
}
}
// This fill return a failure if the tx resulted in an error
// This will return a failure if the tx resulted in an error
pub async fn send_tx<CI: ClientInstruction>(
solana: &SolanaCookie,
ix: CI,
) -> std::result::Result<CI::Accounts, TransportError> {
let (accounts, instruction) = ix.to_instruction(solana).await;
let signers = ix.signers();
let instructions = vec![instruction];
let instructions = vec![instruction.clone()];
println!("IX IX: {:?}", instruction);
let result = solana
.process_transaction(&instructions, Some(&signers[..]))
.await?;
@ -56,6 +57,21 @@ pub async fn send_tx<CI: ClientInstruction>(
Ok(accounts)
}
// This will return a failure if the tx resulted in an error
pub async fn send_tx_with_extra_accounts<CI: ClientInstruction>(
solana: &SolanaCookie,
ix: CI,
account_metas: Vec<AccountMeta>,
) -> std::result::Result<BanksTransactionResultWithMetadata, BanksClientError> {
let (_, mut instruction) = ix.to_instruction(solana).await;
instruction.accounts.extend(account_metas);
let signers = ix.signers();
let instructions = vec![instruction.clone()];
solana
.process_transaction(&instructions, Some(&signers[..]))
.await
}
// This will return success even if the tx failed to finish
pub async fn send_tx_get_metadata<CI: ClientInstruction>(
solana: &SolanaCookie,
@ -428,6 +444,7 @@ pub async fn set_bank_stub_oracle_price(
send_tx(
solana,
StubOracleSetInstruction {
oracle: token.oracle,
group,
admin,
mint: token.mint.pubkey,
@ -722,6 +739,7 @@ impl ClientInstruction for FlashLoanEndInstruction {
}
}
#[derive(Clone)]
pub struct TokenWithdrawInstruction {
pub amount: u64,
pub allow_borrow: bool,
@ -793,6 +811,7 @@ impl ClientInstruction for TokenWithdrawInstruction {
}
}
#[derive(Clone)]
pub struct TokenDepositInstruction {
pub amount: u64,
pub reduce_only: bool,
@ -959,6 +978,7 @@ pub struct TokenRegisterInstruction {
pub group: Pubkey,
pub admin: TestKeypair,
pub mint: Pubkey,
pub oracle: Pubkey,
pub payer: TestKeypair,
}
#[async_trait::async_trait(?Send)]
@ -1042,16 +1062,7 @@ impl ClientInstruction for TokenRegisterInstruction {
&program_id,
)
.0;
// TODO: remove copy pasta of pda derivation, use reference
let oracle = Pubkey::find_program_address(
&[
b"StubOracle".as_ref(),
self.group.as_ref(),
self.mint.as_ref(),
],
&program_id,
)
.0;
let fallback_oracle = Pubkey::default();
let accounts = Self::Accounts {
group: self.group,
@ -1060,7 +1071,8 @@ impl ClientInstruction for TokenRegisterInstruction {
bank,
vault,
mint_info,
oracle,
oracle: self.oracle,
fallback_oracle,
payer: self.payer.pubkey(),
token_program: Token::id(),
system_program: System::id(),
@ -1262,6 +1274,7 @@ pub fn token_edit_instruction_default() -> mango_v4::instruction::TokenEdit {
maint_weight_shift_asset_target_opt: None,
maint_weight_shift_liab_target_opt: None,
maint_weight_shift_abort: false,
set_fallback_oracle: false,
}
}
@ -1269,6 +1282,7 @@ pub struct TokenEdit {
pub group: Pubkey,
pub admin: TestKeypair,
pub mint: Pubkey,
pub fallback_oracle: Pubkey,
pub options: mango_v4::instruction::TokenEdit,
}
#[async_trait::async_trait(?Send)]
@ -1297,6 +1311,7 @@ impl ClientInstruction for TokenEdit {
admin: self.admin.pubkey(),
mint_info: mint_info_key,
oracle: mint_info.oracle,
fallback_oracle: self.fallback_oracle,
};
let mut instruction = make_instruction(program_id, &accounts, &self.options);
@ -1360,6 +1375,7 @@ impl ClientInstruction for TokenEditWeights {
admin: self.admin.pubkey(),
mint_info: mint_info_key,
oracle: mint_info.oracle,
fallback_oracle: mint_info.fallback_oracle,
};
let mut instruction = make_instruction(program_id, &accounts, &instruction);
@ -1416,6 +1432,7 @@ impl ClientInstruction for TokenResetStablePriceModel {
admin: self.admin.pubkey(),
mint_info: mint_info_key,
oracle: mint_info.oracle,
fallback_oracle: mint_info.fallback_oracle,
};
let mut instruction = make_instruction(program_id, &accounts, &instruction);
@ -1477,6 +1494,7 @@ impl ClientInstruction for TokenResetNetBorrows {
admin: self.admin.pubkey(),
mint_info: mint_info_key,
oracle: mint_info.oracle,
fallback_oracle: mint_info.fallback_oracle,
};
let mut instruction = make_instruction(program_id, &accounts, &instruction);
@ -1535,6 +1553,7 @@ impl ClientInstruction for TokenMakeReduceOnly {
admin: self.admin.pubkey(),
mint_info: mint_info_key,
oracle: mint_info.oracle,
fallback_oracle: mint_info.fallback_oracle,
};
let mut instruction = make_instruction(program_id, &accounts, &instruction);
@ -1558,6 +1577,7 @@ pub struct StubOracleSetInstruction {
pub group: Pubkey,
pub admin: TestKeypair,
pub price: f64,
pub oracle: Pubkey,
}
#[async_trait::async_trait(?Send)]
impl ClientInstruction for StubOracleSetInstruction {
@ -1572,19 +1592,9 @@ impl ClientInstruction for StubOracleSetInstruction {
let instruction = Self::Instruction {
price: I80F48::from_num(self.price),
};
// TODO: remove copy pasta of pda derivation, use reference
let oracle = Pubkey::find_program_address(
&[
b"StubOracle".as_ref(),
self.group.as_ref(),
self.mint.as_ref(),
],
&program_id,
)
.0;
let accounts = Self::Accounts {
oracle,
oracle: self.oracle,
group: self.group,
admin: self.admin.pubkey(),
};
@ -1599,6 +1609,7 @@ impl ClientInstruction for StubOracleSetInstruction {
}
pub struct StubOracleSetTestInstruction {
pub oracle: Pubkey,
pub mint: Pubkey,
pub group: Pubkey,
pub admin: TestKeypair,
@ -1621,18 +1632,9 @@ impl ClientInstruction for StubOracleSetTestInstruction {
last_update_slot: self.last_update_slot,
deviation: I80F48::from_num(self.deviation),
};
let oracle = Pubkey::find_program_address(
&[
b"StubOracle".as_ref(),
self.group.as_ref(),
self.mint.as_ref(),
],
&program_id,
)
.0;
let accounts = Self::Accounts {
oracle,
oracle: self.oracle,
group: self.group,
admin: self.admin.pubkey(),
};
@ -1647,10 +1649,11 @@ impl ClientInstruction for StubOracleSetTestInstruction {
}
pub struct StubOracleCreate {
pub group: Pubkey,
pub mint: Pubkey,
pub oracle: TestKeypair,
pub admin: TestKeypair,
pub payer: TestKeypair,
pub group: Pubkey,
pub mint: Pubkey,
}
#[async_trait::async_trait(?Send)]
impl ClientInstruction for StubOracleCreate {
@ -1666,19 +1669,9 @@ impl ClientInstruction for StubOracleCreate {
price: I80F48::from_num(1.0),
};
let oracle = Pubkey::find_program_address(
&[
b"StubOracle".as_ref(),
self.group.as_ref(),
self.mint.as_ref(),
],
&program_id,
)
.0;
let accounts = Self::Accounts {
group: self.group,
oracle,
oracle: self.oracle.pubkey(),
mint: self.mint,
admin: self.admin.pubkey(),
payer: self.payer.pubkey(),
@ -1690,11 +1683,12 @@ impl ClientInstruction for StubOracleCreate {
}
fn signers(&self) -> Vec<TestKeypair> {
vec![self.payer, self.admin]
vec![self.payer, self.admin, self.oracle]
}
}
pub struct StubOracleCloseInstruction {
pub oracle: Pubkey,
pub group: Pubkey,
pub mint: Pubkey,
pub admin: TestKeypair,
@ -1712,20 +1706,10 @@ impl ClientInstruction for StubOracleCloseInstruction {
let program_id = mango_v4::id();
let instruction = Self::Instruction {};
let oracle = Pubkey::find_program_address(
&[
b"StubOracle".as_ref(),
self.group.as_ref(),
self.mint.as_ref(),
],
&program_id,
)
.0;
let accounts = Self::Accounts {
group: self.group,
admin: self.admin.pubkey(),
oracle,
oracle: self.oracle,
sol_destination: self.sol_destination,
token_program: Token::id(),
};

View File

@ -58,6 +58,7 @@ impl<'a> GroupWithTokensConfig {
let create_stub_oracle_accounts = send_tx(
solana,
StubOracleCreate {
oracle: TestKeypair::new(),
group,
mint: mint.pubkey,
admin,
@ -74,6 +75,7 @@ impl<'a> GroupWithTokensConfig {
admin,
mint: mint.pubkey,
price: 1.0,
oracle,
},
)
.await
@ -104,6 +106,7 @@ impl<'a> GroupWithTokensConfig {
liquidation_fee: 0.02,
group,
admin,
oracle,
mint: mint.pubkey,
payer,
min_vault_to_deposits_ratio: 0.2,