diff --git a/programs/mango-v4/src/instructions/create_perp_market.rs b/programs/mango-v4/src/instructions/create_perp_market.rs index 958021e0a..58f267ae7 100644 --- a/programs/mango-v4/src/instructions/create_perp_market.rs +++ b/programs/mango-v4/src/instructions/create_perp_market.rs @@ -30,6 +30,8 @@ pub struct CreatePerpMarket<'info> { #[account(zero)] pub asks: AccountLoader<'info, BookSide>, + pub event_queue: UncheckedAccount<'info>, + #[account(mut)] pub payer: Signer<'info>, @@ -50,6 +52,7 @@ pub fn create_perp_market( oracle: ctx.accounts.oracle.key(), bids: ctx.accounts.bids.key(), asks: ctx.accounts.asks.key(), + event_queue: ctx.accounts.event_queue.key(), quote_lot_size, base_lot_size, seq_num: 0, @@ -65,5 +68,7 @@ pub fn create_perp_market( let mut asks = ctx.accounts.asks.load_init()?; asks.book_side_type = BookSideType::Asks; + // TODO: discriminator on event queue + Ok(()) } diff --git a/programs/mango-v4/src/instructions/deposit.rs b/programs/mango-v4/src/instructions/deposit.rs index 5f894dede..5afbc75d8 100644 --- a/programs/mango-v4/src/instructions/deposit.rs +++ b/programs/mango-v4/src/instructions/deposit.rs @@ -70,7 +70,7 @@ pub fn deposit(ctx: Context, amount: u64) -> Result<()> { // TODO: This will be used to disable is_bankrupt or being_liquidated // when health recovers sufficiently // - let health = compute_health(&account, ctx.remaining_accounts)?; + let health = compute_health_from_fixed_accounts(&account, ctx.remaining_accounts)?; msg!("health: {}", health); // diff --git a/programs/mango-v4/src/instructions/liq_token_with_token.rs b/programs/mango-v4/src/instructions/liq_token_with_token.rs new file mode 100644 index 000000000..4b33d28d4 --- /dev/null +++ b/programs/mango-v4/src/instructions/liq_token_with_token.rs @@ -0,0 +1,49 @@ +use anchor_lang::prelude::*; + +use crate::state::*; + +#[derive(Accounts)] +pub struct LiqTokenWithToken<'info> { + pub group: AccountLoader<'info, Group>, + + #[account( + mut, + has_one = group, + constraint = liqor.load()?.owner == liqor_owner.key(), + )] + pub liqor: AccountLoader<'info, MangoAccount>, + pub liqor_owner: Signer<'info>, + + #[account( + mut, + has_one = group, + )] + pub liqee: AccountLoader<'info, MangoAccount>, + + // TODO: these banks are also passed in remainingAccounts + #[account( + mut, + has_one = group, + constraint = asset_bank.load()?.token_index != liab_bank.load()?.token_index, + )] + pub asset_bank: AccountLoader<'info, Bank>, + + #[account( + mut, + has_one = group, + )] + pub liab_bank: AccountLoader<'info, Bank>, +} + +pub fn liq_token_with_token(ctx: Context) -> Result<()> { + // + // Health computation + // + let liqee = ctx.accounts.liqee.load()?; + let health = compute_health_by_scanning_accounts(&liqee, ctx.remaining_accounts)?; + msg!("health: {}", health); + + // TODO: everything + + Ok(()) +} diff --git a/programs/mango-v4/src/instructions/margin_trade.rs b/programs/mango-v4/src/instructions/margin_trade.rs index 9e9bccfa8..2cda330a9 100644 --- a/programs/mango-v4/src/instructions/margin_trade.rs +++ b/programs/mango-v4/src/instructions/margin_trade.rs @@ -1,5 +1,5 @@ use crate::error::MangoError; -use crate::state::{compute_health, Bank, Group, MangoAccount}; +use crate::state::{compute_health_from_fixed_accounts, Bank, Group, MangoAccount}; use crate::{group_seeds, Mango}; use anchor_lang::prelude::*; use anchor_spl::token::TokenAccount; @@ -94,7 +94,7 @@ pub fn margin_trade<'key, 'accounts, 'remaining, 'info>( } // compute pre cpi health - let pre_cpi_health = compute_health(&account, health_ais)?; + let pre_cpi_health = compute_health_from_fixed_accounts(&account, health_ais)?; require!(pre_cpi_health > 0, MangoError::HealthMustBePositive); msg!("pre_cpi_health {:?}", pre_cpi_health); @@ -118,7 +118,7 @@ pub fn margin_trade<'key, 'accounts, 'remaining, 'info>( // compute post cpi health // todo: this is not working, the health is computed on old bank state and not taking into account // withdraws done in adjust_for_post_cpi_token_amounts - let post_cpi_health = compute_health(&account, health_ais)?; + let post_cpi_health = compute_health_from_fixed_accounts(&account, health_ais)?; require!(post_cpi_health > 0, MangoError::HealthMustBePositive); msg!("post_cpi_health {:?}", post_cpi_health); diff --git a/programs/mango-v4/src/instructions/mod.rs b/programs/mango-v4/src/instructions/mod.rs index f012de8c7..42984b2da 100644 --- a/programs/mango-v4/src/instructions/mod.rs +++ b/programs/mango-v4/src/instructions/mod.rs @@ -4,6 +4,7 @@ pub use create_group::*; pub use create_perp_market::*; pub use create_stub_oracle::*; pub use deposit::*; +pub use liq_token_with_token::*; pub use place_perp_order::*; pub use register_token::*; pub use serum3_cancel_order::*; @@ -20,6 +21,7 @@ mod create_group; mod create_perp_market; mod create_stub_oracle; mod deposit; +mod liq_token_with_token; mod margin_trade; mod place_perp_order; mod register_token; diff --git a/programs/mango-v4/src/instructions/place_perp_order.rs b/programs/mango-v4/src/instructions/place_perp_order.rs index b318e37a9..c3b0d7999 100644 --- a/programs/mango-v4/src/instructions/place_perp_order.rs +++ b/programs/mango-v4/src/instructions/place_perp_order.rs @@ -1,6 +1,6 @@ use anchor_lang::prelude::*; -use crate::state::{Book, BookSide, Group, MangoAccount, OrderType, PerpMarket}; +use crate::state::{Book, BookSide, EventQueue, Group, MangoAccount, OrderType, PerpMarket}; #[derive(Accounts)] pub struct PlacePerpOrder<'info> { @@ -25,6 +25,7 @@ pub struct PlacePerpOrder<'info> { pub asks: AccountLoader<'info, BookSide>, #[account(mut)] pub bids: AccountLoader<'info, BookSide>, + pub event_queue: UncheckedAccount<'info>, pub oracle: UncheckedAccount<'info>, pub owner: Signer<'info>, @@ -50,6 +51,10 @@ pub fn place_perp_order( let asks = &ctx.accounts.asks.to_account_info(); let mut book = Book::load_checked(bids, asks, &perp_market)?; + let event_queue_ai = &ctx.accounts.event_queue.to_account_info(); + let mut event_queue = + EventQueue::load_mut_checked(event_queue_ai, ctx.program_id, &perp_market)?; + // let oracle_price = oracle_price(&ctx.accounts.oracle.to_account_info())?; let now_ts = Clock::get()?.unix_timestamp as u64; @@ -70,6 +75,7 @@ pub fn place_perp_order( // TODO reduce_only based on event queue book.new_bid( + &mut event_queue, &mut perp_market, // oracle_price, // &mut account, diff --git a/programs/mango-v4/src/instructions/serum3_liq_force_cancel_orders.rs b/programs/mango-v4/src/instructions/serum3_liq_force_cancel_orders.rs index 5f17a3238..c649db0be 100644 --- a/programs/mango-v4/src/instructions/serum3_liq_force_cancel_orders.rs +++ b/programs/mango-v4/src/instructions/serum3_liq_force_cancel_orders.rs @@ -101,7 +101,7 @@ pub fn serum3_liq_force_cancel_orders( // TODO: do the correct health / being_liquidated check { let account = ctx.accounts.account.load()?; - let health = compute_health(&account, ctx.remaining_accounts)?; + let health = compute_health_from_fixed_accounts(&account, ctx.remaining_accounts)?; msg!("health: {}", health); require!(health < 0, MangoError::SomeError); } diff --git a/programs/mango-v4/src/instructions/serum3_place_order.rs b/programs/mango-v4/src/instructions/serum3_place_order.rs index 13b1f6dc6..255f42262 100644 --- a/programs/mango-v4/src/instructions/serum3_place_order.rs +++ b/programs/mango-v4/src/instructions/serum3_place_order.rs @@ -234,7 +234,7 @@ pub fn serum3_place_order( // Health check // let account = ctx.accounts.account.load()?; - let health = compute_health(&account, ctx.remaining_accounts)?; + let health = compute_health_from_fixed_accounts(&account, &ctx.remaining_accounts)?; msg!("health: {}", health); require!(health >= 0, MangoError::SomeError); diff --git a/programs/mango-v4/src/instructions/withdraw.rs b/programs/mango-v4/src/instructions/withdraw.rs index 6ad150d1b..78ddfd4de 100644 --- a/programs/mango-v4/src/instructions/withdraw.rs +++ b/programs/mango-v4/src/instructions/withdraw.rs @@ -99,7 +99,7 @@ pub fn withdraw(ctx: Context, amount: u64, allow_borrow: bool) -> Resu // // Health check // - let health = compute_health(&account, ctx.remaining_accounts)?; + let health = compute_health_from_fixed_accounts(&account, &ctx.remaining_accounts)?; msg!("health: {}", health); require!(health >= 0, MangoError::SomeError); diff --git a/programs/mango-v4/src/state/health.rs b/programs/mango-v4/src/state/health.rs index a5d484d36..c4648b784 100644 --- a/programs/mango-v4/src/state/health.rs +++ b/programs/mango-v4/src/state/health.rs @@ -1,25 +1,152 @@ use anchor_lang::prelude::*; use fixed::types::I80F48; +use serum_dex::state::OpenOrders; use std::cell::Ref; use crate::error::MangoError; use crate::serum3_cpi; -use crate::state::{oracle_price, Bank, MangoAccount}; -use crate::util; +use crate::state::{oracle_price, Bank, MangoAccount, TokenIndex}; use crate::util::checked_math as cm; use crate::util::LoadZeroCopy; -pub fn compute_health(account: &MangoAccount, ais: &[AccountInfo]) -> Result { +/// This trait abstracts how to find accounts needed for the health computation. +/// +/// There are different ways they are retrieved from remainingAccounts, based +/// on the instruction: +/// - FixedOrderAccountRetriever requires the remainingAccounts to be in a well +/// defined order and is the fastest. It's used where possible. +/// - ScanningAccountRetriever does a linear scan for each account it needs. +/// It needs more compute, but works when a union of bank/oracle/market accounts +/// are passed because health needs to be computed for different baskets in +/// one instruction (such as for liquidation instructions). +trait AccountRetriever<'a, 'b> { + fn bank_and_oracle( + &self, + group: &Pubkey, + account_index: usize, + token_index: TokenIndex, + ) -> Result<(Ref<'a, Bank>, &'a AccountInfo<'b>)>; + + fn serum_oo(&self, account_index: usize, key: &Pubkey) -> Result>; +} + +/// Assumes the account infos needed for the health computation follow a strict order. +/// +/// 1. n_banks Bank account, in the order of account.token_account_map.iter_active() +/// 2. n_banks oracle accounts, one for each bank in the same order +/// 3. serum3 OpenOrders accounts, in the order of account.serum3_account_map.iter_active() +struct FixedOrderAccountRetriever<'a, 'b> { + ais: &'a [AccountInfo<'b>], + n_banks: usize, +} + +impl<'a, 'b> AccountRetriever<'a, 'b> for FixedOrderAccountRetriever<'a, 'b> { + fn bank_and_oracle( + &self, + group: &Pubkey, + account_index: usize, + token_index: TokenIndex, + ) -> Result<(Ref<'a, Bank>, &'a AccountInfo<'b>)> { + let bank = self.ais[account_index].load::()?; + require!(&bank.group == group, MangoError::SomeError); + require!(bank.token_index == token_index, MangoError::SomeError); + let oracle = &self.ais[self.n_banks + account_index]; + require!(&bank.oracle == oracle.key, MangoError::SomeError); + Ok((bank, oracle)) + } + + fn serum_oo(&self, account_index: usize, key: &Pubkey) -> Result> { + let ai = &self.ais[2 * self.n_banks + account_index]; + require!(key == ai.key, MangoError::SomeError); + serum3_cpi::load_open_orders(ai) + } +} + +/// Takes a list of account infos containing +/// - an unknown number of Banks in any order, followed by +/// - the same number of oracles in the same order as the banks, followed by +/// - an unknown number of serum3 OpenOrders accounts +/// and retrieves accounts needed for the health computation by doing a linear +/// scan for each request. +struct ScanningAccountRetriever<'a, 'b> { + ais: &'a [AccountInfo<'b>], + banks: Vec>, +} + +impl<'a, 'b> ScanningAccountRetriever<'a, 'b> { + fn new(ais: &'a [AccountInfo<'b>]) -> Result { + let mut banks = vec![]; + for ai in ais.iter() { + match ai.load::() { + Ok(bank) => banks.push(bank), + Err(Error::AnchorError(error)) + if error.error_code_number + == ErrorCode::AccountDiscriminatorMismatch as u32 => + { + break; + } + Err(error) => return Err(error), + }; + } + Ok(Self { ais, banks }) + } + + fn n_banks(&self) -> usize { + self.banks.len() + } +} + +impl<'a, 'b> AccountRetriever<'a, 'b> for ScanningAccountRetriever<'a, 'b> { + fn bank_and_oracle( + &self, + group: &Pubkey, + _account_index: usize, + token_index: TokenIndex, + ) -> Result<(Ref<'a, Bank>, &'a AccountInfo<'b>)> { + let (i, bank) = self + .banks + .iter() + .enumerate() + .find(|(_, b)| b.token_index == token_index) + .unwrap(); + require!(&bank.group == group, MangoError::SomeError); + let oracle = &self.ais[self.n_banks() + i]; + require!(&bank.oracle == oracle.key, MangoError::SomeError); + Ok((Ref::clone(bank), oracle)) + } + + fn serum_oo(&self, _account_index: usize, key: &Pubkey) -> Result> { + let oo = self.ais[2 * self.n_banks()..] + .iter() + .find(|ai| ai.key == key) + .unwrap(); + serum3_cpi::load_open_orders(oo) + } +} + +pub fn compute_health_from_fixed_accounts<'a, 'b>( + account: &MangoAccount, + ais: &'a [AccountInfo<'b>], +) -> Result { let active_token_len = account.token_account_map.iter_active().count(); let active_serum_len = account.serum3_account_map.iter_active().count(); let expected_ais = active_token_len * 2 // banks + oracles + active_serum_len; // open_orders require!(ais.len() == expected_ais, MangoError::SomeError); - let banks = &ais[0..active_token_len]; - let oracles = &ais[active_token_len..active_token_len * 2]; - let serum_oos = &ais[active_token_len * 2..]; - compute_health_detail(account, banks, oracles, serum_oos) + let retriever = FixedOrderAccountRetriever { + ais, + n_banks: active_token_len, + }; + compute_health_detail(account, retriever) +} + +pub fn compute_health_by_scanning_accounts<'a, 'b>( + account: &MangoAccount, + ais: &'a [AccountInfo<'b>], +) -> Result { + let retriever = ScanningAccountRetriever::new(ais)?; + compute_health_detail(account, retriever) } struct TokenInfo<'a> { @@ -84,57 +211,34 @@ fn pair_health( Ok(cm!(health1 + health2)) } -fn compute_health_detail( +fn compute_health_detail<'a, 'b: 'a>( account: &MangoAccount, - banks: &[AccountInfo], - oracles: &[AccountInfo], - serum_oos: &[AccountInfo], + retriever: impl AccountRetriever<'a, 'b>, ) -> Result { - // collect the bank and oracle data once - let mut token_infos = util::zip!(banks.iter(), oracles.iter()) - .map(|(bank_ai, oracle_ai)| { - let bank = bank_ai.load::()?; - require!(bank.group == account.group, MangoError::SomeError); - require!(bank.oracle == oracle_ai.key(), MangoError::UnexpectedOracle); - let oracle_price = oracle_price(oracle_ai)?; - Ok(TokenInfo { - bank, - oracle_price, - balance: I80F48::ZERO, - price_asset_cache: I80F48::ZERO, - price_liab_cache: I80F48::ZERO, - price_inv_cache: I80F48::ZERO, - }) - }) - .collect::>>()?; - // token contribution from token accounts - for (position, token_info) in util::zip!( - account.token_account_map.iter_active(), - token_infos.iter_mut() - ) { - let bank = &token_info.bank; - // This assumes banks are passed in order - require!( - bank.token_index == position.token_index, - MangoError::SomeError - ); + let mut token_infos = vec![]; + for (i, position) in account.token_account_map.iter_active().enumerate() { + let (bank, oracle_ai) = + retriever.bank_and_oracle(&account.group, i, position.token_index)?; + let oracle_price = oracle_price(oracle_ai)?; // converts the token value to the basis token value for health computations // TODO: health basis token == USDC? - let native = position.native(bank); - token_info.balance = cm!(token_info.balance + native); + let native = position.native(&bank); + + token_infos.push(TokenInfo { + bank, + oracle_price, + balance: native, + price_asset_cache: I80F48::ZERO, + price_liab_cache: I80F48::ZERO, + price_inv_cache: I80F48::ZERO, + }); } // token contribution from serum accounts - for (serum_account, oo_ai) in - util::zip!(account.serum3_account_map.iter_active(), serum_oos.iter()) - { - // This assumes serum open orders are passed in order - require!( - &serum_account.open_orders == oo_ai.key, - MangoError::SomeError - ); + for (i, serum_account) in account.serum3_account_map.iter_active().enumerate() { + let oo = retriever.serum_oo(i, &serum_account.open_orders)?; // find the TokenInfos for the market's base and quote tokens let base_index = token_infos @@ -153,8 +257,6 @@ fn compute_health_detail( (&mut r[0], &mut l[quote_index]) }; - let oo = serum3_cpi::load_open_orders(oo_ai)?; - // add the amounts that are freely settleable let base_free = I80F48::from_num(oo.native_coin_free); let quote_free = I80F48::from_num(cm!(oo.native_pc_free + oo.referrer_rebates_accrued)); diff --git a/programs/mango-v4/src/state/mod.rs b/programs/mango-v4/src/state/mod.rs index 746559e72..b7f1531bd 100644 --- a/programs/mango-v4/src/state/mod.rs +++ b/programs/mango-v4/src/state/mod.rs @@ -14,6 +14,6 @@ mod health; mod mango_account; mod mint_info; mod oracle; -pub mod orderbook; +mod orderbook; mod perp_market; mod serum3_market; diff --git a/programs/mango-v4/src/state/orderbook/book.rs b/programs/mango-v4/src/state/orderbook/book.rs index 4a281c92d..afa2c88c1 100644 --- a/programs/mango-v4/src/state/orderbook/book.rs +++ b/programs/mango-v4/src/state/orderbook/book.rs @@ -4,10 +4,11 @@ use crate::{ error::MangoError, state::{ orderbook::{bookside::BookSide, nodes::LeafNode}, - PerpMarket, + EventQueue, OutEvent, PerpMarket, }, }; use anchor_lang::prelude::*; +use bytemuck::cast; use fixed::types::I80F48; use fixed_macro::types::I80F48; @@ -340,7 +341,7 @@ impl<'a> Book<'a> { // mango_group: &MangoGroup, // mango_group_pk: &Pubkey, // mango_cache: &MangoCache, - // event_queue: &mut EventQueue, + event_queue: &mut EventQueue, market: &mut PerpMarket, // oracle_price: I80F48, // mango_account: &mut MangoAccount, @@ -404,16 +405,16 @@ impl<'a> Book<'a> { // Remove the order from the book unless we've done that enough if number_of_dropped_expired_orders < DROP_EXPIRED_ORDER_LIMIT { number_of_dropped_expired_orders += 1; - // let event = OutEvent::new( - // Side::Ask, - // best_ask.owner_slot, - // now_ts, - // event_queue.header.seq_num, - // best_ask.owner, - // best_ask.quantity, - // ); - // event_queue.push_back(cast(event)).unwrap(); - // ask_deletes.push(best_ask.key); + let event = OutEvent::new( + Side::Ask, + best_ask.owner_slot, + now_ts, + event_queue.header.seq_num, + best_ask.owner, + best_ask.quantity, + ); + event_queue.push_back(cast(event)).unwrap(); + ask_deletes.push(best_ask.key); } continue; } diff --git a/programs/mango-v4/src/state/orderbook/mod.rs b/programs/mango-v4/src/state/orderbook/mod.rs index df02a29f7..98c287656 100644 --- a/programs/mango-v4/src/state/orderbook/mod.rs +++ b/programs/mango-v4/src/state/orderbook/mod.rs @@ -6,7 +6,6 @@ pub use metadata::*; pub use nodes::*; pub use ob_utils::*; pub use order_type::*; -pub use order_type::*; pub use queue::*; pub mod book; diff --git a/programs/mango-v4/src/state/orderbook/queue.rs b/programs/mango-v4/src/state/orderbook/queue.rs index 39d90d897..328273acf 100644 --- a/programs/mango-v4/src/state/orderbook/queue.rs +++ b/programs/mango-v4/src/state/orderbook/queue.rs @@ -1,437 +1,439 @@ -// use std::cell::RefMut; -// use std::mem::size_of; +use std::cell::RefMut; +use std::mem::size_of; -// use crate::error::MangoError; -// use crate::state::PerpMarket; -// use anchor_lang::prelude::*; -// use fixed::types::I80F48; -// use num_enum::{IntoPrimitive, TryFromPrimitive}; -// use solana_program::account_info::AccountInfo; -// use solana_program::pubkey::Pubkey; -// use solana_program::sysvar::rent::Rent; -// use static_assertions::const_assert_eq; +use crate::error::MangoError; +use crate::state::PerpMarket; +use anchor_lang::prelude::*; +use fixed::types::I80F48; +use num_enum::{IntoPrimitive, TryFromPrimitive}; +use solana_program::account_info::AccountInfo; +use solana_program::pubkey::Pubkey; +use solana_program::sysvar::rent::Rent; +use static_assertions::const_assert_eq; -// // use mango_logs::FillLog; -// use mango_macro::Pod; +// use mango_logs::FillLog; +use mango_macro::Pod; -// use super::metadata::MetaData; -// use super::ob_utils::strip_header_mut; -// use super::order_type::Side; -// // use safe_transmute::{self, trivial::TriviallyTransmutable}; +use super::metadata::MetaData; +use super::ob_utils::strip_header_mut; +use super::order_type::Side; +// use safe_transmute::{self, trivial::TriviallyTransmutable}; -// // use crate::error::{check_assert, MangoErrorCode, MangoResult, SourceFileId}; -// // use crate::matching::Side; -// // use crate::state::{DataType, MetaData, PerpMarket}; -// // use crate::utils::strip_header_mut; +// use crate::error::{check_assert, MangoErrorCode, MangoResult, SourceFileId}; +// use crate::matching::Side; +// use crate::state::{DataType, MetaData, PerpMarket}; +// use crate::utils::strip_header_mut; -// // Don't want event queue to become single threaded if it's logging liquidations -// // Most common scenario will be liqors depositing USDC and withdrawing some other token -// // So tying it to token deposited is not wise -// // also can't tie it to token withdrawn because during bull market, liqs will be depositing all base tokens and withdrawing quote -// // +// Don't want event queue to become single threaded if it's logging liquidations +// Most common scenario will be liqors depositing USDC and withdrawing some other token +// So tying it to token deposited is not wise +// also can't tie it to token withdrawn because during bull market, liqs will be depositing all base tokens and withdrawing quote +// -// pub trait QueueHeader: bytemuck::Pod { -// type Item: bytemuck::Pod + Copy; +pub const MAX_NUM_EVENTS: usize = 512; -// fn head(&self) -> usize; -// fn set_head(&mut self, value: usize); -// fn count(&self) -> usize; -// fn set_count(&mut self, value: usize); +pub trait QueueHeader: bytemuck::Pod { + type Item: bytemuck::Pod + Copy; -// fn incr_event_id(&mut self); -// fn decr_event_id(&mut self, n: usize); -// } + fn head(&self) -> usize; + fn set_head(&mut self, value: usize); + fn count(&self) -> usize; + fn set_count(&mut self, value: usize); -// pub struct Queue<'a, H: QueueHeader> { -// pub header: RefMut<'a, H>, -// pub buf: RefMut<'a, [H::Item]>, -// } + fn incr_event_id(&mut self); + fn decr_event_id(&mut self, n: usize); +} -// impl<'a, H: QueueHeader> Queue<'a, H> { -// pub fn new(header: RefMut<'a, H>, buf: RefMut<'a, [H::Item]>) -> Self { -// Self { header, buf } -// } +pub struct Queue<'a, H: QueueHeader> { + pub header: RefMut<'a, H>, + pub buf: RefMut<'a, [H::Item]>, +} -// pub fn load_mut(account: &'a AccountInfo) -> Result { -// let (header, buf) = strip_header_mut::(account)?; -// Ok(Self { header, buf }) -// } +impl<'a, H: QueueHeader> Queue<'a, H> { + pub fn new(header: RefMut<'a, H>, buf: RefMut<'a, [H::Item]>) -> Self { + Self { header, buf } + } -// pub fn len(&self) -> usize { -// self.header.count() -// } + pub fn load_mut(account: &'a AccountInfo) -> Result { + let (header, buf) = strip_header_mut::(account)?; + Ok(Self { header, buf }) + } -// pub fn full(&self) -> bool { -// self.header.count() == self.buf.len() -// } + pub fn len(&self) -> usize { + self.header.count() + } -// pub fn empty(&self) -> bool { -// self.header.count() == 0 -// } + pub fn full(&self) -> bool { + self.header.count() == self.buf.len() + } -// pub fn push_back(&mut self, value: H::Item) -> std::result::Result<(), H::Item> { -// if self.full() { -// return Err(value); -// } -// let slot = (self.header.head() + self.header.count()) % self.buf.len(); -// self.buf[slot] = value; + pub fn empty(&self) -> bool { + self.header.count() == 0 + } -// let count = self.header.count(); -// self.header.set_count(count + 1); + pub fn push_back(&mut self, value: H::Item) -> std::result::Result<(), H::Item> { + if self.full() { + return Err(value); + } + let slot = (self.header.head() + self.header.count()) % self.buf.len(); + self.buf[slot] = value; -// self.header.incr_event_id(); -// Ok(()) -// } + let count = self.header.count(); + self.header.set_count(count + 1); -// pub fn peek_front(&self) -> Option<&H::Item> { -// if self.empty() { -// return None; -// } -// Some(&self.buf[self.header.head()]) -// } + self.header.incr_event_id(); + Ok(()) + } -// pub fn peek_front_mut(&mut self) -> Option<&mut H::Item> { -// if self.empty() { -// return None; -// } -// Some(&mut self.buf[self.header.head()]) -// } + pub fn peek_front(&self) -> Option<&H::Item> { + if self.empty() { + return None; + } + Some(&self.buf[self.header.head()]) + } -// pub fn pop_front(&mut self) -> std::result::Result { -// if self.empty() { -// return Err(()); -// } -// let value = self.buf[self.header.head()]; + pub fn peek_front_mut(&mut self) -> Option<&mut H::Item> { + if self.empty() { + return None; + } + Some(&mut self.buf[self.header.head()]) + } -// let count = self.header.count(); -// self.header.set_count(count - 1); + pub fn pop_front(&mut self) -> std::result::Result { + if self.empty() { + return Err(()); + } + let value = self.buf[self.header.head()]; -// let head = self.header.head(); -// self.header.set_head((head + 1) % self.buf.len()); + let count = self.header.count(); + self.header.set_count(count - 1); -// Ok(value) -// } + let head = self.header.head(); + self.header.set_head((head + 1) % self.buf.len()); -// pub fn revert_pushes(&mut self, desired_len: usize) -> Result<()> { -// require!(desired_len <= self.header.count(), MangoError::SomeError); -// let len_diff = self.header.count() - desired_len; -// self.header.set_count(desired_len); -// self.header.decr_event_id(len_diff); -// Ok(()) -// } + Ok(value) + } -// pub fn iter(&self) -> impl Iterator { -// QueueIterator { -// queue: self, -// index: 0, -// } -// } -// } + pub fn revert_pushes(&mut self, desired_len: usize) -> Result<()> { + require!(desired_len <= self.header.count(), MangoError::SomeError); + let len_diff = self.header.count() - desired_len; + self.header.set_count(desired_len); + self.header.decr_event_id(len_diff); + Ok(()) + } -// struct QueueIterator<'a, 'b, H: QueueHeader> { -// queue: &'b Queue<'a, H>, -// index: usize, -// } + pub fn iter(&self) -> impl Iterator { + QueueIterator { + queue: self, + index: 0, + } + } +} -// impl<'a, 'b, H: QueueHeader> Iterator for QueueIterator<'a, 'b, H> { -// type Item = &'b H::Item; -// fn next(&mut self) -> Option { -// if self.index == self.queue.len() { -// None -// } else { -// let item = -// &self.queue.buf[(self.queue.header.head() + self.index) % self.queue.buf.len()]; -// self.index += 1; -// Some(item) -// } -// } -// } +struct QueueIterator<'a, 'b, H: QueueHeader> { + queue: &'b Queue<'a, H>, + index: usize, +} -// #[account(zero_copy)] -// pub struct EventQueueHeader { -// pub meta_data: MetaData, -// head: usize, -// count: usize, -// pub seq_num: usize, -// } -// // unsafe impl TriviallyTransmutable for EventQueueHeader {} +impl<'a, 'b, H: QueueHeader> Iterator for QueueIterator<'a, 'b, H> { + type Item = &'b H::Item; + fn next(&mut self) -> Option { + if self.index == self.queue.len() { + None + } else { + let item = + &self.queue.buf[(self.queue.header.head() + self.index) % self.queue.buf.len()]; + self.index += 1; + Some(item) + } + } +} -// impl QueueHeader for EventQueueHeader { -// type Item = AnyEvent; +#[account(zero_copy)] +pub struct EventQueueHeader { + pub meta_data: MetaData, + head: usize, + count: usize, + pub seq_num: usize, +} +// unsafe impl TriviallyTransmutable for EventQueueHeader {} -// fn head(&self) -> usize { -// self.head -// } -// fn set_head(&mut self, value: usize) { -// self.head = value; -// } -// fn count(&self) -> usize { -// self.count -// } -// fn set_count(&mut self, value: usize) { -// self.count = value; -// } -// fn incr_event_id(&mut self) { -// self.seq_num += 1; -// } -// fn decr_event_id(&mut self, n: usize) { -// self.seq_num -= n; -// } -// } +impl QueueHeader for EventQueueHeader { + type Item = AnyEvent; -// pub type EventQueue<'a> = Queue<'a, EventQueueHeader>; + fn head(&self) -> usize { + self.head + } + fn set_head(&mut self, value: usize) { + self.head = value; + } + fn count(&self) -> usize { + self.count + } + fn set_count(&mut self, value: usize) { + self.count = value; + } + fn incr_event_id(&mut self) { + self.seq_num += 1; + } + fn decr_event_id(&mut self, n: usize) { + self.seq_num -= n; + } +} -// impl<'a> EventQueue<'a> { -// pub fn load_mut_checked( -// account: &'a AccountInfo, -// program_id: &Pubkey, -// perp_market: &PerpMarket, -// ) -> Result { -// require!(account.owner == program_id, MangoError::SomeError); // MangoErrorCode::InvalidOwner -// // require!( -// // &perp_market.event_queue == account.key, -// // MangoError::SomeError -// // ); // MangoErrorCode::InvalidAccount -// Self::load_mut(account) -// } +pub type EventQueue<'a> = Queue<'a, EventQueueHeader>; -// pub fn load_and_init( -// account: &'a AccountInfo, -// program_id: &Pubkey, -// rent: &Rent, -// ) -> Result { -// // NOTE: check this first so we can borrow account later -// require!( -// rent.is_exempt(account.lamports(), account.data_len()), -// MangoError::SomeError -// ); //MangoErrorCode::AccountNotRentExempt +impl<'a> EventQueue<'a> { + pub fn load_mut_checked( + account: &'a AccountInfo, + program_id: &Pubkey, + perp_market: &PerpMarket, + ) -> Result { + require!(account.owner == program_id, MangoError::SomeError); // InvalidOwner + require!( + &perp_market.event_queue == account.key, + MangoError::SomeError + ); //InvalidAccount + Self::load_mut(account) + } -// let mut state = Self::load_mut(account)?; -// require!(account.owner == program_id, MangoError::SomeError); // MangoErrorCode::InvalidOwner + pub fn load_and_init( + account: &'a AccountInfo, + program_id: &Pubkey, + rent: &Rent, + ) -> Result { + // NOTE: check this first so we can borrow account later + require!( + rent.is_exempt(account.lamports(), account.data_len()), + MangoError::SomeError + ); //MangoErrorCode::AccountNotRentExempt -// // require!( -// // !state.header.meta_data.is_initialized, -// // MangoError::SomeError -// // ); -// // state.header.meta_data = MetaData::new(DataType::EventQueue, 0, true); + let state = Self::load_mut(account)?; + require!(account.owner == program_id, MangoError::SomeError); // MangoErrorCode::InvalidOwner -// Ok(state) -// } -// } + // require!( + // !state.header.meta_data.is_initialized, + // MangoError::SomeError + // ); + // state.header.meta_data = MetaData::new(DataType::EventQueue, 0, true); -// #[derive(Copy, Clone, IntoPrimitive, TryFromPrimitive, Eq, PartialEq)] -// #[repr(u8)] -// pub enum EventType { -// Fill, -// Out, -// Liquidate, -// } + Ok(state) + } +} -// const EVENT_SIZE: usize = 200; -// #[derive(Copy, Clone, Debug, Pod)] -// #[repr(C)] -// pub struct AnyEvent { -// pub event_type: u8, -// pub padding: [u8; EVENT_SIZE - 1], -// } -// // unsafe impl TriviallyTransmutable for AnyEvent {} +#[derive(Copy, Clone, IntoPrimitive, TryFromPrimitive, Eq, PartialEq)] +#[repr(u8)] +pub enum EventType { + Fill, + Out, + Liquidate, +} -// #[derive(Copy, Clone, Debug, Pod)] -// #[repr(C)] -// pub struct FillEvent { -// pub event_type: u8, -// pub taker_side: Side, // side from the taker's POV -// pub maker_slot: u8, -// pub maker_out: bool, // true if maker order quantity == 0 -// pub version: u8, -// pub market_fees_applied: bool, -// pub padding: [u8; 2], -// pub timestamp: u64, -// pub seq_num: usize, // note: usize same as u64 +const EVENT_SIZE: usize = 200; +#[derive(Copy, Clone, Debug, Pod)] +#[repr(C)] +pub struct AnyEvent { + pub event_type: u8, + pub padding: [u8; EVENT_SIZE - 1], +} +// unsafe impl TriviallyTransmutable for AnyEvent {} -// pub maker: Pubkey, -// pub maker_order_id: i128, -// pub maker_client_order_id: u64, -// pub maker_fee: I80F48, +#[derive(Copy, Clone, Debug, Pod)] +#[repr(C)] +pub struct FillEvent { + pub event_type: u8, + pub taker_side: Side, // side from the taker's POV + pub maker_slot: u8, + pub maker_out: bool, // true if maker order quantity == 0 + pub version: u8, + pub market_fees_applied: bool, + pub padding: [u8; 2], + pub timestamp: u64, + pub seq_num: usize, // note: usize same as u64 -// // The best bid/ask at the time the maker order was placed. Used for liquidity incentives -// pub best_initial: i64, + pub maker: Pubkey, + pub maker_order_id: i128, + pub maker_client_order_id: u64, + pub maker_fee: I80F48, -// // Timestamp of when the maker order was placed; copied over from the LeafNode -// pub maker_timestamp: u64, + // The best bid/ask at the time the maker order was placed. Used for liquidity incentives + pub best_initial: i64, -// pub taker: Pubkey, -// pub taker_order_id: i128, -// pub taker_client_order_id: u64, -// pub taker_fee: I80F48, + // Timestamp of when the maker order was placed; copied over from the LeafNode + pub maker_timestamp: u64, -// pub price: i64, -// pub quantity: i64, // number of quote lots -// } -// // unsafe impl TriviallyTransmutable for FillEvent {} + pub taker: Pubkey, + pub taker_order_id: i128, + pub taker_client_order_id: u64, + pub taker_fee: I80F48, -// impl FillEvent { -// pub fn new( -// taker_side: Side, -// maker_slot: u8, -// maker_out: bool, -// timestamp: u64, -// seq_num: usize, -// maker: Pubkey, -// maker_order_id: i128, -// maker_client_order_id: u64, -// maker_fee: I80F48, -// best_initial: i64, -// maker_timestamp: u64, + pub price: i64, + pub quantity: i64, // number of quote lots +} +// unsafe impl TriviallyTransmutable for FillEvent {} -// taker: Pubkey, -// taker_order_id: i128, -// taker_client_order_id: u64, -// taker_fee: I80F48, -// price: i64, -// quantity: i64, -// version: u8, -// ) -> FillEvent { -// Self { -// event_type: EventType::Fill as u8, -// taker_side, -// maker_slot, -// maker_out, -// version, -// market_fees_applied: true, // Since mango v3.3.5, market fees are adjusted at matching time -// padding: [0u8; 2], -// timestamp, -// seq_num, -// maker, -// maker_order_id, -// maker_client_order_id, -// maker_fee, -// best_initial, -// maker_timestamp, -// taker, -// taker_order_id, -// taker_client_order_id, -// taker_fee, -// price, -// quantity, -// } -// } +impl FillEvent { + pub fn new( + taker_side: Side, + maker_slot: u8, + maker_out: bool, + timestamp: u64, + seq_num: usize, + maker: Pubkey, + maker_order_id: i128, + maker_client_order_id: u64, + maker_fee: I80F48, + best_initial: i64, + maker_timestamp: u64, -// pub fn base_quote_change(&self, side: Side) -> (i64, i64) { -// match side { -// Side::Bid => ( -// self.quantity, -// -self.price.checked_mul(self.quantity).unwrap(), -// ), -// Side::Ask => ( -// -self.quantity, -// self.price.checked_mul(self.quantity).unwrap(), -// ), -// } -// } + taker: Pubkey, + taker_order_id: i128, + taker_client_order_id: u64, + taker_fee: I80F48, + price: i64, + quantity: i64, + version: u8, + ) -> FillEvent { + Self { + event_type: EventType::Fill as u8, + taker_side, + maker_slot, + maker_out, + version, + market_fees_applied: true, // Since mango v3.3.5, market fees are adjusted at matching time + padding: [0u8; 2], + timestamp, + seq_num, + maker, + maker_order_id, + maker_client_order_id, + maker_fee, + best_initial, + maker_timestamp, + taker, + taker_order_id, + taker_client_order_id, + taker_fee, + price, + quantity, + } + } -// // pub fn to_fill_log(&self, mango_group: Pubkey, market_index: usize) -> FillLog { -// // FillLog { -// // mango_group, -// // market_index: market_index as u64, -// // taker_side: self.taker_side as u8, -// // maker_slot: self.maker_slot, -// // maker_out: self.maker_out, -// // timestamp: self.timestamp, -// // seq_num: self.seq_num as u64, -// // maker: self.maker, -// // maker_order_id: self.maker_order_id, -// // maker_client_order_id: self.maker_client_order_id, -// // maker_fee: self.maker_fee.to_bits(), -// // best_initial: self.best_initial, -// // maker_timestamp: self.maker_timestamp, -// // taker: self.taker, -// // taker_order_id: self.taker_order_id, -// // taker_client_order_id: self.taker_client_order_id, -// // taker_fee: self.taker_fee.to_bits(), -// // price: self.price, -// // quantity: self.quantity, -// // } -// // } -// } + pub fn base_quote_change(&self, side: Side) -> (i64, i64) { + match side { + Side::Bid => ( + self.quantity, + -self.price.checked_mul(self.quantity).unwrap(), + ), + Side::Ask => ( + -self.quantity, + self.price.checked_mul(self.quantity).unwrap(), + ), + } + } -// #[derive(Copy, Clone, Debug, Pod)] -// #[repr(C)] -// pub struct OutEvent { -// pub event_type: u8, -// pub side: Side, -// pub slot: u8, -// padding0: [u8; 5], -// pub timestamp: u64, -// pub seq_num: usize, -// pub owner: Pubkey, -// pub quantity: i64, -// padding1: [u8; EVENT_SIZE - 64], -// } -// // unsafe impl TriviallyTransmutable for OutEvent {} -// impl OutEvent { -// pub fn new( -// side: Side, -// slot: u8, -// timestamp: u64, -// seq_num: usize, -// owner: Pubkey, -// quantity: i64, -// ) -> Self { -// Self { -// event_type: EventType::Out.into(), -// side, -// slot, -// padding0: [0; 5], -// timestamp, -// seq_num, -// owner, -// quantity, -// padding1: [0; EVENT_SIZE - 64], -// } -// } -// } + // pub fn to_fill_log(&self, mango_group: Pubkey, market_index: usize) -> FillLog { + // FillLog { + // mango_group, + // market_index: market_index as u64, + // taker_side: self.taker_side as u8, + // maker_slot: self.maker_slot, + // maker_out: self.maker_out, + // timestamp: self.timestamp, + // seq_num: self.seq_num as u64, + // maker: self.maker, + // maker_order_id: self.maker_order_id, + // maker_client_order_id: self.maker_client_order_id, + // maker_fee: self.maker_fee.to_bits(), + // best_initial: self.best_initial, + // maker_timestamp: self.maker_timestamp, + // taker: self.taker, + // taker_order_id: self.taker_order_id, + // taker_client_order_id: self.taker_client_order_id, + // taker_fee: self.taker_fee.to_bits(), + // price: self.price, + // quantity: self.quantity, + // } + // } +} -// #[derive(Copy, Clone, Debug, Pod)] -// #[repr(C)] -// /// Liquidation for the PerpMarket this EventQueue is for -// pub struct LiquidateEvent { -// pub event_type: u8, -// padding0: [u8; 7], -// pub timestamp: u64, -// pub seq_num: usize, -// pub liqee: Pubkey, -// pub liqor: Pubkey, -// pub price: I80F48, // oracle price at the time of liquidation -// pub quantity: i64, // number of contracts that were moved from liqee to liqor -// pub liquidation_fee: I80F48, // liq fee for this earned for this market -// padding1: [u8; EVENT_SIZE - 128], -// } -// // unsafe impl TriviallyTransmutable for LiquidateEvent {} -// impl LiquidateEvent { -// pub fn new( -// timestamp: u64, -// seq_num: usize, -// liqee: Pubkey, -// liqor: Pubkey, -// price: I80F48, -// quantity: i64, -// liquidation_fee: I80F48, -// ) -> Self { -// Self { -// event_type: EventType::Liquidate.into(), -// padding0: [0u8; 7], -// timestamp, -// seq_num, -// liqee, -// liqor, -// price, -// quantity, -// liquidation_fee, -// padding1: [0u8; EVENT_SIZE - 128], -// } -// } -// } -// const_assert_eq!(size_of::(), size_of::()); -// const_assert_eq!(size_of::(), size_of::()); -// const_assert_eq!(size_of::(), size_of::()); +#[derive(Copy, Clone, Debug, Pod)] +#[repr(C)] +pub struct OutEvent { + pub event_type: u8, + pub side: Side, + pub slot: u8, + padding0: [u8; 5], + pub timestamp: u64, + pub seq_num: usize, + pub owner: Pubkey, + pub quantity: i64, + padding1: [u8; EVENT_SIZE - 64], +} +// unsafe impl TriviallyTransmutable for OutEvent {} +impl OutEvent { + pub fn new( + side: Side, + slot: u8, + timestamp: u64, + seq_num: usize, + owner: Pubkey, + quantity: i64, + ) -> Self { + Self { + event_type: EventType::Out.into(), + side, + slot, + padding0: [0; 5], + timestamp, + seq_num, + owner, + quantity, + padding1: [0; EVENT_SIZE - 64], + } + } +} + +#[derive(Copy, Clone, Debug, Pod)] +#[repr(C)] +/// Liquidation for the PerpMarket this EventQueue is for +pub struct LiquidateEvent { + pub event_type: u8, + padding0: [u8; 7], + pub timestamp: u64, + pub seq_num: usize, + pub liqee: Pubkey, + pub liqor: Pubkey, + pub price: I80F48, // oracle price at the time of liquidation + pub quantity: i64, // number of contracts that were moved from liqee to liqor + pub liquidation_fee: I80F48, // liq fee for this earned for this market + padding1: [u8; EVENT_SIZE - 128], +} + +impl LiquidateEvent { + pub fn new( + timestamp: u64, + seq_num: usize, + liqee: Pubkey, + liqor: Pubkey, + price: I80F48, + quantity: i64, + liquidation_fee: I80F48, + ) -> Self { + Self { + event_type: EventType::Liquidate.into(), + padding0: [0u8; 7], + timestamp, + seq_num, + liqee, + liqor, + price, + quantity, + liquidation_fee, + padding1: [0u8; EVENT_SIZE - 128], + } + } +} +const_assert_eq!(size_of::(), size_of::()); +const_assert_eq!(size_of::(), size_of::()); +const_assert_eq!(size_of::(), size_of::()); diff --git a/programs/mango-v4/src/state/perp_market.rs b/programs/mango-v4/src/state/perp_market.rs index a28524a10..835a19913 100644 --- a/programs/mango-v4/src/state/perp_market.rs +++ b/programs/mango-v4/src/state/perp_market.rs @@ -5,9 +5,6 @@ use crate::state::TokenIndex; pub type PerpMarketIndex = u16; -#[account(zero_copy)] -pub struct EventQueue {} - #[account(zero_copy)] pub struct PerpMarket { pub group: Pubkey, @@ -17,8 +14,7 @@ pub struct PerpMarket { pub bids: Pubkey, pub asks: Pubkey, - /// Event queue of TODO - /// pub event_queue: Pubkey, + pub event_queue: Pubkey, /// Number of quote native that reresents min tick /// e.g. when base lot size is 100, and quote lot size is 10, then tick i.e. price increment is 10/100 i.e. 0.1 diff --git a/programs/mango-v4/src/util.rs b/programs/mango-v4/src/util.rs index 6f2f656fb..683ab190c 100644 --- a/programs/mango-v4/src/util.rs +++ b/programs/mango-v4/src/util.rs @@ -12,6 +12,7 @@ macro_rules! zip { zip!($($y), +)) ) } +#[allow(unused_imports)] pub(crate) use zip; #[macro_export] diff --git a/programs/mango-v4/tests/program_test/mango_client.rs b/programs/mango-v4/tests/program_test/mango_client.rs index 633ff66e2..73688eb0d 100644 --- a/programs/mango-v4/tests/program_test/mango_client.rs +++ b/programs/mango-v4/tests/program_test/mango_client.rs @@ -1086,6 +1086,7 @@ pub struct CreatePerpMarketInstruction<'keypair> { pub oracle: Pubkey, pub asks: Pubkey, pub bids: Pubkey, + pub event_queue: Pubkey, pub payer: &'keypair Keypair, pub perp_market_index: PerpMarketIndex, pub base_token_index: TokenIndex, @@ -1127,6 +1128,7 @@ impl<'keypair> ClientInstruction for CreatePerpMarketInstruction<'keypair> { perp_market, asks: self.asks, bids: self.bids, + event_queue: self.event_queue, payer: self.payer.pubkey(), system_program: System::id(), }; @@ -1146,6 +1148,7 @@ pub struct PlacePerpOrderInstruction<'keypair> { pub perp_market: Pubkey, pub asks: Pubkey, pub bids: Pubkey, + pub event_queue: Pubkey, pub oracle: Pubkey, pub owner: &'keypair Keypair, } @@ -1173,6 +1176,7 @@ impl<'keypair> ClientInstruction for PlacePerpOrderInstruction<'keypair> { perp_market: self.perp_market, asks: self.asks, bids: self.bids, + event_queue: self.event_queue, oracle: self.oracle, owner: self.owner.pubkey(), }; diff --git a/programs/mango-v4/tests/program_test/solana.rs b/programs/mango-v4/tests/program_test/solana.rs index 49dd4395d..1d08e4700 100644 --- a/programs/mango-v4/tests/program_test/solana.rs +++ b/programs/mango-v4/tests/program_test/solana.rs @@ -83,7 +83,23 @@ impl SolanaCookie { .newest() } - pub async fn create_account(&self, owner: &Pubkey) -> Pubkey { + pub async fn create_account_from_len(&self, owner: &Pubkey, len: usize) -> Pubkey { + let key = Keypair::new(); + let rent = self.rent.minimum_balance(len); + let create_account_instr = solana_sdk::system_instruction::create_account( + &self.context.borrow().payer.pubkey(), + &key.pubkey(), + rent, + len as u64, + &owner, + ); + self.process_transaction(&[create_account_instr], Some(&[&key])) + .await + .unwrap(); + key.pubkey() + } + + pub async fn create_account_for_type(&self, owner: &Pubkey) -> Pubkey { let key = Keypair::new(); let len = 8 + std::mem::size_of::(); let rent = self.rent.minimum_balance(len); diff --git a/programs/mango-v4/tests/test_perp.rs b/programs/mango-v4/tests/test_perp.rs index 736f4e842..88014d4a0 100644 --- a/programs/mango-v4/tests/test_perp.rs +++ b/programs/mango-v4/tests/test_perp.rs @@ -1,6 +1,6 @@ #![cfg(feature = "test-bpf")] -use mango_v4::state::BookSide; +use mango_v4::state::*; use solana_program_test::*; use solana_sdk::{signature::Keypair, transport::TransportError}; @@ -82,6 +82,7 @@ async fn test_perp() -> Result<(), TransportError> { perp_market, asks, bids, + event_queue, .. } = send_tx( solana, @@ -90,12 +91,20 @@ async fn test_perp() -> Result<(), TransportError> { oracle: tokens[0].oracle, asks: context .solana - .create_account::(&mango_v4::id()) + .create_account_for_type::(&mango_v4::id()) .await, bids: context .solana - .create_account::(&mango_v4::id()) + .create_account_for_type::(&mango_v4::id()) .await, + event_queue: { + let len = std::mem::size_of::() + + std::mem::size_of::() * MAX_NUM_EVENTS; + context + .solana + .create_account_from_len(&mango_v4::id(), len) + .await + }, admin, payer, perp_market_index: 0, @@ -117,6 +126,7 @@ async fn test_perp() -> Result<(), TransportError> { perp_market, asks, bids, + event_queue, oracle: tokens[0].oracle, owner, },