Split into perp bids and perp asks (#322)

Separate, smaller accounts that are easy to extend if needed.
This commit is contained in:
Christian Kamm 2022-12-07 21:03:28 +01:00 committed by GitHub
parent c44dc045cf
commit 947d9b2b60
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 613 additions and 298 deletions

View File

@ -870,7 +870,8 @@ impl MangoClient {
group: self.group(),
account: *liqee.0,
perp_market: perp.address,
orderbook: perp.market.orderbook,
bids: perp.market.bids,
asks: perp.market.asks,
},
None,
);

View File

@ -310,7 +310,8 @@ pub async fn loop_update_funding(
&mango_v4::accounts::PerpUpdateFunding {
group: perp_market.group,
perp_market: pk,
orderbook: perp_market.orderbook,
bids: perp_market.bids,
asks: perp_market.asks,
oracle: perp_market.oracle,
},
None,

View File

@ -1,7 +1,7 @@
use anchor_lang::prelude::*;
use crate::error::MangoError;
use crate::state::{AccountLoaderDynamic, Group, MangoAccount, Orderbook, PerpMarket};
use crate::state::{AccountLoaderDynamic, BookSide, Group, MangoAccount, Orderbook, PerpMarket};
#[derive(Accounts)]
pub struct PerpCancelAllOrders<'info> {
@ -14,11 +14,14 @@ pub struct PerpCancelAllOrders<'info> {
#[account(
mut,
has_one = group,
has_one = orderbook,
has_one = bids,
has_one = asks,
)]
pub perp_market: AccountLoader<'info, PerpMarket>,
#[account(mut)]
pub orderbook: AccountLoader<'info, Orderbook>,
pub bids: AccountLoader<'info, BookSide>,
#[account(mut)]
pub asks: AccountLoader<'info, BookSide>,
}
pub fn perp_cancel_all_orders(ctx: Context<PerpCancelAllOrders>, limit: u8) -> Result<()> {
@ -29,7 +32,10 @@ pub fn perp_cancel_all_orders(ctx: Context<PerpCancelAllOrders>, limit: u8) -> R
);
let mut perp_market = ctx.accounts.perp_market.load_mut()?;
let mut book = ctx.accounts.orderbook.load_mut()?;
let mut book = Orderbook {
bids: ctx.accounts.bids.load_mut()?,
asks: ctx.accounts.asks.load_mut()?,
};
book.cancel_all_orders(&mut account.borrow_mut(), &mut perp_market, limit, None)?;

View File

@ -1,7 +1,9 @@
use anchor_lang::prelude::*;
use crate::error::MangoError;
use crate::state::{AccountLoaderDynamic, Group, MangoAccount, Orderbook, PerpMarket, Side};
use crate::state::{
AccountLoaderDynamic, BookSide, Group, MangoAccount, Orderbook, PerpMarket, Side,
};
#[derive(Accounts)]
pub struct PerpCancelAllOrdersBySide<'info> {
@ -14,11 +16,14 @@ pub struct PerpCancelAllOrdersBySide<'info> {
#[account(
mut,
has_one = group,
has_one = orderbook,
has_one = bids,
has_one = asks,
)]
pub perp_market: AccountLoader<'info, PerpMarket>,
#[account(mut)]
pub orderbook: AccountLoader<'info, Orderbook>,
pub bids: AccountLoader<'info, BookSide>,
#[account(mut)]
pub asks: AccountLoader<'info, BookSide>,
}
pub fn perp_cancel_all_orders_by_side(
@ -34,7 +39,10 @@ pub fn perp_cancel_all_orders_by_side(
);
let mut perp_market = ctx.accounts.perp_market.load_mut()?;
let mut book = ctx.accounts.orderbook.load_mut()?;
let mut book = Orderbook {
bids: ctx.accounts.bids.load_init()?,
asks: ctx.accounts.asks.load_init()?,
};
book.cancel_all_orders(
&mut account.borrow_mut(),

View File

@ -1,7 +1,7 @@
use anchor_lang::prelude::*;
use crate::error::*;
use crate::state::{AccountLoaderDynamic, Group, MangoAccount, Orderbook, PerpMarket};
use crate::state::{AccountLoaderDynamic, BookSide, Group, MangoAccount, Orderbook, PerpMarket};
#[derive(Accounts)]
pub struct PerpCancelOrder<'info> {
@ -14,11 +14,14 @@ pub struct PerpCancelOrder<'info> {
#[account(
mut,
has_one = group,
has_one = orderbook,
has_one = bids,
has_one = asks,
)]
pub perp_market: AccountLoader<'info, PerpMarket>,
#[account(mut)]
pub orderbook: AccountLoader<'info, Orderbook>,
pub bids: AccountLoader<'info, BookSide>,
#[account(mut)]
pub asks: AccountLoader<'info, BookSide>,
}
pub fn perp_cancel_order(ctx: Context<PerpCancelOrder>, order_id: u128) -> Result<()> {
@ -29,7 +32,10 @@ pub fn perp_cancel_order(ctx: Context<PerpCancelOrder>, order_id: u128) -> Resul
);
let perp_market = ctx.accounts.perp_market.load_mut()?;
let mut book = ctx.accounts.orderbook.load_mut()?;
let mut book = Orderbook {
bids: ctx.accounts.bids.load_mut()?,
asks: ctx.accounts.asks.load_mut()?,
};
let oo = account
.perp_find_order_with_order_id(perp_market.perp_market_index, order_id)

View File

@ -1,7 +1,7 @@
use anchor_lang::prelude::*;
use crate::error::*;
use crate::state::{AccountLoaderDynamic, Group, MangoAccount, Orderbook, PerpMarket};
use crate::state::{AccountLoaderDynamic, BookSide, Group, MangoAccount, Orderbook, PerpMarket};
#[derive(Accounts)]
pub struct PerpCancelOrderByClientOrderId<'info> {
@ -14,11 +14,14 @@ pub struct PerpCancelOrderByClientOrderId<'info> {
#[account(
mut,
has_one = group,
has_one = orderbook,
has_one = bids,
has_one = asks,
)]
pub perp_market: AccountLoader<'info, PerpMarket>,
#[account(mut)]
pub orderbook: AccountLoader<'info, Orderbook>,
pub bids: AccountLoader<'info, BookSide>,
#[account(mut)]
pub asks: AccountLoader<'info, BookSide>,
}
pub fn perp_cancel_order_by_client_order_id(
@ -32,7 +35,10 @@ pub fn perp_cancel_order_by_client_order_id(
);
let perp_market = ctx.accounts.perp_market.load_mut()?;
let mut book = ctx.accounts.orderbook.load_mut()?;
let mut book = Orderbook {
bids: ctx.accounts.bids.load_mut()?,
asks: ctx.accounts.asks.load_mut()?,
};
let oo = account
.perp_find_order_with_client_order_id(perp_market.perp_market_index, client_order_id)

View File

@ -15,7 +15,8 @@ pub struct PerpCloseMarket<'info> {
#[account(
mut,
has_one = group,
has_one = orderbook,
has_one = bids,
has_one = asks,
has_one = event_queue,
close = sol_destination
)]
@ -25,7 +26,13 @@ pub struct PerpCloseMarket<'info> {
mut,
close = sol_destination
)]
pub orderbook: AccountLoader<'info, Orderbook>,
pub bids: AccountLoader<'info, BookSide>,
#[account(
mut,
close = sol_destination
)]
pub asks: AccountLoader<'info, BookSide>,
#[account(
mut,

View File

@ -33,7 +33,9 @@ pub struct PerpCreateMarket<'info> {
/// Accounts are initialised by client,
/// anchor discriminator is set first when ix exits,
#[account(zero)]
pub orderbook: AccountLoader<'info, Orderbook>,
pub bids: AccountLoader<'info, BookSide>,
#[account(zero)]
pub asks: AccountLoader<'info, BookSide>,
#[account(zero)]
pub event_queue: AccountLoader<'info, EventQueue>,
@ -95,7 +97,8 @@ pub fn perp_create_market(
bump: *ctx.bumps.get("perp_market").ok_or(MangoError::SomeError)?,
base_decimals,
name: fill_from_str(&name)?,
orderbook: ctx.accounts.orderbook.key(),
bids: ctx.accounts.bids.key(),
asks: ctx.accounts.asks.key(),
event_queue: ctx.accounts.event_queue.key(),
oracle: ctx.accounts.oracle.key(),
oracle_config: oracle_config.to_oracle_config(),
@ -136,7 +139,10 @@ pub fn perp_create_market(
.stable_price_model
.reset_to_price(oracle_price.to_num(), now_ts);
let mut orderbook = ctx.accounts.orderbook.load_init()?;
let mut orderbook = Orderbook {
bids: ctx.accounts.bids.load_init()?,
asks: ctx.accounts.asks.load_init()?,
};
orderbook.init();
emit!(PerpMarketMetaDataLog {

View File

@ -14,11 +14,14 @@ pub struct PerpLiqForceCancelOrders<'info> {
#[account(
mut,
has_one = group,
has_one = orderbook,
has_one = bids,
has_one = asks,
)]
pub perp_market: AccountLoader<'info, PerpMarket>,
#[account(mut)]
pub orderbook: AccountLoader<'info, Orderbook>,
pub bids: AccountLoader<'info, BookSide>,
#[account(mut)]
pub asks: AccountLoader<'info, BookSide>,
}
pub fn perp_liq_force_cancel_orders(
@ -62,7 +65,10 @@ pub fn perp_liq_force_cancel_orders(
//
{
let mut perp_market = ctx.accounts.perp_market.load_mut()?;
let mut book = ctx.accounts.orderbook.load_mut()?;
let mut book = Orderbook {
bids: ctx.accounts.bids.load_mut()?,
asks: ctx.accounts.asks.load_mut()?,
};
book.cancel_all_orders(&mut account.borrow_mut(), &mut perp_market, limit, None)?;

View File

@ -4,8 +4,8 @@ use crate::accounts_zerocopy::*;
use crate::error::*;
use crate::state::MangoAccount;
use crate::state::{
new_fixed_order_account_retriever, new_health_cache, AccountLoaderDynamic, EventQueue, Group,
Order, Orderbook, PerpMarket,
new_fixed_order_account_retriever, new_health_cache, AccountLoaderDynamic, BookSide,
EventQueue, Group, Order, Orderbook, PerpMarket,
};
#[derive(Accounts)]
@ -19,13 +19,16 @@ pub struct PerpPlaceOrder<'info> {
#[account(
mut,
has_one = group,
has_one = orderbook,
has_one = bids,
has_one = asks,
has_one = event_queue,
has_one = oracle,
)]
pub perp_market: AccountLoader<'info, PerpMarket>,
#[account(mut)]
pub orderbook: AccountLoader<'info, Orderbook>,
pub bids: AccountLoader<'info, BookSide>,
#[account(mut)]
pub asks: AccountLoader<'info, BookSide>,
#[account(mut)]
pub event_queue: AccountLoader<'info, EventQueue>,
@ -48,7 +51,10 @@ pub fn perp_place_order(ctx: Context<PerpPlaceOrder>, order: Order, limit: u8) -
// before triggering the funding computation.
{
let mut perp_market = ctx.accounts.perp_market.load_mut()?;
let book = ctx.accounts.orderbook.load_mut()?;
let book = Orderbook {
bids: ctx.accounts.bids.load_mut()?,
asks: ctx.accounts.asks.load_mut()?,
};
oracle_price = perp_market.oracle_price(
&AccountInfoRef::borrow(ctx.accounts.oracle.as_ref())?,
@ -94,7 +100,10 @@ pub fn perp_place_order(ctx: Context<PerpPlaceOrder>, order: Order, limit: u8) -
};
let mut perp_market = ctx.accounts.perp_market.load_mut()?;
let mut book = ctx.accounts.orderbook.load_mut()?;
let mut book = Orderbook {
bids: ctx.accounts.bids.load_mut()?,
asks: ctx.accounts.asks.load_mut()?,
};
let mut event_queue = ctx.accounts.event_queue.load_mut()?;

View File

@ -1,7 +1,7 @@
use anchor_lang::prelude::*;
use crate::accounts_zerocopy::*;
use crate::state::{Group, Orderbook, PerpMarket};
use crate::state::{BookSide, Group, Orderbook, PerpMarket};
#[derive(Accounts)]
pub struct PerpUpdateFunding<'info> {
@ -9,13 +9,16 @@ pub struct PerpUpdateFunding<'info> {
#[account(
mut,
has_one = orderbook,
has_one = bids,
has_one = asks,
has_one = oracle,
constraint = perp_market.load()?.group.key() == group.key(),
)]
pub perp_market: AccountLoader<'info, PerpMarket>,
#[account(mut)]
pub orderbook: AccountLoader<'info, Orderbook>,
pub bids: AccountLoader<'info, BookSide>,
#[account(mut)]
pub asks: AccountLoader<'info, BookSide>,
/// CHECK: The oracle can be one of several different account types and the pubkey is checked above
pub oracle: UncheckedAccount<'info>,
@ -24,7 +27,10 @@ pub fn perp_update_funding(ctx: Context<PerpUpdateFunding>) -> Result<()> {
let now_ts: u64 = Clock::get()?.unix_timestamp.try_into().unwrap();
let mut perp_market = ctx.accounts.perp_market.load_mut()?;
let book = ctx.accounts.orderbook.load_mut()?;
let book = Orderbook {
bids: ctx.accounts.bids.load_mut()?,
asks: ctx.accounts.asks.load_mut()?,
};
let now_slot = Clock::get()?.slot;
let oracle_price = perp_market.oracle_price(

View File

@ -6,7 +6,7 @@ use crate::{
use anchor_lang::prelude::*;
use bytemuck::cast;
use fixed::types::I80F48;
use static_assertions::const_assert_eq;
use std::cell::RefMut;
use super::*;
use crate::util::checked_math as cm;
@ -15,20 +15,12 @@ use crate::util::checked_math as cm;
/// This exists as a guard against excessive compute use.
const DROP_EXPIRED_ORDER_LIMIT: usize = 5;
#[account(zero_copy(safe_bytemuck_derives))]
pub struct Orderbook {
pub bids: BookSide,
pub asks: BookSide,
pub reserved: [u8; 2400],
pub struct Orderbook<'a> {
pub bids: RefMut<'a, BookSide>,
pub asks: RefMut<'a, BookSide>,
}
const_assert_eq!(
std::mem::size_of::<Orderbook>(),
2 * std::mem::size_of::<BookSide>() + 2400
);
const_assert_eq!(std::mem::size_of::<Orderbook>(), 249824);
const_assert_eq!(std::mem::size_of::<Orderbook>() % 8, 0);
impl Orderbook {
impl<'a> Orderbook<'a> {
pub fn init(&mut self) {
self.bids.nodes.order_tree_type = OrderTreeType::Bids.into();
self.asks.nodes.order_tree_type = OrderTreeType::Asks.into();

View File

@ -27,8 +27,7 @@ pub struct BookSideOrderHandle {
pub order_tree: BookSideOrderTree,
}
#[zero_copy]
#[derive(bytemuck::Pod, bytemuck::Zeroable)]
#[account(zero_copy(safe_bytemuck_derives))]
pub struct BookSide {
pub roots: [OrderTreeRoot; 2],
pub reserved_roots: [OrderTreeRoot; 4],

View File

@ -26,6 +26,7 @@ mod tests {
use bytemuck::Zeroable;
use fixed::types::I80F48;
use solana_program::pubkey::Pubkey;
use std::cell::RefCell;
fn order_tree_leaf_by_key(bookside: &BookSide, key: u128) -> Option<&LeafNode> {
for component in [BookSideOrderTree::Fixed, BookSideOrderTree::OraclePegged] {
@ -53,9 +54,32 @@ mod tests {
false
}
fn test_setup(price: f64) -> (PerpMarket, I80F48, EventQueue, Box<Orderbook>) {
let mut book = Box::new(Orderbook::zeroed());
book.init();
struct OrderbookAccounts {
bids: Box<RefCell<BookSide>>,
asks: Box<RefCell<BookSide>>,
}
impl OrderbookAccounts {
fn new() -> Self {
let s = Self {
bids: Box::new(RefCell::new(BookSide::zeroed())),
asks: Box::new(RefCell::new(BookSide::zeroed())),
};
s.bids.borrow_mut().nodes.order_tree_type = OrderTreeType::Bids.into();
s.asks.borrow_mut().nodes.order_tree_type = OrderTreeType::Asks.into();
s
}
fn orderbook(&self) -> Orderbook {
Orderbook {
bids: self.bids.borrow_mut(),
asks: self.asks.borrow_mut(),
}
}
}
fn test_setup(price: f64) -> (PerpMarket, I80F48, EventQueue, OrderbookAccounts) {
let book = OrderbookAccounts::new();
let event_queue = EventQueue::zeroed();
@ -75,7 +99,8 @@ mod tests {
// Check what happens when one side of the book fills up
#[test]
fn book_bids_full() {
let (mut perp_market, oracle_price, mut event_queue, mut book) = test_setup(5000.0);
let (mut perp_market, oracle_price, mut event_queue, book_accs) = test_setup(5000.0);
let mut book = book_accs.orderbook();
let settle_token_index = 0;
let mut new_order = |book: &mut Orderbook,
@ -212,7 +237,8 @@ mod tests {
#[test]
fn book_new_order() {
let (mut market, oracle_price, mut event_queue, mut book) = test_setup(1000.0);
let (mut market, oracle_price, mut event_queue, book_accs) = test_setup(1000.0);
let mut book = book_accs.orderbook();
let settle_token_index = 0;
// Add lots and fees to make sure to exercise unit conversion
@ -405,7 +431,8 @@ mod tests {
#[test]
fn test_fee_penalty_applied_only_on_limit_order() -> Result<()> {
let (mut market, oracle_price, mut event_queue, mut book) = test_setup(1000.0);
let (mut market, oracle_price, mut event_queue, book_accs) = test_setup(1000.0);
let mut book = book_accs.orderbook();
let buffer = MangoAccount::default_for_tests().try_to_vec().unwrap();
let mut account = MangoAccountValue::from_bytes(&buffer).unwrap();

View File

@ -39,7 +39,8 @@ pub struct PerpMarket {
pub name: [u8; 16],
pub orderbook: Pubkey,
pub bids: Pubkey,
pub asks: Pubkey,
pub event_queue: Pubkey,
pub oracle: Pubkey,
@ -116,6 +117,7 @@ const_assert_eq!(
+ 32
+ 32
+ 32
+ 32
+ 96
+ 288
+ 8
@ -137,7 +139,7 @@ const_assert_eq!(
+ 8
+ 1944
);
const_assert_eq!(size_of::<PerpMarket>(), 2776);
const_assert_eq!(size_of::<PerpMarket>(), 2808);
const_assert_eq!(size_of::<PerpMarket>() % 8, 0);
impl PerpMarket {
@ -303,7 +305,8 @@ impl PerpMarket {
trusted_market: 0,
group_insurance_fund: 0,
name: Default::default(),
orderbook: Pubkey::new_unique(),
bids: Pubkey::new_unique(),
asks: Pubkey::new_unique(),
event_queue: Pubkey::new_unique(),
oracle: Pubkey::new_unique(),
oracle_config: OracleConfig {

View File

@ -2410,7 +2410,8 @@ pub struct PerpCreateMarketInstruction {
pub group: Pubkey,
pub admin: TestKeypair,
pub oracle: Pubkey,
pub orderbook: Pubkey,
pub bids: Pubkey,
pub asks: Pubkey,
pub event_queue: Pubkey,
pub payer: TestKeypair,
pub settle_token_index: TokenIndex,
@ -2440,8 +2441,11 @@ impl PerpCreateMarketInstruction {
base: &crate::mango_setup::Token,
) -> Self {
PerpCreateMarketInstruction {
orderbook: solana
.create_account_for_type::<Orderbook>(&mango_v4::id())
bids: solana
.create_account_for_type::<BookSide>(&mango_v4::id())
.await,
asks: solana
.create_account_for_type::<BookSide>(&mango_v4::id())
.await,
event_queue: solana
.create_account_for_type::<EventQueue>(&mango_v4::id())
@ -2507,7 +2511,8 @@ impl ClientInstruction for PerpCreateMarketInstruction {
admin: self.admin.pubkey(),
oracle: self.oracle,
perp_market,
orderbook: self.orderbook,
bids: self.bids,
asks: self.asks,
event_queue: self.event_queue,
payer: self.payer.pubkey(),
system_program: System::id(),
@ -2604,7 +2609,8 @@ impl ClientInstruction for PerpCloseMarketInstruction {
group: perp_market.group,
admin: self.admin.pubkey(),
perp_market: self.perp_market,
orderbook: perp_market.orderbook,
bids: perp_market.bids,
asks: perp_market.asks,
event_queue: perp_market.event_queue,
token_program: Token::id(),
sol_destination: self.sol_destination,
@ -2701,7 +2707,8 @@ impl ClientInstruction for PerpPlaceOrderInstruction {
group: account.fixed.group,
account: self.account,
perp_market: self.perp_market,
orderbook: perp_market.orderbook,
bids: perp_market.bids,
asks: perp_market.asks,
event_queue: perp_market.event_queue,
oracle: perp_market.oracle,
owner: self.owner.pubkey(),
@ -2769,7 +2776,8 @@ impl ClientInstruction for PerpPlaceOrderPeggedInstruction {
group: account.fixed.group,
account: self.account,
perp_market: self.perp_market,
orderbook: perp_market.orderbook,
bids: perp_market.bids,
asks: perp_market.asks,
event_queue: perp_market.event_queue,
oracle: perp_market.oracle,
owner: self.owner.pubkey(),
@ -2808,7 +2816,8 @@ impl ClientInstruction for PerpCancelOrderInstruction {
group: perp_market.group,
account: self.account,
perp_market: self.perp_market,
orderbook: perp_market.orderbook,
bids: perp_market.bids,
asks: perp_market.asks,
owner: self.owner.pubkey(),
};
@ -2844,7 +2853,8 @@ impl ClientInstruction for PerpCancelOrderByClientOrderIdInstruction {
group: perp_market.group,
account: self.account,
perp_market: self.perp_market,
orderbook: perp_market.orderbook,
bids: perp_market.bids,
asks: perp_market.asks,
owner: self.owner.pubkey(),
};
@ -2877,7 +2887,8 @@ impl ClientInstruction for PerpCancelAllOrdersInstruction {
group: perp_market.group,
account: self.account,
perp_market: self.perp_market,
orderbook: perp_market.orderbook,
bids: perp_market.bids,
asks: perp_market.asks,
owner: self.owner.pubkey(),
};
@ -2947,7 +2958,8 @@ impl ClientInstruction for PerpUpdateFundingInstruction {
let accounts = Self::Accounts {
group: perp_market.group,
perp_market: self.perp_market,
orderbook: perp_market.orderbook,
bids: perp_market.bids,
asks: perp_market.asks,
oracle: self.oracle,
};
@ -3109,7 +3121,8 @@ impl ClientInstruction for PerpLiqForceCancelOrdersInstruction {
group: account.fixed.group,
perp_market: self.perp_market,
account: self.account,
orderbook: perp_market.orderbook,
bids: perp_market.bids,
asks: perp_market.asks,
};
let mut instruction = make_instruction(program_id, &accounts, instruction);
instruction.accounts.extend(health_check_metas);

View File

@ -66,9 +66,7 @@ async fn test_perp_fixed() -> Result<(), TransportError> {
// TEST: Create a perp market
//
let mango_v4::accounts::PerpCreateMarket {
perp_market,
orderbook,
..
perp_market, bids, ..
} = send_tx(
solana,
PerpCreateMarketInstruction {
@ -118,8 +116,8 @@ async fn test_perp_fixed() -> Result<(), TransportError> {
.unwrap();
check_prev_instruction_post_health(&solana, account_0).await;
let orderbook_data = solana.get_account_boxed::<Orderbook>(orderbook).await;
assert_eq!(orderbook_data.bids.roots[0].leaf_count, 1);
let bids_data = solana.get_account_boxed::<BookSide>(bids).await;
assert_eq!(bids_data.roots[0].leaf_count, 1);
let order_id_to_cancel = solana
.get_account::<MangoAccount>(account_0)
.await
@ -504,9 +502,7 @@ async fn test_perp_oracle_peg() -> Result<(), TransportError> {
// SETUP: Create a perp market
//
let mango_v4::accounts::PerpCreateMarket {
perp_market,
orderbook,
..
perp_market, bids, ..
} = send_tx(
solana,
PerpCreateMarketInstruction {
@ -558,8 +554,8 @@ async fn test_perp_oracle_peg() -> Result<(), TransportError> {
.unwrap();
check_prev_instruction_post_health(&solana, account_0).await;
let orderbook_data = solana.get_account_boxed::<Orderbook>(orderbook).await;
assert_eq!(orderbook_data.bids.roots[1].leaf_count, 1);
let bids_data = solana.get_account_boxed::<BookSide>(bids).await;
assert_eq!(bids_data.roots[1].leaf_count, 1);
let perp_order = solana
.get_account::<MangoAccount>(account_0)
.await

View File

@ -38,7 +38,8 @@ export class PerpMarket {
private baseLotsToUiConverter: number;
private quoteLotsToUiConverter: number;
private _orderbook: Orderbook;
private _bids: BookSide;
private _asks: BookSide;
static from(
publicKey: PublicKey,
@ -50,7 +51,8 @@ export class PerpMarket {
groupInsuranceFund: number;
baseDecimals: number;
name: number[];
orderbook: PublicKey;
bids: PublicKey;
asks: PublicKey;
eventQueue: PublicKey;
oracle: PublicKey;
oracleConfig: OracleConfig;
@ -92,7 +94,8 @@ export class PerpMarket {
obj.groupInsuranceFund == 1,
obj.baseDecimals,
obj.name,
obj.orderbook,
obj.bids,
obj.asks,
obj.eventQueue,
obj.oracle,
obj.oracleConfig,
@ -135,7 +138,8 @@ export class PerpMarket {
public groupInsuranceFund: boolean,
public baseDecimals: number,
name: number[],
public orderbook: PublicKey,
public bids: PublicKey,
public asks: PublicKey,
public eventQueue: PublicKey,
public oracle: PublicKey,
oracleConfig: OracleConfig,
@ -223,25 +227,26 @@ export class PerpMarket {
return this.priceLotsToUiConverter;
}
private async loadOrderbook(
public async loadAsks(
client: MangoClient,
forceReload = false,
): Promise<Orderbook> {
if (forceReload || !this._orderbook)
this._orderbook = await client.program.account.orderbook.fetch(
this.orderbook,
);
return this._orderbook;
): Promise<BookSide> {
if (forceReload || !this._asks) {
const asks = await client.program.account.bookSide.fetch(this.asks);
this._asks = BookSide.from(client, this, BookSideType.asks, asks as any);
}
return this._asks;
}
public async loadAsks(client: MangoClient): Promise<BookSide> {
await this.loadOrderbook(client);
return BookSide.from(client, this, BookSideType.asks, this._orderbook.asks);
}
public async loadBids(client: MangoClient): Promise<BookSide> {
await this.loadOrderbook(client);
return BookSide.from(client, this, BookSideType.bids, this._orderbook.bids);
public async loadBids(
client: MangoClient,
forceReload = false,
): Promise<BookSide> {
if (forceReload || !this._bids) {
const bids = await client.program.account.bookSide.fetch(this.bids);
this._bids = BookSide.from(client, this, BookSideType.bids, bids as any);
}
return this._bids;
}
public async loadEventQueue(client: MangoClient): Promise<PerpEventQueue> {
@ -452,28 +457,16 @@ export class PerpMarket {
}
}
interface Orderbook {
bids: OrderTree;
asks: OrderTree;
interface OrderTreeNodes {
bumpIndex: number;
freeListLen: number;
freeListHead: number;
nodes: [any];
}
interface OrderTree {
fixed: {
bumpIndex: number;
freeListLen: number;
freeListHead: number;
rootNode: number;
leafCount: number;
nodes: [any];
};
oraclePegged: {
bumpIndex: number;
freeListLen: number;
freeListHead: number;
rootNode: number;
leafCount: number;
nodes: [any];
};
interface OrderTreeRoot {
maybeNode: number;
leafCount: number;
}
export class BookSide {
@ -486,59 +479,34 @@ export class BookSide {
perpMarket: PerpMarket,
bookSideType: BookSideType,
obj: {
fixed: {
bumpIndex: number;
freeListLen: number;
freeListHead: number;
rootNode: number;
leafCount: number;
nodes: [any];
};
oraclePegged: {
bumpIndex: number;
freeListLen: number;
freeListHead: number;
rootNode: number;
leafCount: number;
nodes: [any];
};
roots: OrderTreeRoot[];
orderTree: OrderTreeNodes;
},
): BookSide {
return new BookSide(client, perpMarket, bookSideType, obj);
return new BookSide(
client,
perpMarket,
bookSideType,
obj.roots[0],
obj.roots[1],
obj.orderTree,
);
}
constructor(
public client: MangoClient,
public perpMarket: PerpMarket,
public type: BookSideType,
public orderTree: {
fixed: {
bumpIndex: number;
freeListLen: number;
freeListHead: number;
rootNode: number;
leafCount: number;
nodes: [any];
};
oraclePegged: {
bumpIndex: number;
freeListLen: number;
freeListHead: number;
rootNode: number;
leafCount: number;
nodes: [any];
};
},
public rootFixed: OrderTreeRoot,
public rootOraclePegged: OrderTreeRoot,
public orderTree: OrderTreeNodes,
maxBookDelay?: number,
) {
// Determine the maxTimestamp found on the book to use for tif
// If maxBookDelay is not provided, use 3600 as a very large number
maxBookDelay = maxBookDelay === undefined ? 3600 : maxBookDelay;
let maxTimestamp = new BN(new Date().getTime() / 1000 - maxBookDelay);
for (const node of [
...this.orderTree.fixed.nodes,
...this.orderTree.oraclePegged.nodes,
]) {
for (const node of this.orderTree.nodes) {
if (node.tag !== BookSide.LEAF_NODE_TAG) {
continue;
}
@ -624,16 +592,16 @@ export class BookSide {
}
public *fixedItems(): Generator<PerpOrder> {
if (this.orderTree.fixed.leafCount === 0) {
if (this.rootFixed.leafCount === 0) {
return;
}
const now = this.now;
const stack = [this.orderTree.fixed.rootNode];
const stack = [this.rootFixed.maybeNode];
const [left, right] = this.type === BookSideType.bids ? [1, 0] : [0, 1];
while (stack.length > 0) {
const index = stack.pop()!;
const node = this.orderTree.fixed.nodes[index];
const node = this.orderTree.nodes[index];
if (node.tag === BookSide.INNER_NODE_TAG) {
const innerNode = BookSide.toInnerNode(this.client, node.data);
stack.push(innerNode.children[right], innerNode.children[left]);
@ -654,16 +622,16 @@ export class BookSide {
}
public *oraclePeggedItems(): Generator<PerpOrder> {
if (this.orderTree.oraclePegged.leafCount === 0) {
if (this.rootOraclePegged.leafCount === 0) {
return;
}
const now = this.now;
const stack = [this.orderTree.oraclePegged.rootNode];
const stack = [this.rootOraclePegged.maybeNode];
const [left, right] = this.type === BookSideType.bids ? [1, 0] : [0, 1];
while (stack.length > 0) {
const index = stack.pop()!;
const node = this.orderTree.oraclePegged.nodes[index];
const node = this.orderTree.nodes[index];
if (node.tag === BookSide.INNER_NODE_TAG) {
const innerNode = BookSide.toInnerNode(this.client, node.data);
stack.push(innerNode.children[right], innerNode.children[left]);

View File

@ -1399,11 +1399,12 @@ export class MangoClient {
settlePnlLimitFactor: number,
settlePnlLimitWindowSize: number,
): Promise<TransactionSignature> {
const orderbook = new Keypair();
const bids = new Keypair();
const asks = new Keypair();
const eventQueue = new Keypair();
const orderbookSize = (this.program as any)._coder.accounts.size(
(this.program.account.orderbook as any)._idlAccount,
const bookSideSize = (this.program as any)._coder.accounts.size(
(this.program.account.bookSide as any)._idlAccount,
);
const eventQueueSize = (this.program as any)._coder.accounts.size(
(this.program.account.eventQueue as any)._idlAccount,
@ -1441,7 +1442,8 @@ export class MangoClient {
group: group.publicKey,
admin: (this.program.provider as AnchorProvider).wallet.publicKey,
oracle: oraclePk,
orderbook: orderbook.publicKey,
bids: bids.publicKey,
asks: asks.publicKey,
eventQueue: eventQueue.publicKey,
payer: (this.program.provider as AnchorProvider).wallet.publicKey,
})
@ -1449,14 +1451,25 @@ export class MangoClient {
// book sides
SystemProgram.createAccount({
programId: this.program.programId,
space: orderbookSize,
space: bookSideSize,
lamports:
await this.program.provider.connection.getMinimumBalanceForRentExemption(
orderbookSize,
bookSideSize,
),
fromPubkey: (this.program.provider as AnchorProvider).wallet
.publicKey,
newAccountPubkey: orderbook.publicKey,
newAccountPubkey: bids.publicKey,
}),
SystemProgram.createAccount({
programId: this.program.programId,
space: bookSideSize,
lamports:
await this.program.provider.connection.getMinimumBalanceForRentExemption(
bookSideSize,
),
fromPubkey: (this.program.provider as AnchorProvider).wallet
.publicKey,
newAccountPubkey: asks.publicKey,
}),
// event queue
SystemProgram.createAccount({
@ -1471,7 +1484,7 @@ export class MangoClient {
newAccountPubkey: eventQueue.publicKey,
}),
])
.signers([orderbook, eventQueue])
.signers([bids, asks, eventQueue])
.rpc();
}
@ -1555,7 +1568,8 @@ export class MangoClient {
group: group.publicKey,
admin: (this.program.provider as AnchorProvider).wallet.publicKey,
perpMarket: perpMarket.publicKey,
orderbook: perpMarket.orderbook,
bids: perpMarket.bids,
asks: perpMarket.asks,
eventQueue: perpMarket.eventQueue,
solDestination: (this.program.provider as AnchorProvider).wallet
.publicKey,
@ -1693,7 +1707,8 @@ export class MangoClient {
group: group.publicKey,
account: mangoAccount.publicKey,
perpMarket: perpMarket.publicKey,
orderbook: perpMarket.orderbook,
bids: perpMarket.bids,
asks: perpMarket.asks,
eventQueue: perpMarket.eventQueue,
oracle: perpMarket.oracle,
owner: (this.program.provider as AnchorProvider).wallet.publicKey,
@ -1793,7 +1808,8 @@ export class MangoClient {
group: group.publicKey,
account: mangoAccount.publicKey,
perpMarket: perpMarket.publicKey,
orderbook: perpMarket.orderbook,
bids: perpMarket.bids,
asks: perpMarket.asks,
eventQueue: perpMarket.eventQueue,
oracle: perpMarket.oracle,
owner: (this.program.provider as AnchorProvider).wallet.publicKey,
@ -1821,7 +1837,8 @@ export class MangoClient {
account: mangoAccount.publicKey,
owner: (this.program.provider as AnchorProvider).wallet.publicKey,
perpMarket: perpMarket.publicKey,
orderbook: perpMarket.orderbook,
bids: perpMarket.bids,
asks: perpMarket.asks,
})
.instruction();
}
@ -1885,7 +1902,8 @@ export class MangoClient {
group: group.publicKey,
account: mangoAccount.publicKey,
perpMarket: perpMarket.publicKey,
orderbook: perpMarket.orderbook,
bids: perpMarket.bids,
asks: perpMarket.asks,
owner: (this.program.provider as AnchorProvider).wallet.publicKey,
})
.instruction();

View File

@ -2351,7 +2351,7 @@ export type MangoV4 = {
}
},
{
"name": "orderbook",
"name": "bids",
"isMut": true,
"isSigner": false,
"docs": [
@ -2359,6 +2359,11 @@ export type MangoV4 = {
"anchor discriminator is set first when ix exits,"
]
},
{
"name": "asks",
"isMut": true,
"isSigner": false
},
{
"name": "eventQueue",
"isMut": true,
@ -2672,7 +2677,12 @@ export type MangoV4 = {
"isSigner": false
},
{
"name": "orderbook",
"name": "bids",
"isMut": true,
"isSigner": false
},
{
"name": "asks",
"isMut": true,
"isSigner": false
},
@ -2744,7 +2754,12 @@ export type MangoV4 = {
"isSigner": false
},
{
"name": "orderbook",
"name": "bids",
"isMut": true,
"isSigner": false
},
{
"name": "asks",
"isMut": true,
"isSigner": false
},
@ -2826,7 +2841,12 @@ export type MangoV4 = {
"isSigner": false
},
{
"name": "orderbook",
"name": "bids",
"isMut": true,
"isSigner": false
},
{
"name": "asks",
"isMut": true,
"isSigner": false
},
@ -2916,7 +2936,12 @@ export type MangoV4 = {
"isSigner": false
},
{
"name": "orderbook",
"name": "bids",
"isMut": true,
"isSigner": false
},
{
"name": "asks",
"isMut": true,
"isSigner": false
}
@ -2952,7 +2977,12 @@ export type MangoV4 = {
"isSigner": false
},
{
"name": "orderbook",
"name": "bids",
"isMut": true,
"isSigner": false
},
{
"name": "asks",
"isMut": true,
"isSigner": false
}
@ -2988,7 +3018,12 @@ export type MangoV4 = {
"isSigner": false
},
{
"name": "orderbook",
"name": "bids",
"isMut": true,
"isSigner": false
},
{
"name": "asks",
"isMut": true,
"isSigner": false
}
@ -3024,7 +3059,12 @@ export type MangoV4 = {
"isSigner": false
},
{
"name": "orderbook",
"name": "bids",
"isMut": true,
"isSigner": false
},
{
"name": "asks",
"isMut": true,
"isSigner": false
}
@ -3084,7 +3124,12 @@ export type MangoV4 = {
"isSigner": false
},
{
"name": "orderbook",
"name": "bids",
"isMut": true,
"isSigner": false
},
{
"name": "asks",
"isMut": true,
"isSigner": false
},
@ -3248,7 +3293,12 @@ export type MangoV4 = {
"isSigner": false
},
{
"name": "orderbook",
"name": "bids",
"isMut": true,
"isSigner": false
},
{
"name": "asks",
"isMut": true,
"isSigner": false
}
@ -4032,20 +4082,30 @@ export type MangoV4 = {
}
},
{
"name": "orderbook",
"name": "bookSide",
"type": {
"kind": "struct",
"fields": [
{
"name": "bids",
"name": "roots",
"type": {
"defined": "BookSide"
"array": [
{
"defined": "OrderTreeRoot"
},
2
]
}
},
{
"name": "asks",
"name": "reservedRoots",
"type": {
"defined": "BookSide"
"array": [
{
"defined": "OrderTreeRoot"
},
4
]
}
},
{
@ -4053,9 +4113,15 @@ export type MangoV4 = {
"type": {
"array": [
"u8",
2400
256
]
}
},
{
"name": "nodes",
"type": {
"defined": "OrderTreeNodes"
}
}
]
}
@ -4149,7 +4215,11 @@ export type MangoV4 = {
}
},
{
"name": "orderbook",
"name": "bids",
"type": "publicKey"
},
{
"name": "asks",
"type": "publicKey"
},
{
@ -5077,9 +5147,7 @@ export type MangoV4 = {
"fields": [
{
"name": "sideAndTree",
"type": {
"defined": "SideAndOrderTree"
}
"type": "u8"
},
{
"name": "padding1",
@ -5123,6 +5191,81 @@ export type MangoV4 = {
]
}
},
{
"name": "MangoAccountFixed",
"type": {
"kind": "struct",
"fields": [
{
"name": "group",
"type": "publicKey"
},
{
"name": "owner",
"type": "publicKey"
},
{
"name": "name",
"type": {
"array": [
"u8",
32
]
}
},
{
"name": "delegate",
"type": "publicKey"
},
{
"name": "accountNum",
"type": "u32"
},
{
"name": "beingLiquidated",
"type": "u8"
},
{
"name": "inHealthRegion",
"type": "u8"
},
{
"name": "bump",
"type": "u8"
},
{
"name": "padding",
"type": {
"array": [
"u8",
1
]
}
},
{
"name": "netDeposits",
"type": "i64"
},
{
"name": "perpSpotTransfers",
"type": "i64"
},
{
"name": "healthRegionBeginInitHealth",
"type": "i64"
},
{
"name": "reserved",
"type": {
"array": [
"u8",
240
]
}
}
]
}
},
{
"name": "OracleConfig",
"type": {
@ -5168,26 +5311,6 @@ export type MangoV4 = {
]
}
},
{
"name": "BookSide",
"type": {
"kind": "struct",
"fields": [
{
"name": "fixed",
"type": {
"defined": "OrderTree"
}
},
{
"name": "oraclePegged",
"type": {
"defined": "OrderTree"
}
}
]
}
},
{
"name": "InnerNode",
"docs": [
@ -5376,7 +5499,7 @@ export type MangoV4 = {
}
},
{
"name": "OrderTree",
"name": "OrderTreeNodes",
"docs": [
"A binary tree on AnyNode::key()",
"",
@ -5387,9 +5510,7 @@ export type MangoV4 = {
"fields": [
{
"name": "orderTreeType",
"type": {
"defined": "OrderTreeType"
}
"type": "u8"
},
{
"name": "padding",
@ -5413,12 +5534,13 @@ export type MangoV4 = {
"type": "u32"
},
{
"name": "rootNode",
"type": "u32"
},
{
"name": "leafCount",
"type": "u32"
"name": "reserved",
"type": {
"array": [
"u8",
512
]
}
},
{
"name": "nodes",
@ -5430,15 +5552,6 @@ export type MangoV4 = {
1024
]
}
},
{
"name": "reserved",
"type": {
"array": [
"u8",
256
]
}
}
]
}
@ -7276,6 +7389,11 @@ export type MangoV4 = {
"code": 6027,
"name": "BankNetBorrowsLimitReached",
"msg": "bank net borrows has reached limit - this is an intermittent error - the limit will reset regularly"
},
{
"code": 6028,
"name": "TokenPositionDoesNotExist",
"msg": "token position does not exist"
}
]
};
@ -9633,7 +9751,7 @@ export const IDL: MangoV4 = {
}
},
{
"name": "orderbook",
"name": "bids",
"isMut": true,
"isSigner": false,
"docs": [
@ -9641,6 +9759,11 @@ export const IDL: MangoV4 = {
"anchor discriminator is set first when ix exits,"
]
},
{
"name": "asks",
"isMut": true,
"isSigner": false
},
{
"name": "eventQueue",
"isMut": true,
@ -9954,7 +10077,12 @@ export const IDL: MangoV4 = {
"isSigner": false
},
{
"name": "orderbook",
"name": "bids",
"isMut": true,
"isSigner": false
},
{
"name": "asks",
"isMut": true,
"isSigner": false
},
@ -10026,7 +10154,12 @@ export const IDL: MangoV4 = {
"isSigner": false
},
{
"name": "orderbook",
"name": "bids",
"isMut": true,
"isSigner": false
},
{
"name": "asks",
"isMut": true,
"isSigner": false
},
@ -10108,7 +10241,12 @@ export const IDL: MangoV4 = {
"isSigner": false
},
{
"name": "orderbook",
"name": "bids",
"isMut": true,
"isSigner": false
},
{
"name": "asks",
"isMut": true,
"isSigner": false
},
@ -10198,7 +10336,12 @@ export const IDL: MangoV4 = {
"isSigner": false
},
{
"name": "orderbook",
"name": "bids",
"isMut": true,
"isSigner": false
},
{
"name": "asks",
"isMut": true,
"isSigner": false
}
@ -10234,7 +10377,12 @@ export const IDL: MangoV4 = {
"isSigner": false
},
{
"name": "orderbook",
"name": "bids",
"isMut": true,
"isSigner": false
},
{
"name": "asks",
"isMut": true,
"isSigner": false
}
@ -10270,7 +10418,12 @@ export const IDL: MangoV4 = {
"isSigner": false
},
{
"name": "orderbook",
"name": "bids",
"isMut": true,
"isSigner": false
},
{
"name": "asks",
"isMut": true,
"isSigner": false
}
@ -10306,7 +10459,12 @@ export const IDL: MangoV4 = {
"isSigner": false
},
{
"name": "orderbook",
"name": "bids",
"isMut": true,
"isSigner": false
},
{
"name": "asks",
"isMut": true,
"isSigner": false
}
@ -10366,7 +10524,12 @@ export const IDL: MangoV4 = {
"isSigner": false
},
{
"name": "orderbook",
"name": "bids",
"isMut": true,
"isSigner": false
},
{
"name": "asks",
"isMut": true,
"isSigner": false
},
@ -10530,7 +10693,12 @@ export const IDL: MangoV4 = {
"isSigner": false
},
{
"name": "orderbook",
"name": "bids",
"isMut": true,
"isSigner": false
},
{
"name": "asks",
"isMut": true,
"isSigner": false
}
@ -11314,20 +11482,30 @@ export const IDL: MangoV4 = {
}
},
{
"name": "orderbook",
"name": "bookSide",
"type": {
"kind": "struct",
"fields": [
{
"name": "bids",
"name": "roots",
"type": {
"defined": "BookSide"
"array": [
{
"defined": "OrderTreeRoot"
},
2
]
}
},
{
"name": "asks",
"name": "reservedRoots",
"type": {
"defined": "BookSide"
"array": [
{
"defined": "OrderTreeRoot"
},
4
]
}
},
{
@ -11335,9 +11513,15 @@ export const IDL: MangoV4 = {
"type": {
"array": [
"u8",
2400
256
]
}
},
{
"name": "nodes",
"type": {
"defined": "OrderTreeNodes"
}
}
]
}
@ -11431,7 +11615,11 @@ export const IDL: MangoV4 = {
}
},
{
"name": "orderbook",
"name": "bids",
"type": "publicKey"
},
{
"name": "asks",
"type": "publicKey"
},
{
@ -12359,9 +12547,7 @@ export const IDL: MangoV4 = {
"fields": [
{
"name": "sideAndTree",
"type": {
"defined": "SideAndOrderTree"
}
"type": "u8"
},
{
"name": "padding1",
@ -12405,6 +12591,81 @@ export const IDL: MangoV4 = {
]
}
},
{
"name": "MangoAccountFixed",
"type": {
"kind": "struct",
"fields": [
{
"name": "group",
"type": "publicKey"
},
{
"name": "owner",
"type": "publicKey"
},
{
"name": "name",
"type": {
"array": [
"u8",
32
]
}
},
{
"name": "delegate",
"type": "publicKey"
},
{
"name": "accountNum",
"type": "u32"
},
{
"name": "beingLiquidated",
"type": "u8"
},
{
"name": "inHealthRegion",
"type": "u8"
},
{
"name": "bump",
"type": "u8"
},
{
"name": "padding",
"type": {
"array": [
"u8",
1
]
}
},
{
"name": "netDeposits",
"type": "i64"
},
{
"name": "perpSpotTransfers",
"type": "i64"
},
{
"name": "healthRegionBeginInitHealth",
"type": "i64"
},
{
"name": "reserved",
"type": {
"array": [
"u8",
240
]
}
}
]
}
},
{
"name": "OracleConfig",
"type": {
@ -12450,26 +12711,6 @@ export const IDL: MangoV4 = {
]
}
},
{
"name": "BookSide",
"type": {
"kind": "struct",
"fields": [
{
"name": "fixed",
"type": {
"defined": "OrderTree"
}
},
{
"name": "oraclePegged",
"type": {
"defined": "OrderTree"
}
}
]
}
},
{
"name": "InnerNode",
"docs": [
@ -12658,7 +12899,7 @@ export const IDL: MangoV4 = {
}
},
{
"name": "OrderTree",
"name": "OrderTreeNodes",
"docs": [
"A binary tree on AnyNode::key()",
"",
@ -12669,9 +12910,7 @@ export const IDL: MangoV4 = {
"fields": [
{
"name": "orderTreeType",
"type": {
"defined": "OrderTreeType"
}
"type": "u8"
},
{
"name": "padding",
@ -12695,12 +12934,13 @@ export const IDL: MangoV4 = {
"type": "u32"
},
{
"name": "rootNode",
"type": "u32"
},
{
"name": "leafCount",
"type": "u32"
"name": "reserved",
"type": {
"array": [
"u8",
512
]
}
},
{
"name": "nodes",
@ -12712,15 +12952,6 @@ export const IDL: MangoV4 = {
1024
]
}
},
{
"name": "reserved",
"type": {
"array": [
"u8",
256
]
}
}
]
}
@ -14558,6 +14789,11 @@ export const IDL: MangoV4 = {
"code": 6027,
"name": "BankNetBorrowsLimitReached",
"msg": "bank net borrows has reached limit - this is an intermittent error - the limit will reset regularly"
},
{
"code": 6028,
"name": "TokenPositionDoesNotExist",
"msg": "token position does not exist"
}
]
};

View File

@ -648,7 +648,8 @@ async function createAndPopulateAlt() {
.map((perpMarket) => [
perpMarket.publicKey,
perpMarket.oracle,
perpMarket.orderbook,
perpMarket.bids,
perpMarket.asks,
perpMarket.eventQueue,
])
.flat();