Skip to content

Commit

Permalink
Add order replacement instructions (#235)
Browse files Browse the repository at this point in the history
  • Loading branch information
jhlx authored Apr 21, 2022
1 parent e00bb9e commit bf78142
Show file tree
Hide file tree
Showing 4 changed files with 257 additions and 1 deletion.
1 change: 1 addition & 0 deletions dex/proptest-regressions/instruction.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ cc 9b33e7cb4c2b4e3a34b7b6fdeb7feb49cee9196047870cff53dac8b9483e9245 # shrinks to
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])
cc 716a991e9ba3e555e9fdbd5c717275e42df2fdc677ff12bf6e990a0c03289c9f # shrinks to inst = ReplaceOrdersByClientIds([])
56 changes: 55 additions & 1 deletion dex/src/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,40 @@ pub enum MarketInstruction {
/// 4. `[signer]` the OpenOrders owner
/// 5. `[writable]` event_q
CancelOrdersByClientIds([u64; 8]),
/// 0. `[writable]` the market
/// 1. `[writable]` the OpenOrders account to use
/// 2. `[writable]` the request queue
/// 3. `[writable]` the event queue
/// 4. `[writable]` bids
/// 5. `[writable]` asks
/// 6. `[writable]` the (coin or price currency) account paying for the order
/// 7. `[signer]` owner of the OpenOrders account
/// 8. `[writable]` coin vault
/// 9. `[writable]` pc vault
/// 10. `[]` spl token program
/// 11. `[]` the rent sysvar
/// 12. `[]` (optional) the (M)SRM account used for fee discounts
ReplaceOrderByClientId(NewOrderInstructionV3),
/// 0. `[writable]` the market
/// 1. `[writable]` the OpenOrders account to use
/// 2. `[writable]` the request queue
/// 3. `[writable]` the event queue
/// 4. `[writable]` bids
/// 5. `[writable]` asks
/// 6. `[writable]` the (coin or price currency) account paying for the order
/// 7. `[signer]` owner of the OpenOrders account
/// 8. `[writable]` coin vault
/// 9. `[writable]` pc vault
/// 10. `[]` spl token program
/// 11. `[]` the rent sysvar
/// 12. `[]` (optional) the (M)SRM account used for fee discounts
#[cfg_attr(
test,
proptest(
strategy = "prop::collection::vec(any::<NewOrderInstructionV3>(), 0..=8).prop_map(MarketInstruction::ReplaceOrdersByClientIds)"
)
)]
ReplaceOrdersByClientIds(Vec<NewOrderInstructionV3>),
}

impl MarketInstruction {
Expand All @@ -486,7 +520,7 @@ impl MarketInstruction {
}

pub fn unpack(versioned_bytes: &[u8]) -> Option<Self> {
if versioned_bytes.len() < 5 || versioned_bytes.len() > 69 {
if versioned_bytes.len() < 5 || versioned_bytes.len() > 5 + 8 + 54 * 8 {
return None;
}
let (&[version], &discrim, data) = array_refs![versioned_bytes, 1, 4; ..;];
Expand Down Expand Up @@ -593,6 +627,26 @@ impl MarketInstruction {
}
MarketInstruction::CancelOrdersByClientIds(client_ids)
}
(19, 54) => MarketInstruction::ReplaceOrderByClientId({
let data_arr = array_ref![data, 0, 54];
NewOrderInstructionV3::unpack(data_arr)?
}),
(20, len) if len % 54 == 8 && len <= 8 + 8 * 54 => {
if u64::from_le_bytes(data[0..8].try_into().unwrap())
!= (data.len() as u64 - 8) / 54
{
return None;
}

let new_orders = data[8..]
.chunks_exact(54)
.map(|chunk| {
let chunk_arr = array_ref![chunk, 0, 54];
NewOrderInstructionV3::unpack(chunk_arr)
})
.collect::<Option<Vec<_>>>()?;
MarketInstruction::ReplaceOrdersByClientIds(new_orders)
}
_ => return None,
})
}
Expand Down
79 changes: 79 additions & 0 deletions dex/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1928,6 +1928,39 @@ pub(crate) mod account_parser {
}
}

pub struct ReplaceOrdersByClientIdsArgs<'a, 'b: 'a> {
pub program_id: &'a Pubkey,
pub cancel_accounts: &'a [AccountInfo<'b>],
pub client_order_ids: [u64; 8],
pub instructions: &'a Vec<NewOrderInstructionV3>,
pub accounts: &'a [AccountInfo<'b>],
}
impl<'a, 'b: 'a> ReplaceOrdersByClientIdsArgs<'a, 'b> {
pub fn with_parsed_args<T>(
program_id: &'a Pubkey,
accounts: &'a [AccountInfo<'b>],
instructions: &'a Vec<NewOrderInstructionV3>,
f: impl FnOnce(ReplaceOrdersByClientIdsArgs) -> DexResult<T>,
) -> DexResult<T> {
// Account indices for market, bids, asks, OpenOrders, owner, event_q
let cancel_accounts = [0, 4, 5, 1, 7, 3].map(|i| accounts[i].clone());
let mut client_order_ids = [0; 8];
for (instruction, client_order_id) in
instructions.iter().zip(client_order_ids.iter_mut())
{
*client_order_id = instruction.client_order_id;
}

f(ReplaceOrdersByClientIdsArgs {
program_id,
instructions,
accounts,
cancel_accounts: &cancel_accounts[..],
client_order_ids,
})
}
}

pub struct ConsumeEventsArgs<'a, 'b: 'a> {
pub limit: u16,
pub program_id: &'a Pubkey,
Expand Down Expand Up @@ -2537,6 +2570,22 @@ impl State {
Self::process_new_order_v3,
)?
}
MarketInstruction::ReplaceOrderByClientId(instruction) => {
account_parser::ReplaceOrdersByClientIdsArgs::with_parsed_args(
program_id,
accounts,
&vec![instruction],
Self::process_replace_orders_by_client_ids,
)?
}
MarketInstruction::ReplaceOrdersByClientIds(ref instructions) => {
account_parser::ReplaceOrdersByClientIdsArgs::with_parsed_args(
program_id,
accounts,
instructions,
Self::process_replace_orders_by_client_ids,
)?
}
MarketInstruction::MatchOrders(_limit) => {}
MarketInstruction::ConsumeEvents(limit) => {
account_parser::ConsumeEventsArgs::with_parsed_args(
Expand Down Expand Up @@ -3188,6 +3237,36 @@ impl State {
Ok(())
}

fn process_replace_orders_by_client_ids(
args: account_parser::ReplaceOrdersByClientIdsArgs,
) -> DexResult {
let account_parser::ReplaceOrdersByClientIdsArgs {
program_id,
cancel_accounts,
client_order_ids,
instructions,
accounts,
} = args;

account_parser::CancelOrdersByClientIdsArgs::with_parsed_args(
program_id,
&cancel_accounts,
client_order_ids,
Self::process_cancel_orders_by_client_ids,
)?;

for instruction in instructions {
account_parser::NewOrderV3Args::with_parsed_args(
program_id,
instruction,
accounts,
Self::process_new_order_v3,
)?;
}

Ok(())
}

fn process_disable_market(args: account_parser::DisableMarketArgs) -> DexResult {
let account_parser::DisableMarketArgs {
market,
Expand Down
122 changes: 122 additions & 0 deletions dex/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -595,3 +595,125 @@ fn test_max_ts_order() {
assert_eq!(result, expected);
}
}

#[test]
fn test_replace_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);

// Place orders
// 0xabc1: 50K
// 0xabc2: 50K
// 0xabc3: 50K
for client_order_id in [0xabc1, 0xabc2, 0xabc3] {
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,
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();
}

// Verify orders have been placed
// Total: 150K
{
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);
}

// Replace orders 0xabc1, 0xabc3, 0xabc4
// 0xabc1: 50K -> 70K (replaced)
// 0xabc2: 50K (unchanged)
// 0xabc3: 50K -> 70K (replaced)
// 0xabc4: 70K (new)
{
let params = [0xabc1, 0xabc3, 0xabc4]
.map(|client_order_id| 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(70_000).unwrap(),
order_type: OrderType::Limit,
client_order_id,
self_trade_behavior: SelfTradeBehavior::AbortTransaction,
limit: 5,
max_ts: i64::MAX,
})
.to_vec();

let instruction_data = MarketInstruction::ReplaceOrdersByClientIds(params).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 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), 0);
assert_eq!(identity(open_orders.native_pc_total), 260_000);
}
}

0 comments on commit bf78142

Please sign in to comment.