From db82fcbbcc3b591df5890a7f5599a10d15d8e46e Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Mon, 16 May 2022 10:34:22 +0200 Subject: [PATCH] perp cancel order instructions Signed-off-by: microwavedcola1 --- programs/mango-v4/src/instructions/mod.rs | 8 + .../instructions/perp_cancel_all_orders.rs | 44 +++++ .../perp_cancel_all_orders_by_side.rs | 48 +++++ .../src/instructions/perp_cancel_order.rs | 55 ++++++ .../perp_cancel_order_by_client_order_id.rs | 58 ++++++ programs/mango-v4/src/lib.rs | 46 +++-- programs/mango-v4/src/state/mango_account.rs | 50 ++++- programs/mango-v4/src/state/orderbook/book.rs | 55 +++++- .../tests/program_test/mango_client.rs | 118 ++++++++++- programs/mango-v4/tests/test_perp.rs | 184 ++++++++++++++++++ 10 files changed, 644 insertions(+), 22 deletions(-) create mode 100644 programs/mango-v4/src/instructions/perp_cancel_all_orders.rs create mode 100644 programs/mango-v4/src/instructions/perp_cancel_all_orders_by_side.rs create mode 100644 programs/mango-v4/src/instructions/perp_cancel_order.rs create mode 100644 programs/mango-v4/src/instructions/perp_cancel_order_by_client_order_id.rs diff --git a/programs/mango-v4/src/instructions/mod.rs b/programs/mango-v4/src/instructions/mod.rs index ed94c9f71..35b3f6b63 100644 --- a/programs/mango-v4/src/instructions/mod.rs +++ b/programs/mango-v4/src/instructions/mod.rs @@ -6,6 +6,10 @@ pub use create_group::*; pub use create_stub_oracle::*; pub use deposit::*; pub use liq_token_with_token::*; +pub use perp_cancel_all_orders::*; +pub use perp_cancel_all_orders_by_side::*; +pub use perp_cancel_order::*; +pub use perp_cancel_order_by_client_order_id::*; pub use perp_consume_events::*; pub use perp_create_market::*; pub use perp_place_order::*; @@ -28,6 +32,10 @@ mod create_stub_oracle; mod deposit; mod liq_token_with_token; mod margin_trade; +mod perp_cancel_all_orders; +mod perp_cancel_all_orders_by_side; +mod perp_cancel_order; +mod perp_cancel_order_by_client_order_id; mod perp_consume_events; mod perp_create_market; mod perp_place_order; diff --git a/programs/mango-v4/src/instructions/perp_cancel_all_orders.rs b/programs/mango-v4/src/instructions/perp_cancel_all_orders.rs new file mode 100644 index 000000000..2c4421160 --- /dev/null +++ b/programs/mango-v4/src/instructions/perp_cancel_all_orders.rs @@ -0,0 +1,44 @@ +use anchor_lang::prelude::*; + +use crate::error::MangoError; +use crate::state::{Book, Group, MangoAccount, PerpMarket}; + +#[derive(Accounts)] +pub struct PerpCancelAllOrders<'info> { + pub group: AccountLoader<'info, Group>, + + #[account( + mut, + has_one = group, + has_one = owner, + )] + pub account: AccountLoader<'info, MangoAccount>, + + #[account( + mut, + has_one = group, + has_one = bids, + has_one = asks + )] + pub perp_market: AccountLoader<'info, PerpMarket>, + #[account(mut)] + pub asks: UncheckedAccount<'info>, + #[account(mut)] + pub bids: UncheckedAccount<'info>, + + pub owner: Signer<'info>, +} + +pub fn perp_cancel_all_orders(ctx: Context, limit: u8) -> Result<()> { + let mut mango_account = ctx.accounts.account.load_mut()?; + require!(mango_account.is_bankrupt == 0, MangoError::IsBankrupt); + + let mut perp_market = ctx.accounts.perp_market.load_mut()?; + let bids = &ctx.accounts.bids.to_account_info(); + let asks = &ctx.accounts.asks.to_account_info(); + let mut book = Book::load_mut(bids, asks, &perp_market)?; + + book.cancel_all_order(&mut mango_account, &mut perp_market, limit, None)?; + + Ok(()) +} diff --git a/programs/mango-v4/src/instructions/perp_cancel_all_orders_by_side.rs b/programs/mango-v4/src/instructions/perp_cancel_all_orders_by_side.rs new file mode 100644 index 000000000..802b52569 --- /dev/null +++ b/programs/mango-v4/src/instructions/perp_cancel_all_orders_by_side.rs @@ -0,0 +1,48 @@ +use anchor_lang::prelude::*; + +use crate::error::MangoError; +use crate::state::{Book, Group, MangoAccount, PerpMarket, Side}; + +#[derive(Accounts)] +pub struct PerpCancelAllOrdersBySide<'info> { + pub group: AccountLoader<'info, Group>, + + #[account( + mut, + has_one = group, + has_one = owner, + )] + pub account: AccountLoader<'info, MangoAccount>, + + #[account( + mut, + has_one = group, + has_one = bids, + has_one = asks + )] + pub perp_market: AccountLoader<'info, PerpMarket>, + #[account(mut)] + pub asks: UncheckedAccount<'info>, + #[account(mut)] + pub bids: UncheckedAccount<'info>, + + pub owner: Signer<'info>, +} + +pub fn perp_cancel_all_orders_by_side( + ctx: Context, + side_option: Option, + limit: u8, +) -> Result<()> { + let mut mango_account = ctx.accounts.account.load_mut()?; + require!(mango_account.is_bankrupt == 0, MangoError::IsBankrupt); + + let mut perp_market = ctx.accounts.perp_market.load_mut()?; + let bids = &ctx.accounts.bids.to_account_info(); + let asks = &ctx.accounts.asks.to_account_info(); + let mut book = Book::load_mut(bids, asks, &perp_market)?; + + book.cancel_all_order(&mut mango_account, &mut perp_market, limit, side_option)?; + + Ok(()) +} diff --git a/programs/mango-v4/src/instructions/perp_cancel_order.rs b/programs/mango-v4/src/instructions/perp_cancel_order.rs new file mode 100644 index 000000000..c18f19701 --- /dev/null +++ b/programs/mango-v4/src/instructions/perp_cancel_order.rs @@ -0,0 +1,55 @@ +use anchor_lang::prelude::*; + +use crate::error::MangoError; +use crate::state::{Book, Group, MangoAccount, PerpMarket}; + +#[derive(Accounts)] +pub struct PerpCancelOrder<'info> { + pub group: AccountLoader<'info, Group>, + + #[account( + mut, + has_one = group, + has_one = owner, + )] + pub account: AccountLoader<'info, MangoAccount>, + + #[account( + mut, + has_one = group, + has_one = bids, + has_one = asks + )] + pub perp_market: AccountLoader<'info, PerpMarket>, + #[account(mut)] + pub asks: UncheckedAccount<'info>, + #[account(mut)] + pub bids: UncheckedAccount<'info>, + + pub owner: Signer<'info>, +} + +pub fn perp_cancel_order(ctx: Context, order_id: i128) -> Result<()> { + let mut mango_account = ctx.accounts.account.load_mut()?; + require!(mango_account.is_bankrupt == 0, MangoError::IsBankrupt); + + let perp_market = ctx.accounts.perp_market.load_mut()?; + let bids = &ctx.accounts.bids.to_account_info(); + let asks = &ctx.accounts.asks.to_account_info(); + let mut book = Book::load_mut(bids, asks, &perp_market)?; + + let side = mango_account + .perps + .find_order_side(perp_market.perp_market_index, order_id) + .ok_or_else(|| error!(MangoError::SomeError))?; // InvalidOrderId + + let order = book.cancel_order(order_id, side)?; + require!( + order.owner == ctx.accounts.account.key(), + MangoError::SomeError // InvalidOwner + ); + + mango_account + .perps + .remove_order(order.owner_slot as usize, order.quantity) +} diff --git a/programs/mango-v4/src/instructions/perp_cancel_order_by_client_order_id.rs b/programs/mango-v4/src/instructions/perp_cancel_order_by_client_order_id.rs new file mode 100644 index 000000000..92fde4544 --- /dev/null +++ b/programs/mango-v4/src/instructions/perp_cancel_order_by_client_order_id.rs @@ -0,0 +1,58 @@ +use anchor_lang::prelude::*; + +use crate::error::MangoError; +use crate::state::{Book, Group, MangoAccount, PerpMarket}; + +#[derive(Accounts)] +pub struct PerpCancelOrderByClientOrderId<'info> { + pub group: AccountLoader<'info, Group>, + + #[account( + mut, + has_one = group, + has_one = owner, + )] + pub account: AccountLoader<'info, MangoAccount>, + + #[account( + mut, + has_one = group, + has_one = bids, + has_one = asks + )] + pub perp_market: AccountLoader<'info, PerpMarket>, + #[account(mut)] + pub asks: UncheckedAccount<'info>, + #[account(mut)] + pub bids: UncheckedAccount<'info>, + + pub owner: Signer<'info>, +} + +pub fn perp_cancel_order_by_client_order_id( + ctx: Context, + client_order_id: u64, +) -> Result<()> { + let mut mango_account = ctx.accounts.account.load_mut()?; + require!(mango_account.is_bankrupt == 0, MangoError::IsBankrupt); + + let perp_market = ctx.accounts.perp_market.load_mut()?; + let bids = &ctx.accounts.bids.to_account_info(); + let asks = &ctx.accounts.asks.to_account_info(); + let mut book = Book::load_mut(bids, asks, &perp_market)?; + + let (order_id, side) = mango_account + .perps + .find_order_with_client_order_id(perp_market.perp_market_index, client_order_id) + .ok_or_else(|| error!(MangoError::SomeError))?; + + let order = book.cancel_order(order_id, side)?; + require!( + order.owner == ctx.accounts.account.key(), + MangoError::SomeError // InvalidOwner + ); + + mango_account + .perps + .remove_order(order.owner_slot as usize, order.quantity) +} diff --git a/programs/mango-v4/src/lib.rs b/programs/mango-v4/src/lib.rs index 1b96a78ca..e5f990099 100644 --- a/programs/mango-v4/src/lib.rs +++ b/programs/mango-v4/src/lib.rs @@ -70,6 +70,8 @@ pub mod mango_v4 { instructions::create_account(ctx, account_num, name) } + // TODO set delegate + pub fn close_account(ctx: Context) -> Result<()> { instructions::close_account(ctx) } @@ -107,6 +109,8 @@ pub mod mango_v4 { /// Serum /// + // TODO deposit/withdraw msrm + pub fn serum3_register_market( ctx: Context, market_index: Serum3MarketIndex, @@ -115,6 +119,8 @@ pub mod mango_v4 { instructions::serum3_register_market(ctx, market_index, name) } + // TODO serum3_change_spot_market_params + pub fn serum3_create_open_orders(ctx: Context) -> Result<()> { instructions::serum3_create_open_orders(ctx) } @@ -163,6 +169,8 @@ pub mod mango_v4 { instructions::serum3_liq_force_cancel_orders(ctx, limit) } + // TODO serum3_cancel_all_spot_orders + pub fn liq_token_with_token( ctx: Context, asset_token_index: TokenIndex, @@ -216,6 +224,8 @@ pub mod mango_v4 { ) } + // TODO perp_change_perp_market_params + #[allow(clippy::too_many_arguments)] pub fn perp_place_order( ctx: Context, @@ -241,23 +251,35 @@ pub mod mango_v4 { ) } + pub fn perp_cancel_order(ctx: Context, order_id: i128) -> Result<()> { + instructions::perp_cancel_order(ctx, order_id) + } + + pub fn perp_cancel_order_by_client_order_id( + ctx: Context, + client_order_id: u64, + ) -> Result<()> { + instructions::perp_cancel_order_by_client_order_id(ctx, client_order_id) + } + + pub fn perp_cancel_all_orders(ctx: Context, limit: u8) -> Result<()> { + instructions::perp_cancel_all_orders(ctx, limit) + } + + pub fn perp_cancel_all_orders_by_side( + ctx: Context, + side_option: Option, + limit: u8, + ) -> Result<()> { + instructions::perp_cancel_all_orders_by_side(ctx, side_option, limit) + } + pub fn perp_consume_events(ctx: Context, limit: usize) -> Result<()> { instructions::perp_consume_events(ctx, limit) } // TODO - // set delegate - - // serum3_cancel_all_spot_orders - // serum3_change_spot_market_params - - // msrm - - // perp_cancel_order - // perp_cancel_order_by_client_id - // perp_cancel_all - // perp_cancel_all_side // perp_force_cancel_order // liquidate_token_and_perp @@ -270,7 +292,7 @@ pub mod mango_v4 { /// /// benchmark /// - /// + pub fn benchmark(ctx: Context) -> Result<()> { instructions::benchmark(ctx) } diff --git a/programs/mango-v4/src/state/mango_account.rs b/programs/mango-v4/src/state/mango_account.rs index 85df7061f..c75ee0d24 100644 --- a/programs/mango-v4/src/state/mango_account.rs +++ b/programs/mango-v4/src/state/mango_account.rs @@ -277,7 +277,6 @@ impl MangoAccountSerum3 { } #[zero_copy] -#[derive(Debug)] pub struct PerpAccount { pub market_index: PerpMarketIndex, pub reserved: [u8; 6], @@ -304,6 +303,20 @@ pub struct PerpAccount { pub taker_base_lots: i64, pub taker_quote_lots: i64, } + +impl std::fmt::Debug for PerpAccount { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("PerpAccount") + .field("market_index", &self.market_index) + .field("base_position_lots", &self.base_position_lots) + .field("quote_position_native", &self.quote_position_native) + .field("bids_base_lots", &self.bids_base_lots) + .field("asks_base_lots", &self.asks_base_lots) + .field("taker_base_lots", &self.taker_base_lots) + .field("taker_quote_lots", &self.taker_quote_lots) + .finish() + } +} const_assert_eq!(size_of::(), 8 + 8 * 5 + 16); const_assert_eq!(size_of::() % 8, 0); @@ -366,7 +379,7 @@ pub struct MangoAccountPerps { pub order_market: [PerpMarketIndex; MAX_PERP_OPEN_ORDERS], pub order_side: [Side; MAX_PERP_OPEN_ORDERS], // TODO: storing enums isn't POD pub order_id: [i128; MAX_PERP_OPEN_ORDERS], - pub order_client_id: [u64; MAX_PERP_OPEN_ORDERS], + pub client_order_id: [u64; MAX_PERP_OPEN_ORDERS], } const_assert_eq!( size_of::(), @@ -412,9 +425,9 @@ impl std::fmt::Debug for MangoAccountPerps { .collect::>(), ) .field( - "order_client_id", + "client_order_id", &self - .order_client_id + .client_order_id .iter() .filter(|value| **value != 0) .collect::>(), @@ -430,7 +443,7 @@ impl MangoAccountPerps { order_market: [FREE_ORDER_SLOT; MAX_PERP_OPEN_ORDERS], order_side: [Side::Bid; MAX_PERP_OPEN_ORDERS], order_id: [0; MAX_PERP_OPEN_ORDERS], - order_client_id: [0; MAX_PERP_OPEN_ORDERS], + client_order_id: [0; MAX_PERP_OPEN_ORDERS], } } @@ -495,7 +508,7 @@ impl MangoAccountPerps { self.order_market[slot] = perp_market_index; self.order_side[slot] = side; self.order_id[slot] = order.key; - self.order_client_id[slot] = order.client_order_id; + self.client_order_id[slot] = order.client_order_id; Ok(()) } @@ -521,10 +534,9 @@ impl MangoAccountPerps { // release space self.order_market[slot] = FREE_ORDER_SLOT; - // TODO OPT - remove these; unnecessary self.order_side[slot] = Side::Bid; self.order_id[slot] = 0i128; - self.order_client_id[slot] = 0u64; + self.client_order_id[slot] = 0u64; Ok(()) } @@ -584,6 +596,28 @@ impl MangoAccountPerps { pa.quote_position_native += quote; Ok(()) } + + pub fn find_order_with_client_order_id( + &self, + market_index: PerpMarketIndex, + client_order_id: u64, + ) -> Option<(i128, Side)> { + for i in 0..MAX_PERP_OPEN_ORDERS { + if self.order_market[i] == market_index && self.client_order_id[i] == client_order_id { + return Some((self.order_id[i], self.order_side[i])); + } + } + None + } + + pub fn find_order_side(&self, market_index: PerpMarketIndex, order_id: i128) -> Option { + for i in 0..MAX_PERP_OPEN_ORDERS { + if self.order_market[i] == market_index && self.order_id[i] == order_id { + return Some(self.order_side[i]); + } + } + None + } } impl Default for MangoAccountPerps { diff --git a/programs/mango-v4/src/state/orderbook/book.rs b/programs/mango-v4/src/state/orderbook/book.rs index b865b995c..55dcc2c0c 100644 --- a/programs/mango-v4/src/state/orderbook/book.rs +++ b/programs/mango-v4/src/state/orderbook/book.rs @@ -4,7 +4,8 @@ use crate::{ error::MangoError, state::{ orderbook::{bookside::BookSide, nodes::LeafNode}, - EventQueue, MangoAccountPerps, PerpMarket, + EventQueue, MangoAccount, MangoAccountPerps, PerpMarket, FREE_ORDER_SLOT, + MAX_PERP_OPEN_ORDERS, }, util::LoadZeroCopy, }; @@ -306,6 +307,7 @@ impl<'a> Book<'a> { // If there are still quantity unmatched, place on the book let book_base_quantity = remaining_base_lots.min(remaining_quote_lots / price_lots); + msg!("{:?}", post_allowed); if post_allowed && book_base_quantity > 0 { // Drop an expired order if possible let bookside = self.get_bookside(side); @@ -378,6 +380,57 @@ impl<'a> Book<'a> { Ok(()) } + + pub fn cancel_all_order( + &mut self, + mango_account: &mut MangoAccount, + perp_market: &mut PerpMarket, + mut limit: u8, + side_to_cancel_option: Option, + ) -> Result<()> { + for i in 0..MAX_PERP_OPEN_ORDERS { + if mango_account.perps.order_market[i] == FREE_ORDER_SLOT + || mango_account.perps.order_market[i] != perp_market.perp_market_index + { + continue; + } + + let order_id = mango_account.perps.order_id[i]; + let order_side = mango_account.perps.order_side[i]; + + if let Some(side_to_cancel) = side_to_cancel_option { + if side_to_cancel != order_side { + continue; + } + } + + if let Ok(leaf_node) = self.cancel_order(order_id, order_side) { + mango_account + .perps + .remove_order(leaf_node.owner_slot as usize, leaf_node.quantity)? + }; + + limit = limit - 1; + if limit == 0 { + break; + } + } + + Ok(()) + } + + pub fn cancel_order(&mut self, order_id: i128, side: Side) -> Result { + match side { + Side::Bid => self + .bids + .remove_by_key(order_id) + .ok_or_else(|| error!(MangoError::SomeError)), // InvalidOrderId + Side::Ask => self + .asks + .remove_by_key(order_id) + .ok_or_else(|| error!(MangoError::SomeError)), // InvalidOrderId + } + } } /// Apply taker fees to the taker account and update the markets' fees_accrued for diff --git a/programs/mango-v4/tests/program_test/mango_client.rs b/programs/mango-v4/tests/program_test/mango_client.rs index 77e4c5b76..752d59f3d 100644 --- a/programs/mango-v4/tests/program_test/mango_client.rs +++ b/programs/mango-v4/tests/program_test/mango_client.rs @@ -1356,6 +1356,7 @@ pub struct PerpPlaceOrderInstruction<'keypair> { pub price_lots: i64, pub max_base_lots: i64, pub max_quote_lots: i64, + pub client_order_id: u64, } #[async_trait::async_trait(?Send)] impl<'keypair> ClientInstruction for PerpPlaceOrderInstruction<'keypair> { @@ -1371,7 +1372,7 @@ impl<'keypair> ClientInstruction for PerpPlaceOrderInstruction<'keypair> { price_lots: self.price_lots, max_base_lots: self.max_base_lots, max_quote_lots: self.max_quote_lots, - client_order_id: u64::MAX, + client_order_id: self.client_order_id, order_type: OrderType::Limit, expiry_timestamp: 0, limit: 1, @@ -1395,6 +1396,121 @@ impl<'keypair> ClientInstruction for PerpPlaceOrderInstruction<'keypair> { vec![self.owner] } } + +pub struct PerpCancelOrderInstruction<'keypair> { + pub group: Pubkey, + pub account: Pubkey, + pub perp_market: Pubkey, + pub asks: Pubkey, + pub bids: Pubkey, + pub owner: &'keypair Keypair, + pub order_id: i128, +} +#[async_trait::async_trait(?Send)] +impl<'keypair> ClientInstruction for PerpCancelOrderInstruction<'keypair> { + type Accounts = mango_v4::accounts::PerpCancelOrder; + type Instruction = mango_v4::instruction::PerpCancelOrder; + async fn to_instruction( + &self, + _loader: impl ClientAccountLoader + 'async_trait, + ) -> (Self::Accounts, instruction::Instruction) { + let program_id = mango_v4::id(); + let instruction = Self::Instruction { + order_id: self.order_id, + }; + let accounts = Self::Accounts { + group: self.group, + account: self.account, + perp_market: self.perp_market, + asks: self.asks, + bids: self.bids, + owner: self.owner.pubkey(), + }; + + let instruction = make_instruction(program_id, &accounts, instruction); + (accounts, instruction) + } + + fn signers(&self) -> Vec<&Keypair> { + vec![self.owner] + } +} + +pub struct PerpCancelOrderByClientOrderIdInstruction<'keypair> { + pub group: Pubkey, + pub account: Pubkey, + pub perp_market: Pubkey, + pub asks: Pubkey, + pub bids: Pubkey, + pub owner: &'keypair Keypair, + pub client_order_id: u64, +} +#[async_trait::async_trait(?Send)] +impl<'keypair> ClientInstruction for PerpCancelOrderByClientOrderIdInstruction<'keypair> { + type Accounts = mango_v4::accounts::PerpCancelOrderByClientOrderId; + type Instruction = mango_v4::instruction::PerpCancelOrderByClientOrderId; + async fn to_instruction( + &self, + _loader: impl ClientAccountLoader + 'async_trait, + ) -> (Self::Accounts, instruction::Instruction) { + let program_id = mango_v4::id(); + let instruction = Self::Instruction { + client_order_id: self.client_order_id, + }; + let accounts = Self::Accounts { + group: self.group, + account: self.account, + perp_market: self.perp_market, + asks: self.asks, + bids: self.bids, + owner: self.owner.pubkey(), + }; + + let instruction = make_instruction(program_id, &accounts, instruction); + (accounts, instruction) + } + + fn signers(&self) -> Vec<&Keypair> { + vec![self.owner] + } +} + +pub struct PerpCancelAllOrdersInstruction<'keypair> { + pub group: Pubkey, + pub account: Pubkey, + pub perp_market: Pubkey, + pub asks: Pubkey, + pub bids: Pubkey, + pub owner: &'keypair Keypair, +} +#[async_trait::async_trait(?Send)] +impl<'keypair> ClientInstruction for PerpCancelAllOrdersInstruction<'keypair> { + type Accounts = mango_v4::accounts::PerpCancelAllOrders; + type Instruction = mango_v4::instruction::PerpCancelAllOrders; + async fn to_instruction( + &self, + _loader: impl ClientAccountLoader + 'async_trait, + ) -> (Self::Accounts, instruction::Instruction) { + let program_id = mango_v4::id(); + let instruction = Self::Instruction { limit: 5 }; + let accounts = Self::Accounts { + group: self.group, + account: self.account, + perp_market: self.perp_market, + asks: self.asks, + bids: self.bids, + owner: self.owner.pubkey(), + }; + + let instruction = make_instruction(program_id, &accounts, instruction); + (accounts, instruction) + } + + fn signers(&self) -> Vec<&Keypair> { + vec![self.owner] + } +} + pub struct PerpConsumeEventsInstruction { pub group: Pubkey, pub perp_market: Pubkey, diff --git a/programs/mango-v4/tests/test_perp.rs b/programs/mango-v4/tests/test_perp.rs index 180cee254..eec2a18a2 100644 --- a/programs/mango-v4/tests/test_perp.rs +++ b/programs/mango-v4/tests/test_perp.rs @@ -1,5 +1,6 @@ #![cfg(all(feature = "test-bpf"))] +use anchor_lang::prelude::Pubkey; use fixed_macro::types::I80F48; use mango_v4::state::*; use program_test::*; @@ -168,6 +169,9 @@ async fn test_perp() -> Result<(), BanksClientError> { perp_market.native_price_to_lot(I80F48!(1)) }; + // + // Place and cancel order with order_id + // send_tx( solana, PerpPlaceOrderInstruction { @@ -183,6 +187,174 @@ async fn test_perp() -> Result<(), BanksClientError> { price_lots, max_base_lots: 1, max_quote_lots: i64::MAX, + client_order_id: 0, + }, + ) + .await + .unwrap(); + + let order_id_to_cancel = solana + .get_account::(account_0) + .await + .perps + .order_id[0]; + send_tx( + solana, + PerpCancelOrderInstruction { + group, + account: account_0, + perp_market, + asks, + bids, + owner, + order_id: order_id_to_cancel, + }, + ) + .await + .unwrap(); + + assert_no_perp_orders(solana, account_0).await; + + // + // Place and cancel order with client_order_id + // + send_tx( + solana, + PerpPlaceOrderInstruction { + group, + account: account_0, + perp_market, + asks, + bids, + event_queue, + oracle: tokens[0].oracle, + owner, + side: Side::Bid, + price_lots, + max_base_lots: 1, + max_quote_lots: i64::MAX, + client_order_id: 1, + }, + ) + .await + .unwrap(); + + send_tx( + solana, + PerpCancelOrderByClientOrderIdInstruction { + group, + account: account_0, + perp_market, + asks, + bids, + owner, + client_order_id: 1, + }, + ) + .await + .unwrap(); + + assert_no_perp_orders(solana, account_0).await; + + // + // Place and cancel all orders + // + send_tx( + solana, + PerpPlaceOrderInstruction { + group, + account: account_0, + perp_market, + asks, + bids, + event_queue, + oracle: tokens[0].oracle, + owner, + side: Side::Bid, + price_lots, + max_base_lots: 1, + max_quote_lots: i64::MAX, + client_order_id: 2, + }, + ) + .await + .unwrap(); + send_tx( + solana, + PerpPlaceOrderInstruction { + group, + account: account_0, + perp_market, + asks, + bids, + event_queue, + oracle: tokens[0].oracle, + owner, + side: Side::Bid, + price_lots, + max_base_lots: 1, + max_quote_lots: i64::MAX, + client_order_id: 3, + }, + ) + .await + .unwrap(); + send_tx( + solana, + PerpPlaceOrderInstruction { + group, + account: account_0, + perp_market, + asks, + bids, + event_queue, + oracle: tokens[0].oracle, + owner, + side: Side::Bid, + price_lots, + max_base_lots: 1, + max_quote_lots: i64::MAX, + client_order_id: 4, + }, + ) + .await + .unwrap(); + + send_tx( + solana, + PerpCancelAllOrdersInstruction { + group, + account: account_0, + perp_market, + asks, + bids, + owner, + }, + ) + .await + .unwrap(); + + assert_no_perp_orders(solana, account_0).await; + + // + // Place a bid, corresponding ask, and consume event + // + send_tx( + solana, + PerpPlaceOrderInstruction { + group, + account: account_0, + perp_market, + asks, + bids, + event_queue, + oracle: tokens[0].oracle, + owner, + side: Side::Bid, + price_lots, + max_base_lots: 1, + max_quote_lots: i64::MAX, + client_order_id: 5, }, ) .await @@ -203,6 +375,7 @@ async fn test_perp() -> Result<(), BanksClientError> { price_lots, max_base_lots: 1, max_quote_lots: i64::MAX, + client_order_id: 6, }, ) .await @@ -230,3 +403,14 @@ async fn test_perp() -> Result<(), BanksClientError> { Ok(()) } + +async fn assert_no_perp_orders(solana: &SolanaCookie, account_0: Pubkey) { + let mango_account_0 = solana.get_account::(account_0).await; + + for i in 0..MAX_PERP_OPEN_ORDERS { + assert!(mango_account_0.perps.order_id[i] == 0); + assert!(mango_account_0.perps.order_side[i] == Side::Bid); + assert!(mango_account_0.perps.client_order_id[i] == 0); + assert!(mango_account_0.perps.order_market[i] == FREE_ORDER_SLOT); + } +}