allow skipping banks and oracles in fixed-order health account list (#891)

The following instructions now allow skipping banks and oracles if health
and the token position balance is not negative:
- token_withdraw
- token_deposit
- serum3_place_order
- perp_place_order
- flash_loan

They also allow skipping oracles that are stale.
This commit is contained in:
Christian Kamm 2024-03-04 15:49:14 +01:00 committed by GitHub
parent df15672522
commit a7aaaff07e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 1334 additions and 251 deletions

View File

@ -145,6 +145,8 @@ pub enum MangoError {
MissingFeedForCLMMOracle,
#[msg("the asset does not allow liquidation")]
TokenAssetLiquidationDisabled,
#[msg("for borrows the bank must be in the health account list")]
BorrowsRequireHealthAccountBank,
}
impl MangoError {

View File

@ -26,6 +26,8 @@ use crate::state::{Bank, MangoAccountRef, PerpMarket, PerpMarketIndex, TokenInde
/// are passed because health needs to be computed for different baskets in
/// one instruction (such as for liquidation instructions).
pub trait AccountRetriever {
fn available_banks(&self) -> Result<Vec<TokenIndex>>;
fn bank_and_oracle(
&self,
group: &Pubkey,
@ -45,11 +47,12 @@ pub trait AccountRetriever {
/// Assumes the account infos needed for the health computation follow a strict order.
///
/// 1. n_banks Bank account, in the order of account.token_iter_active()
/// 1. n_banks Bank account, in the order of account.active_token_positions() although it's
/// allowed for some of the banks (and their oracles in 2.) to be skipped
/// 2. n_banks oracle accounts, one for each bank in the same order
/// 3. PerpMarket accounts, in the order of account.perps.iter_active_accounts()
/// 3. PerpMarket accounts, in the order of account.perps.active_perp_positions()
/// 4. PerpMarket oracle accounts, in the order of the perp market accounts
/// 5. serum3 OpenOrders accounts, in the order of account.serum3.iter_active()
/// 5. serum3 OpenOrders accounts, in the order of account.active_serum3_orders()
/// 6. fallback oracle accounts, order and existence of accounts is not guaranteed
pub struct FixedOrderAccountRetriever<T: KeyedAccountReader> {
pub ais: Vec<T>,
@ -63,20 +66,61 @@ pub struct FixedOrderAccountRetriever<T: KeyedAccountReader> {
pub sol_oracle_index: Option<usize>,
}
/// Creates a FixedOrderAccountRetriever where all banks are present
pub fn new_fixed_order_account_retriever<'a, 'info>(
ais: &'a [AccountInfo<'info>],
account: &MangoAccountRef,
now_slot: u64,
) -> Result<FixedOrderAccountRetriever<AccountInfoRef<'a, 'info>>> {
let active_token_len = account.active_token_positions().count();
// Load the banks early to verify them
for ai in &ais[0..active_token_len] {
ai.load::<Bank>()?;
}
new_fixed_order_account_retriever_inner(ais, account, now_slot, active_token_len)
}
/// A FixedOrderAccountRetriever with n_banks <= active_token_positions().count(),
/// depending on which banks were passed.
pub fn new_fixed_order_account_retriever_with_optional_banks<'a, 'info>(
ais: &'a [AccountInfo<'info>],
account: &MangoAccountRef,
now_slot: u64,
) -> Result<FixedOrderAccountRetriever<AccountInfoRef<'a, 'info>>> {
// Scan for the number of banks provided
let mut n_banks = 0;
for ai in ais {
if let Some((_, bank_result)) = can_load_as::<Bank>((0, ai)) {
bank_result?;
n_banks += 1;
} else {
break;
}
}
let active_token_len = account.active_token_positions().count();
require_gte!(active_token_len, n_banks);
new_fixed_order_account_retriever_inner(ais, account, now_slot, n_banks)
}
pub fn new_fixed_order_account_retriever_inner<'a, 'info>(
ais: &'a [AccountInfo<'info>],
account: &MangoAccountRef,
now_slot: u64,
n_banks: usize,
) -> Result<FixedOrderAccountRetriever<AccountInfoRef<'a, 'info>>> {
let active_serum3_len = account.active_serum3_orders().count();
let active_perp_len = account.active_perp_positions().count();
let expected_ais = active_token_len * 2 // banks + oracles
let expected_ais = n_banks * 2 // banks + oracles
+ active_perp_len * 2 // PerpMarkets + Oracles
+ active_serum3_len; // open_orders
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
n_banks, n_banks, active_perp_len, active_perp_len, active_serum3_len
);
let usdc_oracle_index = ais[..]
.iter()
@ -87,11 +131,11 @@ pub fn new_fixed_order_account_retriever<'a, 'info>(
Ok(FixedOrderAccountRetriever {
ais: AccountInfoRef::borrow_slice(ais)?,
n_banks: active_token_len,
n_banks,
n_perps: active_perp_len,
begin_perp: active_token_len * 2,
begin_serum3: active_token_len * 2 + active_perp_len * 2,
staleness_slot: Some(Clock::get()?.slot),
begin_perp: n_banks * 2,
begin_serum3: n_banks * 2 + active_perp_len * 2,
staleness_slot: Some(now_slot),
begin_fallback_oracles: expected_ais,
usdc_oracle_index,
sol_oracle_index,
@ -99,11 +143,28 @@ pub fn new_fixed_order_account_retriever<'a, 'info>(
}
impl<T: KeyedAccountReader> FixedOrderAccountRetriever<T> {
fn bank(&self, group: &Pubkey, account_index: usize, token_index: TokenIndex) -> Result<&Bank> {
let bank = self.ais[account_index].load::<Bank>()?;
require_keys_eq!(bank.group, *group);
require_eq!(bank.token_index, token_index);
Ok(bank)
fn bank(
&self,
group: &Pubkey,
active_token_position_index: usize,
token_index: TokenIndex,
) -> Result<(usize, &Bank)> {
// Maybe not all banks were passed: The desired bank must be at or
// to the left of account_index and left of n_banks.
let end_index = (active_token_position_index + 1).min(self.n_banks);
for i in (0..end_index).rev() {
let ai = &self.ais[i];
let bank = ai.load_fully_unchecked::<Bank>()?;
if bank.token_index == token_index {
require_keys_eq!(bank.group, *group);
return Ok((i, bank));
}
}
Err(error_msg_typed!(
MangoError::InvalidHealthAccountCount,
"bank for token index {} not found",
token_index
))
}
fn perp_market(
@ -146,25 +207,25 @@ impl<T: KeyedAccountReader> FixedOrderAccountRetriever<T> {
}
impl<T: KeyedAccountReader> AccountRetriever for FixedOrderAccountRetriever<T> {
fn available_banks(&self) -> Result<Vec<TokenIndex>> {
let mut result = Vec::with_capacity(self.n_banks);
for bank_ai in &self.ais[0..self.n_banks] {
let bank = bank_ai.load_fully_unchecked::<Bank>()?;
result.push(bank.token_index);
}
Ok(result)
}
fn bank_and_oracle(
&self,
group: &Pubkey,
active_token_position_index: usize,
token_index: TokenIndex,
) -> Result<(&Bank, I80F48)> {
let bank_account_index = active_token_position_index;
let bank = self
.bank(group, bank_account_index, token_index)
.with_context(|| {
format!(
"loading bank with health account index {}, token index {}, passed account {}",
bank_account_index,
token_index,
self.ais[bank_account_index].key(),
)
})?;
let (bank_account_index, bank) =
self.bank(group, active_token_position_index, token_index)?;
let oracle_index = self.n_banks + active_token_position_index;
let oracle_index = self.n_banks + bank_account_index;
let oracle_acc_infos = &self.create_oracle_infos(oracle_index, &bank.fallback_oracle);
let oracle_price_result = bank.oracle_price(oracle_acc_infos, self.staleness_slot);
let oracle_price = oracle_price_result.with_context(|| {
@ -505,6 +566,10 @@ impl<'a, 'info> ScanningAccountRetriever<'a, 'info> {
}
impl<'a, 'info> AccountRetriever for ScanningAccountRetriever<'a, 'info> {
fn available_banks(&self) -> Result<Vec<TokenIndex>> {
Ok(self.banks_and_oracles.index_map.keys().copied().collect())
}
fn bank_and_oracle(
&self,
_group: &Pubkey,
@ -530,6 +595,8 @@ impl<'a, 'info> AccountRetriever for ScanningAccountRetriever<'a, 'info> {
#[cfg(test)]
mod tests {
use crate::state::{MangoAccount, MangoAccountValue};
use super::super::test::*;
use super::*;
use serum_dex::state::OpenOrders;
@ -650,4 +717,98 @@ mod tests {
.perp_market_and_oracle_price(&group, 1, 5)
.is_err());
}
#[test]
fn test_fixed_account_retriever_with_skips() {
let group = Pubkey::new_unique();
let (mut bank1, mut oracle1) = mock_bank_and_oracle(group, 10, 1.0, 0.2, 0.1);
let (mut bank2, mut oracle2) = mock_bank_and_oracle(group, 20, 2.0, 0.2, 0.1);
let (mut bank3, mut oracle3) = mock_bank_and_oracle(group, 30, 3.0, 0.2, 0.1);
let mut perp1 = mock_perp_market(group, oracle2.pubkey, 2.0, 9, (0.2, 0.1), (0.05, 0.02));
let mut oracle2_clone = oracle2.clone();
let buffer = MangoAccount::default_for_tests().try_to_vec().unwrap();
let mut account = MangoAccountValue::from_bytes(&buffer).unwrap();
account.ensure_token_position(10).unwrap();
account.ensure_token_position(20).unwrap();
account.ensure_token_position(30).unwrap();
account.ensure_perp_position(9, 10).unwrap();
// pass all
{
let ais = vec![
bank1.as_account_info(),
bank2.as_account_info(),
bank3.as_account_info(),
oracle1.as_account_info(),
oracle2.as_account_info(),
oracle3.as_account_info(),
perp1.as_account_info(),
oracle2_clone.as_account_info(),
];
let retriever =
new_fixed_order_account_retriever_with_optional_banks(&ais, &account.borrow(), 0)
.unwrap();
assert_eq!(retriever.available_banks(), Ok(vec![10, 20, 30]));
let (i, bank) = retriever.bank(&group, 0, 10).unwrap();
assert_eq!(i, 0);
assert_eq!(bank.token_index, 10);
let (i, bank) = retriever.bank(&group, 1, 20).unwrap();
assert_eq!(i, 1);
assert_eq!(bank.token_index, 20);
let (i, bank) = retriever.bank(&group, 2, 30).unwrap();
assert_eq!(i, 2);
assert_eq!(bank.token_index, 30);
assert!(retriever.perp_market(&group, 6, 9).is_ok());
}
// skip bank2
{
let ais = vec![
bank1.as_account_info(),
bank3.as_account_info(),
oracle1.as_account_info(),
oracle3.as_account_info(),
perp1.as_account_info(),
oracle2_clone.as_account_info(),
];
let retriever =
new_fixed_order_account_retriever_with_optional_banks(&ais, &account.borrow(), 0)
.unwrap();
assert_eq!(retriever.available_banks(), Ok(vec![10, 30]));
let (i, bank) = retriever.bank(&group, 0, 10).unwrap();
assert_eq!(i, 0);
assert_eq!(bank.token_index, 10);
let (i, bank) = retriever.bank(&group, 2, 30).unwrap();
assert_eq!(i, 1);
assert_eq!(bank.token_index, 30);
assert!(retriever.bank(&group, 1, 20).is_err());
assert!(retriever.perp_market(&group, 4, 9).is_ok());
}
// skip all
{
let ais = vec![perp1.as_account_info(), oracle2_clone.as_account_info()];
let retriever =
new_fixed_order_account_retriever_with_optional_banks(&ais, &account.borrow(), 0)
.unwrap();
assert_eq!(retriever.available_banks(), Ok(vec![]));
assert!(retriever.bank(&group, 0, 10).is_err());
assert!(retriever.bank(&group, 1, 20).is_err());
assert!(retriever.bank(&group, 2, 30).is_err());
assert!(retriever.perp_market(&group, 0, 9).is_ok());
}
}
}

View File

@ -96,7 +96,7 @@ pub fn compute_health_from_fixed_accounts(
ais: &[AccountInfo],
now_ts: u64,
) -> Result<I80F48> {
let retriever = new_fixed_order_account_retriever(ais, account)?;
let retriever = new_fixed_order_account_retriever(ais, account, Clock::get()?.slot)?;
Ok(new_health_cache(account, &retriever, now_ts)?.health(health_type))
}
@ -1230,7 +1230,7 @@ pub fn new_health_cache(
retriever: &impl AccountRetriever,
now_ts: u64,
) -> Result<HealthCache> {
new_health_cache_impl(account, retriever, now_ts, false)
new_health_cache_impl(account, retriever, now_ts, false, false)
}
/// Generate a special HealthCache for an account and its health accounts
@ -1243,7 +1243,15 @@ pub fn new_health_cache_skipping_bad_oracles(
retriever: &impl AccountRetriever,
now_ts: u64,
) -> Result<HealthCache> {
new_health_cache_impl(account, retriever, now_ts, true)
new_health_cache_impl(account, retriever, now_ts, true, false)
}
pub fn new_health_cache_skipping_missing_banks_and_bad_oracles(
account: &MangoAccountRef,
retriever: &impl AccountRetriever,
now_ts: u64,
) -> Result<HealthCache> {
new_health_cache_impl(account, retriever, now_ts, true, true)
}
fn new_health_cache_impl(
@ -1254,13 +1262,40 @@ fn new_health_cache_impl(
// not be negative, skip it. This decreases health, but maybe overall it's
// still positive?
skip_bad_oracles: bool,
skip_missing_banks: bool,
) -> Result<HealthCache> {
// token contribution from token accounts
let mut token_infos = Vec::with_capacity(account.active_token_positions().count());
// As a CU optimization, don't call available_banks() unless necessary
let available_banks_opt = if skip_missing_banks {
Some(retriever.available_banks()?)
} else {
None
};
for (i, position) in account.active_token_positions().enumerate() {
// Allow skipping of missing banks only if the account has a nonnegative balance
if skip_missing_banks {
let bank_is_available = available_banks_opt
.as_ref()
.unwrap()
.contains(&position.token_index);
if !bank_is_available {
require_msg_typed!(
position.indexed_position >= 0,
MangoError::InvalidBank,
"the bank for token index {} is a required health account when the account has a negative balance in it",
position.token_index
);
continue;
}
}
let bank_oracle_result =
retriever.bank_and_oracle(&account.fixed.group, i, position.token_index);
// Allow skipping of bad-oracle banks if the account has a nonnegative balance
if skip_bad_oracles
&& bank_oracle_result.is_oracle_error()
&& position.indexed_position >= 0
@ -1301,9 +1336,25 @@ fn new_health_cache_impl(
let oo = retriever.serum_oo(i, &serum_account.open_orders)?;
// find the TokenInfos for the market's base and quote tokens
let base_info_index = find_token_info_index(&token_infos, serum_account.base_token_index)?;
let quote_info_index =
find_token_info_index(&token_infos, serum_account.quote_token_index)?;
// and potentially skip the whole serum contribution if they are not available
let info_index_results = (
find_token_info_index(&token_infos, serum_account.base_token_index),
find_token_info_index(&token_infos, serum_account.quote_token_index),
);
let (base_info_index, quote_info_index) = match info_index_results {
(Ok(base), Ok(quote)) => (base, quote),
_ => {
require_msg_typed!(
skip_bad_oracles || skip_missing_banks,
MangoError::InvalidBank,
"serum market {} misses health accounts for bank {} or {}",
serum_account.market_index,
serum_account.base_token_index,
serum_account.quote_token_index,
);
continue;
}
};
// add the amounts that are freely settleable immediately to token balances
let base_free = I80F48::from(oo.native_coin_free);
@ -1329,6 +1380,12 @@ fn new_health_cache_impl(
i,
perp_position.market_index,
)?;
// Ensure the settle token is available in the health cache
if skip_bad_oracles || skip_missing_banks {
find_token_info_index(&token_infos, perp_market.settle_token_index)?;
}
perp_infos.push(PerpInfo::new(
perp_position,
perp_market,
@ -1879,4 +1936,170 @@ mod tests {
test_health1_runner(testcase);
}
}
#[test]
fn test_health_with_skips() {
let testcase = TestHealth1Case {
// 6, reserved oo funds
token1: 100,
token2: 10,
token3: -10,
oo_1_2: (5, 1),
oo_1_3: (0, 0),
..Default::default()
};
let buffer = MangoAccount::default_for_tests().try_to_vec().unwrap();
let mut account = MangoAccountValue::from_bytes(&buffer).unwrap();
let group = Pubkey::new_unique();
account.fixed.group = group;
let (mut bank1, mut oracle1) = mock_bank_and_oracle(group, 0, 1.0, 0.2, 0.1);
let (mut bank2, mut oracle2) = mock_bank_and_oracle(group, 4, 5.0, 0.5, 0.3);
let (mut bank3, mut oracle3) = mock_bank_and_oracle(group, 5, 10.0, 0.5, 0.3);
bank1
.data()
.change_without_fee(
account.ensure_token_position(0).unwrap().0,
I80F48::from(testcase.token1),
DUMMY_NOW_TS,
)
.unwrap();
bank2
.data()
.change_without_fee(
account.ensure_token_position(4).unwrap().0,
I80F48::from(testcase.token2),
DUMMY_NOW_TS,
)
.unwrap();
bank3
.data()
.change_without_fee(
account.ensure_token_position(5).unwrap().0,
I80F48::from(testcase.token3),
DUMMY_NOW_TS,
)
.unwrap();
let mut oo1 = TestAccount::<OpenOrders>::new_zeroed();
let serum3account1 = account.create_serum3_orders(2).unwrap();
serum3account1.open_orders = oo1.pubkey;
serum3account1.base_token_index = 4;
serum3account1.quote_token_index = 0;
oo1.data().native_pc_total = testcase.oo_1_2.0;
oo1.data().native_coin_total = testcase.oo_1_2.1;
fn compute_health_with_retriever<'a, 'info>(
ais: &[AccountInfo],
account: &MangoAccountValue,
group: Pubkey,
kind: bool,
) -> Result<I80F48> {
let hc = if kind {
let retriever =
ScanningAccountRetriever::new_with_staleness(&ais, &group, None).unwrap();
new_health_cache_skipping_missing_banks_and_bad_oracles(
&account.borrow(),
&retriever,
DUMMY_NOW_TS,
)?
} else {
let retriever = new_fixed_order_account_retriever_with_optional_banks(
&ais,
&account.borrow(),
0,
)
.unwrap();
new_health_cache_skipping_missing_banks_and_bad_oracles(
&account.borrow(),
&retriever,
DUMMY_NOW_TS,
)?
};
Ok(hc.health(HealthType::Init))
}
for retriever_kind in [false, true] {
// baseline with everything
{
let ais = vec![
bank1.as_account_info(),
bank2.as_account_info(),
bank3.as_account_info(),
oracle1.as_account_info(),
oracle2.as_account_info(),
oracle3.as_account_info(),
oo1.as_account_info(),
];
let health =
compute_health_with_retriever(&ais, &account, group, retriever_kind).unwrap();
assert!(health_eq(
health,
0.8 * 100.0 + 0.5 * 5.0 * (10.0 + 2.0) - 1.5 * 10.0 * 10.0
));
}
// missing bank1
{
let ais = vec![
bank2.as_account_info(),
bank3.as_account_info(),
oracle2.as_account_info(),
oracle3.as_account_info(),
oo1.as_account_info(),
];
let health =
compute_health_with_retriever(&ais, &account, group, retriever_kind).unwrap();
assert!(health_eq(health, 0.5 * 5.0 * 10.0 - 1.5 * 10.0 * 10.0));
}
// missing bank2
{
let ais = vec![
bank1.as_account_info(),
bank3.as_account_info(),
oracle1.as_account_info(),
oracle3.as_account_info(),
oo1.as_account_info(),
];
let health =
compute_health_with_retriever(&ais, &account, group, retriever_kind).unwrap();
assert!(health_eq(health, 0.8 * 100.0 - 1.5 * 10.0 * 10.0));
}
// missing bank1 and 2
{
let ais = vec![
bank3.as_account_info(),
oracle3.as_account_info(),
oo1.as_account_info(),
];
let health =
compute_health_with_retriever(&ais, &account, group, retriever_kind).unwrap();
assert!(health_eq(health, -1.5 * 10.0 * 10.0));
}
// missing bank3
{
let ais = vec![
bank1.as_account_info(),
bank2.as_account_info(),
oracle1.as_account_info(),
oracle2.as_account_info(),
oo1.as_account_info(),
];
// bank3 has a negative balance and can't be skipped!
assert!(
compute_health_with_retriever(&ais, &account, group, retriever_kind).is_err()
);
}
}
}
}

View File

@ -2,9 +2,10 @@ use crate::accounts_ix::*;
use crate::accounts_zerocopy::*;
use crate::error::*;
use crate::group_seeds;
use crate::health::{new_fixed_order_account_retriever, new_health_cache, AccountRetriever};
use crate::health::*;
use crate::logs::{emit_stack, FlashLoanLogV3, FlashLoanTokenDetailV3, TokenBalanceLog};
use crate::state::*;
use crate::util::clock_now;
use anchor_lang::prelude::*;
use anchor_lang::solana_program::sysvar::instructions as tx_instructions;
@ -368,8 +369,9 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>(
// all vaults must have had matching banks
for (i, has_bank) in vaults_with_banks.iter().enumerate() {
require_msg!(
require_msg_typed!(
has_bank,
MangoError::InvalidBank,
"missing bank for vault index {}, address {}",
i,
vaults[i].key
@ -387,12 +389,26 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>(
}
// Check health before balance adjustments
let retriever = new_fixed_order_account_retriever(health_ais, &account.borrow())?;
let now_ts: u64 = Clock::get()?.unix_timestamp.try_into().unwrap();
let health_cache = new_health_cache(&account.borrow(), &retriever, now_ts)?;
// The vault-to-bank matching above ensures that the banks for the affected tokens are available.
let (now_ts, now_slot) = clock_now();
let retriever = new_fixed_order_account_retriever_with_optional_banks(
health_ais,
&account.borrow(),
now_slot,
)?;
let health_cache = new_health_cache_skipping_missing_banks_and_bad_oracles(
&account.borrow(),
&retriever,
now_ts,
)?;
let pre_init_health = account.check_health_pre(&health_cache)?;
// Prices for logging and net borrow checks
//
// This also verifies that all affected banks/oracles are available in health_cache:
// That is essential to avoid issues around withdrawing tokens when init health is negative
// (similar issue to token_withdraw)
let mut oracle_prices = vec![];
for change in &changes {
let (_, oracle_price) = retriever.bank_and_oracle(
@ -400,6 +416,8 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>(
change.bank_index,
change.token_index,
)?;
// Sanity check
health_cache.token_info_index(change.token_index)?;
oracle_prices.push(oracle_price);
}
@ -502,8 +520,16 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>(
});
// Check health after account position changes
let retriever = new_fixed_order_account_retriever(health_ais, &account.borrow())?;
let health_cache = new_health_cache(&account.borrow(), &retriever, now_ts)?;
let retriever = new_fixed_order_account_retriever_with_optional_banks(
health_ais,
&account.borrow(),
now_slot,
)?;
let health_cache = new_health_cache_skipping_missing_banks_and_bad_oracles(
&account.borrow(),
&retriever,
now_ts,
)?;
account.check_health_post(&health_cache, pre_init_health)?;
// Deactivate inactive token accounts after health check

View File

@ -4,6 +4,7 @@ use crate::accounts_ix::*;
use crate::error::*;
use crate::health::*;
use crate::state::*;
use crate::util::clock_now;
pub fn perp_liq_force_cancel_orders(
ctx: Context<PerpLiqForceCancelOrders>,
@ -11,10 +12,10 @@ pub fn perp_liq_force_cancel_orders(
) -> Result<()> {
let mut account = ctx.accounts.account.load_full_mut()?;
let now_ts: u64 = Clock::get()?.unix_timestamp.try_into().unwrap();
let (now_ts, now_slot) = clock_now();
let mut health_cache = {
let retriever =
new_fixed_order_account_retriever(ctx.remaining_accounts, &account.borrow())?;
new_fixed_order_account_retriever(ctx.remaining_accounts, &account.borrow(), now_slot)?;
new_health_cache(&account.borrow(), &retriever, now_ts).context("create health cache")?
};

View File

@ -3,8 +3,9 @@ use anchor_lang::prelude::*;
use crate::accounts_ix::*;
use crate::accounts_zerocopy::*;
use crate::error::*;
use crate::health::{new_fixed_order_account_retriever, new_health_cache};
use crate::health::*;
use crate::state::*;
use crate::util::clock_now;
// TODO
#[allow(clippy::too_many_arguments)]
@ -16,7 +17,7 @@ pub fn perp_place_order(
require_gte!(order.max_base_lots, 0);
require_gte!(order.max_quote_lots, 0);
let now_ts: u64 = Clock::get()?.unix_timestamp.try_into().unwrap();
let (now_ts, now_slot) = clock_now();
let oracle_price;
// Update funding if possible.
@ -66,10 +67,21 @@ pub fn perp_place_order(
// Pre-health computation, _after_ perp position is created
//
let pre_health_opt = if !account.fixed.is_in_health_region() {
let retriever =
new_fixed_order_account_retriever(ctx.remaining_accounts, &account.borrow())?;
let health_cache = new_health_cache(&account.borrow(), &retriever, now_ts)
.context("pre-withdraw init health")?;
let retriever = new_fixed_order_account_retriever_with_optional_banks(
ctx.remaining_accounts,
&account.borrow(),
now_slot,
)?;
let health_cache = new_health_cache_skipping_missing_banks_and_bad_oracles(
&account.borrow(),
&retriever,
now_ts,
)
.context("pre init health")?;
// The settle token banks/oracles must be passed and be valid
health_cache.token_info_index(settle_token_index)?;
let pre_init_health = account.check_health_pre(&health_cache)?;
Some((health_cache, pre_init_health))
} else {

View File

@ -9,6 +9,7 @@ use crate::state::*;
use crate::accounts_ix::*;
use crate::logs::{emit_perp_balances, emit_stack, PerpSettleFeesLog, TokenBalanceLog};
use crate::util::clock_now;
pub fn perp_settle_fees(ctx: Context<PerpSettleFees>, max_settle_amount: u64) -> Result<()> {
// max_settle_amount must greater than zero
@ -123,8 +124,9 @@ pub fn perp_settle_fees(ctx: Context<PerpSettleFees>, max_settle_amount: u64) ->
drop(perp_market);
// Verify that the result of settling did not violate the health of the account that lost money
let retriever = new_fixed_order_account_retriever(ctx.remaining_accounts, &account.borrow())?;
let now_ts: u64 = Clock::get()?.unix_timestamp.try_into().unwrap();
let (now_ts, now_slot) = clock_now();
let retriever =
new_fixed_order_account_retriever(ctx.remaining_accounts, &account.borrow(), now_slot)?;
let health = compute_health(&account.borrow(), HealthType::Init, &retriever, now_ts)?;
require!(health >= 0, MangoError::HealthMustBePositive);

View File

@ -8,6 +8,7 @@ use crate::instructions::charge_loan_origination_fees;
use crate::logs::{emit_stack, Serum3OpenOrdersBalanceLogV2};
use crate::serum3_cpi::{load_open_orders_ref, OpenOrdersAmounts, OpenOrdersSlim};
use crate::state::*;
use crate::util::clock_now;
pub fn serum3_liq_force_cancel_orders(
ctx: Context<Serum3LiqForceCancelOrders>,
@ -50,14 +51,15 @@ pub fn serum3_liq_force_cancel_orders(
);
}
let (now_ts, now_slot) = clock_now();
//
// Early return if if liquidation is not allowed or if market is not in force close
//
let mut health_cache = {
let mut account = ctx.accounts.account.load_full_mut()?;
let retriever =
new_fixed_order_account_retriever(ctx.remaining_accounts, &account.borrow())?;
let now_ts: u64 = Clock::get()?.unix_timestamp.try_into().unwrap();
new_fixed_order_account_retriever(ctx.remaining_accounts, &account.borrow(), now_slot)?;
let health_cache = new_health_cache(&account.borrow(), &retriever, now_ts)
.context("create health cache")?;

View File

@ -9,6 +9,7 @@ use crate::logs::{emit_stack, Serum3OpenOrdersBalanceLogV2, TokenBalanceLog};
use crate::serum3_cpi::{
load_market_state, load_open_orders_ref, OpenOrdersAmounts, OpenOrdersSlim,
};
use crate::util::clock_now;
use anchor_lang::prelude::*;
use fixed::types::I80F48;
@ -40,6 +41,7 @@ pub fn serum3_place_order(
// Validation
//
let receiver_token_index;
let payer_token_index;
{
let account = ctx.accounts.account.load_full()?;
// account constraint #1
@ -60,7 +62,7 @@ pub fn serum3_place_order(
// Validate bank and vault #3
let payer_bank = ctx.accounts.payer_bank.load()?;
require_keys_eq!(payer_bank.vault, ctx.accounts.payer_vault.key());
let payer_token_index = match side {
payer_token_index = match side {
Serum3Side::Bid => serum_market.quote_token_index,
Serum3Side::Ask => serum_market.base_token_index,
};
@ -76,10 +78,23 @@ pub fn serum3_place_order(
// Pre-health computation
//
let mut account = ctx.accounts.account.load_full_mut()?;
let retriever = new_fixed_order_account_retriever(ctx.remaining_accounts, &account.borrow())?;
let now_ts: u64 = Clock::get()?.unix_timestamp.try_into().unwrap();
let mut health_cache = new_health_cache(&account.borrow(), &retriever, now_ts)
.context("pre-withdraw init health")?;
let (now_ts, now_slot) = clock_now();
let retriever = new_fixed_order_account_retriever_with_optional_banks(
ctx.remaining_accounts,
&account.borrow(),
now_slot,
)?;
let mut health_cache = new_health_cache_skipping_missing_banks_and_bad_oracles(
&account.borrow(),
&retriever,
now_ts,
)
.context("pre init health")?;
// The payer and receiver token banks/oracles must be passed and be valid
health_cache.token_info_index(payer_token_index)?;
health_cache.token_info_index(receiver_token_index)?;
let pre_health_opt = if !account.fixed.is_in_health_region() {
let pre_init_health = account.check_health_pre(&health_cache)?;
Some(pre_init_health)
@ -412,6 +427,20 @@ pub fn serum3_place_order(
// Note that all orders on the book executing can still cause a net deposit. That's because
// the total serum3 potential amount assumes all reserved amounts convert at the current
// oracle price.
//
// This also requires that all serum3 oos that touch the receiver_token are avaliable in the
// health cache. We make this a general requirement to avoid surprises.
for serum3 in account.active_serum3_orders() {
if serum3.base_token_index == receiver_token_index
|| serum3.quote_token_index == receiver_token_index
{
require_msg!(
health_cache.serum3_infos.iter().any(|s3| s3.market_index == serum3.market_index),
"health cache is missing serum3 info {} involving receiver token {}; passed banks and oracles?",
serum3.market_index, receiver_token_index
);
}
}
if receiver_bank_reduce_only {
let balance = health_cache.token_info(receiver_token_index)?.balance_spot;
let potential =

View File

@ -1,6 +1,7 @@
use crate::accounts_zerocopy::*;
use crate::health::*;
use crate::state::*;
use crate::util::clock_now;
use anchor_lang::prelude::*;
use fixed::types::I80F48;
@ -10,7 +11,7 @@ use crate::logs::{emit_stack, TokenBalanceLog, TokenCollateralFeeLog};
pub fn token_charge_collateral_fees(ctx: Context<TokenChargeCollateralFees>) -> Result<()> {
let group = ctx.accounts.group.load()?;
let mut account = ctx.accounts.account.load_full_mut()?;
let now_ts: u64 = Clock::get()?.unix_timestamp.try_into().unwrap();
let (now_ts, now_slot) = clock_now();
if group.collateral_fee_interval == 0 {
// By resetting, a new enabling of collateral fees will not immediately create a charge
@ -42,7 +43,7 @@ pub fn token_charge_collateral_fees(ctx: Context<TokenChargeCollateralFees>) ->
let health_cache = {
let retriever =
new_fixed_order_account_retriever(ctx.remaining_accounts, &account.borrow())?;
new_fixed_order_account_retriever(ctx.remaining_accounts, &account.borrow(), now_slot)?;
new_health_cache(&account.borrow(), &retriever, now_ts)?
};

View File

@ -11,6 +11,7 @@ use crate::state::*;
use crate::accounts_ix::*;
use crate::logs::*;
use crate::util::clock_now;
struct DepositCommon<'a, 'info> {
pub group: &'a AccountLoader<'info, Group>,
@ -119,13 +120,21 @@ impl<'a, 'info> DepositCommon<'a, 'info> {
//
// Health computation
//
let retriever = new_fixed_order_account_retriever(remaining_accounts, &account.borrow())?;
let now_ts: u64 = Clock::get()?.unix_timestamp.try_into().unwrap();
let (now_ts, now_slot) = clock_now();
let retriever = new_fixed_order_account_retriever_with_optional_banks(
remaining_accounts,
&account.borrow(),
now_slot,
)?;
// We only compute health to check if the account leaves the being_liquidated state.
// So it's ok to possibly skip token positions for bad oracles and compute a health
// So it's ok to possibly skip nonnegative token positions and compute a health
// value that is too low.
let cache = new_health_cache_skipping_bad_oracles(&account.borrow(), &retriever, now_ts)?;
let cache = new_health_cache_skipping_missing_banks_and_bad_oracles(
&account.borrow(),
&retriever,
now_ts,
)?;
// Since depositing can only increase health, we can skip the usual pre-health computation.
// Also, TokenDeposit is one of the rare instructions that is allowed even during being_liquidated.
@ -143,6 +152,13 @@ impl<'a, 'info> DepositCommon<'a, 'info> {
// Group level deposit limit on account
let group = self.group.load()?;
if group.deposit_limit_quote > 0 {
// Requires that all banks were provided an all oracles are healthy, otherwise we
// can't know how much this account has deposited
require_eq!(
cache.token_infos.len(),
account.active_token_positions().count()
);
let assets = cache
.health_assets_and_liabs_stable_assets(HealthType::Init)
.0

View File

@ -2,6 +2,7 @@ use crate::accounts_zerocopy::*;
use crate::error::*;
use crate::health::*;
use crate::state::*;
use crate::util::clock_now;
use anchor_lang::prelude::*;
use anchor_spl::associated_token;
use anchor_spl::token;
@ -19,7 +20,7 @@ pub fn token_withdraw(ctx: Context<TokenWithdraw>, amount: u64, allow_borrow: bo
let group = ctx.accounts.group.load()?;
let token_index = ctx.accounts.bank.load()?.token_index;
let now_ts: u64 = Clock::get()?.unix_timestamp.try_into().unwrap();
let (now_ts, now_slot) = clock_now();
// Create the account's position for that token index
let mut account = ctx.accounts.account.load_full_mut()?;
@ -27,21 +28,19 @@ pub fn token_withdraw(ctx: Context<TokenWithdraw>, amount: u64, allow_borrow: bo
// Health check _after_ the token position is guaranteed to exist
let pre_health_opt = if !account.fixed.is_in_health_region() {
let retriever =
new_fixed_order_account_retriever(ctx.remaining_accounts, &account.borrow())?;
let hc_result = new_health_cache(&account.borrow(), &retriever, now_ts)
.context("pre-withdraw health cache");
if hc_result.is_oracle_error() {
// We allow NOT checking the pre init health. That means later on the health
// check will be stricter (post_init > 0, without the post_init >= pre_init option)
// Then later we can compute the health while ignoring potential nonnegative
// health contributions from tokens with stale oracles.
None
} else {
let health_cache = hc_result?;
let pre_init_health = account.check_health_pre(&health_cache)?;
Some((health_cache, pre_init_health))
}
let retriever = new_fixed_order_account_retriever_with_optional_banks(
ctx.remaining_accounts,
&account.borrow(),
now_slot,
)?;
let health_cache = new_health_cache_skipping_missing_banks_and_bad_oracles(
&account.borrow(),
&retriever,
now_ts,
)
.context("pre-withdraw health cache")?;
let pre_init_health = account.check_health_pre(&health_cache)?;
Some((health_cache, pre_init_health))
} else {
None
};
@ -156,26 +155,29 @@ pub fn token_withdraw(ctx: Context<TokenWithdraw>, amount: u64, allow_borrow: bo
//
// Health check
//
if !account.fixed.is_in_health_region() {
if let Some((mut health_cache, pre_init_health)) = pre_health_opt {
// This is the normal case
if let Some((mut health_cache, pre_init_health_lower_bound)) = pre_health_opt {
if health_cache.token_info_index(token_index).is_ok() {
// This is the normal case: the health cache knows about the token, we can
// compute the health for the new state by adjusting its balance
health_cache.adjust_token_balance(&bank, native_position_after - native_position)?;
account.check_health_post(&health_cache, pre_init_health)?;
account.check_health_post(&health_cache, pre_init_health_lower_bound)?;
} else {
// Some oracle was stale/not confident enough above.
// The health cache does not know about the token! It has a bad oracle or wasn't
// provided in the health accounts. Borrows are out of the question!
require!(!is_borrow, MangoError::BorrowsRequireHealthAccountBank);
// Since the health cache isn't aware of the bank we changed, the health
// estimation is the same.
let post_init_health_lower_bound = pre_init_health_lower_bound;
// If health without the token is positive, then full health is positive and
// withdrawing all of the token would still keep it positive.
// However, if health without it is negative then full health could be negative
// and could be made worse by withdrawals.
//
// Try computing health while ignoring nonnegative contributions from bad oracles.
// If the health is good enough without those, we can pass.
//
// Note that this must include the normal pre and post health checks.
let retriever =
new_fixed_order_account_retriever(ctx.remaining_accounts, &account.borrow())?;
let health_cache =
new_health_cache_skipping_bad_oracles(&account.borrow(), &retriever, now_ts)
.context("special post-withdraw health-cache")?;
let post_init_health = health_cache.health(HealthType::Init);
account.check_health_pre_checks(&health_cache, post_init_health)?;
account.check_health_post_checks(I80F48::MAX, post_init_health)?;
// We don't know the true pre_init_health, and substitute MAX. That way the check
// won't pass because post == pre.
account.check_health_post_checks(I80F48::MAX, post_init_health_lower_bound)?;
}
}

View File

@ -31,6 +31,12 @@ pub fn format_zero_terminated_utf8_bytes(
)
}
// Returns (now_ts, now_slot)
pub fn clock_now() -> (u64, u64) {
let clock = Clock::get().unwrap();
(clock.unix_timestamp.try_into().unwrap(), clock.slot)
}
#[cfg(test)]
mod tests {
use super::*;

View File

@ -687,3 +687,263 @@ async fn test_bank_deposit_limit() -> Result<(), TransportError> {
Ok(())
}
#[tokio::test]
async fn test_withdraw_skip_bank() -> Result<(), TransportError> {
let context = TestContext::new().await;
let solana = &context.solana.clone();
let admin = TestKeypair::new();
let owner = context.users[0].key;
let payer = context.users[1].key;
let payer_token_accounts = &context.users[1].token_accounts;
let mints = &context.mints[0..3];
let mango_setup::GroupWithTokens { group, tokens, .. } = mango_setup::GroupWithTokensConfig {
admin,
payer,
mints: mints.to_vec(),
zero_token_is_quote: true,
..mango_setup::GroupWithTokensConfig::default()
}
.create(solana)
.await;
// Funding to fill the vaults
create_funded_account(
&solana,
group,
owner,
0,
&context.users[1],
&mints,
1_000_000,
0,
)
.await;
let account = create_funded_account(
&solana,
group,
owner,
1,
&context.users[1],
&mints[0..2],
1000,
0,
)
.await;
//
// TEST: when all balances are positive
//
send_tx(
solana,
HealthAccountSkipping {
inner: TokenWithdrawInstruction {
amount: 1,
allow_borrow: false,
account,
owner,
token_account: payer_token_accounts[0],
bank_index: 0,
},
skip_banks: vec![tokens[0].bank],
},
)
.await
.unwrap();
send_tx(
solana,
HealthAccountSkipping {
inner: TokenWithdrawInstruction {
amount: 1,
allow_borrow: false,
account,
owner,
token_account: payer_token_accounts[0],
bank_index: 0,
},
skip_banks: vec![tokens[1].bank],
},
)
.await
.unwrap();
// ok even when total health = 0
send_tx(
solana,
HealthAccountSkipping {
inner: TokenWithdrawInstruction {
amount: 1,
allow_borrow: false,
account,
owner,
token_account: payer_token_accounts[0],
bank_index: 0,
},
skip_banks: vec![tokens[0].bank, tokens[1].bank],
},
)
.await
.unwrap();
send_tx_expect_error!(
solana,
HealthAccountSkipping {
inner: TokenWithdrawInstruction {
amount: 1001,
allow_borrow: true,
account,
owner,
token_account: payer_token_accounts[0],
bank_index: 0,
},
skip_banks: vec![tokens[0].bank],
},
MangoError::BorrowsRequireHealthAccountBank
);
send_tx_expect_error!(
solana,
HealthAccountSkipping {
inner: TokenWithdrawInstruction {
amount: 1001,
allow_borrow: true,
account,
owner,
token_account: payer_token_accounts[0],
bank_index: 0,
},
skip_banks: vec![tokens[1].bank],
},
MangoError::HealthMustBePositiveOrIncrease
);
//
// TEST: create a borrow
//
send_tx_expect_error!(
solana,
HealthAccountSkipping {
inner: TokenWithdrawInstruction {
amount: 1,
allow_borrow: true,
account,
owner,
token_account: payer_token_accounts[2],
bank_index: 0,
},
skip_banks: vec![tokens[0].bank, tokens[1].bank],
},
MangoError::HealthMustBePositiveOrIncrease
);
send_tx_expect_error!(
solana,
HealthAccountSkipping {
inner: TokenWithdrawInstruction {
amount: 1,
allow_borrow: true,
account,
owner,
token_account: payer_token_accounts[2],
bank_index: 0,
},
skip_banks: vec![tokens[2].bank],
},
MangoError::BorrowsRequireHealthAccountBank
);
send_tx(
solana,
HealthAccountSkipping {
inner: TokenWithdrawInstruction {
amount: 1,
allow_borrow: true,
account,
owner,
token_account: payer_token_accounts[2],
bank_index: 0,
},
skip_banks: vec![tokens[0].bank],
},
)
.await
.unwrap();
//
// TEST: withdraw positive balances when there's a borrow
//
send_tx(
solana,
HealthAccountSkipping {
inner: TokenWithdrawInstruction {
amount: 1,
allow_borrow: false,
account,
owner,
token_account: payer_token_accounts[0],
bank_index: 0,
},
skip_banks: vec![tokens[0].bank],
},
)
.await
.unwrap();
send_tx(
solana,
HealthAccountSkipping {
inner: TokenWithdrawInstruction {
amount: 1,
allow_borrow: false,
account,
owner,
token_account: payer_token_accounts[0],
bank_index: 0,
},
skip_banks: vec![tokens[1].bank],
},
)
.await
.unwrap();
send_tx_expect_error!(
solana,
HealthAccountSkipping {
inner: TokenWithdrawInstruction {
amount: 1,
allow_borrow: false,
account,
owner,
token_account: payer_token_accounts[0],
bank_index: 0,
},
skip_banks: vec![tokens[2].bank],
},
MangoError::InvalidBank
);
send_tx_expect_error!(
solana,
HealthAccountSkipping {
inner: TokenWithdrawInstruction {
amount: 1,
allow_borrow: false,
account,
owner,
token_account: payer_token_accounts[0],
bank_index: 0,
},
skip_banks: vec![tokens[0].bank, tokens[1].bank],
},
MangoError::HealthMustBePositiveOrIncrease
);
Ok(())
}

View File

@ -344,7 +344,7 @@ async fn test_health_compute_tokens_fallback_oracles() -> Result<(), TransportEr
#[tokio::test]
async fn test_health_compute_serum() -> Result<(), TransportError> {
let mut test_builder = TestContextBuilder::new();
test_builder.test().set_compute_max_units(135_000);
test_builder.test().set_compute_max_units(137_000);
let context = test_builder.start_default().await;
let solana = &context.solana.clone();

View File

@ -730,3 +730,112 @@ async fn test_margin_trade_deposit_limit() -> Result<(), BanksClientError> {
Ok(())
}
#[tokio::test]
async fn test_margin_trade_skip_bank() -> Result<(), BanksClientError> {
let mut test_builder = TestContextBuilder::new();
test_builder.test().set_compute_max_units(100_000);
let context = test_builder.start_default().await;
let solana = &context.solana.clone();
let admin = TestKeypair::new();
let owner = context.users[0].key;
let payer = context.users[1].key;
let mints = &context.mints[0..2];
let payer_mint0_account = context.users[1].token_accounts[0];
//
// SETUP: Create a group, account, register a token (mint0)
//
let GroupWithTokens { group, tokens, .. } = GroupWithTokensConfig {
admin,
payer,
mints: mints.to_vec(),
..GroupWithTokensConfig::default()
}
.create(solana)
.await;
let bank = tokens[0].bank;
//
// create the test user account
//
let deposit_amount_initial = 100;
let account = create_funded_account(
&solana,
group,
owner,
0,
&context.users[1],
&mints,
deposit_amount_initial,
0,
)
.await;
//
// TEST: Margin trade
//
let margin_account = payer_mint0_account;
let target_token_account = context.users[0].token_accounts[0];
let make_flash_loan_tx = |solana, deposit_amount, skip_banks| async move {
let mut tx = ClientTransaction::new(solana);
let loans = vec![FlashLoanPart {
bank,
token_account: target_token_account,
withdraw_amount: 0,
}];
tx.add_instruction(FlashLoanBeginInstruction {
account,
owner,
loans: loans.clone(),
})
.await;
tx.add_instruction_direct(
spl_token::instruction::transfer(
&spl_token::ID,
&margin_account,
&target_token_account,
&payer.pubkey(),
&[&payer.pubkey()],
deposit_amount,
)
.unwrap(),
);
tx.add_signer(payer);
tx.add_instruction(HealthAccountSkipping {
inner: FlashLoanEndInstruction {
account,
owner,
loans,
// the test only accesses a single token: not a swap
flash_loan_type: mango_v4::accounts_ix::FlashLoanType::Unknown,
},
skip_banks,
})
.await;
tx
};
make_flash_loan_tx(solana, 1, vec![])
.await
.send()
.await
.unwrap();
make_flash_loan_tx(solana, 1, vec![tokens[1].bank])
.await
.send()
.await
.unwrap();
make_flash_loan_tx(solana, 1, vec![tokens[0].bank])
.await
.send_expect_error(MangoError::InvalidBank)
.await
.unwrap();
Ok(())
}

View File

@ -1581,6 +1581,138 @@ async fn test_perp_cancel_with_in_flight_events() -> Result<(), TransportError>
Ok(())
}
#[tokio::test]
async fn test_perp_skip_bank() -> Result<(), TransportError> {
let context = TestContext::new().await;
let solana = &context.solana.clone();
let admin = TestKeypair::new();
let owner = context.users[0].key;
let payer = context.users[1].key;
let mints = &context.mints[0..2];
//
// SETUP: Create a group and an account
//
let GroupWithTokens { group, tokens, .. } = GroupWithTokensConfig {
admin,
payer,
mints: mints.to_vec(),
..GroupWithTokensConfig::default()
}
.create(solana)
.await;
let deposit_amount = 1000;
let account = create_funded_account(
&solana,
group,
owner,
0,
&context.users[1],
mints,
deposit_amount,
0,
)
.await;
//
// SETUP: Create a perp market
//
let mango_v4::accounts::PerpCreateMarket { perp_market, .. } = send_tx(
solana,
PerpCreateMarketInstruction {
group,
admin,
payer,
perp_market_index: 0,
quote_lot_size: 10,
base_lot_size: 100,
maint_base_asset_weight: 0.975,
init_base_asset_weight: 0.95,
maint_base_liab_weight: 1.025,
init_base_liab_weight: 1.05,
base_liquidation_fee: 0.012,
maker_fee: 0.0000,
taker_fee: 0.0000,
settle_pnl_limit_factor: -1.0,
settle_pnl_limit_window_size_ts: 24 * 60 * 60,
..PerpCreateMarketInstruction::with_new_book_and_queue(&solana, &tokens[1]).await
},
)
.await
.unwrap();
let perp_market_data = solana.get_account::<PerpMarket>(perp_market).await;
let price_lots = perp_market_data.native_price_to_lot(I80F48::from(1));
//
// TESTS
//
// good without skips
send_tx(
solana,
HealthAccountSkipping {
inner: PerpPlaceOrderInstruction {
account,
perp_market,
owner,
side: Side::Bid,
price_lots,
max_base_lots: 2,
client_order_id: 5,
..PerpPlaceOrderInstruction::default()
},
skip_banks: vec![],
},
)
.await
.unwrap();
// can skip unrelated
send_tx(
solana,
HealthAccountSkipping {
inner: PerpPlaceOrderInstruction {
account,
perp_market,
owner,
side: Side::Bid,
price_lots,
max_base_lots: 2,
client_order_id: 5,
..PerpPlaceOrderInstruction::default()
},
skip_banks: vec![tokens[1].bank],
},
)
.await
.unwrap();
// can't skip settle token index
send_tx_expect_error!(
solana,
HealthAccountSkipping {
inner: PerpPlaceOrderInstruction {
account,
perp_market,
owner,
side: Side::Bid,
price_lots,
max_base_lots: 2,
client_order_id: 5,
..PerpPlaceOrderInstruction::default()
},
skip_banks: vec![tokens[0].bank],
},
MangoError::TokenPositionDoesNotExist,
);
Ok(())
}
async fn assert_no_perp_orders(solana: &SolanaCookie, account_0: Pubkey) {
let mango_account_0 = solana.get_account::<MangoAccount>(account_0).await;

View File

@ -36,35 +36,39 @@ impl SerumOrderPlacer {
None
}
fn bid_ix(
&mut self,
limit_price: f64,
max_base: u64,
taker: bool,
) -> Serum3PlaceOrderInstruction {
let client_order_id = self.inc_client_order_id();
let fees = if taker { 0.0004 } else { 0.0 };
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 + fees))
.ceil() as u64,
self_trade_behavior: Serum3SelfTradeBehavior::AbortTransaction,
order_type: Serum3OrderType::Limit,
client_order_id,
limit: 10,
account: self.account,
owner: self.owner,
serum_market: self.serum_market,
}
}
async fn try_bid(
&mut self,
limit_price: f64,
max_base: u64,
taker: bool,
) -> Result<mango_v4::accounts::Serum3PlaceOrder, TransportError> {
let client_order_id = self.inc_client_order_id();
let fees = if taker { 0.0004 } else { 0.0 };
send_tx(
&self.solana,
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 + fees))
.ceil() as u64,
self_trade_behavior: Serum3SelfTradeBehavior::AbortTransaction,
order_type: Serum3OrderType::Limit,
client_order_id,
limit: 10,
account: self.account,
owner: self.owner,
serum_market: self.serum_market,
},
)
.await
let ix = self.bid_ix(limit_price, max_base, taker);
send_tx(&self.solana, ix).await
}
async fn bid_maker(&mut self, limit_price: f64, max_base: u64) -> Option<(u128, u64)> {
@ -1025,7 +1029,7 @@ async fn test_serum_reduce_only_deposits1() -> Result<(), TransportError> {
#[tokio::test]
async fn test_serum_reduce_only_deposits2() -> Result<(), TransportError> {
let mut test_builder = TestContextBuilder::new();
test_builder.test().set_compute_max_units(95_000); // Serum3PlaceOrder needs 92.8k
test_builder.test().set_compute_max_units(97_000); // Serum3PlaceOrder needs 95.8k
let context = test_builder.start_default().await;
let solana = &context.solana.clone();
@ -1948,6 +1952,71 @@ async fn test_serum_deposit_limits() -> Result<(), TransportError> {
Ok(())
}
#[tokio::test]
async fn test_serum_skip_bank() -> Result<(), TransportError> {
let mut test_builder = TestContextBuilder::new();
test_builder.test().set_compute_max_units(150_000); // Serum3PlaceOrder needs lots
let context = test_builder.start_default().await;
let solana = &context.solana.clone();
//
// SETUP: Create a group, accounts, market etc
//
let deposit_amount = 5000;
let CommonSetup {
group_with_tokens,
mut order_placer,
..
} = common_setup(&context, deposit_amount).await;
let tokens = group_with_tokens.tokens;
//
// TESTS
//
// verify generally good
send_tx(
solana,
HealthAccountSkipping {
inner: order_placer.bid_ix(1.0, 100, false),
skip_banks: vec![],
},
)
.await
.unwrap();
// can skip uninvolved token
send_tx(
solana,
HealthAccountSkipping {
inner: order_placer.bid_ix(1.0, 100, false),
skip_banks: vec![tokens[2].bank],
},
)
.await
.unwrap();
// can't skip base or quote token
send_tx_expect_error!(
solana,
HealthAccountSkipping {
inner: order_placer.bid_ix(1.0, 100, false),
skip_banks: vec![tokens[0].bank],
},
MangoError::TokenPositionDoesNotExist
);
send_tx_expect_error!(
solana,
HealthAccountSkipping {
inner: order_placer.bid_ix(1.0, 100, false),
skip_banks: vec![tokens[1].bank],
},
MangoError::TokenPositionDoesNotExist
);
Ok(())
}
struct CommonSetup {
group_with_tokens: GroupWithTokens,
serum_market_cookie: SpotMarketCookie,

View File

@ -35,7 +35,7 @@ pub trait ClientAccountLoader {
}
#[async_trait::async_trait(?Send)]
impl ClientAccountLoader for &SolanaCookie {
impl ClientAccountLoader for SolanaCookie {
async fn load_bytes(&self, pubkey: &Pubkey) -> Option<Vec<u8>> {
self.get_account_data(*pubkey).await
}
@ -186,7 +186,7 @@ pub trait ClientInstruction {
async fn to_instruction(
&self,
loader: impl ClientAccountLoader + 'async_trait,
loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction);
fn signers(&self) -> Vec<TestKeypair>;
}
@ -552,7 +552,7 @@ impl ClientInstruction for FlashLoanBeginInstruction {
type Instruction = mango_v4::instruction::FlashLoanBegin;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
@ -626,7 +626,7 @@ impl ClientInstruction for FlashLoanSwapBeginInstruction {
type Instruction = mango_v4::instruction::FlashLoanSwapBegin;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
@ -718,7 +718,7 @@ impl ClientInstruction for FlashLoanEndInstruction {
type Instruction = mango_v4::instruction::FlashLoanEndV2;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {
@ -735,14 +735,9 @@ impl ClientInstruction for FlashLoanEndInstruction {
account.ensure_token_position(bank.token_index).unwrap();
}
let health_check_metas = derive_health_check_remaining_account_metas(
&account_loader,
&account,
None,
true,
None,
)
.await;
let health_check_metas =
derive_health_check_remaining_account_metas(account_loader, &account, None, true, None)
.await;
let accounts = Self::Accounts {
account: self.account,
@ -797,7 +792,7 @@ impl ClientInstruction for TokenWithdrawInstruction {
type Instruction = mango_v4::instruction::TokenWithdraw;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {
@ -823,7 +818,7 @@ impl ClientInstruction for TokenWithdrawInstruction {
let mint_info: MintInfo = account_loader.load(&mint_info).await.unwrap();
let health_check_metas = derive_health_check_remaining_account_metas(
&account_loader,
account_loader,
&account,
Some(mint_info.banks[self.bank_index]),
false,
@ -869,7 +864,7 @@ impl ClientInstruction for TokenDepositInstruction {
type Instruction = mango_v4::instruction::TokenDeposit;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {
@ -895,7 +890,7 @@ impl ClientInstruction for TokenDepositInstruction {
let mint_info: MintInfo = account_loader.load(&mint_info).await.unwrap();
let health_check_metas = derive_health_check_remaining_account_metas(
&account_loader,
account_loader,
&account,
Some(mint_info.banks[self.bank_index]),
false,
@ -940,7 +935,7 @@ impl ClientInstruction for TokenDepositIntoExistingInstruction {
type Instruction = mango_v4::instruction::TokenDepositIntoExisting;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {
@ -966,7 +961,7 @@ impl ClientInstruction for TokenDepositIntoExistingInstruction {
let mint_info: MintInfo = account_loader.load(&mint_info).await.unwrap();
let health_check_metas = derive_health_check_remaining_account_metas(
&account_loader,
account_loader,
&account,
Some(mint_info.banks[self.bank_index]),
false,
@ -1030,7 +1025,7 @@ impl ClientInstruction for TokenRegisterInstruction {
type Instruction = mango_v4::instruction::TokenRegister;
async fn to_instruction(
&self,
_account_loader: impl ClientAccountLoader + 'async_trait,
_account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {
@ -1150,7 +1145,7 @@ impl ClientInstruction for TokenAddBankInstruction {
type Instruction = mango_v4::instruction::TokenAddBank;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {
@ -1239,7 +1234,7 @@ impl ClientInstruction for TokenDeregisterInstruction {
async fn to_instruction(
&self,
_loader: impl ClientAccountLoader + 'async_trait,
_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {};
@ -1345,7 +1340,7 @@ impl ClientInstruction for TokenEdit {
type Instruction = mango_v4::instruction::TokenEdit;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
@ -1401,7 +1396,7 @@ impl ClientInstruction for TokenEditWeights {
type Instruction = mango_v4::instruction::TokenEdit;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
@ -1460,7 +1455,7 @@ impl ClientInstruction for TokenResetStablePriceModel {
type Instruction = mango_v4::instruction::TokenEdit;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
@ -1520,7 +1515,7 @@ impl ClientInstruction for TokenResetNetBorrows {
type Instruction = mango_v4::instruction::TokenEdit;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
@ -1581,7 +1576,7 @@ impl ClientInstruction for TokenMakeReduceOnly {
type Instruction = mango_v4::instruction::TokenEdit;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
@ -1640,7 +1635,7 @@ impl ClientInstruction for StubOracleSetInstruction {
async fn to_instruction(
&self,
_loader: impl ClientAccountLoader + 'async_trait,
_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {
@ -1678,7 +1673,7 @@ impl ClientInstruction for StubOracleSetTestInstruction {
async fn to_instruction(
&self,
_loader: impl ClientAccountLoader + 'async_trait,
_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {
@ -1716,7 +1711,7 @@ impl ClientInstruction for StubOracleCreate {
async fn to_instruction(
&self,
_loader: impl ClientAccountLoader + 'async_trait,
_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {
@ -1755,7 +1750,7 @@ impl ClientInstruction for StubOracleCloseInstruction {
async fn to_instruction(
&self,
_loader: impl ClientAccountLoader + 'async_trait,
_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {};
@ -1788,7 +1783,7 @@ impl ClientInstruction for GroupCreateInstruction {
type Instruction = mango_v4::instruction::GroupCreate;
async fn to_instruction(
&self,
_account_loader: impl ClientAccountLoader + 'async_trait,
_account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {
@ -1864,7 +1859,7 @@ impl ClientInstruction for GroupEditFeeParameters {
type Instruction = mango_v4::instruction::GroupEdit;
async fn to_instruction(
&self,
_account_loader: impl ClientAccountLoader + 'async_trait,
_account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {
@ -1900,7 +1895,7 @@ impl ClientInstruction for GroupEdit {
type Instruction = mango_v4::instruction::GroupEdit;
async fn to_instruction(
&self,
_account_loader: impl ClientAccountLoader + 'async_trait,
_account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = &self.options;
@ -1930,7 +1925,7 @@ impl ClientInstruction for IxGateSetInstruction {
type Instruction = mango_v4::instruction::IxGateSet;
async fn to_instruction(
&self,
_account_loader: impl ClientAccountLoader + 'async_trait,
_account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {
@ -1962,7 +1957,7 @@ impl ClientInstruction for GroupCloseInstruction {
type Instruction = mango_v4::instruction::GroupClose;
async fn to_instruction(
&self,
_account_loader: impl ClientAccountLoader + 'async_trait,
_account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {};
@ -2022,7 +2017,7 @@ impl ClientInstruction for AccountCreateInstruction {
type Instruction = mango_v4::instruction::AccountCreateV2;
async fn to_instruction(
&self,
_account_loader: impl ClientAccountLoader + 'async_trait,
_account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {
@ -2081,7 +2076,7 @@ impl ClientInstruction for AccountExpandInstruction {
type Instruction = mango_v4::instruction::AccountExpandV2;
async fn to_instruction(
&self,
_account_loader: impl ClientAccountLoader + 'async_trait,
_account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {
@ -2131,7 +2126,7 @@ impl ClientInstruction for AccountSizeMigrationInstruction {
type Instruction = mango_v4::instruction::AccountSizeMigration;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {};
@ -2170,7 +2165,7 @@ impl ClientInstruction for AccountEditInstruction {
type Instruction = mango_v4::instruction::AccountEdit;
async fn to_instruction(
&self,
_account_loader: impl ClientAccountLoader + 'async_trait,
_account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = mango_v4::instruction::AccountEdit {
@ -2218,7 +2213,7 @@ impl ClientInstruction for AccountCloseInstruction {
type Instruction = mango_v4::instruction::AccountClose;
async fn to_instruction(
&self,
_account_loader: impl ClientAccountLoader + 'async_trait,
_account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction { force_close: false };
@ -2252,7 +2247,7 @@ impl ClientInstruction for AccountBuybackFeesWithMngo {
type Instruction = mango_v4::instruction::AccountBuybackFeesWithMngo;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {
@ -2308,7 +2303,7 @@ impl ClientInstruction for Serum3RegisterMarketInstruction {
type Instruction = mango_v4::instruction::Serum3RegisterMarket;
async fn to_instruction(
&self,
_account_loader: impl ClientAccountLoader + 'async_trait,
_account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {
@ -2380,7 +2375,7 @@ impl ClientInstruction for Serum3EditMarketInstruction {
type Instruction = mango_v4::instruction::Serum3EditMarket;
async fn to_instruction(
&self,
_account_loader: impl ClientAccountLoader + 'async_trait,
_account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
@ -2411,7 +2406,7 @@ impl ClientInstruction for Serum3DeregisterMarketInstruction {
type Instruction = mango_v4::instruction::Serum3DeregisterMarket;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {};
@ -2467,7 +2462,7 @@ impl ClientInstruction for Serum3CreateOpenOrdersInstruction {
type Instruction = mango_v4::instruction::Serum3CreateOpenOrders;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {};
@ -2518,7 +2513,7 @@ impl ClientInstruction for Serum3CloseOpenOrdersInstruction {
type Instruction = mango_v4::instruction::Serum3CloseOpenOrders;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {};
@ -2576,7 +2571,7 @@ impl ClientInstruction for Serum3PlaceOrderInstruction {
type Instruction = mango_v4::instruction::Serum3PlaceOrderV2;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {
@ -2600,10 +2595,10 @@ impl ClientInstruction for Serum3PlaceOrderInstruction {
.unwrap()
.open_orders;
let quote_info =
get_mint_info_by_token_index(&account_loader, &account, serum_market.quote_token_index)
get_mint_info_by_token_index(account_loader, &account, serum_market.quote_token_index)
.await;
let base_info =
get_mint_info_by_token_index(&account_loader, &account, serum_market.base_token_index)
get_mint_info_by_token_index(account_loader, &account, serum_market.base_token_index)
.await;
let market_external_bytes = account_loader
@ -2628,7 +2623,7 @@ impl ClientInstruction for Serum3PlaceOrderInstruction {
.unwrap();
let mut health_check_metas = derive_health_check_remaining_account_metas(
&account_loader,
account_loader,
&account,
None,
false,
@ -2694,7 +2689,7 @@ impl ClientInstruction for Serum3CancelOrderInstruction {
type Instruction = mango_v4::instruction::Serum3CancelOrder;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {
@ -2760,7 +2755,7 @@ impl ClientInstruction for Serum3CancelOrderByClientOrderIdInstruction {
type Instruction = mango_v4::instruction::Serum3CancelOrderByClientOrderId;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {
@ -2823,7 +2818,7 @@ impl ClientInstruction for Serum3CancelAllOrdersInstruction {
type Instruction = mango_v4::instruction::Serum3CancelAllOrders;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction { limit: self.limit };
@ -2885,7 +2880,7 @@ impl ClientInstruction for Serum3SettleFundsV2Instruction {
type Instruction = mango_v4::instruction::Serum3SettleFundsV2;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {
@ -2902,10 +2897,10 @@ impl ClientInstruction for Serum3SettleFundsV2Instruction {
.unwrap()
.open_orders;
let quote_info =
get_mint_info_by_token_index(&account_loader, &account, serum_market.quote_token_index)
get_mint_info_by_token_index(account_loader, &account, serum_market.quote_token_index)
.await;
let base_info =
get_mint_info_by_token_index(&account_loader, &account, serum_market.base_token_index)
get_mint_info_by_token_index(account_loader, &account, serum_market.base_token_index)
.await;
let market_external_bytes = account_loader
@ -2969,7 +2964,7 @@ impl ClientInstruction for Serum3LiqForceCancelOrdersInstruction {
type Instruction = mango_v4::instruction::Serum3LiqForceCancelOrders;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction { limit: self.limit };
@ -2984,10 +2979,10 @@ impl ClientInstruction for Serum3LiqForceCancelOrdersInstruction {
.unwrap()
.open_orders;
let quote_info =
get_mint_info_by_token_index(&account_loader, &account, serum_market.quote_token_index)
get_mint_info_by_token_index(account_loader, &account, serum_market.quote_token_index)
.await;
let base_info =
get_mint_info_by_token_index(&account_loader, &account, serum_market.base_token_index)
get_mint_info_by_token_index(account_loader, &account, serum_market.base_token_index)
.await;
let market_external_bytes = account_loader
@ -3011,7 +3006,7 @@ impl ClientInstruction for Serum3LiqForceCancelOrdersInstruction {
.unwrap();
let health_check_metas = derive_health_check_remaining_account_metas(
&account_loader,
account_loader,
&account,
None,
false,
@ -3067,7 +3062,7 @@ impl ClientInstruction for TokenForceCloseBorrowsWithTokenInstruction {
type Instruction = mango_v4::instruction::TokenForceCloseBorrowsWithToken;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {
@ -3085,7 +3080,7 @@ impl ClientInstruction for TokenForceCloseBorrowsWithTokenInstruction {
.await
.unwrap();
let health_check_metas = derive_liquidation_remaining_account_metas(
&account_loader,
account_loader,
&liqee,
&liqor,
self.asset_token_index,
@ -3124,7 +3119,7 @@ impl ClientInstruction for TokenForceWithdrawInstruction {
type Instruction = mango_v4::instruction::TokenForceWithdraw;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {};
@ -3135,7 +3130,7 @@ impl ClientInstruction for TokenForceWithdrawInstruction {
.unwrap();
let bank = account_loader.load::<Bank>(&self.bank).await.unwrap();
let health_check_metas = derive_health_check_remaining_account_metas(
&account_loader,
account_loader,
&account,
None,
false,
@ -3182,7 +3177,7 @@ impl ClientInstruction for TokenLiqWithTokenInstruction {
type Instruction = mango_v4::instruction::TokenLiqWithToken;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {
@ -3200,7 +3195,7 @@ impl ClientInstruction for TokenLiqWithTokenInstruction {
.await
.unwrap();
let health_check_metas = derive_liquidation_remaining_account_metas(
&account_loader,
account_loader,
&liqee,
&liqor,
self.asset_token_index,
@ -3242,7 +3237,7 @@ impl ClientInstruction for TokenLiqBankruptcyInstruction {
type Instruction = mango_v4::instruction::TokenLiqBankruptcy;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {
@ -3259,7 +3254,7 @@ impl ClientInstruction for TokenLiqBankruptcyInstruction {
.await
.unwrap();
let health_check_metas = derive_liquidation_remaining_account_metas(
&account_loader,
account_loader,
&liqee,
&liqor,
QUOTE_TOKEN_INDEX,
@ -3381,7 +3376,7 @@ impl ClientInstruction for PerpCreateMarketInstruction {
type Instruction = mango_v4::instruction::PerpCreateMarket;
async fn to_instruction(
&self,
_loader: impl ClientAccountLoader + 'async_trait,
_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {
@ -3497,7 +3492,7 @@ impl ClientInstruction for PerpResetStablePriceModel {
type Instruction = mango_v4::instruction::PerpEditMarket;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
@ -3537,7 +3532,7 @@ impl ClientInstruction for PerpSetSettleLimitWindow {
type Instruction = mango_v4::instruction::PerpEditMarket;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
@ -3578,7 +3573,7 @@ impl ClientInstruction for PerpMakeReduceOnly {
type Instruction = mango_v4::instruction::PerpEditMarket;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
@ -3620,7 +3615,7 @@ impl ClientInstruction for PerpChangeWeights {
type Instruction = mango_v4::instruction::PerpEditMarket;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
@ -3659,7 +3654,7 @@ impl ClientInstruction for PerpCloseMarketInstruction {
type Instruction = mango_v4::instruction::PerpCloseMarket;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {};
@ -3696,7 +3691,7 @@ impl ClientInstruction for PerpDeactivatePositionInstruction {
type Instruction = mango_v4::instruction::PerpDeactivatePosition;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let perp_market: PerpMarket = account_loader.load(&self.perp_market).await.unwrap();
@ -3754,7 +3749,7 @@ impl ClientInstruction for PerpPlaceOrderInstruction {
type Instruction = mango_v4::instruction::PerpPlaceOrderV2;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {
@ -3776,7 +3771,7 @@ impl ClientInstruction for PerpPlaceOrderInstruction {
.await
.unwrap();
let health_check_metas = derive_health_check_remaining_account_metas(
&account_loader,
account_loader,
&account,
None,
false,
@ -3822,7 +3817,7 @@ impl ClientInstruction for PerpPlaceOrderPeggedInstruction {
type Instruction = mango_v4::instruction::PerpPlaceOrderPeggedV2;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {
@ -3846,7 +3841,7 @@ impl ClientInstruction for PerpPlaceOrderPeggedInstruction {
.await
.unwrap();
let health_check_metas = derive_health_check_remaining_account_metas(
&account_loader,
account_loader,
&account,
None,
false,
@ -3887,7 +3882,7 @@ impl ClientInstruction for PerpCancelOrderInstruction {
type Instruction = mango_v4::instruction::PerpCancelOrder;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {
@ -3924,7 +3919,7 @@ impl ClientInstruction for PerpCancelOrderByClientOrderIdInstruction {
type Instruction = mango_v4::instruction::PerpCancelOrderByClientOrderId;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {
@ -3961,7 +3956,7 @@ impl ClientInstruction for PerpCancelAllOrdersInstruction {
type Instruction = mango_v4::instruction::PerpCancelAllOrders;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction { limit: self.limit };
@ -3994,7 +3989,7 @@ impl ClientInstruction for PerpConsumeEventsInstruction {
type Instruction = mango_v4::instruction::PerpConsumeEvents;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction { limit: 10 };
@ -4033,7 +4028,7 @@ impl ClientInstruction for PerpUpdateFundingInstruction {
type Instruction = mango_v4::instruction::PerpUpdateFunding;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {};
@ -4068,7 +4063,7 @@ impl ClientInstruction for PerpSettlePnlInstruction {
type Instruction = mango_v4::instruction::PerpSettlePnl;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {};
@ -4083,7 +4078,7 @@ impl ClientInstruction for PerpSettlePnlInstruction {
.await
.unwrap();
let health_check_metas = derive_liquidation_remaining_account_metas(
&account_loader,
account_loader,
&account_a,
&account_b,
TokenIndex::MAX,
@ -4093,7 +4088,7 @@ impl ClientInstruction for PerpSettlePnlInstruction {
)
.await;
let settle_mint_info = get_mint_info_by_token_index(
&account_loader,
account_loader,
&account_a,
perp_market.settle_token_index,
)
@ -4133,7 +4128,7 @@ impl ClientInstruction for PerpForceClosePositionInstruction {
type Instruction = mango_v4::instruction::PerpForceClosePosition;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {};
@ -4169,7 +4164,7 @@ impl ClientInstruction for PerpSettleFeesInstruction {
type Instruction = mango_v4::instruction::PerpSettleFees;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {
@ -4182,7 +4177,7 @@ impl ClientInstruction for PerpSettleFeesInstruction {
.await
.unwrap();
let health_check_metas = derive_health_check_remaining_account_metas(
&account_loader,
account_loader,
&account,
None,
false,
@ -4190,7 +4185,7 @@ impl ClientInstruction for PerpSettleFeesInstruction {
)
.await;
let settle_mint_info =
get_mint_info_by_token_index(&account_loader, &account, perp_market.settle_token_index)
get_mint_info_by_token_index(account_loader, &account, perp_market.settle_token_index)
.await;
let accounts = Self::Accounts {
@ -4222,7 +4217,7 @@ impl ClientInstruction for PerpLiqForceCancelOrdersInstruction {
type Instruction = mango_v4::instruction::PerpLiqForceCancelOrders;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction { limit: 10 };
@ -4233,7 +4228,7 @@ impl ClientInstruction for PerpLiqForceCancelOrdersInstruction {
.await
.unwrap();
let health_check_metas = derive_health_check_remaining_account_metas(
&account_loader,
account_loader,
&account,
None,
false,
@ -4273,7 +4268,7 @@ impl ClientInstruction for PerpLiqBaseOrPositivePnlInstruction {
type Instruction = mango_v4::instruction::PerpLiqBaseOrPositivePnl;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {
@ -4292,7 +4287,7 @@ impl ClientInstruction for PerpLiqBaseOrPositivePnlInstruction {
.await
.unwrap();
let health_check_metas = derive_liquidation_remaining_account_metas(
&account_loader,
account_loader,
&liqee,
&liqor,
TokenIndex::MAX,
@ -4303,7 +4298,7 @@ impl ClientInstruction for PerpLiqBaseOrPositivePnlInstruction {
.await;
let settle_mint_info =
get_mint_info_by_token_index(&account_loader, &liqee, perp_market.settle_token_index)
get_mint_info_by_token_index(account_loader, &liqee, perp_market.settle_token_index)
.await;
let accounts = Self::Accounts {
@ -4341,7 +4336,7 @@ impl ClientInstruction for PerpLiqNegativePnlOrBankruptcyInstruction {
type Instruction = mango_v4::instruction::PerpLiqNegativePnlOrBankruptcyV2;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {
@ -4359,7 +4354,7 @@ impl ClientInstruction for PerpLiqNegativePnlOrBankruptcyInstruction {
.await
.unwrap();
let health_check_metas = derive_liquidation_remaining_account_metas(
&account_loader,
account_loader,
&liqee,
&liqor,
TokenIndex::MAX,
@ -4371,10 +4366,10 @@ impl ClientInstruction for PerpLiqNegativePnlOrBankruptcyInstruction {
let group = account_loader.load::<Group>(&group_key).await.unwrap();
let settle_mint_info =
get_mint_info_by_token_index(&account_loader, &liqee, perp_market.settle_token_index)
get_mint_info_by_token_index(account_loader, &liqee, perp_market.settle_token_index)
.await;
let insurance_mint_info =
get_mint_info_by_token_index(&account_loader, &liqee, QUOTE_TOKEN_INDEX).await;
get_mint_info_by_token_index(account_loader, &liqee, QUOTE_TOKEN_INDEX).await;
let accounts = Self::Accounts {
group: group_key,
@ -4410,7 +4405,7 @@ impl ClientInstruction for BenchmarkInstruction {
type Instruction = mango_v4::instruction::Benchmark;
async fn to_instruction(
&self,
_loader: impl ClientAccountLoader + 'async_trait,
_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {};
@ -4435,7 +4430,7 @@ impl ClientInstruction for TokenUpdateIndexAndRateInstruction {
type Instruction = mango_v4::instruction::TokenUpdateIndexAndRate;
async fn to_instruction(
&self,
loader: impl ClientAccountLoader + 'async_trait,
loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {};
@ -4478,7 +4473,7 @@ impl ClientInstruction for ComputeAccountDataInstruction {
type Instruction = mango_v4::instruction::ComputeAccountData;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {};
@ -4489,7 +4484,7 @@ impl ClientInstruction for ComputeAccountDataInstruction {
.unwrap();
let health_check_metas = derive_health_check_remaining_account_metas(
&account_loader,
account_loader,
&account,
None,
false,
@ -4522,7 +4517,7 @@ impl ClientInstruction for HealthRegionBeginInstruction {
type Instruction = mango_v4::instruction::HealthRegionBegin;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {};
@ -4533,7 +4528,7 @@ impl ClientInstruction for HealthRegionBeginInstruction {
.unwrap();
let health_check_metas = derive_health_check_remaining_account_metas(
&account_loader,
account_loader,
&account,
None,
false,
@ -4568,7 +4563,7 @@ impl ClientInstruction for HealthRegionEndInstruction {
type Instruction = mango_v4::instruction::HealthRegionEnd;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {};
@ -4579,7 +4574,7 @@ impl ClientInstruction for HealthRegionEndInstruction {
.unwrap();
let health_check_metas = derive_health_check_remaining_account_metas(
&account_loader,
account_loader,
&account,
self.affected_bank,
false,
@ -4614,7 +4609,7 @@ impl ClientInstruction for AltSetInstruction {
type Instruction = mango_v4::instruction::AltSet;
async fn to_instruction(
&self,
_account_loader: impl ClientAccountLoader + 'async_trait,
_account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction { index: self.index };
@ -4648,7 +4643,7 @@ impl ClientInstruction for AltExtendInstruction {
type Instruction = mango_v4::instruction::AltExtend;
async fn to_instruction(
&self,
_account_loader: impl ClientAccountLoader + 'async_trait,
_account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {
@ -4692,7 +4687,7 @@ impl ClientInstruction for TokenConditionalSwapCreateInstruction {
type Instruction = mango_v4::instruction::TokenConditionalSwapCreateV2;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {
@ -4773,7 +4768,7 @@ impl ClientInstruction for TokenConditionalSwapCreateLinearAuctionInstruction {
type Instruction = mango_v4::instruction::TokenConditionalSwapCreateLinearAuction;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {
@ -4853,7 +4848,7 @@ impl ClientInstruction for TokenConditionalSwapCreatePremiumAuctionInstruction {
type Instruction = mango_v4::instruction::TokenConditionalSwapCreatePremiumAuction;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {
@ -4926,7 +4921,7 @@ impl ClientInstruction for TokenConditionalSwapCancelInstruction {
type Instruction = mango_v4::instruction::TokenConditionalSwapCancel;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {
@ -4941,9 +4936,9 @@ impl ClientInstruction for TokenConditionalSwapCancelInstruction {
let tcs = account.token_conditional_swap_by_id(self.id).unwrap().1;
let buy_mint_info =
get_mint_info_by_token_index(&account_loader, &account, tcs.buy_token_index).await;
get_mint_info_by_token_index(account_loader, &account, tcs.buy_token_index).await;
let sell_mint_info =
get_mint_info_by_token_index(&account_loader, &account, tcs.sell_token_index).await;
get_mint_info_by_token_index(account_loader, &account, tcs.sell_token_index).await;
let accounts = Self::Accounts {
group: account.fixed.group,
@ -4979,7 +4974,7 @@ impl ClientInstruction for TokenConditionalSwapTriggerInstruction {
type Instruction = mango_v4::instruction::TokenConditionalSwapTriggerV2;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
@ -5007,7 +5002,7 @@ impl ClientInstruction for TokenConditionalSwapTriggerInstruction {
};
let health_check_metas = derive_liquidation_remaining_account_metas(
&account_loader,
account_loader,
&liqee,
&liqor,
tcs.buy_token_index,
@ -5047,7 +5042,7 @@ impl ClientInstruction for TokenConditionalSwapStartInstruction {
type Instruction = mango_v4::instruction::TokenConditionalSwapStart;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
@ -5062,7 +5057,7 @@ impl ClientInstruction for TokenConditionalSwapStartInstruction {
.clone();
let sell_mint_info =
get_mint_info_by_token_index(&account_loader, &liqee, tcs.sell_token_index).await;
get_mint_info_by_token_index(account_loader, &liqee, tcs.sell_token_index).await;
let instruction = Self::Instruction {
token_conditional_swap_index: self.index,
@ -5070,7 +5065,7 @@ impl ClientInstruction for TokenConditionalSwapStartInstruction {
};
let health_check_metas = derive_health_check_remaining_account_metas(
&account_loader,
account_loader,
&liqee,
Some(sell_mint_info.first_bank()),
true,
@ -5105,7 +5100,7 @@ impl ClientInstruction for TokenChargeCollateralFeesInstruction {
type Instruction = mango_v4::instruction::TokenChargeCollateralFees;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
@ -5116,14 +5111,9 @@ impl ClientInstruction for TokenChargeCollateralFeesInstruction {
let instruction = Self::Instruction {};
let health_check_metas = derive_health_check_remaining_account_metas(
&account_loader,
&account,
None,
true,
None,
)
.await;
let health_check_metas =
derive_health_check_remaining_account_metas(account_loader, &account, None, true, None)
.await;
let accounts = Self::Accounts {
group: account.fixed.group,
@ -5139,3 +5129,43 @@ impl ClientInstruction for TokenChargeCollateralFeesInstruction {
vec![]
}
}
#[derive(Clone)]
pub struct HealthAccountSkipping<T: ClientInstruction> {
pub inner: T,
pub skip_banks: Vec<Pubkey>,
}
#[async_trait::async_trait(?Send)]
impl<T: ClientInstruction> ClientInstruction for HealthAccountSkipping<T> {
type Accounts = T::Accounts;
type Instruction = T::Instruction;
async fn to_instruction(
&self,
account_loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let (accounts, mut instruction) = self.inner.to_instruction(account_loader).await;
let ams = &mut instruction.accounts;
for bank_pk in &self.skip_banks {
let bank_pos =
ams.len() - 1 - ams.iter().rev().position(|m| m.pubkey == *bank_pk).unwrap();
ams.remove(bank_pos);
let bank = account_loader.load::<Bank>(&bank_pk).await.unwrap();
let oracle_pk = bank.oracle;
let oracle_pos = bank_pos
+ ams[bank_pos..]
.iter()
.position(|m| m.pubkey == oracle_pk)
.unwrap();
ams.remove(oracle_pos);
}
(accounts, instruction)
}
fn signers(&self) -> Vec<TestKeypair> {
self.inner.signers()
}
}