Serum: serum3_liq_force_cancel_orders instruction

Still needs a test.
This commit is contained in:
Christian Kamm 2022-03-21 12:55:05 +01:00
parent 27f882a333
commit 34d14ef267
14 changed files with 520 additions and 43 deletions

5
Cargo.lock generated
View File

@ -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",

View File

@ -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"

View File

@ -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;

View File

@ -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)
}

View File

@ -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<Serum3CreateOpenOrders>) -> 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<Serum3CreateOpenOrders>) -> 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)
}

View File

@ -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<'info, TokenAccount>>,
#[account(mut, has_one = group)]
pub base_bank: AccountLoader<'info, Bank>,
#[account(mut)]
pub base_vault: Box<Account<'info, TokenAccount>>,
pub token_program: Program<'info, Token>,
}
pub fn serum3_liq_force_cancel_orders(
ctx: Context<Serum3LiqForceCancelOrders>,
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)
}

View File

@ -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::*;

View File

@ -114,6 +114,13 @@ pub mod mango_v4 {
instructions::serum3_settle_funds(ctx)
}
pub fn serum3_liq_force_cancel_orders(
ctx: Context<Serum3LiqForceCancelOrders>,
limit: u8,
) -> Result<()> {
instructions::serum3_liq_force_cancel_orders(ctx, limit)
}
///
/// Perps
///

View File

@ -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<Ref<'a, [u8]>> {
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<RefMut<'a, [u8]>> {
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<T: bytemuck::Pod>(bytes: &mut [u8]) -> &mut [T] {
let slop = bytes.len() % size_of::<T>();
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::<H>());
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<Ref<'a, serum_dex::state::MarketState>> {
require!(market_account.owner == program_id, MangoError::SomeError);
let state: Ref<serum_dex::state::MarketState> =
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<RefMut<'a, serum_dex::critbit::Slab>> {
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::<OrderBookStateHeader, u8>(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<RefMut<'a, serum_dex::critbit::Slab>> {
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::<OrderBookStateHeader, u8>(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<Ref<'a, serum_dex::state::OpenOrders>> {
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(())
}
}

View File

@ -1,5 +1,4 @@
use anchor_lang::prelude::*;
use anchor_spl::dex::serum_dex;
use fixed::types::I80F48;
use std::cell::Ref;

View File

@ -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::<serum_dex::state::MarketState>()],
);
// 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,

View File

@ -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
}

View File

@ -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::{

View File

@ -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};