Compute health in perp_place_order
This commit is contained in:
parent
65a5139db7
commit
48cb1f0b88
|
@ -2,7 +2,8 @@ use anchor_lang::prelude::*;
|
|||
|
||||
use crate::error::*;
|
||||
use crate::state::{
|
||||
oracle_price, Book, EventQueue, Group, MangoAccount, OrderType, PerpMarket, Side,
|
||||
compute_health_from_fixed_accounts, oracle_price, Book, EventQueue, Group, HealthType,
|
||||
MangoAccount, OrderType, PerpMarket, Side,
|
||||
};
|
||||
|
||||
#[derive(Accounts)]
|
||||
|
@ -76,54 +77,62 @@ pub fn perp_place_order(
|
|||
// When the limit is reached, processing stops and the instruction succeeds.
|
||||
limit: u8,
|
||||
) -> Result<()> {
|
||||
// TODO: check pre and post health
|
||||
|
||||
let mut mango_account = ctx.accounts.account.load_mut()?;
|
||||
require!(mango_account.is_bankrupt == 0, MangoError::IsBankrupt);
|
||||
let mango_account_pk = ctx.accounts.account.key();
|
||||
|
||||
let mut perp_market = ctx.accounts.perp_market.load_mut()?;
|
||||
let bids = &ctx.accounts.bids.to_account_info();
|
||||
let asks = &ctx.accounts.asks.to_account_info();
|
||||
let mut book = Book::load_mut(bids, asks, &perp_market)?;
|
||||
{
|
||||
let mut perp_market = ctx.accounts.perp_market.load_mut()?;
|
||||
let bids = &ctx.accounts.bids.to_account_info();
|
||||
let asks = &ctx.accounts.asks.to_account_info();
|
||||
let mut book = Book::load_mut(bids, asks, &perp_market)?;
|
||||
|
||||
let mut event_queue = ctx.accounts.event_queue.load_mut()?;
|
||||
let mut event_queue = ctx.accounts.event_queue.load_mut()?;
|
||||
|
||||
let oracle_price = oracle_price(&ctx.accounts.oracle.to_account_info())?;
|
||||
let oracle_price = oracle_price(&ctx.accounts.oracle.to_account_info())?;
|
||||
|
||||
let now_ts = Clock::get()?.unix_timestamp as u64;
|
||||
let time_in_force = if expiry_timestamp != 0 {
|
||||
// If expiry is far in the future, clamp to 255 seconds
|
||||
let tif = expiry_timestamp.saturating_sub(now_ts).min(255);
|
||||
if tif == 0 {
|
||||
// If expiry is in the past, ignore the order
|
||||
msg!("Order is already expired");
|
||||
return Ok(());
|
||||
}
|
||||
tif as u8
|
||||
} else {
|
||||
// Never expire
|
||||
0
|
||||
};
|
||||
let now_ts = Clock::get()?.unix_timestamp as u64;
|
||||
let time_in_force = if expiry_timestamp != 0 {
|
||||
// If expiry is far in the future, clamp to 255 seconds
|
||||
let tif = expiry_timestamp.saturating_sub(now_ts).min(255);
|
||||
if tif == 0 {
|
||||
// If expiry is in the past, ignore the order
|
||||
msg!("Order is already expired");
|
||||
return Ok(());
|
||||
}
|
||||
tif as u8
|
||||
} else {
|
||||
// Never expire
|
||||
0
|
||||
};
|
||||
|
||||
// TODO reduce_only based on event queue
|
||||
// TODO reduce_only based on event queue
|
||||
|
||||
book.new_order(
|
||||
side,
|
||||
&mut perp_market,
|
||||
&mut event_queue,
|
||||
oracle_price,
|
||||
&mut mango_account.perps,
|
||||
&mango_account_pk,
|
||||
price_lots,
|
||||
max_base_lots,
|
||||
max_quote_lots,
|
||||
order_type,
|
||||
time_in_force,
|
||||
client_order_id,
|
||||
now_ts,
|
||||
limit,
|
||||
book.new_order(
|
||||
side,
|
||||
&mut perp_market,
|
||||
&mut event_queue,
|
||||
oracle_price,
|
||||
&mut mango_account.perps,
|
||||
&mango_account_pk,
|
||||
price_lots,
|
||||
max_base_lots,
|
||||
max_quote_lots,
|
||||
order_type,
|
||||
time_in_force,
|
||||
client_order_id,
|
||||
now_ts,
|
||||
limit,
|
||||
)?;
|
||||
}
|
||||
|
||||
let health = compute_health_from_fixed_accounts(
|
||||
&mango_account,
|
||||
HealthType::Init,
|
||||
ctx.remaining_accounts,
|
||||
)?;
|
||||
msg!("health: {}", health);
|
||||
require!(health >= 0, MangoError::HealthMustBePositive);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -244,15 +244,16 @@ pub fn compute_health_from_fixed_accounts(
|
|||
let active_serum3_len = account.serum3.iter_active().count();
|
||||
let active_perp_len = account.perps.iter_active_accounts().count();
|
||||
let expected_ais = cm!(active_token_len * 2 // banks + oracles
|
||||
+ active_serum3_len // open_orders
|
||||
+ active_perp_len); // PerpMarkets
|
||||
+ active_perp_len // PerpMarkets
|
||||
+ active_serum3_len); // open_orders
|
||||
msg!("{} {}", ais.len(), expected_ais);
|
||||
require!(ais.len() == expected_ais, MangoError::SomeError);
|
||||
|
||||
let retriever = FixedOrderAccountRetriever {
|
||||
ais,
|
||||
n_banks: active_token_len,
|
||||
begin_serum3: cm!(active_token_len * 2),
|
||||
begin_perp: cm!(active_token_len * 2 + active_serum3_len),
|
||||
begin_perp: cm!(active_token_len * 2),
|
||||
begin_serum3: cm!(active_token_len * 2 + active_perp_len),
|
||||
};
|
||||
compute_health_detail(account, &retriever, health_type, true)?.health(health_type)
|
||||
}
|
||||
|
|
|
@ -121,11 +121,27 @@ async fn derive_health_check_remaining_account_metas(
|
|||
account: &MangoAccount,
|
||||
affected_bank: Option<Pubkey>,
|
||||
writable_banks: bool,
|
||||
affected_perp_market_index: Option<PerpMarketIndex>,
|
||||
) -> Vec<AccountMeta> {
|
||||
let mut adjusted_account = account.clone();
|
||||
if let Some(affected_bank) = affected_bank {
|
||||
let bank: Bank = account_loader.load(&affected_bank).await.unwrap();
|
||||
adjusted_account
|
||||
.tokens
|
||||
.get_mut_or_create(bank.token_index)
|
||||
.unwrap();
|
||||
}
|
||||
if let Some(affected_perp_market_index) = affected_perp_market_index {
|
||||
adjusted_account
|
||||
.perps
|
||||
.get_account_mut_or_create(affected_perp_market_index)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// figure out all the banks/oracles that need to be passed for the health check
|
||||
let mut banks = vec![];
|
||||
let mut oracles = vec![];
|
||||
for position in account.tokens.iter_active() {
|
||||
for position in adjusted_account.tokens.iter_active() {
|
||||
let mint_info =
|
||||
get_mint_info_by_token_index(account_loader, account, position.token_index).await;
|
||||
// TODO: ALTs are unavailable
|
||||
|
@ -139,26 +155,12 @@ async fn derive_health_check_remaining_account_metas(
|
|||
banks.push(mint_info.bank);
|
||||
oracles.push(mint_info.oracle);
|
||||
}
|
||||
if let Some(affected_bank) = affected_bank {
|
||||
if banks.iter().find(|&&v| v == affected_bank).is_none() {
|
||||
// If there is not yet an active position for the token, we need to pass
|
||||
// the bank/oracle for health check anyway.
|
||||
let new_position = account
|
||||
.tokens
|
||||
.values
|
||||
.iter()
|
||||
.position(|p| !p.is_active())
|
||||
.unwrap();
|
||||
banks.insert(new_position, affected_bank);
|
||||
let affected_bank: Bank = account_loader.load(&affected_bank).await.unwrap();
|
||||
oracles.insert(new_position, affected_bank.oracle);
|
||||
}
|
||||
}
|
||||
|
||||
let perp_markets = account
|
||||
let perp_markets = adjusted_account
|
||||
.perps
|
||||
.iter_active_accounts()
|
||||
.map(|perp| get_perp_market_address_by_index(account.group, perp.market_index));
|
||||
|
||||
let serum_oos = account.serum3.iter_active().map(|&s| s.open_orders);
|
||||
|
||||
let to_account_meta = |pubkey| AccountMeta {
|
||||
|
@ -311,6 +313,7 @@ impl<'keypair> ClientInstruction for MarginTradeInstruction<'keypair> {
|
|||
&account,
|
||||
Some(self.mango_token_bank),
|
||||
true,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
||||
|
@ -405,6 +408,7 @@ impl<'keypair> ClientInstruction for WithdrawInstruction<'keypair> {
|
|||
&account,
|
||||
Some(mint_info.bank),
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
||||
|
@ -468,6 +472,7 @@ impl<'keypair> ClientInstruction for DepositInstruction<'keypair> {
|
|||
&account,
|
||||
Some(mint_info.bank),
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
||||
|
@ -1003,9 +1008,14 @@ impl<'keypair> ClientInstruction for Serum3PlaceOrderInstruction<'keypair> {
|
|||
)
|
||||
.unwrap();
|
||||
|
||||
let health_check_metas =
|
||||
derive_health_check_remaining_account_metas(&account_loader, &account, None, false)
|
||||
.await;
|
||||
let health_check_metas = derive_health_check_remaining_account_metas(
|
||||
&account_loader,
|
||||
&account,
|
||||
None,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
||||
let accounts = Self::Accounts {
|
||||
group: account.group,
|
||||
|
@ -1230,9 +1240,14 @@ impl ClientInstruction for Serum3LiqForceCancelOrdersInstruction {
|
|||
)
|
||||
.unwrap();
|
||||
|
||||
let health_check_metas =
|
||||
derive_health_check_remaining_account_metas(&account_loader, &account, None, false)
|
||||
.await;
|
||||
let health_check_metas = derive_health_check_remaining_account_metas(
|
||||
&account_loader,
|
||||
&account,
|
||||
None,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
||||
let accounts = Self::Accounts {
|
||||
group: account.group,
|
||||
|
@ -1419,7 +1434,7 @@ impl<'keypair> ClientInstruction for PerpPlaceOrderInstruction<'keypair> {
|
|||
type Instruction = mango_v4::instruction::PerpPlaceOrder;
|
||||
async fn to_instruction(
|
||||
&self,
|
||||
_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 {
|
||||
|
@ -1443,7 +1458,20 @@ impl<'keypair> ClientInstruction for PerpPlaceOrderInstruction<'keypair> {
|
|||
owner: self.owner.pubkey(),
|
||||
};
|
||||
|
||||
let instruction = make_instruction(program_id, &accounts, instruction);
|
||||
let perp_market: PerpMarket = account_loader.load(&self.perp_market).await.unwrap();
|
||||
let account: MangoAccount = account_loader.load(&self.account).await.unwrap();
|
||||
let health_check_metas = derive_health_check_remaining_account_metas(
|
||||
&account_loader,
|
||||
&account,
|
||||
None,
|
||||
false,
|
||||
Some(perp_market.perp_market_index),
|
||||
)
|
||||
.await;
|
||||
|
||||
let mut instruction = make_instruction(program_id, &accounts, instruction);
|
||||
instruction.accounts.extend(health_check_metas);
|
||||
|
||||
(accounts, instruction)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#![cfg(feature = "test-bpf")]
|
||||
|
||||
use fixed::types::I80F48;
|
||||
use mango_v4::state::*;
|
||||
use solana_program_test::*;
|
||||
use solana_sdk::{signature::Keypair, transport::TransportError};
|
||||
|
@ -284,6 +285,11 @@ async fn test_health_compute_perp() -> Result<(), TransportError> {
|
|||
perp_markets.push((perp_market, asks, bids, event_queue));
|
||||
}
|
||||
|
||||
let price_lots = {
|
||||
let perp_market = solana.get_account::<PerpMarket>(perp_markets[0].0).await;
|
||||
perp_market.native_price_to_lot(I80F48::from(1))
|
||||
};
|
||||
|
||||
//
|
||||
// TEST: Create a perp order for each market
|
||||
//
|
||||
|
@ -301,7 +307,7 @@ async fn test_health_compute_perp() -> Result<(), TransportError> {
|
|||
oracle: tokens[i + 1].oracle,
|
||||
owner,
|
||||
side: Side::Bid,
|
||||
price_lots: 1,
|
||||
price_lots,
|
||||
max_base_lots: 1,
|
||||
max_quote_lots: i64::MAX,
|
||||
client_order_id: 0,
|
||||
|
|
Loading…
Reference in New Issue