Add max_ts to NewOrderV3 and bulk cancellation instruction (#230)
* Add CancelOrdersByClientIds instruction to allow users to cancel orders in bulk * Added max_ts to NewOrderV3 to prevent orders from being placed on the orderbook if the current unix timestamp exceeds max_ts
This commit is contained in:
parent
9a6f6abcb0
commit
4bddbbc364
|
@ -8,6 +8,7 @@ use std::convert::identity;
|
|||
use std::mem::size_of;
|
||||
use std::num::NonZeroU64;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::SystemTime;
|
||||
use std::{thread, time};
|
||||
|
||||
use anyhow::{format_err, Result};
|
||||
|
@ -42,6 +43,7 @@ use serum_common::client::rpc::{
|
|||
use serum_common::client::Cluster;
|
||||
use serum_dex::instruction::{
|
||||
cancel_order_by_client_order_id as cancel_order_by_client_order_id_ix,
|
||||
cancel_orders_by_client_order_ids as cancel_orders_by_client_order_ids_ix,
|
||||
close_open_orders as close_open_orders_ix, init_open_orders as init_open_orders_ix,
|
||||
MarketInstruction, NewOrderInstructionV3, SelfTradeBehavior,
|
||||
};
|
||||
|
@ -865,7 +867,11 @@ fn whole_shebang(client: &RpcClient, program_id: &Pubkey, payer: &Keypair) -> Re
|
|||
debug_println!("Initializing open orders");
|
||||
init_open_orders(client, program_id, payer, &market_keys, &mut orders)?;
|
||||
|
||||
debug_println!("Placing bid...");
|
||||
debug_println!("Placing successful bid...");
|
||||
let now = SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs() as i64;
|
||||
place_order(
|
||||
client,
|
||||
program_id,
|
||||
|
@ -882,44 +888,84 @@ fn whole_shebang(client: &RpcClient, program_id: &Pubkey, payer: &Keypair) -> Re
|
|||
client_order_id: 019269,
|
||||
self_trade_behavior: SelfTradeBehavior::DecrementTake,
|
||||
limit: std::u16::MAX,
|
||||
max_ts: now + 20,
|
||||
},
|
||||
)?;
|
||||
|
||||
debug_println!("Bid account: {}", orders.unwrap());
|
||||
|
||||
debug_println!("Placing offer...");
|
||||
let mut orders = None;
|
||||
place_order(
|
||||
debug_println!("Placing failing bid...");
|
||||
|
||||
let result = place_order(
|
||||
client,
|
||||
program_id,
|
||||
payer,
|
||||
&coin_wallet.pubkey(),
|
||||
&pc_wallet.pubkey(),
|
||||
&market_keys,
|
||||
&mut orders,
|
||||
NewOrderInstructionV3 {
|
||||
side: Side::Ask,
|
||||
limit_price: NonZeroU64::new(499).unwrap(),
|
||||
side: Side::Bid,
|
||||
limit_price: NonZeroU64::new(500).unwrap(),
|
||||
max_coin_qty: NonZeroU64::new(1_000).unwrap(),
|
||||
max_native_pc_qty_including_fees: NonZeroU64::new(std::u64::MAX).unwrap(),
|
||||
max_native_pc_qty_including_fees: NonZeroU64::new(500_000).unwrap(),
|
||||
order_type: OrderType::Limit,
|
||||
limit: std::u16::MAX,
|
||||
client_order_id: 019269,
|
||||
self_trade_behavior: SelfTradeBehavior::DecrementTake,
|
||||
client_order_id: 985982,
|
||||
limit: std::u16::MAX,
|
||||
max_ts: now - 5,
|
||||
},
|
||||
);
|
||||
assert!(result.is_err());
|
||||
|
||||
debug_println!("Placing 3 offers...");
|
||||
|
||||
let mut orders = None;
|
||||
|
||||
for i in 0..3 {
|
||||
place_order(
|
||||
client,
|
||||
program_id,
|
||||
payer,
|
||||
&coin_wallet.pubkey(),
|
||||
&market_keys,
|
||||
&mut orders,
|
||||
NewOrderInstructionV3 {
|
||||
side: Side::Ask,
|
||||
limit_price: NonZeroU64::new(499).unwrap(),
|
||||
max_coin_qty: NonZeroU64::new(1_000).unwrap(),
|
||||
max_native_pc_qty_including_fees: NonZeroU64::new(std::u64::MAX).unwrap(),
|
||||
order_type: OrderType::Limit,
|
||||
limit: std::u16::MAX,
|
||||
self_trade_behavior: SelfTradeBehavior::DecrementTake,
|
||||
// 985982, 985983, 985984
|
||||
client_order_id: 985982 + i,
|
||||
max_ts: i64::MAX,
|
||||
},
|
||||
)?;
|
||||
}
|
||||
|
||||
debug_println!("Ask account: {}", orders.unwrap());
|
||||
|
||||
// Cancel 1st open offer (985982), 3rd open offer (985984), and fake orders
|
||||
cancel_orders_by_client_order_ids(
|
||||
client,
|
||||
program_id,
|
||||
payer,
|
||||
&market_keys,
|
||||
&orders.unwrap(),
|
||||
[985985, 985982, 100, 985984, 234, 0, 985984, 0],
|
||||
)?;
|
||||
|
||||
// Cancel the open order so that we can close it later.
|
||||
// Cancel the 2nd open order so that we can close it later.
|
||||
cancel_order_by_client_order_id(
|
||||
client,
|
||||
program_id,
|
||||
payer,
|
||||
&market_keys,
|
||||
&orders.unwrap(),
|
||||
985982,
|
||||
985983,
|
||||
)?;
|
||||
|
||||
debug_println!("Ask account: {}", orders.unwrap());
|
||||
|
||||
debug_println!("Consuming events in 15s ...");
|
||||
std::thread::sleep(std::time::Duration::new(15, 0));
|
||||
consume_events(
|
||||
|
@ -973,8 +1019,8 @@ pub fn cancel_order_by_client_order_id(
|
|||
|
||||
debug_println!("Canceling order by client order id instruction ...");
|
||||
let result = simulate_transaction(client, &txn, true, CommitmentConfig::confirmed())?;
|
||||
debug_println!("{:#?}", result.value.logs);
|
||||
if let Some(e) = result.value.err {
|
||||
debug_println!("{:#?}", result.value.logs);
|
||||
return Err(format_err!("simulate_transaction error: {:?}", e));
|
||||
}
|
||||
|
||||
|
@ -982,6 +1028,39 @@ pub fn cancel_order_by_client_order_id(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn cancel_orders_by_client_order_ids(
|
||||
client: &RpcClient,
|
||||
program_id: &Pubkey,
|
||||
owner: &Keypair,
|
||||
state: &MarketPubkeys,
|
||||
orders: &Pubkey,
|
||||
client_order_ids: [u64; 8],
|
||||
) -> Result<()> {
|
||||
let ixs = &[cancel_orders_by_client_order_ids_ix(
|
||||
program_id,
|
||||
&state.market,
|
||||
&state.bids,
|
||||
&state.asks,
|
||||
orders,
|
||||
&owner.pubkey(),
|
||||
&state.event_q,
|
||||
client_order_ids,
|
||||
)?];
|
||||
let recent_hash = client.get_latest_blockhash()?;
|
||||
let txn = Transaction::new_signed_with_payer(ixs, Some(&owner.pubkey()), &[owner], recent_hash);
|
||||
|
||||
debug_println!("Canceling orders by client order ids instruction ...");
|
||||
let result = simulate_transaction(client, &txn, true, CommitmentConfig::confirmed())?;
|
||||
debug_println!("{:#?}", result.value.logs);
|
||||
if let Some(e) = result.value.err {
|
||||
return Err(format_err!("simulate_transaction error: {:?}", e));
|
||||
}
|
||||
|
||||
send_txn(client, &txn, false)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn close_open_orders(
|
||||
client: &RpcClient,
|
||||
program_id: &Pubkey,
|
||||
|
|
|
@ -789,7 +789,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serum_dex"
|
||||
version = "0.4.0"
|
||||
version = "0.5.5"
|
||||
dependencies = [
|
||||
"arbitrary",
|
||||
"arrayref",
|
||||
|
|
|
@ -11,3 +11,4 @@ cc f8cb82aea20fce7b1585efea458f5a8d08c967df78da4720197145e6fa587f80 # shrinks to
|
|||
cc 9b33e7cb4c2b4e3a34b7b6fdeb7feb49cee9196047870cff53dac8b9483e9245 # shrinks to inst = NewOrder(NewOrderInstruction { side: Bid, limit_price: 1, max_qty: 1, order_type: Limit, client_id: 0 })
|
||||
cc 2a279ee739ee4b89416a838543703ace70b994031ac37da2f863025d828bfca6 # shrinks to inst = NewOrder(NewOrderInstruction { side: Bid, limit_price: 1, max_qty: 1, order_type: Limit, client_id: 0 })
|
||||
cc 4cd7226dfe9ac17741d91c4a3d8fbd22116654b0ea54c82f2357882404c281b4 # shrinks to inst = CancelOrderByClientIdV2(0)
|
||||
cc c7e67c88e2301173b27e072b861860fa44b86a7ec4588e2cabd706f1d784118a # shrinks to inst = CancelOrdersByClientIds([0, 0, 0, 0, 0, 0, 0, 0])
|
||||
|
|
|
@ -107,10 +107,11 @@ pub enum DexErrorCode {
|
|||
RentNotProvided,
|
||||
OrdersNotRentExempt,
|
||||
OrderNotFound,
|
||||
OrderNotYours,
|
||||
OrderNotYours = 60,
|
||||
|
||||
WouldSelfTrade,
|
||||
InvalidOpenOrdersAuthority,
|
||||
OrderMaxTimestampExceeded,
|
||||
|
||||
Unknown = 1000,
|
||||
|
||||
|
|
|
@ -126,6 +126,7 @@ pub struct NewOrderInstructionV3 {
|
|||
pub order_type: OrderType,
|
||||
pub client_order_id: u64,
|
||||
pub limit: u16,
|
||||
pub max_ts: i64,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)]
|
||||
|
@ -222,7 +223,7 @@ impl SendTakeInstruction {
|
|||
}
|
||||
|
||||
impl NewOrderInstructionV3 {
|
||||
fn unpack(data: &[u8; 46]) -> Option<Self> {
|
||||
fn unpack(data: &[u8; 54]) -> Option<Self> {
|
||||
let (
|
||||
&side_arr,
|
||||
&price_arr,
|
||||
|
@ -232,7 +233,8 @@ impl NewOrderInstructionV3 {
|
|||
&otype_arr,
|
||||
&client_order_id_bytes,
|
||||
&limit_arr,
|
||||
) = array_refs![data, 4, 8, 8, 8, 4, 4, 8, 2];
|
||||
&max_ts,
|
||||
) = array_refs![data, 4, 8, 8, 8, 4, 4, 8, 2, 8];
|
||||
|
||||
let side = 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))?;
|
||||
|
@ -249,6 +251,7 @@ impl NewOrderInstructionV3 {
|
|||
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 max_ts = i64::from_le_bytes(max_ts);
|
||||
|
||||
Some(NewOrderInstructionV3 {
|
||||
side,
|
||||
|
@ -259,6 +262,7 @@ impl NewOrderInstructionV3 {
|
|||
order_type,
|
||||
client_order_id,
|
||||
limit,
|
||||
max_ts,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -467,6 +471,13 @@ pub enum MarketInstruction {
|
|||
/// accounts.len() - 2 `[writable]` event queue
|
||||
/// accounts.len() - 1 `[signer]` crank authority
|
||||
ConsumeEventsPermissioned(u16),
|
||||
/// 0. `[writable]` market
|
||||
/// 1. `[writable]` bids
|
||||
/// 2. `[writable]` asks
|
||||
/// 3. `[writable]` OpenOrders
|
||||
/// 4. `[signer]` the OpenOrders owner
|
||||
/// 5. `[writable]` event_q
|
||||
CancelOrdersByClientIds([u64; 8]),
|
||||
}
|
||||
|
||||
impl MarketInstruction {
|
||||
|
@ -475,7 +486,7 @@ impl MarketInstruction {
|
|||
}
|
||||
|
||||
pub fn unpack(versioned_bytes: &[u8]) -> Option<Self> {
|
||||
if versioned_bytes.len() < 5 || versioned_bytes.len() > 58 {
|
||||
if versioned_bytes.len() < 5 || versioned_bytes.len() > 69 {
|
||||
return None;
|
||||
}
|
||||
let (&[version], &discrim, data) = array_refs![versioned_bytes, 1, 4; ..;];
|
||||
|
@ -542,8 +553,13 @@ impl MarketInstruction {
|
|||
.ok()?;
|
||||
v1_instr.add_self_trade_behavior(self_trade_behavior)
|
||||
}),
|
||||
(10, 46) => MarketInstruction::NewOrderV3({
|
||||
let data_arr = array_ref![data, 0, 46];
|
||||
(10, len) if len == 46 || len == 54 => MarketInstruction::NewOrderV3({
|
||||
let extended_data = match len {
|
||||
46 => Some([data, &i64::MAX.to_le_bytes()].concat()),
|
||||
54 => Some(data.to_vec()),
|
||||
_ => None,
|
||||
}?;
|
||||
let data_arr = array_ref![extended_data, 0, 54];
|
||||
NewOrderInstructionV3::unpack(data_arr)?
|
||||
}),
|
||||
(11, 20) => MarketInstruction::CancelOrderV2({
|
||||
|
@ -568,6 +584,15 @@ impl MarketInstruction {
|
|||
let limit = array_ref![data, 0, 2];
|
||||
MarketInstruction::ConsumeEventsPermissioned(u16::from_le_bytes(*limit))
|
||||
}
|
||||
// At most 8 client ids, each of which is 8 bytes
|
||||
(18, len) if len % 8 == 0 && len <= 8 * 8 => {
|
||||
let mut client_ids = [0; 8];
|
||||
// convert chunks of 8 bytes to client ids
|
||||
for (chunk, client_id) in data.chunks_exact(8).zip(client_ids.iter_mut()) {
|
||||
*client_id = u64::from_le_bytes(chunk.try_into().unwrap());
|
||||
}
|
||||
MarketInstruction::CancelOrdersByClientIds(client_ids)
|
||||
}
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
@ -684,6 +709,7 @@ pub fn new_order(
|
|||
self_trade_behavior: SelfTradeBehavior,
|
||||
limit: u16,
|
||||
max_native_pc_qty_including_fees: NonZeroU64,
|
||||
max_ts: i64,
|
||||
) -> Result<Instruction, DexError> {
|
||||
let data = MarketInstruction::NewOrderV3(NewOrderInstructionV3 {
|
||||
side,
|
||||
|
@ -694,6 +720,7 @@ pub fn new_order(
|
|||
self_trade_behavior,
|
||||
limit,
|
||||
max_native_pc_qty_including_fees,
|
||||
max_ts,
|
||||
})
|
||||
.pack();
|
||||
let mut accounts = vec![
|
||||
|
@ -888,6 +915,32 @@ pub fn cancel_order_by_client_order_id(
|
|||
})
|
||||
}
|
||||
|
||||
pub fn cancel_orders_by_client_order_ids(
|
||||
program_id: &Pubkey,
|
||||
market: &Pubkey,
|
||||
market_bids: &Pubkey,
|
||||
market_asks: &Pubkey,
|
||||
open_orders_account: &Pubkey,
|
||||
open_orders_account_owner: &Pubkey,
|
||||
event_queue: &Pubkey,
|
||||
client_order_ids: [u64; 8],
|
||||
) -> Result<Instruction, DexError> {
|
||||
let data = MarketInstruction::CancelOrdersByClientIds(client_order_ids).pack();
|
||||
let accounts: Vec<AccountMeta> = vec![
|
||||
AccountMeta::new(*market, false),
|
||||
AccountMeta::new(*market_bids, false),
|
||||
AccountMeta::new(*market_asks, false),
|
||||
AccountMeta::new(*open_orders_account, false),
|
||||
AccountMeta::new_readonly(*open_orders_account_owner, true),
|
||||
AccountMeta::new(*event_queue, false),
|
||||
];
|
||||
Ok(Instruction {
|
||||
program_id: *program_id,
|
||||
data,
|
||||
accounts,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn disable_market(
|
||||
program_id: &Pubkey,
|
||||
market: &Pubkey,
|
||||
|
|
|
@ -7,7 +7,6 @@ use proptest_derive::Arbitrary;
|
|||
use serde::{Deserialize, Serialize};
|
||||
#[cfg(feature = "program")]
|
||||
use solana_program::msg;
|
||||
use solana_program::pubkey::Pubkey;
|
||||
|
||||
use crate::critbit::SlabTreeError;
|
||||
use crate::error::{DexErrorCode, DexResult, SourceFileId};
|
||||
|
|
123
dex/src/state.rs
123
dex/src/state.rs
|
@ -2125,6 +2125,73 @@ pub(crate) mod account_parser {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct CancelOrdersByClientIdsArgs<'a, 'b: 'a> {
|
||||
pub client_order_ids: &'a [NonZeroU64],
|
||||
pub open_orders_address: [u64; 4],
|
||||
pub open_orders: &'a mut OpenOrders,
|
||||
pub open_orders_signer: SignerAccount<'a, 'b>,
|
||||
pub order_book_state: OrderBookState<'a>,
|
||||
pub event_q: EventQueue<'a>,
|
||||
}
|
||||
impl<'a, 'b: 'a> CancelOrdersByClientIdsArgs<'a, 'b> {
|
||||
pub fn with_parsed_args<T>(
|
||||
program_id: &'a Pubkey,
|
||||
accounts: &'a [AccountInfo<'b>],
|
||||
client_order_ids: [u64; 8],
|
||||
f: impl FnOnce(CancelOrdersByClientIdsArgs) -> DexResult<T>,
|
||||
) -> DexResult<T> {
|
||||
check_assert!(accounts.len() >= 6)?;
|
||||
#[rustfmt::skip]
|
||||
let &[
|
||||
ref market_acc,
|
||||
ref bids_acc,
|
||||
ref asks_acc,
|
||||
ref open_orders_acc,
|
||||
ref open_orders_signer_acc,
|
||||
ref event_q_acc,
|
||||
] = array_ref![accounts, 0, 6];
|
||||
|
||||
let client_order_ids = client_order_ids
|
||||
.iter()
|
||||
.cloned()
|
||||
.filter_map(NonZeroU64::new)
|
||||
.collect::<Vec<NonZeroU64>>();
|
||||
|
||||
let mut market = Market::load(market_acc, program_id, true).or(check_unreachable!())?;
|
||||
|
||||
let open_orders_signer = SignerAccount::new(open_orders_signer_acc)?;
|
||||
let mut open_orders = market.load_orders_mut(
|
||||
open_orders_acc,
|
||||
Some(open_orders_signer.inner()),
|
||||
program_id,
|
||||
None,
|
||||
None,
|
||||
)?;
|
||||
let open_orders_address = open_orders_acc.key.to_aligned_bytes();
|
||||
|
||||
let mut bids = market.load_bids_mut(bids_acc).or(check_unreachable!())?;
|
||||
let mut asks = market.load_asks_mut(asks_acc).or(check_unreachable!())?;
|
||||
|
||||
let event_q = market.load_event_queue_mut(event_q_acc)?;
|
||||
|
||||
let order_book_state = OrderBookState {
|
||||
bids: bids.deref_mut(),
|
||||
asks: asks.deref_mut(),
|
||||
market_state: market.deref_mut(),
|
||||
};
|
||||
|
||||
let args = CancelOrdersByClientIdsArgs {
|
||||
client_order_ids: client_order_ids.as_slice(),
|
||||
open_orders_address,
|
||||
open_orders: open_orders.deref_mut(),
|
||||
open_orders_signer,
|
||||
order_book_state,
|
||||
event_q,
|
||||
};
|
||||
f(args)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SettleFundsArgs<'a, 'b: 'a> {
|
||||
pub market: Market<'a>,
|
||||
pub open_orders: &'a mut OpenOrders,
|
||||
|
@ -2514,6 +2581,14 @@ impl State {
|
|||
Self::process_cancel_order_by_client_id_v2,
|
||||
)?
|
||||
}
|
||||
MarketInstruction::CancelOrdersByClientIds(client_ids) => {
|
||||
account_parser::CancelOrdersByClientIdsArgs::with_parsed_args(
|
||||
program_id,
|
||||
accounts,
|
||||
client_ids,
|
||||
Self::process_cancel_orders_by_client_ids,
|
||||
)?
|
||||
}
|
||||
MarketInstruction::DisableMarket => {
|
||||
account_parser::DisableMarketArgs::with_parsed_args(
|
||||
program_id,
|
||||
|
@ -2744,6 +2819,39 @@ impl State {
|
|||
)
|
||||
}
|
||||
|
||||
fn process_cancel_orders_by_client_ids(
|
||||
args: account_parser::CancelOrdersByClientIdsArgs,
|
||||
) -> DexResult {
|
||||
let account_parser::CancelOrdersByClientIdsArgs {
|
||||
client_order_ids,
|
||||
open_orders_address,
|
||||
open_orders,
|
||||
open_orders_signer: _,
|
||||
|
||||
mut order_book_state,
|
||||
mut event_q,
|
||||
} = args;
|
||||
|
||||
let mut orders = Vec::new();
|
||||
for (client_order_id, order_id, side) in open_orders.orders_with_client_ids() {
|
||||
if client_order_ids.contains(&client_order_id) {
|
||||
orders.push((order_id, side));
|
||||
}
|
||||
}
|
||||
|
||||
for (order_id, side) in orders {
|
||||
let _ = order_book_state.cancel_order_v2(
|
||||
side,
|
||||
open_orders_address,
|
||||
open_orders,
|
||||
order_id,
|
||||
&mut event_q,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_cancel_order_v2(args: account_parser::CancelOrderV2Args) -> DexResult {
|
||||
let account_parser::CancelOrderV2Args {
|
||||
instruction: &CancelOrderInstructionV2 { side, order_id },
|
||||
|
@ -2906,6 +3014,17 @@ impl State {
|
|||
fee_tier,
|
||||
} = args;
|
||||
|
||||
if instruction.max_ts < i64::MAX {
|
||||
// Dynamic sysvars don't work in unit tests.
|
||||
#[cfg(any(test, feature = "fuzz"))]
|
||||
let cur_ts = 1_650_000_000;
|
||||
#[cfg(not(any(test, feature = "fuzz")))]
|
||||
let cur_ts = Clock::get()?.unix_timestamp;
|
||||
if cur_ts > instruction.max_ts {
|
||||
return Err(DexErrorCode::OrderMaxTimestampExceeded.into());
|
||||
}
|
||||
}
|
||||
|
||||
let open_orders_mut = open_orders.deref_mut();
|
||||
|
||||
check_assert_eq!(req_q.header.count(), 0)?;
|
||||
|
@ -3027,6 +3146,10 @@ impl State {
|
|||
// `invoke_spl_token` will try to borrow the account info refcell,
|
||||
// which would cause an error (as there would be two borrows while
|
||||
// one of them is mutable).
|
||||
|
||||
use solana_program::clock::Clock;
|
||||
|
||||
use crate::error::DexError;
|
||||
drop(open_orders);
|
||||
|
||||
if deposit_amount != 0 {
|
||||
|
|
188
dex/src/tests.rs
188
dex/src/tests.rs
|
@ -21,6 +21,9 @@ use matching::{OrderType, Side};
|
|||
use state::gen_vault_signer_key;
|
||||
use state::{Market, MarketState, OpenOrders, State, ToAlignedBytes};
|
||||
|
||||
use crate::error::DexErrorCode;
|
||||
use crate::state::account_parser::CancelOrderByClientIdV2Args;
|
||||
|
||||
use super::*;
|
||||
|
||||
fn random_pubkey<'bump, G: rand::Rng>(_rng: &mut G, bump: &'bump Bump) -> &'bump Pubkey {
|
||||
|
@ -281,6 +284,7 @@ fn test_new_order() {
|
|||
client_order_id: 0xabcd,
|
||||
self_trade_behavior: SelfTradeBehavior::AbortTransaction,
|
||||
limit: 5,
|
||||
max_ts: i64::MAX,
|
||||
})
|
||||
.pack();
|
||||
let instruction_accounts: &[AccountInfo] = bump_vec![in ≎
|
||||
|
@ -310,6 +314,7 @@ fn test_new_order() {
|
|||
self_trade_behavior: SelfTradeBehavior::AbortTransaction,
|
||||
client_order_id: 0,
|
||||
limit: 5,
|
||||
max_ts: i64::MAX,
|
||||
})
|
||||
.pack();
|
||||
let instruction_accounts = bump_vec![in ≎
|
||||
|
@ -407,3 +412,186 @@ fn test_new_order() {
|
|||
assert_eq!(identity(open_orders_seller.native_pc_total), 399_840);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cancel_orders() {
|
||||
let mut rng = StdRng::seed_from_u64(1);
|
||||
let bump = Bump::new();
|
||||
|
||||
let accounts = setup_market(&mut rng, &bump);
|
||||
|
||||
let dex_program_id = accounts.market.owner;
|
||||
|
||||
let owner = new_sol_account(&mut rng, 1_000_000_000, &bump);
|
||||
let orders_account =
|
||||
new_dex_owned_account(&mut rng, size_of::<OpenOrders>(), dex_program_id, &bump);
|
||||
let coin_account =
|
||||
new_token_account(&mut rng, accounts.coin_mint.key, owner.key, 10_000, &bump);
|
||||
let pc_account = new_token_account(&mut rng, accounts.pc_mint.key, owner.key, 1_000_000, &bump);
|
||||
let spl_token_program = new_spl_token_program(&bump);
|
||||
|
||||
for i in 0..3 {
|
||||
let instruction_data = MarketInstruction::NewOrderV3(NewOrderInstructionV3 {
|
||||
side: Side::Bid,
|
||||
limit_price: NonZeroU64::new(10_000).unwrap(),
|
||||
max_coin_qty: NonZeroU64::new(10).unwrap(),
|
||||
max_native_pc_qty_including_fees: NonZeroU64::new(50_000).unwrap(),
|
||||
order_type: OrderType::Limit,
|
||||
// 0x123a, 0x123b, 0x123c
|
||||
client_order_id: 0x123a + i,
|
||||
self_trade_behavior: SelfTradeBehavior::AbortTransaction,
|
||||
limit: 5,
|
||||
max_ts: i64::MAX,
|
||||
})
|
||||
.pack();
|
||||
|
||||
let instruction_accounts: &[AccountInfo] = bump_vec![in ≎
|
||||
accounts.market.clone(),
|
||||
orders_account.clone(),
|
||||
accounts.req_q.clone(),
|
||||
accounts.event_q.clone(),
|
||||
accounts.bids.clone(),
|
||||
accounts.asks.clone(),
|
||||
pc_account.clone(),
|
||||
owner.clone(),
|
||||
accounts.coin_vault.clone(),
|
||||
accounts.pc_vault.clone(),
|
||||
spl_token_program.clone(),
|
||||
accounts.rent_sysvar.clone(),
|
||||
]
|
||||
.into_bump_slice();
|
||||
|
||||
State::process(dex_program_id, instruction_accounts, &instruction_data).unwrap();
|
||||
}
|
||||
|
||||
{
|
||||
let market = Market::load(&accounts.market, &dex_program_id, false).unwrap();
|
||||
assert_eq!(identity(market.pc_fees_accrued), 0);
|
||||
assert_eq!(identity(market.pc_deposits_total), 150_000);
|
||||
let open_orders = market
|
||||
.load_orders_mut(&orders_account, None, &dex_program_id, None, None)
|
||||
.unwrap();
|
||||
assert_eq!(identity(open_orders.native_coin_free), 0);
|
||||
assert_eq!(identity(open_orders.native_coin_total), 0);
|
||||
assert_eq!(identity(open_orders.native_pc_free), 0);
|
||||
assert_eq!(identity(open_orders.native_pc_total), 150_000);
|
||||
}
|
||||
|
||||
{
|
||||
// cancel 0x123a, do nothing to 0x123b, cancel 0x123c, 0x123d does not exist
|
||||
let instruction_data =
|
||||
MarketInstruction::CancelOrdersByClientIds([0x123a, 0x123d, 0, 0x123c, 0, 0, 0, 0])
|
||||
.pack();
|
||||
|
||||
let instruction_accounts: &[AccountInfo] = bump_vec![in ≎
|
||||
accounts.market.clone(),
|
||||
accounts.bids.clone(),
|
||||
accounts.asks.clone(),
|
||||
orders_account.clone(),
|
||||
owner.clone(),
|
||||
accounts.event_q.clone(),
|
||||
]
|
||||
.into_bump_slice();
|
||||
|
||||
State::process(dex_program_id, instruction_accounts, &instruction_data).unwrap();
|
||||
}
|
||||
|
||||
{
|
||||
let open_orders = Market::load(&accounts.market, &dex_program_id, false)
|
||||
.unwrap()
|
||||
.load_orders_mut(&orders_account, None, &dex_program_id, None, None)
|
||||
.unwrap();
|
||||
assert_eq!(identity(open_orders.native_coin_free), 0);
|
||||
assert_eq!(identity(open_orders.native_coin_total), 0);
|
||||
assert_eq!(identity(open_orders.native_pc_free), 100_000);
|
||||
assert_eq!(identity(open_orders.native_pc_total), 150_000);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_max_ts_order() {
|
||||
let mut rng = StdRng::seed_from_u64(1);
|
||||
let bump = Bump::new();
|
||||
|
||||
let accounts = setup_market(&mut rng, &bump);
|
||||
|
||||
let dex_program_id = accounts.market.owner;
|
||||
|
||||
let owner = new_sol_account(&mut rng, 1_000_000_000, &bump);
|
||||
let orders_account =
|
||||
new_dex_owned_account(&mut rng, size_of::<OpenOrders>(), dex_program_id, &bump);
|
||||
let coin_account =
|
||||
new_token_account(&mut rng, accounts.coin_mint.key, owner.key, 10_000, &bump);
|
||||
let pc_account = new_token_account(&mut rng, accounts.pc_mint.key, owner.key, 1_000_000, &bump);
|
||||
let spl_token_program = new_spl_token_program(&bump);
|
||||
|
||||
let instruction_data = MarketInstruction::NewOrderV3(NewOrderInstructionV3 {
|
||||
side: Side::Bid,
|
||||
limit_price: NonZeroU64::new(10_000).unwrap(),
|
||||
max_coin_qty: NonZeroU64::new(10).unwrap(),
|
||||
max_native_pc_qty_including_fees: NonZeroU64::new(50_000).unwrap(),
|
||||
order_type: OrderType::Limit,
|
||||
client_order_id: 0xabcd,
|
||||
self_trade_behavior: SelfTradeBehavior::AbortTransaction,
|
||||
limit: 5,
|
||||
max_ts: 1_649_999_999,
|
||||
})
|
||||
.pack();
|
||||
|
||||
let instruction_accounts: &[AccountInfo] = bump_vec![in ≎
|
||||
accounts.market.clone(),
|
||||
orders_account.clone(),
|
||||
accounts.req_q.clone(),
|
||||
accounts.event_q.clone(),
|
||||
accounts.bids.clone(),
|
||||
accounts.asks.clone(),
|
||||
pc_account.clone(),
|
||||
owner.clone(),
|
||||
accounts.coin_vault.clone(),
|
||||
accounts.pc_vault.clone(),
|
||||
spl_token_program.clone(),
|
||||
accounts.rent_sysvar.clone(),
|
||||
]
|
||||
.into_bump_slice();
|
||||
|
||||
{
|
||||
let result = State::process(dex_program_id, instruction_accounts, &instruction_data);
|
||||
let expected = Err(DexErrorCode::OrderMaxTimestampExceeded.into());
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
let instruction_data = MarketInstruction::NewOrderV3(NewOrderInstructionV3 {
|
||||
side: Side::Bid,
|
||||
limit_price: NonZeroU64::new(10_000).unwrap(),
|
||||
max_coin_qty: NonZeroU64::new(10).unwrap(),
|
||||
max_native_pc_qty_including_fees: NonZeroU64::new(50_000).unwrap(),
|
||||
order_type: OrderType::Limit,
|
||||
client_order_id: 0xabcd,
|
||||
self_trade_behavior: SelfTradeBehavior::AbortTransaction,
|
||||
limit: 5,
|
||||
max_ts: 1_650_000_000,
|
||||
})
|
||||
.pack();
|
||||
|
||||
let instruction_accounts: &[AccountInfo] = bump_vec![in ≎
|
||||
accounts.market.clone(),
|
||||
orders_account.clone(),
|
||||
accounts.req_q.clone(),
|
||||
accounts.event_q.clone(),
|
||||
accounts.bids.clone(),
|
||||
accounts.asks.clone(),
|
||||
pc_account.clone(),
|
||||
owner.clone(),
|
||||
accounts.coin_vault.clone(),
|
||||
accounts.pc_vault.clone(),
|
||||
spl_token_program.clone(),
|
||||
accounts.rent_sysvar.clone(),
|
||||
]
|
||||
.into_bump_slice();
|
||||
|
||||
{
|
||||
let result = State::process(dex_program_id, instruction_accounts, &instruction_data);
|
||||
let expected = Ok(());
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue