Compute health in perp_place_order

This commit is contained in:
Christian Kamm 2022-05-25 19:29:53 +02:00
parent 65a5139db7
commit 48cb1f0b88
4 changed files with 113 additions and 69 deletions

View File

@ -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(())
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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,