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:
parent
df15672522
commit
a7aaaff07e
|
@ -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 {
|
||||
|
|
|
@ -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>()?;
|
||||
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);
|
||||
require_eq!(bank.token_index, token_index);
|
||||
Ok(bank)
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")?
|
||||
};
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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")?;
|
||||
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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)?
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 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)?;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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::*;
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -36,24 +36,20 @@ impl SerumOrderPlacer {
|
|||
None
|
||||
}
|
||||
|
||||
async fn try_bid(
|
||||
fn bid_ix(
|
||||
&mut self,
|
||||
limit_price: f64,
|
||||
max_base: u64,
|
||||
taker: bool,
|
||||
) -> Result<mango_v4::accounts::Serum3PlaceOrder, TransportError> {
|
||||
) -> Serum3PlaceOrderInstruction {
|
||||
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))
|
||||
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,
|
||||
|
@ -62,9 +58,17 @@ impl SerumOrderPlacer {
|
|||
account: self.account,
|
||||
owner: self.owner,
|
||||
serum_market: self.serum_market,
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
async fn try_bid(
|
||||
&mut self,
|
||||
limit_price: f64,
|
||||
max_base: u64,
|
||||
taker: bool,
|
||||
) -> Result<mango_v4::accounts::Serum3PlaceOrder, TransportError> {
|
||||
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,
|
||||
|
|
|
@ -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,13 +735,8 @@ 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,
|
||||
)
|
||||
let health_check_metas =
|
||||
derive_health_check_remaining_account_metas(account_loader, &account, None, true, None)
|
||||
.await;
|
||||
|
||||
let accounts = Self::Accounts {
|
||||
|
@ -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,13 +5111,8 @@ impl ClientInstruction for TokenChargeCollateralFeesInstruction {
|
|||
|
||||
let instruction = Self::Instruction {};
|
||||
|
||||
let health_check_metas = derive_health_check_remaining_account_metas(
|
||||
&account_loader,
|
||||
&account,
|
||||
None,
|
||||
true,
|
||||
None,
|
||||
)
|
||||
let health_check_metas =
|
||||
derive_health_check_remaining_account_metas(account_loader, &account, None, true, None)
|
||||
.await;
|
||||
|
||||
let accounts = Self::Accounts {
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue