diff --git a/Cargo.lock b/Cargo.lock index e89ce4604..5bc10324f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -204,7 +204,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e49a20ac8743546bc76a247ff0a81ceb0cee7653b087ccb330bf3553d907d522" dependencies = [ "anchor-lang", - "serum_dex", "solana-program", "spl-associated-token-account", "spl-token", @@ -1563,6 +1562,7 @@ dependencies = [ "num_enum", "pyth-client", "serde", + "serum_dex", "solana-logger", "solana-program", "solana-program-test", @@ -2400,8 +2400,7 @@ dependencies = [ [[package]] name = "serum_dex" version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02705854bae4622e552346c8edd43ab90c7425da35d63d2c689f39238f8d8b25" +source = "git+https://github.com/blockworks-foundation/serum-dex.git#3104f424ee38a415418a1cdef67970771f832857" dependencies = [ "arrayref", "bincode", diff --git a/programs/mango-v4/Cargo.toml b/programs/mango-v4/Cargo.toml index 3646dcde2..ed2bf6a47 100644 --- a/programs/mango-v4/Cargo.toml +++ b/programs/mango-v4/Cargo.toml @@ -21,7 +21,7 @@ test-bpf = [] # todo: when to fix, when to use caret? need a regular chore to bump dependencies # note: possibly need init-if-needed feature anchor-lang = { version = "0.22.0", features = [] } -anchor-spl = { version = "0.22.0", features = ["dex"] } +anchor-spl = { version = "0.22.0", features = [] } bytemuck = "^1.7.2" # todo: higher versions don't work fixed = { version = "=1.11.0", features = ["serde", "borsh"] } @@ -30,7 +30,7 @@ pyth-client = {version = "0.5.0", features = ["no-entrypoint"]} serde = "^1.0" solana-program = "1.9.5" static_assertions = "1.1" -#serum_dex = { version = "0.4.0", git = "https://github.com/blockworks-foundation/serum-dex.git", default-features=false, features = ["no-entrypoint", "program"] } +serum_dex = { version = "0.4.0", git = "https://github.com/blockworks-foundation/serum-dex.git", default-features=false, features = ["no-entrypoint", "program"] } checked_math = { path = "../../lib/checked_math" } arrayref = "0.3.6" num_enum = "0.5.1" diff --git a/programs/mango-v4/src/instructions/mod.rs b/programs/mango-v4/src/instructions/mod.rs index 6f29bfeb4..f012de8c7 100644 --- a/programs/mango-v4/src/instructions/mod.rs +++ b/programs/mango-v4/src/instructions/mod.rs @@ -8,6 +8,7 @@ pub use place_perp_order::*; pub use register_token::*; pub use serum3_cancel_order::*; pub use serum3_create_open_orders::*; +pub use serum3_liq_force_cancel_orders::*; pub use serum3_place_order::*; pub use serum3_register_market::*; pub use serum3_settle_funds::*; @@ -24,6 +25,7 @@ mod place_perp_order; mod register_token; mod serum3_cancel_order; mod serum3_create_open_orders; +mod serum3_liq_force_cancel_orders; mod serum3_place_order; mod serum3_register_market; mod serum3_settle_funds; diff --git a/programs/mango-v4/src/instructions/serum3_cancel_order.rs b/programs/mango-v4/src/instructions/serum3_cancel_order.rs index c0f21a44f..bdecc8c5e 100644 --- a/programs/mango-v4/src/instructions/serum3_cancel_order.rs +++ b/programs/mango-v4/src/instructions/serum3_cancel_order.rs @@ -4,8 +4,6 @@ use borsh::{BorshDeserialize, BorshSerialize}; use num_enum::TryFromPrimitive; use std::io::Write; -use anchor_spl::dex; -use dex::serum_dex; use serum_dex::matching::Side; use crate::error::*; @@ -112,7 +110,7 @@ pub fn serum3_cancel_order( } // - // Settle + // Cancel // cpi_cancel_order(&ctx.accounts, order)?; @@ -132,5 +130,5 @@ fn cpi_cancel_order(ctx: &Serum3CancelOrder, order: CancelOrderInstructionData) open_orders: ctx.open_orders.to_account_info(), open_orders_authority: ctx.group.to_account_info(), } - .call(&group, order.0) + .cancel_one(&group, order.0) } diff --git a/programs/mango-v4/src/instructions/serum3_create_open_orders.rs b/programs/mango-v4/src/instructions/serum3_create_open_orders.rs index 795d11f8c..b7704d75e 100644 --- a/programs/mango-v4/src/instructions/serum3_create_open_orders.rs +++ b/programs/mango-v4/src/instructions/serum3_create_open_orders.rs @@ -1,6 +1,4 @@ use anchor_lang::prelude::*; -use anchor_spl::dex; -use dex::serum_dex; use crate::state::*; @@ -49,26 +47,9 @@ pub struct Serum3CreateOpenOrders<'info> { } pub fn serum3_create_open_orders(ctx: Context) -> Result<()> { - let serum_market = ctx.accounts.serum_market.load()?; - let context = CpiContext::new( - ctx.accounts.serum_program.to_account_info(), - dex::InitOpenOrders { - open_orders: ctx.accounts.open_orders.to_account_info(), - // The open order authority must be the same as the authority on - // the vault accounts, because serum's placeorder doesn't distinguish - // the two authorities. - authority: ctx.accounts.group.to_account_info(), - market: ctx.accounts.serum_market_external.to_account_info(), - rent: ctx.accounts.rent.to_account_info(), - }, - ); - let group = ctx.accounts.group.load()?; - let seeds = group_seeds!(group); - // TODO: Anchor's code _forces_ anchor_spl::dex::id() as a program id. - // Are we ok with that? that would mean storing serum_program is not - // necessary. - dex::init_open_orders(context.with_signer(&[seeds]))?; + cpi_init_open_orders(&ctx.accounts)?; + let serum_market = ctx.accounts.serum_market.load()?; let mut account = ctx.accounts.account.load_mut()?; let serum_account = account .serum3_account_map @@ -91,3 +72,16 @@ pub fn serum3_create_open_orders(ctx: Context) -> Result Ok(()) } + +fn cpi_init_open_orders(ctx: &Serum3CreateOpenOrders) -> Result<()> { + use crate::serum3_cpi; + let group = ctx.group.load()?; + serum3_cpi::InitOpenOrders { + program: ctx.serum_program.to_account_info(), + market: ctx.serum_market_external.to_account_info(), + open_orders: ctx.open_orders.to_account_info(), + open_orders_authority: ctx.group.to_account_info(), + rent: ctx.rent.to_account_info(), + } + .call(&group) +} 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 new file mode 100644 index 000000000..e275a897e --- /dev/null +++ b/programs/mango-v4/src/instructions/serum3_liq_force_cancel_orders.rs @@ -0,0 +1,180 @@ +use anchor_lang::prelude::*; +use anchor_spl::token::{Token, TokenAccount}; + +use crate::error::*; +use crate::state::*; + +#[derive(Accounts)] +pub struct Serum3LiqForceCancelOrders<'info> { + pub group: AccountLoader<'info, Group>, + + #[account( + mut, + has_one = group, + )] + pub account: AccountLoader<'info, MangoAccount>, + + // Validated inline + #[account(mut)] + pub open_orders: UncheckedAccount<'info>, + + #[account( + has_one = group, + has_one = serum_program, + has_one = serum_market_external, + )] + pub serum_market: AccountLoader<'info, Serum3Market>, + pub serum_program: UncheckedAccount<'info>, + #[account(mut)] + pub serum_market_external: UncheckedAccount<'info>, + + // These accounts are forwarded directly to the serum cpi call + // and are validated there. + #[account(mut)] + pub market_bids: UncheckedAccount<'info>, + #[account(mut)] + pub market_asks: UncheckedAccount<'info>, + #[account(mut)] + pub market_event_queue: UncheckedAccount<'info>, + #[account(mut)] + pub market_base_vault: UncheckedAccount<'info>, + #[account(mut)] + pub market_quote_vault: UncheckedAccount<'info>, + pub market_vault_signer: UncheckedAccount<'info>, + + // token_index and bank.vault == vault is validated inline + #[account(mut, has_one = group)] + pub quote_bank: AccountLoader<'info, Bank>, + #[account(mut)] + pub quote_vault: Box>, + #[account(mut, has_one = group)] + pub base_bank: AccountLoader<'info, Bank>, + #[account(mut)] + pub base_vault: Box>, + + pub token_program: Program<'info, Token>, +} + +pub fn serum3_liq_force_cancel_orders( + ctx: Context, + limit: u8, +) -> Result<()> { + // + // Validation + // + { + let account = ctx.accounts.account.load()?; + let serum_market = ctx.accounts.serum_market.load()?; + + // Validate open_orders + require!( + account + .serum3_account_map + .find(serum_market.market_index) + .ok_or_else(|| error!(MangoError::SomeError))? + .open_orders + == ctx.accounts.open_orders.key(), + MangoError::SomeError + ); + + // Validate banks and vaults + let quote_bank = ctx.accounts.quote_bank.load()?; + require!( + quote_bank.vault == ctx.accounts.quote_vault.key(), + MangoError::SomeError + ); + require!( + quote_bank.token_index == serum_market.quote_token_index, + MangoError::SomeError + ); + let base_bank = ctx.accounts.base_bank.load()?; + require!( + base_bank.vault == ctx.accounts.base_vault.key(), + MangoError::SomeError + ); + require!( + base_bank.token_index == serum_market.base_token_index, + MangoError::SomeError + ); + } + + // TODO: do the correct health / being_liquidated check + { + let account = ctx.accounts.account.load()?; + let health = compute_health(&account, &ctx.remaining_accounts)?; + msg!("health: {}", health); + require!(health < 0, MangoError::SomeError); + } + + // + // Before-settle tracking + // + let before_base_vault = ctx.accounts.base_vault.amount; + let before_quote_vault = ctx.accounts.quote_vault.amount; + + // + // Cancel all and settle + // + cpi_cancel_all_orders(&ctx.accounts, limit)?; + cpi_settle_funds(&ctx.accounts)?; + + // + // After-settle tracking + // + ctx.accounts.base_vault.reload()?; + ctx.accounts.quote_vault.reload()?; + let after_base_vault = ctx.accounts.base_vault.amount; + let after_quote_vault = ctx.accounts.quote_vault.amount; + + // Charge the difference in vault balances to the user's account + { + let mut account = ctx.accounts.account.load_mut()?; + + let mut base_bank = ctx.accounts.base_bank.load_mut()?; + let base_position = account.token_account_map.get_mut(base_bank.token_index)?; + base_bank.change(base_position, (after_base_vault - before_base_vault) as i64)?; + + let mut quote_bank = ctx.accounts.quote_bank.load_mut()?; + let quote_position = account.token_account_map.get_mut(quote_bank.token_index)?; + quote_bank.change( + quote_position, + (after_quote_vault - before_quote_vault) as i64, + )?; + } + + Ok(()) +} + +fn cpi_cancel_all_orders(ctx: &Serum3LiqForceCancelOrders, limit: u8) -> Result<()> { + use crate::serum3_cpi; + let group = ctx.group.load()?; + serum3_cpi::CancelOrder { + program: ctx.serum_program.to_account_info(), + market: ctx.serum_market_external.to_account_info(), + bids: ctx.market_bids.to_account_info(), + asks: ctx.market_asks.to_account_info(), + event_queue: ctx.market_event_queue.to_account_info(), + + open_orders: ctx.open_orders.to_account_info(), + open_orders_authority: ctx.group.to_account_info(), + } + .cancel_all(&group, limit) +} + +fn cpi_settle_funds(ctx: &Serum3LiqForceCancelOrders) -> Result<()> { + use crate::serum3_cpi; + let group = ctx.group.load()?; + serum3_cpi::SettleFunds { + program: ctx.serum_program.to_account_info(), + market: ctx.serum_market_external.to_account_info(), + open_orders: ctx.open_orders.to_account_info(), + open_orders_authority: ctx.group.to_account_info(), + base_vault: ctx.market_base_vault.to_account_info(), + quote_vault: ctx.market_quote_vault.to_account_info(), + user_base_wallet: ctx.base_vault.to_account_info(), + user_quote_wallet: ctx.quote_vault.to_account_info(), + vault_signer: ctx.market_vault_signer.to_account_info(), + token_program: ctx.token_program.to_account_info(), + } + .call(&group) +} diff --git a/programs/mango-v4/src/instructions/serum3_place_order.rs b/programs/mango-v4/src/instructions/serum3_place_order.rs index 9d4bb0e9b..b2a185418 100644 --- a/programs/mango-v4/src/instructions/serum3_place_order.rs +++ b/programs/mango-v4/src/instructions/serum3_place_order.rs @@ -3,13 +3,10 @@ use anchor_spl::token::{Token, TokenAccount}; use arrayref::array_refs; use borsh::{BorshDeserialize, BorshSerialize}; use num_enum::TryFromPrimitive; +use serum_dex::matching::Side; use std::io::Write; use std::num::NonZeroU64; -use anchor_spl::dex; -use dex::serum_dex; -use serum_dex::matching::Side; - use crate::error::*; use crate::state::*; diff --git a/programs/mango-v4/src/lib.rs b/programs/mango-v4/src/lib.rs index d6fa4fdad..9dcd816fe 100644 --- a/programs/mango-v4/src/lib.rs +++ b/programs/mango-v4/src/lib.rs @@ -114,6 +114,13 @@ pub mod mango_v4 { instructions::serum3_settle_funds(ctx) } + pub fn serum3_liq_force_cancel_orders( + ctx: Context, + limit: u8, + ) -> Result<()> { + instructions::serum3_liq_force_cancel_orders(ctx, limit) + } + /// /// Perps /// diff --git a/programs/mango-v4/src/serum3_cpi.rs b/programs/mango-v4/src/serum3_cpi.rs index 446a86c40..31edd6e7f 100644 --- a/programs/mango-v4/src/serum3_cpi.rs +++ b/programs/mango-v4/src/serum3_cpi.rs @@ -1,8 +1,149 @@ use anchor_lang::prelude::*; -use anchor_spl::dex::serum_dex; +use serum_dex::state::ToAlignedBytes; +use std::cell::{Ref, RefMut}; +use std::cmp::min; +use std::convert::identity; +use std::mem::size_of; + +use crate::error::*; use crate::state::*; +fn strip_dex_padding<'a>(acc: &'a AccountInfo) -> Result> { + require!(acc.data_len() >= 12, MangoError::SomeError); + Ok(Ref::map(acc.try_borrow_data()?, |data| { + &data[5..data.len() - 12] + })) +} + +fn strip_dex_padding_mut<'a>(acc: &'a AccountInfo) -> Result> { + require!(acc.data_len() >= 12, MangoError::SomeError); + Ok(RefMut::map(acc.try_borrow_mut_data()?, |data| { + let len = data.len(); + &mut data[5..len - 12] + })) +} + +#[inline] +pub fn remove_slop_mut(bytes: &mut [u8]) -> &mut [T] { + let slop = bytes.len() % size_of::(); + let new_len = bytes.len() - slop; + bytemuck::cast_slice_mut(&mut bytes[..new_len]) +} + +fn strip_data_header_mut<'a, H: bytemuck::Pod, D: bytemuck::Pod>( + orig_data: RefMut<'a, [u8]>, +) -> Result<(RefMut<'a, H>, RefMut<'a, [D]>)> { + Ok(RefMut::map_split(orig_data, |data| { + let (header_bytes, inner_bytes) = data.split_at_mut(size_of::()); + let header = bytemuck::try_from_bytes_mut(header_bytes).unwrap(); + let inner = remove_slop_mut(inner_bytes); + (header, inner) + })) +} + +pub fn load_market_state<'a>( + market_account: &'a AccountInfo, + program_id: &Pubkey, +) -> Result> { + require!(market_account.owner == program_id, MangoError::SomeError); + let state: Ref = + Ref::map(strip_dex_padding(market_account)?, |data| { + bytemuck::from_bytes(data) + }); + state + .check_flags() + .map_err(|_| error!(MangoError::SomeError))?; + Ok(state) +} + +/// Copied over from serum dex +#[derive(Copy, Clone)] +#[repr(C, packed)] +pub struct OrderBookStateHeader { + pub account_flags: u64, // Initialized, (Bids or Asks) +} +unsafe impl bytemuck::Zeroable for OrderBookStateHeader {} +unsafe impl bytemuck::Pod for OrderBookStateHeader {} + +pub fn load_bids_mut<'a>( + sm: &serum_dex::state::MarketState, + bids: &'a AccountInfo, +) -> Result> { + require!( + &bids.key.to_aligned_bytes() == &identity(sm.bids), + MangoError::SomeError + ); + let orig_data = strip_dex_padding_mut(bids)?; + let (header, buf) = strip_data_header_mut::(orig_data)?; + require!( + header.account_flags + == serum_dex::state::AccountFlag::Initialized as u64 + + serum_dex::state::AccountFlag::Bids as u64, + MangoError::SomeError + ); + Ok(RefMut::map(buf, serum_dex::critbit::Slab::new)) +} + +pub fn load_asks_mut<'a>( + sm: &serum_dex::state::MarketState, + asks: &'a AccountInfo, +) -> Result> { + require!( + &asks.key.to_aligned_bytes() == &identity(sm.asks), + MangoError::SomeError + ); + let orig_data = strip_dex_padding_mut(asks)?; + let (header, buf) = strip_data_header_mut::(orig_data)?; + require!( + header.account_flags + == serum_dex::state::AccountFlag::Initialized as u64 + + serum_dex::state::AccountFlag::Asks as u64, + MangoError::SomeError + ); + Ok(RefMut::map(buf, serum_dex::critbit::Slab::new)) +} + +pub fn load_open_orders<'a>(acc: &'a AccountInfo) -> Result> { + Ok(Ref::map(strip_dex_padding(acc)?, bytemuck::from_bytes)) +} + +pub struct InitOpenOrders<'info> { + pub program: AccountInfo<'info>, + pub market: AccountInfo<'info>, + pub open_orders: AccountInfo<'info>, + pub open_orders_authority: AccountInfo<'info>, + pub rent: AccountInfo<'info>, +} + +impl<'info> InitOpenOrders<'info> { + pub fn call(self, group: &Group) -> Result<()> { + let data = serum_dex::instruction::MarketInstruction::InitOpenOrders.pack(); + let instruction = solana_program::instruction::Instruction { + program_id: *self.program.key, + data, + accounts: vec![ + AccountMeta::new(*self.open_orders.key, false), + AccountMeta::new_readonly(*self.open_orders_authority.key, true), + AccountMeta::new_readonly(*self.market.key, false), + AccountMeta::new_readonly(*self.rent.key, false), + ], + }; + + let account_infos = [ + self.program, + self.open_orders, + self.open_orders_authority, + self.market, + self.rent, + ]; + + let seeds = group_seeds!(group); + solana_program::program::invoke_signed_unchecked(&instruction, &account_infos, &[seeds])?; + Ok(()) + } +} + pub struct SettleFunds<'info> { pub program: AccountInfo<'info>, pub market: AccountInfo<'info>, @@ -134,7 +275,7 @@ pub struct CancelOrder<'info> { } impl<'a> CancelOrder<'a> { - pub fn call( + pub fn cancel_one( self, group: &Group, order: serum_dex::instruction::CancelOrderInstructionV2, @@ -167,4 +308,88 @@ impl<'a> CancelOrder<'a> { Ok(()) } + + pub fn cancel_all(self, group: &Group, mut limit: u8) -> Result<()> { + // find all cancels by scanning open_orders/bids/asks + let mut cancels = vec![]; + { + let open_orders = load_open_orders(&self.open_orders)?; + + let market = load_market_state(&self.market, self.program.key)?; + let bids = load_bids_mut(&market, &self.bids)?; + let asks = load_asks_mut(&market, &self.asks)?; + + limit = min(limit, open_orders.free_slot_bits.count_zeros() as u8); + if limit == 0 { + return Ok(()); + } + for j in 0..128 { + let slot_mask = 1u128 << j; + if open_orders.free_slot_bits & slot_mask != 0 { + // means slot is free + continue; + } + let order_id = open_orders.orders[j]; + + // free_slot_bits is only updated when the event queue is processed, + // that means we need to scan bids/asks to see if the order is still alive + let side = if open_orders.is_bid_bits & slot_mask != 0 { + match bids.find_by_key(order_id) { + None => continue, + Some(_) => serum_dex::matching::Side::Bid, + } + } else { + match asks.find_by_key(order_id) { + None => continue, + Some(_) => serum_dex::matching::Side::Ask, + } + }; + + let cancel_instruction = + serum_dex::instruction::CancelOrderInstructionV2 { side, order_id }; + + cancels.push(cancel_instruction); + + limit -= 1; + if limit == 0 { + break; + } + } + } + + let mut instruction = solana_program::instruction::Instruction { + program_id: *self.program.key, + data: vec![], + accounts: vec![ + AccountMeta::new(*self.market.key, false), + AccountMeta::new(*self.bids.key, false), + AccountMeta::new(*self.asks.key, false), + AccountMeta::new(*self.open_orders.key, false), + AccountMeta::new_readonly(*self.open_orders_authority.key, true), + AccountMeta::new(*self.event_queue.key, false), + ], + }; + let account_infos = [ + self.program, + self.market, + self.bids, + self.asks, + self.open_orders, + self.open_orders_authority, + self.event_queue, + ]; + let seeds = group_seeds!(group); + + for cancel in cancels.into_iter() { + instruction.data = + serum_dex::instruction::MarketInstruction::CancelOrderV2(cancel).pack(); + solana_program::program::invoke_signed_unchecked( + &instruction, + &account_infos, + &[seeds], + )?; + } + + Ok(()) + } } diff --git a/programs/mango-v4/src/state/health.rs b/programs/mango-v4/src/state/health.rs index a769be9b4..b6eef0c38 100644 --- a/programs/mango-v4/src/state/health.rs +++ b/programs/mango-v4/src/state/health.rs @@ -1,5 +1,4 @@ use anchor_lang::prelude::*; -use anchor_spl::dex::serum_dex; use fixed::types::I80F48; use std::cell::Ref; diff --git a/programs/mango-v4/tests/program_test/mango_client.rs b/programs/mango-v4/tests/program_test/mango_client.rs index 1d703e6a9..88c6f8347 100644 --- a/programs/mango-v4/tests/program_test/mango_client.rs +++ b/programs/mango-v4/tests/program_test/mango_client.rs @@ -2,7 +2,6 @@ use anchor_lang::prelude::*; use anchor_lang::solana_program::sysvar::{self, SysvarId}; -use anchor_spl::dex::serum_dex; use anchor_spl::token::{Token, TokenAccount}; use fixed::types::I80F48; use solana_program::instruction::Instruction; @@ -764,7 +763,7 @@ impl<'keypair> ClientInstruction for Serum3PlaceOrderInstruction<'keypair> { let program_id = mango_v4::id(); let instruction = Self::Instruction { order: mango_v4::instructions::NewOrderInstructionData( - anchor_spl::dex::serum_dex::instruction::NewOrderInstructionV3 { + serum_dex::instruction::NewOrderInstructionV3 { side: self.side.try_into().unwrap(), limit_price: self.limit_price.try_into().unwrap(), max_coin_qty: self.max_base_qty.try_into().unwrap(), @@ -872,7 +871,7 @@ impl<'keypair> ClientInstruction for Serum3CancelOrderInstruction<'keypair> { let program_id = mango_v4::id(); let instruction = Self::Instruction { order: mango_v4::instructions::CancelOrderInstructionData( - anchor_spl::dex::serum_dex::instruction::CancelOrderInstructionV2 { + serum_dex::instruction::CancelOrderInstructionV2 { side: self.side.try_into().unwrap(), order_id: self.order_id, }, @@ -996,6 +995,85 @@ impl<'keypair> ClientInstruction for Serum3SettleFundsInstruction<'keypair> { } } +pub struct Serum3LiqForceCancelOrdersInstruction { + pub account: Pubkey, + pub serum_market: Pubkey, + pub limit: u8, +} +#[async_trait::async_trait(?Send)] +impl ClientInstruction for Serum3LiqForceCancelOrdersInstruction { + type Accounts = mango_v4::accounts::Serum3LiqForceCancelOrders; + type Instruction = mango_v4::instruction::Serum3LiqForceCancelOrders; + async fn to_instruction( + &self, + account_loader: impl ClientAccountLoader + 'async_trait, + ) -> (Self::Accounts, instruction::Instruction) { + let program_id = mango_v4::id(); + let instruction = Self::Instruction { limit: self.limit }; + + let account: MangoAccount = account_loader.load(&self.account).await.unwrap(); + let serum_market: Serum3Market = account_loader.load(&self.serum_market).await.unwrap(); + let open_orders = account + .serum3_account_map + .find(serum_market.market_index) + .unwrap() + .open_orders; + let quote_info = + get_mint_info_by_token_index(&account_loader, &account, serum_market.quote_token_index) + .await; + let base_info = + get_mint_info_by_token_index(&account_loader, &account, serum_market.base_token_index) + .await; + + let market_external_bytes = account_loader + .load_bytes(&serum_market.serum_market_external) + .await + .unwrap(); + let market_external: &serum_dex::state::MarketState = bytemuck::from_bytes( + &market_external_bytes[5..5 + std::mem::size_of::()], + ); + // unpack the data, to avoid unaligned references + let bids = market_external.bids; + let asks = market_external.asks; + let event_q = market_external.event_q; + let coin_vault = market_external.coin_vault; + let pc_vault = market_external.pc_vault; + let vault_signer = serum_dex::state::gen_vault_signer_key( + market_external.vault_signer_nonce, + &serum_market.serum_market_external, + &serum_market.serum_program, + ) + .unwrap(); + + let accounts = Self::Accounts { + group: account.group, + account: self.account, + open_orders, + quote_bank: quote_info.bank, + quote_vault: quote_info.vault, + base_bank: base_info.bank, + base_vault: base_info.vault, + serum_market: self.serum_market, + serum_program: serum_market.serum_program, + serum_market_external: serum_market.serum_market_external, + market_bids: from_serum_style_pubkey(&bids), + market_asks: from_serum_style_pubkey(&asks), + market_event_queue: from_serum_style_pubkey(&event_q), + market_base_vault: from_serum_style_pubkey(&coin_vault), + market_quote_vault: from_serum_style_pubkey(&pc_vault), + market_vault_signer: vault_signer, + token_program: Token::id(), + }; + + let instruction = make_instruction(program_id, &accounts, instruction); + (accounts, instruction) + } + + fn signers(&self) -> Vec<&Keypair> { + vec![] + } +} + pub struct CreatePerpMarketInstruction<'keypair> { pub group: Pubkey, pub mint: Pubkey, diff --git a/programs/mango-v4/tests/program_test/mod.rs b/programs/mango-v4/tests/program_test/mod.rs index f7bed075b..97ef97e06 100644 --- a/programs/mango-v4/tests/program_test/mod.rs +++ b/programs/mango-v4/tests/program_test/mod.rs @@ -215,7 +215,7 @@ impl TestContextBuilder { } pub fn add_serum_program(&mut self) -> Pubkey { - let serum_program_id = anchor_spl::dex::id(); + let serum_program_id = Pubkey::new_unique(); self.test.add_program("serum_dex", serum_program_id, None); serum_program_id } diff --git a/programs/mango-v4/tests/program_test/serum.rs b/programs/mango-v4/tests/program_test/serum.rs index 3354cd983..7d78675dc 100644 --- a/programs/mango-v4/tests/program_test/serum.rs +++ b/programs/mango-v4/tests/program_test/serum.rs @@ -1,6 +1,5 @@ use std::{mem, sync::Arc}; -use anchor_spl::dex::serum_dex; use bytemuck::from_bytes; use solana_sdk::pubkey::Pubkey; use solana_sdk::{ diff --git a/programs/mango-v4/tests/test_serum.rs b/programs/mango-v4/tests/test_serum.rs index 3c8e1fba3..ce4de4685 100644 --- a/programs/mango-v4/tests/test_serum.rs +++ b/programs/mango-v4/tests/test_serum.rs @@ -1,6 +1,5 @@ #![cfg(feature = "test-bpf")] -use anchor_spl::dex::serum_dex; use solana_program_test::*; use solana_sdk::{signature::Keypair, transport::TransportError};