add fallback CU and serum tests
This commit is contained in:
parent
aed2afbde4
commit
2b0a0e3a3e
|
@ -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> {
|
||||
|
|
|
@ -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, "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,
|
||||
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,
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue