Serum: Cancel order instruction

This commit is contained in:
Christian Kamm 2022-03-19 12:11:56 +01:00
parent 63050c3661
commit 6f72cd27e8
9 changed files with 468 additions and 195 deletions

View File

@ -34,6 +34,7 @@ static_assertions = "1.1"
checked_math = { path = "../../lib/checked_math" }
arrayref = "0.3.6"
num_enum = "0.5.1"
bincode = "1.3.3"
[dev-dependencies]
solana-sdk = { version = "1.9.5", default-features = false }

View File

@ -5,6 +5,7 @@ pub use create_perp_market::*;
pub use create_stub_oracle::*;
pub use deposit::*;
pub use register_token::*;
pub use serum3_cancel_order::*;
pub use serum3_create_open_orders::*;
pub use serum3_place_order::*;
pub use serum3_register_market::*;
@ -19,6 +20,7 @@ mod create_stub_oracle;
mod deposit;
mod margin_trade;
mod register_token;
mod serum3_cancel_order;
mod serum3_create_open_orders;
mod serum3_place_order;
mod serum3_register_market;

View File

@ -0,0 +1,136 @@
use anchor_lang::prelude::*;
use arrayref::array_refs;
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::*;
use crate::state::*;
/// Unfortunately CancelOrderInstructionV2 isn't borsh serializable.
///
/// Make a newtype and implement the traits for it.
pub struct CancelOrderInstructionData(pub serum_dex::instruction::CancelOrderInstructionV2);
impl CancelOrderInstructionData {
// Copy of CancelOrderInstructionV2::unpack(), which we wish were public!
fn unpack(data: &[u8; 20]) -> Option<Self> {
let (&side_arr, &oid_arr) = array_refs![data, 4, 16];
let side = Side::try_from_primitive(u32::from_le_bytes(side_arr).try_into().ok()?).ok()?;
let order_id = u128::from_le_bytes(oid_arr);
Some(Self(serum_dex::instruction::CancelOrderInstructionV2 {
side,
order_id,
}))
}
}
impl BorshDeserialize for CancelOrderInstructionData {
fn deserialize(buf: &mut &[u8]) -> std::result::Result<Self, std::io::Error> {
let data: &[u8; 20] = buf[0..20]
.try_into()
.map_err(|e| std::io::Error::new(std::io::ErrorKind::UnexpectedEof, e))?;
*buf = &buf[20..];
Ok(Self::unpack(data).ok_or_else(|| {
std::io::Error::new(
std::io::ErrorKind::InvalidInput,
error!(MangoError::SomeError),
)
})?)
}
}
impl BorshSerialize for CancelOrderInstructionData {
fn serialize<W: Write>(&self, writer: &mut W) -> std::result::Result<(), std::io::Error> {
// serum_dex uses bincode::serialize() internally, see MarketInstruction::pack()
writer.write(&bincode::serialize(&self.0).unwrap())?;
Ok(())
}
}
#[derive(Accounts)]
pub struct Serum3CancelOrder<'info> {
pub group: AccountLoader<'info, Group>,
#[account(
mut,
has_one = group,
has_one = owner,
)]
pub account: AccountLoader<'info, MangoAccount>,
pub owner: Signer<'info>,
// 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>,
}
pub fn serum3_cancel_order(
ctx: Context<Serum3CancelOrder>,
order: CancelOrderInstructionData,
) -> 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(error!(MangoError::SomeError))?
.open_orders
== ctx.accounts.open_orders.key(),
MangoError::SomeError
);
}
//
// Settle
//
cpi_cancel_order(&ctx.accounts, order)?;
Ok(())
}
fn cpi_cancel_order(ctx: &Serum3CancelOrder, order: CancelOrderInstructionData) -> 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(),
}
.call(&group, order.0)
}

View File

@ -8,7 +8,6 @@ use std::num::NonZeroU64;
use anchor_spl::dex;
use dex::serum_dex;
use serum_dex::instruction::NewOrderInstructionV3;
use serum_dex::matching::Side;
use crate::error::*;
@ -19,52 +18,52 @@ use crate::state::*;
/// Make a newtype and implement the traits for it.
pub struct NewOrderInstructionData(pub serum_dex::instruction::NewOrderInstructionV3);
/// mango-v3's deserialization code
fn unpack_dex_new_order_v3(
data: &[u8; 46],
) -> Option<serum_dex::instruction::NewOrderInstructionV3> {
let (
&side_arr,
&price_arr,
&max_coin_qty_arr,
&max_native_pc_qty_arr,
&self_trade_behavior_arr,
&otype_arr,
&client_order_id_bytes,
&limit_arr,
) = array_refs![data, 4, 8, 8, 8, 4, 4, 8, 2];
impl NewOrderInstructionData {
// Copy of NewOrderInstructionV3::unpack(), which we wish were public!
fn unpack(data: &[u8; 46]) -> Option<Self> {
let (
&side_arr,
&price_arr,
&max_coin_qty_arr,
&max_native_pc_qty_arr,
&self_trade_behavior_arr,
&otype_arr,
&client_order_id_bytes,
&limit_arr,
) = array_refs![data, 4, 8, 8, 8, 4, 4, 8, 2];
let side = serum_dex::matching::Side::try_from_primitive(
u32::from_le_bytes(side_arr).try_into().ok()?,
)
.ok()?;
let limit_price = NonZeroU64::new(u64::from_le_bytes(price_arr))?;
let max_coin_qty = NonZeroU64::new(u64::from_le_bytes(max_coin_qty_arr))?;
let max_native_pc_qty_including_fees =
NonZeroU64::new(u64::from_le_bytes(max_native_pc_qty_arr))?;
let self_trade_behavior = serum_dex::instruction::SelfTradeBehavior::try_from_primitive(
u32::from_le_bytes(self_trade_behavior_arr)
.try_into()
.ok()?,
)
.ok()?;
let order_type = serum_dex::matching::OrderType::try_from_primitive(
u32::from_le_bytes(otype_arr).try_into().ok()?,
)
.ok()?;
let client_order_id = u64::from_le_bytes(client_order_id_bytes);
let limit = u16::from_le_bytes(limit_arr);
let side = serum_dex::matching::Side::try_from_primitive(
u32::from_le_bytes(side_arr).try_into().ok()?,
)
.ok()?;
let limit_price = NonZeroU64::new(u64::from_le_bytes(price_arr))?;
let max_coin_qty = NonZeroU64::new(u64::from_le_bytes(max_coin_qty_arr))?;
let max_native_pc_qty_including_fees =
NonZeroU64::new(u64::from_le_bytes(max_native_pc_qty_arr))?;
let self_trade_behavior = serum_dex::instruction::SelfTradeBehavior::try_from_primitive(
u32::from_le_bytes(self_trade_behavior_arr)
.try_into()
.ok()?,
)
.ok()?;
let order_type = serum_dex::matching::OrderType::try_from_primitive(
u32::from_le_bytes(otype_arr).try_into().ok()?,
)
.ok()?;
let client_order_id = u64::from_le_bytes(client_order_id_bytes);
let limit = u16::from_le_bytes(limit_arr);
Some(serum_dex::instruction::NewOrderInstructionV3 {
side,
limit_price,
max_coin_qty,
max_native_pc_qty_including_fees,
self_trade_behavior,
order_type,
client_order_id,
limit,
})
Some(Self(serum_dex::instruction::NewOrderInstructionV3 {
side,
limit_price,
max_coin_qty,
max_native_pc_qty_including_fees,
self_trade_behavior,
order_type,
client_order_id,
limit,
}))
}
}
impl BorshDeserialize for NewOrderInstructionData {
@ -73,30 +72,19 @@ impl BorshDeserialize for NewOrderInstructionData {
.try_into()
.map_err(|e| std::io::Error::new(std::io::ErrorKind::UnexpectedEof, e))?;
*buf = &buf[46..];
Ok(Self(unpack_dex_new_order_v3(data).ok_or(
Ok(Self::unpack(data).ok_or_else(|| {
std::io::Error::new(
std::io::ErrorKind::InvalidInput,
error!(MangoError::SomeError),
),
)?))
)
})?)
}
}
impl BorshSerialize for NewOrderInstructionData {
fn serialize<W: Write>(&self, writer: &mut W) -> std::result::Result<(), std::io::Error> {
let d = &self.0;
let side: u8 = d.side.into();
// TODO: why use four bytes here? (also in deserialization above)
writer.write(&(side as u32).to_le_bytes())?;
writer.write(&u64::from(d.limit_price).to_le_bytes())?;
writer.write(&u64::from(d.max_coin_qty).to_le_bytes())?;
writer.write(&u64::from(d.max_native_pc_qty_including_fees).to_le_bytes())?;
let self_trade_behavior: u8 = d.self_trade_behavior.into();
writer.write(&(self_trade_behavior as u32).to_le_bytes())?;
let order_type: u8 = d.order_type.into();
writer.write(&(order_type as u32).to_le_bytes())?;
writer.write(&u64::from(d.client_order_id).to_le_bytes())?;
writer.write(&u16::from(d.limit).to_le_bytes())?;
// serum_dex uses bincode::serialize() internally, see MarketInstruction::pack()
writer.write(&bincode::serialize(&self.0).unwrap())?;
Ok(())
}
}
@ -218,7 +206,7 @@ pub fn serum3_place_order(
// Apply the order to serum. Also immediately settle, in case the order
// matched against an existing other order.
//
cpi_place_order(&ctx.accounts, order.0)?;
cpi_place_order(&ctx.accounts, order)?;
cpi_settle_funds(&ctx.accounts)?;
//
@ -256,56 +244,47 @@ pub fn serum3_place_order(
Ok(())
}
fn cpi_place_order(ctx: &Serum3PlaceOrder, order: NewOrderInstructionV3) -> Result<()> {
fn cpi_place_order(ctx: &Serum3PlaceOrder, order: NewOrderInstructionData) -> Result<()> {
use crate::serum3_cpi;
let order_payer_token_account = match order.side {
let order_payer_token_account = match order.0.side {
Side::Bid => &ctx.quote_vault,
Side::Ask => &ctx.base_vault,
};
let group = ctx.group.load()?;
serum3_cpi::place_order(
&group,
serum3_cpi::PlaceOrder {
program: ctx.serum_program.to_account_info(),
market: ctx.serum_market_external.to_account_info(),
request_queue: ctx.market_request_queue.to_account_info(),
event_queue: ctx.market_event_queue.to_account_info(),
bids: ctx.market_bids.to_account_info(),
asks: ctx.market_asks.to_account_info(),
base_vault: ctx.market_base_vault.to_account_info(),
quote_vault: ctx.market_quote_vault.to_account_info(),
token_program: ctx.token_program.to_account_info(),
serum3_cpi::PlaceOrder {
program: ctx.serum_program.to_account_info(),
market: ctx.serum_market_external.to_account_info(),
request_queue: ctx.market_request_queue.to_account_info(),
event_queue: ctx.market_event_queue.to_account_info(),
bids: ctx.market_bids.to_account_info(),
asks: ctx.market_asks.to_account_info(),
base_vault: ctx.market_base_vault.to_account_info(),
quote_vault: ctx.market_quote_vault.to_account_info(),
token_program: ctx.token_program.to_account_info(),
open_orders: ctx.open_orders.to_account_info(),
order_payer_token_account: order_payer_token_account.to_account_info(),
user_authority: ctx.group.to_account_info(),
},
order,
)?;
Ok(())
open_orders: ctx.open_orders.to_account_info(),
order_payer_token_account: order_payer_token_account.to_account_info(),
user_authority: ctx.group.to_account_info(),
}
.call(&group, order.0)
}
fn cpi_settle_funds(ctx: &Serum3PlaceOrder) -> Result<()> {
use crate::serum3_cpi;
let group = ctx.group.load()?;
serum3_cpi::settle_funds(
&group,
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(),
},
)?;
Ok(())
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

@ -139,21 +139,17 @@ pub fn serum3_settle_funds(ctx: Context<Serum3SettleFunds>) -> Result<()> {
fn cpi_settle_funds(ctx: &Serum3SettleFunds) -> Result<()> {
use crate::serum3_cpi;
let group = ctx.group.load()?;
serum3_cpi::settle_funds(
&group,
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(),
},
)?;
Ok(())
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

@ -99,6 +99,13 @@ pub mod mango_v4 {
instructions::serum3_place_order(ctx, order)
}
pub fn serum3_cancel_order(
ctx: Context<Serum3CancelOrder>,
order: instructions::CancelOrderInstructionData,
) -> Result<()> {
instructions::serum3_cancel_order(ctx, order)
}
pub fn serum3_settle_funds(ctx: Context<Serum3SettleFunds>) -> Result<()> {
instructions::serum3_settle_funds(ctx)
}

View File

@ -16,43 +16,45 @@ pub struct SettleFunds<'info> {
pub token_program: AccountInfo<'info>,
}
pub fn settle_funds(group: &Group, ctx: SettleFunds) -> Result<()> {
let data = serum_dex::instruction::MarketInstruction::SettleFunds.pack();
let instruction = solana_program::instruction::Instruction {
program_id: *ctx.program.key,
data,
accounts: vec![
AccountMeta::new(*ctx.market.key, false),
AccountMeta::new(*ctx.open_orders.key, false),
AccountMeta::new_readonly(*ctx.open_orders_authority.key, true),
AccountMeta::new(*ctx.base_vault.key, false),
AccountMeta::new(*ctx.quote_vault.key, false),
AccountMeta::new(*ctx.user_base_wallet.key, false),
AccountMeta::new(*ctx.user_quote_wallet.key, false),
AccountMeta::new_readonly(*ctx.vault_signer.key, false),
AccountMeta::new_readonly(*ctx.token_program.key, false),
AccountMeta::new(*ctx.user_quote_wallet.key, false),
],
};
impl<'a> SettleFunds<'a> {
pub fn call(self, group: &Group) -> Result<()> {
let data = serum_dex::instruction::MarketInstruction::SettleFunds.pack();
let instruction = solana_program::instruction::Instruction {
program_id: *self.program.key,
data,
accounts: vec![
AccountMeta::new(*self.market.key, false),
AccountMeta::new(*self.open_orders.key, false),
AccountMeta::new_readonly(*self.open_orders_authority.key, true),
AccountMeta::new(*self.base_vault.key, false),
AccountMeta::new(*self.quote_vault.key, false),
AccountMeta::new(*self.user_base_wallet.key, false),
AccountMeta::new(*self.user_quote_wallet.key, false),
AccountMeta::new_readonly(*self.vault_signer.key, false),
AccountMeta::new_readonly(*self.token_program.key, false),
AccountMeta::new(*self.user_quote_wallet.key, false),
],
};
let account_infos = [
ctx.program,
ctx.market,
ctx.open_orders,
ctx.open_orders_authority,
ctx.base_vault,
ctx.quote_vault,
ctx.user_base_wallet,
ctx.user_quote_wallet.clone(),
ctx.vault_signer,
ctx.token_program,
ctx.user_quote_wallet,
];
let account_infos = [
self.program,
self.market,
self.open_orders,
self.open_orders_authority,
self.base_vault,
self.quote_vault,
self.user_base_wallet,
self.user_quote_wallet.clone(),
self.vault_signer,
self.token_program,
self.user_quote_wallet,
];
let seeds = group_seeds!(group);
solana_program::program::invoke_signed_unchecked(&instruction, &account_infos, &[seeds])?;
let seeds = group_seeds!(group);
solana_program::program::invoke_signed_unchecked(&instruction, &account_infos, &[seeds])?;
Ok(())
Ok(())
}
}
pub struct PlaceOrder<'info> {
@ -72,48 +74,97 @@ pub struct PlaceOrder<'info> {
pub user_authority: AccountInfo<'info>,
}
pub fn place_order(
group: &Group,
ctx: PlaceOrder,
order: serum_dex::instruction::NewOrderInstructionV3,
) -> Result<()> {
let data = serum_dex::instruction::MarketInstruction::NewOrderV3(order).pack();
let instruction = solana_program::instruction::Instruction {
program_id: *ctx.program.key,
data,
accounts: vec![
AccountMeta::new(*ctx.market.key, false),
AccountMeta::new(*ctx.open_orders.key, false),
AccountMeta::new(*ctx.request_queue.key, false),
AccountMeta::new(*ctx.event_queue.key, false),
AccountMeta::new(*ctx.bids.key, false),
AccountMeta::new(*ctx.asks.key, false),
AccountMeta::new(*ctx.order_payer_token_account.key, false),
AccountMeta::new_readonly(*ctx.user_authority.key, true),
AccountMeta::new(*ctx.base_vault.key, false),
AccountMeta::new(*ctx.quote_vault.key, false),
AccountMeta::new_readonly(*ctx.token_program.key, false),
AccountMeta::new_readonly(*ctx.user_authority.key, false),
],
};
let account_infos = [
ctx.program,
ctx.market,
ctx.open_orders,
ctx.request_queue,
ctx.event_queue,
ctx.bids,
ctx.asks,
ctx.order_payer_token_account,
ctx.user_authority.clone(),
ctx.base_vault,
ctx.quote_vault,
ctx.token_program,
ctx.user_authority,
];
impl<'a> PlaceOrder<'a> {
pub fn call(
self,
group: &Group,
order: serum_dex::instruction::NewOrderInstructionV3,
) -> Result<()> {
let data = serum_dex::instruction::MarketInstruction::NewOrderV3(order).pack();
let instruction = solana_program::instruction::Instruction {
program_id: *self.program.key,
data,
accounts: vec![
AccountMeta::new(*self.market.key, false),
AccountMeta::new(*self.open_orders.key, false),
AccountMeta::new(*self.request_queue.key, false),
AccountMeta::new(*self.event_queue.key, false),
AccountMeta::new(*self.bids.key, false),
AccountMeta::new(*self.asks.key, false),
AccountMeta::new(*self.order_payer_token_account.key, false),
AccountMeta::new_readonly(*self.user_authority.key, true),
AccountMeta::new(*self.base_vault.key, false),
AccountMeta::new(*self.quote_vault.key, false),
AccountMeta::new_readonly(*self.token_program.key, false),
AccountMeta::new_readonly(*self.user_authority.key, false),
],
};
let account_infos = [
self.program,
self.market,
self.open_orders,
self.request_queue,
self.event_queue,
self.bids,
self.asks,
self.order_payer_token_account,
self.user_authority.clone(),
self.base_vault,
self.quote_vault,
self.token_program,
self.user_authority,
];
let seeds = group_seeds!(group);
solana_program::program::invoke_signed_unchecked(&instruction, &account_infos, &[seeds])?;
let seeds = group_seeds!(group);
solana_program::program::invoke_signed_unchecked(&instruction, &account_infos, &[seeds])?;
Ok(())
Ok(())
}
}
pub struct CancelOrder<'info> {
pub program: AccountInfo<'info>,
pub market: AccountInfo<'info>,
pub event_queue: AccountInfo<'info>,
pub bids: AccountInfo<'info>,
pub asks: AccountInfo<'info>,
pub open_orders: AccountInfo<'info>,
pub open_orders_authority: AccountInfo<'info>,
}
impl<'a> CancelOrder<'a> {
pub fn call(
self,
group: &Group,
order: serum_dex::instruction::CancelOrderInstructionV2,
) -> Result<()> {
let data = serum_dex::instruction::MarketInstruction::CancelOrderV2(order).pack();
let instruction = solana_program::instruction::Instruction {
program_id: *self.program.key,
data,
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);
solana_program::program::invoke_signed_unchecked(&instruction, &account_infos, &[seeds])?;
Ok(())
}
}

View File

@ -852,6 +852,75 @@ impl<'keypair> ClientInstruction for Serum3PlaceOrderInstruction<'keypair> {
}
}
pub struct Serum3CancelOrderInstruction<'keypair> {
pub side: u8,
pub order_id: u128,
pub account: Pubkey,
pub owner: &'keypair Keypair,
pub serum_market: Pubkey,
}
#[async_trait::async_trait(?Send)]
impl<'keypair> ClientInstruction for Serum3CancelOrderInstruction<'keypair> {
type Accounts = mango_v4::accounts::Serum3CancelOrder;
type Instruction = mango_v4::instruction::Serum3CancelOrder;
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 {
order: mango_v4::instructions::CancelOrderInstructionData(
anchor_spl::dex::serum_dex::instruction::CancelOrderInstructionV2 {
side: self.side.try_into().unwrap(),
order_id: self.order_id,
},
),
};
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 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 accounts = Self::Accounts {
group: account.group,
account: self.account,
open_orders,
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),
owner: self.owner.pubkey(),
};
let instruction = make_instruction(program_id, &accounts, instruction);
(accounts, instruction)
}
fn signers(&self) -> Vec<&Keypair> {
vec![self.owner]
}
}
pub struct Serum3SettleFundsInstruction<'keypair> {
pub account: Pubkey,
pub owner: &'keypair Keypair,

View File

@ -1,5 +1,6 @@
#![cfg(feature = "test-bpf")]
use anchor_spl::dex::serum_dex;
use solana_program::pubkey::Pubkey;
use solana_program_test::*;
use solana_sdk::{signature::Keypair, transport::TransportError};
@ -208,7 +209,33 @@ async fn test_serum() -> Result<(), TransportError> {
assert_eq!(native0, 1000);
assert_eq!(native1, 900);
// TODO: Currently has no effect
// get the order id
let open_orders_bytes = solana.get_account_data(open_orders).await.unwrap();
let open_orders_data: &serum_dex::state::OpenOrders = bytemuck::from_bytes(
&open_orders_bytes[5..5 + std::mem::size_of::<serum_dex::state::OpenOrders>()],
);
let order_id = open_orders_data.orders[0];
assert!(order_id != 0);
//
// TEST: Cancel the order
//
send_tx(
solana,
Serum3CancelOrderInstruction {
side: 0,
order_id,
account,
owner,
serum_market,
},
)
.await
.unwrap();
//
// TEST: Settle, moving the freed up funds back
//
send_tx(
solana,
Serum3SettleFundsInstruction {
@ -220,5 +247,10 @@ async fn test_serum() -> Result<(), TransportError> {
.await
.unwrap();
let native0 = account_position(solana, account, bank0).await;
let native1 = account_position(solana, account, bank1).await;
assert_eq!(native0, 1000);
assert_eq!(native1, 1000);
Ok(())
}