From bfcb67211641401533a79ee4fb453b2aa931c4c6 Mon Sep 17 00:00:00 2001 From: Christian Kamm Date: Tue, 22 Mar 2022 10:51:15 +0100 Subject: [PATCH 1/4] Health: allow account order to be fixed, or to scan for accounts The latter is important when we want to compute health for two different accounts. Then the remainingAccounts would hold the union of the necessary accounts, and each health check would need to look for the accounts that it needs. This patch al so reduces the compute needs for fixed-layout health checks significantly (Deposit for an account with 8 tokens and 7 markets takes 65k instead of 70k cu) --- programs/mango-v4/src/instructions/deposit.rs | 2 +- .../mango-v4/src/instructions/margin_trade.rs | 6 +- programs/mango-v4/src/instructions/mod.rs | 2 + .../serum3_liq_force_cancel_orders.rs | 2 +- .../src/instructions/serum3_place_order.rs | 2 +- .../mango-v4/src/instructions/withdraw.rs | 2 +- programs/mango-v4/src/state/health.rs | 202 +++++++++++++----- programs/mango-v4/src/util.rs | 1 + 8 files changed, 162 insertions(+), 57 deletions(-) diff --git a/programs/mango-v4/src/instructions/deposit.rs b/programs/mango-v4/src/instructions/deposit.rs index 701bc046a..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/margin_trade.rs b/programs/mango-v4/src/instructions/margin_trade.rs index e4fb603cd..2d71c4e94 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/serum3_liq_force_cancel_orders.rs b/programs/mango-v4/src/instructions/serum3_liq_force_cancel_orders.rs index e275a897e..b0f815e81 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 b2a185418..e321510e0 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 62d15e2b7..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 3de774bf9..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); + + 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/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] From 696806ee77645a5169567fa2e0a4eb1d7b5ab74f Mon Sep 17 00:00:00 2001 From: Christian Kamm Date: Tue, 22 Mar 2022 19:25:33 +0100 Subject: [PATCH 2/4] Add liq_token_with_token file outline --- .../src/instructions/liq_token_with_token.rs | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 programs/mango-v4/src/instructions/liq_token_with_token.rs 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(()) +} From f7fb9b7e3522de4398d61f18e9449366878f62a6 Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Wed, 23 Mar 2022 07:53:45 +0100 Subject: [PATCH 3/4] add event queue Signed-off-by: microwavedcola1 --- .../src/instructions/create_perp_market.rs | 7 +- .../src/instructions/place_perp_order.rs | 8 +- programs/mango-v4/src/state/mod.rs | 2 +- programs/mango-v4/src/state/orderbook/book.rs | 25 +- programs/mango-v4/src/state/orderbook/mod.rs | 1 - .../mango-v4/src/state/orderbook/queue.rs | 776 +++++++++--------- programs/mango-v4/src/state/perp_market.rs | 6 +- .../tests/program_test/mango_client.rs | 4 + .../mango-v4/tests/program_test/solana.rs | 18 +- programs/mango-v4/tests/test_perp.rs | 16 +- 10 files changed, 451 insertions(+), 412 deletions(-) diff --git a/programs/mango-v4/src/instructions/create_perp_market.rs b/programs/mango-v4/src/instructions/create_perp_market.rs index 958021e0a..b78d717ca 100644 --- a/programs/mango-v4/src/instructions/create_perp_market.rs +++ b/programs/mango-v4/src/instructions/create_perp_market.rs @@ -1,7 +1,7 @@ use anchor_lang::prelude::*; use crate::error::MangoError; -use crate::state::*; +use crate::{mango_v4, state::*}; #[derive(Accounts)] #[instruction(perp_market_index: PerpMarketIndex)] @@ -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/place_perp_order.rs b/programs/mango-v4/src/instructions/place_perp_order.rs index 38f644815..03d05ea5c 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/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 3de1c3a0b..63958c672 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; @@ -339,7 +340,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, @@ -403,16 +404,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..b89a12c12 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 mut 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], +} +// 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::()); 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/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, }, From b0d6a14d00fd3c477e90f5584d987f7070cc936a Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Wed, 23 Mar 2022 08:34:23 +0100 Subject: [PATCH 4/4] fix clippy warnings Signed-off-by: microwavedcola1 --- programs/mango-v4/src/address_lookup_table/mod.rs | 4 ++-- programs/mango-v4/src/instructions/create_perp_market.rs | 2 +- programs/mango-v4/src/state/orderbook/queue.rs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/programs/mango-v4/src/address_lookup_table/mod.rs b/programs/mango-v4/src/address_lookup_table/mod.rs index 9c6d64161..3767763f6 100644 --- a/programs/mango-v4/src/address_lookup_table/mod.rs +++ b/programs/mango-v4/src/address_lookup_table/mod.rs @@ -5,7 +5,7 @@ use solana_program::pubkey::{Pubkey, PUBKEY_BYTES}; use std::str::FromStr; pub fn id() -> Pubkey { - Pubkey::from_str(&"AddressLookupTab1e1111111111111111111111111").unwrap() + Pubkey::from_str("AddressLookupTab1e1111111111111111111111111").unwrap() } /// The maximum number of addresses that a lookup table can hold @@ -17,7 +17,7 @@ pub const LOOKUP_TABLE_META_SIZE: usize = 56; pub const LOOKUP_TABLE_MAX_ACCOUNT_SIZE: usize = LOOKUP_TABLE_META_SIZE + LOOKUP_TABLE_MAX_ADDRESSES * PUBKEY_BYTES; -pub fn addresses<'a>(table: &[u8]) -> &[Pubkey] { +pub fn addresses(table: &[u8]) -> &[Pubkey] { bytemuck::try_cast_slice(&table[LOOKUP_TABLE_META_SIZE..]).unwrap() } diff --git a/programs/mango-v4/src/instructions/create_perp_market.rs b/programs/mango-v4/src/instructions/create_perp_market.rs index b78d717ca..58f267ae7 100644 --- a/programs/mango-v4/src/instructions/create_perp_market.rs +++ b/programs/mango-v4/src/instructions/create_perp_market.rs @@ -1,7 +1,7 @@ use anchor_lang::prelude::*; use crate::error::MangoError; -use crate::{mango_v4, state::*}; +use crate::state::*; #[derive(Accounts)] #[instruction(perp_market_index: PerpMarketIndex)] diff --git a/programs/mango-v4/src/state/orderbook/queue.rs b/programs/mango-v4/src/state/orderbook/queue.rs index b89a12c12..328273acf 100644 --- a/programs/mango-v4/src/state/orderbook/queue.rs +++ b/programs/mango-v4/src/state/orderbook/queue.rs @@ -208,7 +208,7 @@ impl<'a> EventQueue<'a> { MangoError::SomeError ); //MangoErrorCode::AccountNotRentExempt - let mut state = Self::load_mut(account)?; + let state = Self::load_mut(account)?; require!(account.owner == program_id, MangoError::SomeError); // MangoErrorCode::InvalidOwner // require!( @@ -409,7 +409,7 @@ pub struct LiquidateEvent { 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,