reduce CU cost of fallback oracles
This commit is contained in:
parent
2b0a0e3a3e
commit
59001b3631
|
@ -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
|
||||
)
|
||||
|
|
|
@ -22,8 +22,8 @@ pub struct TokenEdit<'info> {
|
|||
/// CHECK: The oracle can be one of several different account types
|
||||
pub oracle: UncheckedAccount<'info>,
|
||||
|
||||
/// The oracle account is optional and only used when reset_stable_price is set.
|
||||
/// The fallback oracle account is optional and only used when set_fallback_oracle is true.
|
||||
///
|
||||
/// CHECK: The oracle can be one of several different account types
|
||||
/// CHECK: The fallback oracle can be one of several different account types
|
||||
pub fallback_oracle: UncheckedAccount<'info>,
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
let slot_opt = Some(Clock::get()?.slot);
|
||||
unsafe_oracle_state.check_confidence_and_maybe_staleness(
|
||||
&bank.oracle,
|
||||
&bank.name(),
|
||||
&bank.oracle_config,
|
||||
Some(Clock::get()?.slot),
|
||||
)?;
|
||||
slot_opt,
|
||||
).with_context(|| oracle_log_context(&unsafe_oracle_state, &bank.oracle_config, slot_opt))?;
|
||||
bank.check_net_borrows(unsafe_oracle_state.price)?;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
@ -949,20 +950,10 @@ impl Bank {
|
|||
staleness_slot: Option<u64>,
|
||||
) -> Result<I80F48> {
|
||||
require_keys_eq!(self.oracle, *oracle_acc.key());
|
||||
self.oracle_price_inner(oracle_acc, staleness_slot)
|
||||
}
|
||||
|
||||
fn oracle_price_inner(
|
||||
&self,
|
||||
oracle_acc: &impl KeyedAccountReader,
|
||||
staleness_slot: Option<u64>,
|
||||
) -> Result<I80F48> {
|
||||
let state = oracle::oracle_state_unchecked(oracle_acc, self.mint_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.price)
|
||||
}
|
||||
|
||||
|
@ -973,13 +964,32 @@ impl Bank {
|
|||
fallback_oracle_acc_opt: Option<&impl KeyedAccountReader>,
|
||||
staleness_slot: Option<u64>,
|
||||
) -> Result<I80F48> {
|
||||
let primary_price = self.oracle_price(oracle_acc, staleness_slot);
|
||||
if primary_price.is_oracle_error() && fallback_oracle_acc_opt.is_some() {
|
||||
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,
|
||||
);
|
||||
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());
|
||||
self.oracle_price_inner(fallback_oracle_acc, staleness_slot)
|
||||
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(|| {
|
||||
oracle_log_context(&fallback_state, &self.oracle_config, staleness_slot)
|
||||
})?;
|
||||
Ok(fallback_state.price)
|
||||
} else {
|
||||
primary_price
|
||||
primary_ok.with_context(|| {
|
||||
oracle_log_context(&primary_state, &self.oracle_config, staleness_slot)
|
||||
})?;
|
||||
Ok(primary_state.price)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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(())
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -94,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 < 3230);
|
||||
assert!(avg_cu_increase < 3350);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -164,7 +164,7 @@ 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(())
|
||||
}
|
||||
|
@ -188,7 +188,7 @@ async fn test_health_compute_tokens_fallback_oracles() -> Result<(), TransportEr
|
|||
for _ in 0..num_tokens {
|
||||
fallback_oracle_kps.push(TestKeypair::new());
|
||||
}
|
||||
let fallback_metas: Vec<AccountMeta> = fallback_oracle_kps
|
||||
let success_metas: Vec<AccountMeta> = fallback_oracle_kps
|
||||
.iter()
|
||||
.map(|x| AccountMeta {
|
||||
pubkey: x.pubkey(),
|
||||
|
@ -197,7 +197,7 @@ async fn test_health_compute_tokens_fallback_oracles() -> Result<(), TransportEr
|
|||
})
|
||||
.collect();
|
||||
|
||||
// let fallback_metas = vec![];
|
||||
let failure_metas = vec![];
|
||||
|
||||
//
|
||||
// SETUP: Create a group and an account
|
||||
|
@ -215,7 +215,8 @@ async fn test_health_compute_tokens_fallback_oracles() -> Result<(), TransportEr
|
|||
let account =
|
||||
create_funded_account(&solana, group, owner, 0, &context.users[1], &[], 1000, 0).await;
|
||||
|
||||
let mut cu_measurements = vec![];
|
||||
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;
|
||||
}
|
||||
|
@ -279,19 +280,39 @@ async fn test_health_compute_tokens_fallback_oracles() -> Result<(), TransportEr
|
|||
.await
|
||||
.unwrap();
|
||||
|
||||
cu_measurements.push(
|
||||
success_measurements.push(
|
||||
deposit_cu_fallbacks_datapoint(
|
||||
solana,
|
||||
account,
|
||||
owner,
|
||||
*token_account,
|
||||
fallback_metas.clone(),
|
||||
success_metas.clone(),
|
||||
)
|
||||
.await,
|
||||
);
|
||||
|
||||
failure_measurements.push(
|
||||
deposit_cu_fallbacks_datapoint(
|
||||
solana,
|
||||
account,
|
||||
owner,
|
||||
*token_account,
|
||||
failure_metas.clone(),
|
||||
)
|
||||
.await,
|
||||
);
|
||||
}
|
||||
|
||||
for (i, pair) in cu_measurements.windows(2).enumerate() {
|
||||
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,
|
||||
|
@ -300,10 +321,21 @@ async fn test_health_compute_tokens_fallback_oracles() -> Result<(), TransportEr
|
|||
);
|
||||
}
|
||||
|
||||
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 < 16_600);
|
||||
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(())
|
||||
}
|
||||
|
|
|
@ -1491,26 +1491,27 @@ async fn test_fallback_oracle_serum() -> Result<(), TransportError> {
|
|||
|
||||
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];
|
||||
|
||||
//
|
||||
// SETUP: Create a group and an account
|
||||
//
|
||||
|
||||
let GroupWithTokens { group, tokens, .. } = GroupWithTokensConfig {
|
||||
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,
|
||||
payer,
|
||||
mints: mints.to_vec(),
|
||||
..GroupWithTokensConfig::default()
|
||||
}
|
||||
.create(solana)
|
||||
.await;
|
||||
let base_token = &tokens[0];
|
||||
let quote_token = &tokens[1];
|
||||
tokens,
|
||||
..
|
||||
} = group_with_tokens;
|
||||
|
||||
//
|
||||
// SETUP: Create a fallback oracle
|
||||
|
@ -1520,7 +1521,7 @@ async fn test_fallback_oracle_serum() -> Result<(), TransportError> {
|
|||
StubOracleCreate {
|
||||
oracle: fallback_oracle_kp,
|
||||
group,
|
||||
mint: mints[2].pubkey,
|
||||
mint: tokens[2].mint.pubkey,
|
||||
admin,
|
||||
payer,
|
||||
},
|
||||
|
@ -1536,7 +1537,7 @@ async fn test_fallback_oracle_serum() -> Result<(), TransportError> {
|
|||
TokenEdit {
|
||||
group,
|
||||
admin,
|
||||
mint: mints[2].pubkey,
|
||||
mint: tokens[2].mint.pubkey,
|
||||
fallback_oracle,
|
||||
options: mango_v4::instruction::TokenEdit {
|
||||
set_fallback_oracle: true,
|
||||
|
@ -1550,41 +1551,13 @@ async fn test_fallback_oracle_serum() -> Result<(), TransportError> {
|
|||
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;
|
||||
|
||||
//
|
||||
// SETUP: Create account
|
||||
//
|
||||
let account = create_funded_account(
|
||||
&solana,
|
||||
group,
|
||||
owner,
|
||||
0,
|
||||
&context.users[1],
|
||||
&[mints[1]],
|
||||
1_000,
|
||||
0,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Create some token1 borrows
|
||||
send_tx(
|
||||
solana,
|
||||
TokenWithdrawInstruction {
|
||||
amount: 300,
|
||||
amount: 1_500,
|
||||
allow_borrow: true,
|
||||
account,
|
||||
account: order_placer.account,
|
||||
owner,
|
||||
token_account: payer_token_accounts[2],
|
||||
bank_index: 0,
|
||||
|
@ -1593,76 +1566,13 @@ async fn test_fallback_oracle_serum() -> Result<(), TransportError> {
|
|||
.await
|
||||
.unwrap();
|
||||
|
||||
//
|
||||
// SETUP: Create serum market
|
||||
//
|
||||
let serum_market_cookie = context
|
||||
.serum
|
||||
.list_spot_market(&base_token.mint, "e_token.mint)
|
||||
.await;
|
||||
|
||||
//
|
||||
// TEST: Register a serum market
|
||||
//
|
||||
let serum_market = send_tx(
|
||||
solana,
|
||||
Serum3RegisterMarketInstruction {
|
||||
group,
|
||||
admin,
|
||||
serum_program: context.serum.program_id,
|
||||
serum_market_external: serum_market_cookie.market,
|
||||
market_index: 0,
|
||||
base_bank: base_token.bank,
|
||||
quote_bank: quote_token.bank,
|
||||
payer,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.serum_market;
|
||||
|
||||
//
|
||||
// TEST: Create an open orders account
|
||||
//
|
||||
let open_orders = send_tx(
|
||||
solana,
|
||||
Serum3CreateOpenOrdersInstruction {
|
||||
account,
|
||||
serum_market,
|
||||
owner,
|
||||
payer,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.open_orders;
|
||||
|
||||
let account_data = get_mango_account(solana, account).await;
|
||||
assert_eq!(
|
||||
account_data
|
||||
.active_serum3_orders()
|
||||
.map(|v| (v.open_orders, v.market_index))
|
||||
.collect::<Vec<_>>(),
|
||||
[(open_orders, 0)]
|
||||
);
|
||||
|
||||
let mut order_placer = SerumOrderPlacer {
|
||||
solana: solana.clone(),
|
||||
serum: context.serum.clone(),
|
||||
account,
|
||||
owner: owner.clone(),
|
||||
serum_market,
|
||||
open_orders,
|
||||
next_client_order_id: 0,
|
||||
};
|
||||
|
||||
// Make oracle invalid by increasing deviation
|
||||
send_tx(
|
||||
solana,
|
||||
StubOracleSetTestInstruction {
|
||||
oracle: tokens[2].oracle,
|
||||
group,
|
||||
mint: mints[2].pubkey,
|
||||
mint: tokens[2].mint.pubkey,
|
||||
admin,
|
||||
price: 1.0,
|
||||
last_update_slot: 0,
|
||||
|
@ -1713,7 +1623,7 @@ async fn test_fallback_oracle_serum() -> Result<(), TransportError> {
|
|||
.unwrap();
|
||||
result.result.unwrap();
|
||||
|
||||
let account_data = get_mango_account(solana, account).await;
|
||||
let account_data = get_mango_account(solana, order_placer.account).await;
|
||||
assert_eq!(
|
||||
account_data
|
||||
.token_position_by_raw_index(0)
|
||||
|
@ -1726,14 +1636,14 @@ async fn test_fallback_oracle_serum() -> Result<(), TransportError> {
|
|||
.token_position_by_raw_index(1)
|
||||
.unwrap()
|
||||
.in_use_count,
|
||||
0
|
||||
1
|
||||
);
|
||||
assert_eq!(
|
||||
account_data
|
||||
.token_position_by_raw_index(2)
|
||||
.unwrap()
|
||||
.in_use_count,
|
||||
1
|
||||
0
|
||||
);
|
||||
let serum_orders = account_data.serum3_orders_by_raw_index(0).unwrap();
|
||||
assert_eq!(serum_orders.base_borrows_without_fee, 0);
|
||||
|
|
Loading…
Reference in New Issue