Merge pull request #790 from blockworks-foundation/lou/fallback-oracle
Add fallback oracles
This commit is contained in:
commit
838df8cd7c
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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>(),
|
||||
)]
|
||||
|
|
|
@ -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>,
|
||||
}
|
||||
|
|
|
@ -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>,
|
||||
|
||||
|
|
|
@ -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>,
|
||||
|
||||
|
|
|
@ -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::*;
|
||||
|
|
|
@ -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(),
|
||||
});
|
||||
|
||||
|
|
|
@ -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(),
|
||||
});
|
||||
|
||||
|
|
|
@ -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(),
|
||||
});
|
||||
|
||||
|
|
|
@ -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)?;
|
||||
}
|
||||
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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>(),
|
||||
|
|
|
@ -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::*;
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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(),
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue