Perps: Place accounts and open orders in MangoAccount::perp

Also make test_perp succeed by fixing the FillEvent size.
This commit is contained in:
Christian Kamm 2022-04-01 12:22:03 +02:00
parent f48a7f5ed9
commit f2607889e3
6 changed files with 124 additions and 117 deletions

View File

@ -34,11 +34,7 @@ pub fn create_account(ctx: Context<CreateAccount>, account_num: u8) -> Result<()
delegate: Pubkey::default(),
token_account_map: TokenAccountMap::new(),
serum3_account_map: Serum3AccountMap::new(),
perp_account_map: PerpAccountMap::new(),
order_market: [PerpMarketIndex::MAX; MAX_PERP_OPEN_ORDERS],
order_side: [Side::Bid; MAX_PERP_OPEN_ORDERS],
orders: [i128::MAX; MAX_PERP_OPEN_ORDERS],
client_order_ids: [u64::MAX; MAX_PERP_OPEN_ORDERS],
perp: PerpData::new(),
being_liquidated: 0,
is_bankrupt: 0,
account_num,

View File

@ -50,8 +50,10 @@ pub fn perp_consume_events(ctx: Context<PerpConsumeEvents>, limit: usize) -> Res
Some(account_info) => account_info.load_mut::<MangoAccount>()?,
};
ma.execute_maker(perp_market.perp_market_index, &mut perp_market, fill)?;
ma.execute_taker(perp_market.perp_market_index, &mut perp_market, fill)?;
ma.perp
.execute_maker(perp_market.perp_market_index, &mut perp_market, fill)?;
ma.perp
.execute_taker(perp_market.perp_market_index, &mut perp_market, fill)?;
} else {
let mut maker = match mango_account_ais.iter().find(|ai| ai.key == &fill.maker)
{
@ -70,8 +72,16 @@ pub fn perp_consume_events(ctx: Context<PerpConsumeEvents>, limit: usize) -> Res
Some(account_info) => account_info.load_mut::<MangoAccount>()?,
};
maker.execute_maker(perp_market.perp_market_index, &mut perp_market, fill)?;
taker.execute_taker(perp_market.perp_market_index, &mut perp_market, fill)?;
maker.perp.execute_maker(
perp_market.perp_market_index,
&mut perp_market,
fill,
)?;
taker.perp.execute_taker(
perp_market.perp_market_index,
&mut perp_market,
fill,
)?;
}
}
EventType::Out => {
@ -85,7 +95,8 @@ pub fn perp_consume_events(ctx: Context<PerpConsumeEvents>, limit: usize) -> Res
Some(account_info) => account_info.load_mut::<MangoAccount>()?,
};
ma.remove_order(out.owner_slot as usize, out.quantity)?;
ma.perp
.remove_order(out.owner_slot as usize, out.quantity)?;
}
EventType::Liquidate => {
// This is purely for record keeping. Can be removed if program logs are superior

View File

@ -14,8 +14,9 @@ use crate::state::*;
// we could probably support 1 token (quote currency) + 15 active perp markets at the same time
// It's a tradeoff between allowing users to trade on many markets with one account,
// MangoAccount size and health compute needs.
const MAX_INDEXED_POSITIONS: usize = 16;
const MAX_SERUM_OPEN_ORDERS: usize = 8;
const MAX_TOKEN_ACCOUNTS: usize = 16;
const MAX_SERUM3_ACCOUNTS: usize = 8;
const MAX_PERP_ACCOUNTS: usize = 8;
pub const MAX_PERP_OPEN_ORDERS: usize = 8;
pub const FREE_ORDER_SLOT: PerpMarketIndex = PerpMarketIndex::MAX;
@ -65,7 +66,7 @@ impl TokenAccount {
#[zero_copy]
pub struct TokenAccountMap {
pub values: [TokenAccount; MAX_INDEXED_POSITIONS],
pub values: [TokenAccount; MAX_TOKEN_ACCOUNTS],
}
impl Default for TokenAccountMap {
@ -82,7 +83,7 @@ impl TokenAccountMap {
token_index: TokenIndex::MAX,
in_use_count: 0,
reserved: Default::default(),
}; MAX_INDEXED_POSITIONS],
}; MAX_TOKEN_ACCOUNTS],
}
}
@ -186,7 +187,7 @@ impl Default for Serum3Account {
#[zero_copy]
pub struct Serum3AccountMap {
pub values: [Serum3Account; MAX_SERUM_OPEN_ORDERS],
pub values: [Serum3Account; MAX_SERUM3_ACCOUNTS],
}
impl Default for Serum3AccountMap {
@ -198,7 +199,7 @@ impl Default for Serum3AccountMap {
impl Serum3AccountMap {
pub fn new() -> Self {
Self {
values: [Serum3Account::default(); MAX_SERUM_OPEN_ORDERS],
values: [Serum3Account::default(); MAX_SERUM3_ACCOUNTS],
}
}
@ -308,108 +309,69 @@ impl PerpAccount {
}
#[zero_copy]
pub struct PerpAccountMap {
pub values: [PerpAccount; MAX_PERP_OPEN_ORDERS],
}
pub struct PerpData {
pub accounts: [PerpAccount; MAX_PERP_ACCOUNTS],
impl PerpAccountMap {
// TODO: possibly it's more convenient to store a single list of PerpOpenOrder structs?
pub order_market: [PerpMarketIndex; MAX_PERP_OPEN_ORDERS],
pub order_side: [Side; MAX_PERP_OPEN_ORDERS], // TODO: storing enums isn't POD
pub order_id: [i128; MAX_PERP_OPEN_ORDERS],
pub order_client_id: [u64; MAX_PERP_OPEN_ORDERS],
}
const_assert_eq!(
size_of::<PerpData>(),
MAX_PERP_ACCOUNTS * size_of::<PerpAccount>() + MAX_PERP_OPEN_ORDERS * (2 + 1 + 16 + 8)
);
const_assert_eq!(size_of::<PerpData>() % 8, 0);
impl PerpData {
pub fn new() -> Self {
Self {
values: [PerpAccount::default(); MAX_PERP_OPEN_ORDERS],
accounts: [PerpAccount::default(); MAX_PERP_ACCOUNTS],
order_market: [FREE_ORDER_SLOT; MAX_PERP_OPEN_ORDERS],
order_side: [Side::Bid; MAX_PERP_OPEN_ORDERS],
order_id: [0; MAX_PERP_OPEN_ORDERS],
order_client_id: [0; MAX_PERP_OPEN_ORDERS],
}
}
pub fn get_mut_or_create(
pub fn get_account_mut_or_create(
&mut self,
perp_market_index: PerpMarketIndex,
) -> Result<(&mut PerpAccount, usize)> {
let mut pos = self
.values
.accounts
.iter()
.position(|p| p.is_active_for_market(perp_market_index));
if pos.is_none() {
pos = self.values.iter().position(|p| !p.is_active());
pos = self.accounts.iter().position(|p| !p.is_active());
if let Some(i) = pos {
self.values[i] = PerpAccount {
self.accounts[i] = PerpAccount {
..Default::default()
};
}
}
if let Some(i) = pos {
Ok((&mut self.values[i], i))
Ok((&mut self.accounts[i], i))
} else {
err!(MangoError::SomeError) // TODO: No free space
}
}
pub fn deactivate(&mut self, index: usize) {
self.values[index].market_index = PerpMarketIndex::MAX;
pub fn deactivate_account(&mut self, index: usize) {
self.accounts[index].market_index = PerpMarketIndex::MAX;
}
pub fn iter_active(&self) -> impl Iterator<Item = &PerpAccount> {
self.values.iter().filter(|p| p.is_active())
pub fn iter_active_accounts(&self) -> impl Iterator<Item = &PerpAccount> {
self.accounts.iter().filter(|p| p.is_active())
}
pub fn find(&self, market_index: PerpMarketIndex) -> Option<&PerpAccount> {
self.values
pub fn find_account(&self, market_index: PerpMarketIndex) -> Option<&PerpAccount> {
self.accounts
.iter()
.find(|p| p.is_active_for_market(market_index))
}
}
impl Default for PerpAccountMap {
fn default() -> Self {
Self::new()
}
}
#[account(zero_copy)]
pub struct MangoAccount {
pub group: Pubkey,
pub owner: Pubkey,
// Alternative authority/signer of transactions for a mango account
pub delegate: Pubkey,
// Maps token_index -> deposit/borrow account for each token
// that is active on this MangoAccount.
pub token_account_map: TokenAccountMap,
// Maps serum_market_index -> open orders for each serum market
// that is active on this MangoAccount.
pub serum3_account_map: Serum3AccountMap,
pub perp_account_map: PerpAccountMap,
pub order_market: [PerpMarketIndex; MAX_PERP_OPEN_ORDERS],
pub order_side: [Side; MAX_PERP_OPEN_ORDERS],
pub orders: [i128; MAX_PERP_OPEN_ORDERS],
pub client_order_ids: [u64; MAX_PERP_OPEN_ORDERS],
/// This account cannot open new positions or borrow until `init_health >= 0`
pub being_liquidated: u8,
/// This account cannot do anything except go through `resolve_bankruptcy`
pub is_bankrupt: u8,
pub account_num: u8,
pub bump: u8,
// pub info: [u8; INFO_LEN], // TODO: Info could be in a separate PDA?
pub reserved: [u8; 4],
}
const_assert_eq!(
size_of::<MangoAccount>(),
3 * 32
+ MAX_INDEXED_POSITIONS * size_of::<TokenAccount>()
+ MAX_SERUM_OPEN_ORDERS * size_of::<Serum3Account>()
+ MAX_PERP_OPEN_ORDERS * size_of::<PerpAccount>()
+ MAX_PERP_OPEN_ORDERS * (2 + 1 + 16 + 8)
+ 4
+ 4
);
const_assert_eq!(size_of::<MangoAccount>() % 8, 0);
impl MangoAccount {
pub fn next_order_slot(&self) -> Option<usize> {
self.order_market.iter().position(|&i| i == FREE_ORDER_SLOT)
}
@ -420,11 +382,7 @@ impl MangoAccount {
side: Side,
order: &LeafNode,
) -> Result<()> {
let mut perp_account = self
.perp_account_map
.get_mut_or_create(perp_market_index)
.unwrap()
.0;
let mut perp_account = self.get_account_mut_or_create(perp_market_index).unwrap().0;
match side {
Side::Bid => {
perp_account.bids_quantity = perp_account
@ -442,8 +400,8 @@ impl MangoAccount {
let slot = order.owner_slot as usize;
self.order_market[slot] = perp_market_index;
self.order_side[slot] = side;
self.orders[slot] = order.key;
self.client_order_ids[slot] = order.client_order_id;
self.order_id[slot] = order.key;
self.order_client_id[slot] = order.client_order_id;
Ok(())
}
@ -452,15 +410,12 @@ impl MangoAccount {
self.order_market[slot] != FREE_ORDER_SLOT,
MangoError::SomeError
);
let order_side = self.order_side[slot];
let perp_market_index = self.order_market[slot];
let perp_account = self
.perp_account_map
.get_mut_or_create(perp_market_index)
.unwrap()
.0;
let perp_account = self.get_account_mut_or_create(perp_market_index).unwrap().0;
// accounting
match self.order_side[slot] {
match order_side {
Side::Bid => {
perp_account.bids_quantity -= quantity;
}
@ -474,8 +429,8 @@ impl MangoAccount {
// TODO OPT - remove these; unnecessary
self.order_side[slot] = Side::Bid;
self.orders[slot] = 0i128;
self.client_order_ids[slot] = 0u64;
self.order_id[slot] = 0i128;
self.order_client_id[slot] = 0u64;
Ok(())
}
@ -485,11 +440,7 @@ impl MangoAccount {
perp_market: &mut PerpMarket,
fill: &FillEvent,
) -> Result<()> {
let pa = self
.perp_account_map
.get_mut_or_create(perp_market_index)
.unwrap()
.0;
let pa = self.get_account_mut_or_create(perp_market_index).unwrap().0;
// pa.settle_funding(cache);
let side = fill.taker_side.invert_side();
@ -528,11 +479,7 @@ impl MangoAccount {
perp_market: &mut PerpMarket,
fill: &FillEvent,
) -> Result<()> {
let pa = self
.perp_account_map
.get_mut_or_create(perp_market_index)
.unwrap()
.0;
let pa = self.get_account_mut_or_create(perp_market_index).unwrap().0;
let (base_change, quote_change) = fill.base_quote_change(fill.taker_side);
pa.remove_taker_trade(base_change, quote_change);
pa.change_base_position(perp_market, base_change);
@ -545,6 +492,53 @@ impl MangoAccount {
}
}
impl Default for PerpData {
fn default() -> Self {
Self::new()
}
}
#[account(zero_copy)]
pub struct MangoAccount {
pub group: Pubkey,
pub owner: Pubkey,
// Alternative authority/signer of transactions for a mango account
pub delegate: Pubkey,
// Maps token_index -> deposit/borrow account for each token
// that is active on this MangoAccount.
pub token_account_map: TokenAccountMap,
// Maps serum_market_index -> open orders for each serum market
// that is active on this MangoAccount.
pub serum3_account_map: Serum3AccountMap,
pub perp: PerpData,
/// This account cannot open new positions or borrow until `init_health >= 0`
pub being_liquidated: u8,
/// This account cannot do anything except go through `resolve_bankruptcy`
pub is_bankrupt: u8,
pub account_num: u8,
pub bump: u8,
// pub info: [u8; INFO_LEN], // TODO: Info could be in a separate PDA?
pub reserved: [u8; 4],
}
const_assert_eq!(
size_of::<MangoAccount>(),
3 * 32
+ MAX_TOKEN_ACCOUNTS * size_of::<TokenAccount>()
+ MAX_SERUM3_ACCOUNTS * size_of::<Serum3Account>()
+ size_of::<PerpData>()
+ 4
+ 4
);
const_assert_eq!(size_of::<MangoAccount>() % 8, 0);
#[macro_export]
macro_rules! account_seeds {
( $account:expr ) => {

View File

@ -334,6 +334,7 @@ impl<'a> Book<'a> {
}
let owner_slot = mango_account
.perp
.next_order_slot()
.ok_or_else(|| error!(MangoError::SomeError))?;
let new_order = LeafNode::new(
@ -394,8 +395,8 @@ fn apply_fees(
let taker_fees = taker_quote_native * market.taker_fee;
let perp_account = mango_account
.perp_account_map
.get_mut_or_create(market.perp_market_index)?
.perp
.get_account_mut_or_create(market.perp_market_index)?
.0;
perp_account.quote_position -= taker_fees;
market.fees_accrued += taker_fees + maker_fees;

View File

@ -3,6 +3,8 @@ use anchor_lang::prelude::*;
use fixed::types::I80F48;
use mango_macro::Pod;
use num_enum::{IntoPrimitive, TryFromPrimitive};
use static_assertions::const_assert_eq;
use std::mem::size_of;
use super::Side;
@ -176,7 +178,7 @@ pub struct FillEvent {
pub maker_out: bool, // true if maker order quantity == 0
pub maker_slot: u8,
pub market_fees_applied: bool,
pub padding: [u8; 2],
pub padding: [u8; 3],
pub timestamp: u64,
pub seq_num: usize, // note: usize same as u64
@ -195,7 +197,9 @@ pub struct FillEvent {
pub price: i64,
pub quantity: i64, // number of quote lots
pub reserved: [u8; 8],
}
const_assert_eq!(size_of::<FillEvent>(), EVENT_SIZE);
impl FillEvent {
#[allow(clippy::too_many_arguments)]
@ -224,7 +228,7 @@ impl FillEvent {
maker_out,
maker_slot,
market_fees_applied: true, // Since mango v3.3.5, market fees are adjusted at matching time
padding: [0u8; 2],
padding: Default::default(),
timestamp,
seq_num,
maker,
@ -238,6 +242,7 @@ impl FillEvent {
taker_fee,
price,
quantity,
reserved: Default::default(),
}
}
@ -268,6 +273,7 @@ pub struct OutEvent {
pub quantity: i64,
padding1: [u8; EVENT_SIZE - 64],
}
const_assert_eq!(size_of::<OutEvent>(), EVENT_SIZE);
impl OutEvent {
pub fn new(

View File

@ -1,5 +1,4 @@
// TODO: Test disabled since it fails
#![cfg(all(feature = "test-bpf", feature = "disabled-perp-test"))]
#![cfg(all(feature = "test-bpf"))]
use mango_v4::state::*;
use solana_program_test::*;