add fallback CU and serum tests

This commit is contained in:
Lou-Kamades 2023-11-22 16:40:48 -06:00
parent aed2afbde4
commit 2b0a0e3a3e
3 changed files with 436 additions and 6 deletions

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> {
@ -143,6 +169,145 @@ async fn test_health_compute_tokens_during_maint_weight_shift() -> Result<(), Tr
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 fallback_metas: Vec<AccountMeta> = fallback_oracle_kps
.iter()
.map(|x| AccountMeta {
pubkey: x.pubkey(),
is_signer: false,
is_writable: false,
})
.collect();
// let fallback_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 cu_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();
cu_measurements.push(
deposit_cu_fallbacks_datapoint(
solana,
account,
owner,
*token_account,
fallback_metas.clone(),
)
.await,
);
}
for (i, pair) in cu_measurements.windows(2).enumerate() {
println!(
"after adding token {}: {} (+{})",
i,
pair[1],
pair[1] - pair[0]
);
}
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);
Ok(())
}
// Try to reach compute limits in health checks by having many serum markets in an account
#[tokio::test]
async fn test_health_compute_serum() -> Result<(), TransportError> {

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;
@ -1481,6 +1482,272 @@ 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 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 {
admin,
payer,
mints: mints.to_vec(),
..GroupWithTokensConfig::default()
}
.create(solana)
.await;
let base_token = &tokens[0];
let quote_token = &tokens[1];
//
// SETUP: Create a fallback oracle
//
send_tx(
solana,
StubOracleCreate {
oracle: fallback_oracle_kp,
group,
mint: mints[2].pubkey,
admin,
payer,
},
)
.await
.unwrap();
//
// SETUP: 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;
//
// 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,
allow_borrow: true,
account,
owner,
token_account: payer_token_accounts[2],
bank_index: 0,
},
)
.await
.unwrap();
//
// SETUP: Create serum market
//
let serum_market_cookie = context
.serum
.list_spot_market(&base_token.mint, &quote_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,
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, 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,
0
);
assert_eq!(
account_data
.token_position_by_raw_index(2)
.unwrap()
.in_use_count,
1
);
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

@ -62,16 +62,14 @@ pub async fn send_tx_with_extra_accounts<CI: ClientInstruction>(
solana: &SolanaCookie,
ix: CI,
account_metas: Vec<AccountMeta>,
) -> std::result::Result<CI::Accounts, TransportError> {
let (accounts, mut instruction) = ix.to_instruction(solana).await;
) -> 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()];
let result = solana
solana
.process_transaction(&instructions, Some(&signers[..]))
.await?;
result.result?;
Ok(accounts)
.await
}
// This will return success even if the tx failed to finish