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:
jhl 2022-04-04 16:43:08 -04:00 committed by GitHub
parent 9a6f6abcb0
commit 4bddbbc364
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 467 additions and 23 deletions

View File

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

2
dex/fuzz/Cargo.lock generated
View File

@ -789,7 +789,7 @@ dependencies = [
[[package]]
name = "serum_dex"
version = "0.4.0"
version = "0.5.5"
dependencies = [
"arbitrary",
"arrayref",

View File

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

View File

@ -107,10 +107,11 @@ pub enum DexErrorCode {
RentNotProvided,
OrdersNotRentExempt,
OrderNotFound,
OrderNotYours,
OrderNotYours = 60,
WouldSelfTrade,
InvalidOpenOrdersAuthority,
OrderMaxTimestampExceeded,
Unknown = 1000,

View File

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

View File

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

View File

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

View File

@ -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 &bump;
@ -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 &bump;
@ -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 &bump;
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 &bump;
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 &bump;
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 &bump;
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);
}
}