Merge branch 'project-serum:master' into master
This commit is contained in:
commit
244f0d27bd
10
.travis.yml
10
.travis.yml
|
@ -6,7 +6,7 @@ cache: cargo
|
|||
env:
|
||||
global:
|
||||
- NODE_VERSION="v14.7.0"
|
||||
- SOLANA_VERSION="v1.9.1"
|
||||
- SOLANA_VERSION="v1.10.2"
|
||||
|
||||
_defaults: &defaults
|
||||
language: rust
|
||||
|
@ -26,10 +26,10 @@ jobs:
|
|||
name: Dex tests
|
||||
script:
|
||||
- ./scripts/travis/dex-tests.sh
|
||||
- <<: *defaults
|
||||
name: Permissioned Dex tests
|
||||
script:
|
||||
- cd dex/tests/permissioned/ && yarn && yarn build && yarn test && cd ../../../
|
||||
# - <<: *defaults
|
||||
# name: Permissioned Dex tests
|
||||
# script:
|
||||
# - cd dex/tests/permissioned/ && yarn && yarn build && yarn test && cd ../../../
|
||||
- <<: *defaults
|
||||
name: Fmt and Common Tests
|
||||
script:
|
||||
|
|
|
@ -1134,9 +1134,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "event-listener"
|
||||
version = "2.5.2"
|
||||
version = "2.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77f3309417938f28bf8228fcff79a4a37103981e3e186d2ccd19c74b38f4eb71"
|
||||
checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
|
|
|
@ -8,7 +8,7 @@ name = "crank"
|
|||
path = "src/bin/main.rs"
|
||||
|
||||
[dependencies]
|
||||
serum_dex = { path = "../", default-features = false, features = ["client"] }
|
||||
serum_dex = { path = "../", default-features = false, features = ["client", "program"] }
|
||||
serum-common = { path = "../../common", features = ["client"] }
|
||||
spl-token = { version = "3.0.0-pre1", features = ["no-entrypoint"], default-features = false }
|
||||
clap = { version = "3.1.6", features = ["derive"] }
|
||||
|
|
|
@ -113,6 +113,8 @@ pub enum DexErrorCode {
|
|||
InvalidOpenOrdersAuthority,
|
||||
OrderMaxTimestampExceeded,
|
||||
|
||||
MinAmountNotMet,
|
||||
|
||||
Unknown = 1000,
|
||||
|
||||
// This contains the line number in the lower 16 bits,
|
||||
|
|
|
@ -449,7 +449,8 @@ pub enum MarketInstruction {
|
|||
/// 8. `[writable]` coin vault
|
||||
/// 9. `[writable]` pc vault
|
||||
/// 10. `[]` spl token program
|
||||
/// 11. `[]` (optional) the (M)SRM account used for fee discounts
|
||||
/// 11. `[]` vault signer
|
||||
/// 12. `[]` (optional) the (M)SRM account used for fee discounts
|
||||
SendTake(SendTakeInstruction),
|
||||
/// 0. `[writable]` OpenOrders
|
||||
/// 1. `[signer]` the OpenOrders owner
|
||||
|
@ -1043,6 +1044,63 @@ pub fn sweep_fees(
|
|||
})
|
||||
}
|
||||
|
||||
pub fn send_take(
|
||||
market: &Pubkey,
|
||||
request_queue: &Pubkey,
|
||||
event_queue: &Pubkey,
|
||||
market_bids: &Pubkey,
|
||||
market_asks: &Pubkey,
|
||||
coin_wallet: &Pubkey,
|
||||
pc_wallet: &Pubkey,
|
||||
wallet_owner: &Pubkey,
|
||||
coin_vault: &Pubkey,
|
||||
pc_vault: &Pubkey,
|
||||
spl_token_program_id: &Pubkey,
|
||||
vault_signer: &Pubkey,
|
||||
srm_account_referral: Option<&Pubkey>,
|
||||
program_id: &Pubkey,
|
||||
side: Side,
|
||||
limit_price: NonZeroU64,
|
||||
max_coin_qty: NonZeroU64,
|
||||
max_native_pc_qty_including_fees: NonZeroU64,
|
||||
min_coin_qty: u64,
|
||||
min_native_pc_qty: u64,
|
||||
limit: u16,
|
||||
) -> Result<Instruction, DexError> {
|
||||
let data = MarketInstruction::SendTake(SendTakeInstruction {
|
||||
side,
|
||||
limit_price,
|
||||
max_coin_qty,
|
||||
max_native_pc_qty_including_fees,
|
||||
min_coin_qty,
|
||||
min_native_pc_qty,
|
||||
limit,
|
||||
})
|
||||
.pack();
|
||||
let mut accounts = vec![
|
||||
AccountMeta::new(*market, false),
|
||||
AccountMeta::new(*request_queue, false),
|
||||
AccountMeta::new(*event_queue, false),
|
||||
AccountMeta::new(*market_bids, false),
|
||||
AccountMeta::new(*market_asks, false),
|
||||
AccountMeta::new(*coin_wallet, false),
|
||||
AccountMeta::new(*pc_wallet, false),
|
||||
AccountMeta::new_readonly(*wallet_owner, true),
|
||||
AccountMeta::new(*coin_vault, false),
|
||||
AccountMeta::new(*pc_vault, false),
|
||||
AccountMeta::new_readonly(*spl_token_program_id, false),
|
||||
AccountMeta::new_readonly(*vault_signer, false),
|
||||
];
|
||||
if let Some(key) = srm_account_referral {
|
||||
accounts.push(AccountMeta::new_readonly(*key, false))
|
||||
}
|
||||
Ok(Instruction {
|
||||
program_id: *program_id,
|
||||
data,
|
||||
accounts,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn close_open_orders(
|
||||
program_id: &Pubkey,
|
||||
open_orders: &Pubkey,
|
||||
|
|
|
@ -6,7 +6,7 @@ use num_enum::{IntoPrimitive, TryFromPrimitive};
|
|||
use proptest_derive::Arbitrary;
|
||||
use serde::{Deserialize, Serialize};
|
||||
#[cfg(feature = "program")]
|
||||
use solana_program::msg;
|
||||
use solana_program::{msg, pubkey::Pubkey, system_program};
|
||||
|
||||
use crate::critbit::SlabTreeError;
|
||||
use crate::error::{DexErrorCode, DexResult, SourceFileId};
|
||||
|
@ -16,12 +16,25 @@ use crate::{
|
|||
state::{Event, EventQueue, EventView, MarketState, OpenOrders, RequestView},
|
||||
};
|
||||
|
||||
use bytemuck::cast;
|
||||
|
||||
#[cfg(not(feature = "program"))]
|
||||
macro_rules! msg {
|
||||
($($i:expr),*) => { { ($($i),*) } };
|
||||
}
|
||||
declare_check_assert_macros!(SourceFileId::Matching);
|
||||
|
||||
pub trait ToAlignedBytes {
|
||||
fn to_aligned_bytes(&self) -> [u64; 4];
|
||||
}
|
||||
|
||||
impl ToAlignedBytes for Pubkey {
|
||||
#[inline]
|
||||
fn to_aligned_bytes(&self) -> [u64; 4] {
|
||||
cast(self.to_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Eq, PartialEq, Copy, Clone, TryFromPrimitive, IntoPrimitive, Debug, Serialize, Deserialize,
|
||||
)]
|
||||
|
@ -243,7 +256,7 @@ impl<'ob> OrderBookState<'ob> {
|
|||
client_order_id,
|
||||
self_trade_behavior,
|
||||
} = params;
|
||||
let (mut post_only, mut post_allowed) = match order_type {
|
||||
let (mut post_only, post_allowed) = match order_type {
|
||||
OrderType::Limit => (false, true),
|
||||
OrderType::ImmediateOrCancel => (false, false),
|
||||
OrderType::PostOnly => (true, true),
|
||||
|
@ -253,7 +266,9 @@ impl<'ob> OrderBookState<'ob> {
|
|||
if *limit == 0 {
|
||||
// Stop matching and release funds if we're out of cycles
|
||||
post_only = true;
|
||||
post_allowed = true;
|
||||
// Remove this block of code as it can lead to undefined behavior where
|
||||
// an ImmediateOrCancel order is allowed to place orders on the book
|
||||
// post_allowed = true;
|
||||
}
|
||||
|
||||
let remaining_order = match side {
|
||||
|
@ -341,6 +356,8 @@ impl<'ob> OrderBookState<'ob> {
|
|||
client_order_id,
|
||||
self_trade_behavior,
|
||||
} = params;
|
||||
|
||||
let is_send_take = system_program::ID.to_aligned_bytes() == owner;
|
||||
let mut unfilled_qty = max_qty.get();
|
||||
let mut accum_fill_price = 0;
|
||||
|
||||
|
@ -504,7 +521,7 @@ impl<'ob> OrderBookState<'ob> {
|
|||
to_release.credit_native_pc(net_taker_pc_qty);
|
||||
to_release.debit_coin(coin_lots_traded);
|
||||
|
||||
if native_taker_pc_qty > 0 {
|
||||
if native_taker_pc_qty > 0 && !is_send_take {
|
||||
let taker_fill = Event::new(EventView::Fill {
|
||||
side: Side::Ask,
|
||||
maker: false,
|
||||
|
@ -531,6 +548,7 @@ impl<'ob> OrderBookState<'ob> {
|
|||
self.market_state.pc_fees_accrued += net_fees;
|
||||
self.market_state.pc_deposits_total -= net_fees_before_referrer_rebate;
|
||||
|
||||
|
||||
if !done {
|
||||
if let Some(coin_qty_remaining) = NonZeroU64::new(unfilled_qty) {
|
||||
return Ok(Some(OrderRemaining {
|
||||
|
@ -541,6 +559,7 @@ impl<'ob> OrderBookState<'ob> {
|
|||
}
|
||||
|
||||
if post_allowed && !crossed && unfilled_qty > 0 {
|
||||
check_assert!(!is_send_take)?;
|
||||
let offers = self.orders_mut(Side::Ask);
|
||||
let new_order = LeafNode::new(
|
||||
owner_slot,
|
||||
|
@ -572,7 +591,7 @@ impl<'ob> OrderBookState<'ob> {
|
|||
} else {
|
||||
insert_result.unwrap();
|
||||
}
|
||||
} else {
|
||||
} else if !is_send_take {
|
||||
to_release.unlock_coin(unfilled_qty);
|
||||
let out = Event::new(EventView::Out {
|
||||
side: Side::Ask,
|
||||
|
@ -631,6 +650,8 @@ impl<'ob> OrderBookState<'ob> {
|
|||
check_assert!(limit_price.is_some())?;
|
||||
}
|
||||
|
||||
let is_send_take = system_program::ID.to_aligned_bytes() == owner;
|
||||
|
||||
let pc_lot_size = self.market_state.pc_lot_size;
|
||||
let coin_lot_size = self.market_state.coin_lot_size;
|
||||
|
||||
|
@ -822,7 +843,7 @@ impl<'ob> OrderBookState<'ob> {
|
|||
to_release.credit_coin(coin_lots_received);
|
||||
to_release.debit_native_pc(native_pc_paid);
|
||||
|
||||
if native_accum_fill_price > 0 {
|
||||
if native_accum_fill_price > 0 && !is_send_take {
|
||||
let taker_fill = Event::new(EventView::Fill {
|
||||
side: Side::Bid,
|
||||
maker: false,
|
||||
|
@ -869,26 +890,28 @@ impl<'ob> OrderBookState<'ob> {
|
|||
_ => (0, 0),
|
||||
};
|
||||
|
||||
let out = {
|
||||
let native_qty_still_locked = pc_qty_to_keep_locked * pc_lot_size;
|
||||
let native_qty_unlocked = native_pc_qty_remaining - native_qty_still_locked;
|
||||
if !is_send_take {
|
||||
let out = {
|
||||
let native_qty_still_locked = pc_qty_to_keep_locked * pc_lot_size;
|
||||
let native_qty_unlocked = native_pc_qty_remaining - native_qty_still_locked;
|
||||
|
||||
to_release.unlock_native_pc(native_qty_unlocked);
|
||||
to_release.unlock_native_pc(native_qty_unlocked);
|
||||
|
||||
Event::new(EventView::Out {
|
||||
side: Side::Bid,
|
||||
release_funds: false,
|
||||
native_qty_unlocked,
|
||||
native_qty_still_locked,
|
||||
order_id,
|
||||
owner,
|
||||
owner_slot,
|
||||
client_order_id: NonZeroU64::new(client_order_id),
|
||||
})
|
||||
};
|
||||
event_q
|
||||
.push_back(out)
|
||||
.map_err(|_| DexErrorCode::EventQueueFull)?;
|
||||
Event::new(EventView::Out {
|
||||
side: Side::Bid,
|
||||
release_funds: false,
|
||||
native_qty_unlocked,
|
||||
native_qty_still_locked,
|
||||
order_id,
|
||||
owner,
|
||||
owner_slot,
|
||||
client_order_id: NonZeroU64::new(client_order_id),
|
||||
})
|
||||
};
|
||||
event_q
|
||||
.push_back(out)
|
||||
.map_err(|_| DexErrorCode::EventQueueFull)?;
|
||||
}
|
||||
|
||||
if pc_qty_to_keep_locked > 0 {
|
||||
let bids = self.orders_mut(Side::Bid);
|
||||
|
|
206
dex/src/state.rs
206
dex/src/state.rs
|
@ -17,13 +17,13 @@ use safe_transmute::{self, to_bytes::transmute_to_bytes, trivial::TriviallyTrans
|
|||
|
||||
use solana_program::{
|
||||
account_info::AccountInfo, program_error::ProgramError, program_pack::Pack, pubkey::Pubkey,
|
||||
rent::Rent, sysvar::Sysvar,
|
||||
rent::Rent, system_program, sysvar::Sysvar,
|
||||
};
|
||||
use spl_token::error::TokenError;
|
||||
|
||||
use crate::{
|
||||
critbit::Slab,
|
||||
error::{DexErrorCode, DexResult, SourceFileId},
|
||||
error::{DexErrorCode, DexResult, SourceFileId, DexError},
|
||||
fees::{self, FeeTier},
|
||||
instruction::{
|
||||
disable_authority, fee_sweeper, msrm_token, srm_token, CancelOrderInstructionV2,
|
||||
|
@ -1360,7 +1360,6 @@ fn gen_vault_signer_seeds<'a>(nonce: &'a u64, market: &'a Pubkey) -> [&'a [u8];
|
|||
[market.as_ref(), bytes_of(nonce)]
|
||||
}
|
||||
|
||||
#[cfg(not(any(test, feature = "fuzz")))]
|
||||
#[inline]
|
||||
pub fn gen_vault_signer_key(
|
||||
nonce: u64,
|
||||
|
@ -1371,16 +1370,6 @@ pub fn gen_vault_signer_key(
|
|||
Ok(Pubkey::create_program_address(&seeds, program_id)?)
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "fuzz"))]
|
||||
pub fn gen_vault_signer_key(
|
||||
nonce: u64,
|
||||
market: &Pubkey,
|
||||
_program_id: &Pubkey,
|
||||
) -> Result<Pubkey, ProgramError> {
|
||||
gen_vault_signer_seeds(&nonce, market);
|
||||
Ok(Pubkey::default())
|
||||
}
|
||||
|
||||
#[cfg(not(any(test, feature = "fuzz")))]
|
||||
fn invoke_spl_token(
|
||||
instruction: &solana_program::instruction::Instruction,
|
||||
|
@ -1741,6 +1730,7 @@ pub(crate) mod account_parser {
|
|||
pub pc_vault: PcVault<'a, 'b>,
|
||||
pub spl_token_program: SplTokenProgram<'a, 'b>,
|
||||
pub fee_tier: FeeTier,
|
||||
pub vault_signer: VaultSigner<'a, 'b>,
|
||||
}
|
||||
impl<'a, 'b: 'a> SendTakeArgs<'a, 'b> {
|
||||
pub fn with_parsed_args<T>(
|
||||
|
@ -1749,7 +1739,7 @@ pub(crate) mod account_parser {
|
|||
accounts: &'a [AccountInfo<'b>],
|
||||
f: impl FnOnce(SendTakeArgs) -> DexResult<T>,
|
||||
) -> DexResult<T> {
|
||||
const MIN_ACCOUNTS: usize = 11;
|
||||
const MIN_ACCOUNTS: usize = 12;
|
||||
check_assert!(accounts.len() == MIN_ACCOUNTS || accounts.len() == MIN_ACCOUNTS + 1)?;
|
||||
let (fixed_accounts, fee_discount_account): (
|
||||
&'a [AccountInfo<'b>; MIN_ACCOUNTS],
|
||||
|
@ -1767,6 +1757,7 @@ pub(crate) mod account_parser {
|
|||
ref coin_vault_acc,
|
||||
ref pc_vault_acc,
|
||||
ref spl_token_program_acc,
|
||||
ref vault_signer_acc,
|
||||
]: &'a [AccountInfo<'b>; MIN_ACCOUNTS] = fixed_accounts;
|
||||
let srm_or_msrm_account = match fee_discount_account {
|
||||
&[] => None,
|
||||
|
@ -1791,6 +1782,8 @@ pub(crate) mod account_parser {
|
|||
|
||||
let spl_token_program = SplTokenProgram::new(spl_token_program_acc)?;
|
||||
|
||||
let vault_signer = VaultSigner::new(vault_signer_acc, &market, program_id)?;
|
||||
|
||||
let mut bids = market.load_bids_mut(bids_acc).or(check_unreachable!())?;
|
||||
let mut asks = market.load_asks_mut(asks_acc).or(check_unreachable!())?;
|
||||
|
||||
|
@ -1812,6 +1805,7 @@ pub(crate) mod account_parser {
|
|||
pc_vault,
|
||||
order_book_state,
|
||||
spl_token_program,
|
||||
vault_signer,
|
||||
};
|
||||
f(args)
|
||||
}
|
||||
|
@ -2686,8 +2680,168 @@ impl State {
|
|||
}
|
||||
|
||||
#[cfg(feature = "program")]
|
||||
fn process_send_take(_args: account_parser::SendTakeArgs) -> DexResult {
|
||||
unimplemented!()
|
||||
fn process_send_take(args: account_parser::SendTakeArgs) -> DexResult {
|
||||
let account_parser::SendTakeArgs {
|
||||
instruction,
|
||||
signer,
|
||||
mut event_q,
|
||||
mut req_q,
|
||||
mut order_book_state,
|
||||
coin_wallet,
|
||||
pc_wallet,
|
||||
coin_vault,
|
||||
pc_vault,
|
||||
spl_token_program,
|
||||
fee_tier,
|
||||
vault_signer,
|
||||
} = args;
|
||||
|
||||
let order_id = req_q.gen_order_id(instruction.limit_price.get(), instruction.side);
|
||||
let native_pc_qty_locked = match instruction.side {
|
||||
Side::Bid => {
|
||||
let lock_qty_native = instruction.max_native_pc_qty_including_fees;
|
||||
Some(lock_qty_native)
|
||||
}
|
||||
Side::Ask => None,
|
||||
};
|
||||
|
||||
let request = RequestView::NewOrder {
|
||||
side: instruction.side,
|
||||
order_type: OrderType::ImmediateOrCancel,
|
||||
order_id,
|
||||
fee_tier,
|
||||
self_trade_behavior: SelfTradeBehavior::AbortTransaction,
|
||||
// Pass dummy account - system program pubkey
|
||||
owner: system_program::ID.to_aligned_bytes(),
|
||||
// Pass 0
|
||||
owner_slot: 0,
|
||||
max_coin_qty: instruction.max_coin_qty,
|
||||
native_pc_qty_locked,
|
||||
client_order_id: None,
|
||||
};
|
||||
|
||||
let mut limit = instruction.limit;
|
||||
let mut proceeds = RequestProceeds::zero();
|
||||
let _unfilled_portion = order_book_state.process_orderbook_request(
|
||||
&request,
|
||||
&mut event_q,
|
||||
&mut proceeds,
|
||||
&mut limit,
|
||||
)?;
|
||||
|
||||
let market_state = order_book_state.market_state;
|
||||
|
||||
let coin_lot_size = market_state.coin_lot_size;
|
||||
|
||||
let RequestProceeds {
|
||||
coin_unlocked: _,
|
||||
coin_credit,
|
||||
|
||||
native_pc_unlocked: _,
|
||||
native_pc_credit,
|
||||
|
||||
coin_debit,
|
||||
native_pc_debit,
|
||||
} = proceeds;
|
||||
|
||||
let abort = match instruction.side {
|
||||
Side::Bid if coin_credit < instruction.min_coin_qty => true,
|
||||
Side::Ask if native_pc_credit < instruction.min_native_pc_qty => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if abort {
|
||||
solana_program::msg!("Min amount requested not met! Aborting");
|
||||
return Err(DexErrorCode::MinAmountNotMet.into());
|
||||
};
|
||||
|
||||
// Amount that user deposits into the program
|
||||
let deposit_amount;
|
||||
// Amount that user receives after the exchange
|
||||
let withdraw_amount;
|
||||
|
||||
// Token accounts for transfers
|
||||
let deposit_vault;
|
||||
let withdraw_vault;
|
||||
let deposit_source;
|
||||
let withdraw_destination;
|
||||
|
||||
match instruction.side {
|
||||
Side::Bid => {
|
||||
deposit_amount = native_pc_debit;
|
||||
withdraw_amount = coin_credit.checked_mul(coin_lot_size).unwrap();
|
||||
deposit_vault = pc_vault.token_account();
|
||||
deposit_source = pc_wallet.token_account();
|
||||
withdraw_destination = coin_wallet.token_account();
|
||||
withdraw_vault = coin_vault.token_account();
|
||||
|
||||
market_state.pc_deposits_total += deposit_amount;
|
||||
check_assert!(market_state.coin_deposits_total >= withdraw_amount)?;
|
||||
market_state.coin_deposits_total -= withdraw_amount;
|
||||
}
|
||||
Side::Ask => {
|
||||
deposit_amount = coin_debit.checked_mul(coin_lot_size).unwrap();
|
||||
withdraw_amount = native_pc_credit;
|
||||
deposit_vault = coin_vault.token_account();
|
||||
deposit_source = coin_wallet.token_account();
|
||||
withdraw_destination = pc_wallet.token_account();
|
||||
withdraw_vault = pc_vault.token_account();
|
||||
|
||||
market_state.coin_deposits_total += deposit_amount;
|
||||
check_assert!(market_state.pc_deposits_total >= withdraw_amount)?;
|
||||
market_state.pc_deposits_total -= withdraw_amount;
|
||||
}
|
||||
};
|
||||
|
||||
if deposit_amount != 0 {
|
||||
let balance_before = deposit_vault.balance()?;
|
||||
let deposit_instruction = spl_token::instruction::transfer(
|
||||
&spl_token::ID,
|
||||
deposit_source.inner().key,
|
||||
deposit_vault.inner().key,
|
||||
signer.inner().key,
|
||||
&[],
|
||||
deposit_amount,
|
||||
)
|
||||
.unwrap();
|
||||
invoke_spl_token(
|
||||
&deposit_instruction,
|
||||
&[
|
||||
deposit_source.inner().clone(),
|
||||
deposit_vault.inner().clone(),
|
||||
signer.inner().clone(),
|
||||
spl_token_program.inner().clone(),
|
||||
],
|
||||
&[],
|
||||
)
|
||||
.map_err(|err| match err {
|
||||
ProgramError::Custom(i) => match TokenError::from_u32(i) {
|
||||
Some(TokenError::InsufficientFunds) => DexErrorCode::InsufficientFunds,
|
||||
_ => DexErrorCode::TransferFailed,
|
||||
},
|
||||
_ => DexErrorCode::TransferFailed,
|
||||
})?;
|
||||
let balance_after = deposit_vault.balance()?;
|
||||
let balance_change = balance_after.checked_sub(balance_before);
|
||||
check_assert_eq!(Some(deposit_amount), balance_change)?;
|
||||
}
|
||||
|
||||
let nonce = market_state.vault_signer_nonce;
|
||||
let market_pubkey = market_state.pubkey();
|
||||
let vault_signer_seeds = gen_vault_signer_seeds(&nonce, &market_pubkey);
|
||||
|
||||
if withdraw_amount != 0 {
|
||||
send_from_vault(
|
||||
withdraw_amount,
|
||||
withdraw_destination,
|
||||
withdraw_vault,
|
||||
spl_token_program,
|
||||
vault_signer,
|
||||
&vault_signer_seeds,
|
||||
)?;
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_prune(args: account_parser::PruneArgs) -> DexResult {
|
||||
|
@ -2892,13 +3046,29 @@ impl State {
|
|||
}
|
||||
|
||||
for (order_id, side) in orders {
|
||||
let _ = order_book_state.cancel_order_v2(
|
||||
if let Err(err) = order_book_state.cancel_order_v2(
|
||||
side,
|
||||
open_orders_address,
|
||||
open_orders,
|
||||
order_id,
|
||||
&mut event_q,
|
||||
);
|
||||
) {
|
||||
// Cancel returns OrderNotFound when the order isn't in bids or
|
||||
// asks. In this case, no Serum state is modified so it is safe
|
||||
// to continue. Any other type of error could be accompanied by
|
||||
// a partial state change, so we must error. For example, if the
|
||||
// event queue is full, we should abort to prevent the cancelled
|
||||
// order from being removed from bids/asks.
|
||||
match err {
|
||||
DexError::ErrorCode(code) => {
|
||||
if code == DexErrorCode::OrderNotFound {
|
||||
continue;
|
||||
}
|
||||
return Err(err);
|
||||
}
|
||||
_ => return Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
705
dex/src/tests.rs
705
dex/src/tests.rs
|
@ -18,11 +18,12 @@ use spl_token::state::{Account, AccountState, Mint};
|
|||
|
||||
use instruction::{initialize_market, MarketInstruction, NewOrderInstructionV3, SelfTradeBehavior};
|
||||
use matching::{OrderType, Side};
|
||||
use state::gen_vault_signer_key;
|
||||
use state::{Market, MarketState, OpenOrders, State, ToAlignedBytes};
|
||||
|
||||
use crate::critbit::SlabView;
|
||||
use crate::error::DexErrorCode;
|
||||
use crate::state::account_parser::CancelOrderByClientIdV2Args;
|
||||
use crate::instruction::SendTakeInstruction;
|
||||
use crate::state::account_parser::TokenAccount;
|
||||
|
||||
use super::*;
|
||||
|
||||
|
@ -41,6 +42,7 @@ struct MarketAccounts<'bump> {
|
|||
coin_mint: AccountInfo<'bump>,
|
||||
pc_mint: AccountInfo<'bump>,
|
||||
rent_sysvar: AccountInfo<'bump>,
|
||||
vault_signer: AccountInfo<'bump>,
|
||||
}
|
||||
|
||||
fn allocate_dex_owned_account(unpadded_size: usize, bump: &Bump) -> &mut [u8] {
|
||||
|
@ -168,7 +170,44 @@ fn new_spl_token_program<'bump>(bump: &'bump Bump) -> AccountInfo<'bump> {
|
|||
|
||||
fn setup_market<'bump, R: Rng>(rng: &mut R, bump: &'bump Bump) -> MarketAccounts<'bump> {
|
||||
let program_id = random_pubkey(rng, bump);
|
||||
let market = new_dex_owned_account(rng, size_of::<MarketState>(), program_id, bump);
|
||||
|
||||
let mut i: u64 = 0;
|
||||
let (market_key, vault_signer_nonce, vault_signer) = loop {
|
||||
assert!(i < 100);
|
||||
let market = Pubkey::new(transmute_to_bytes(&rand::random::<[u64; 4]>()));
|
||||
new_dex_owned_account(rng, size_of::<MarketState>(), program_id, bump);
|
||||
let seeds = [market.as_ref(), bytemuck::bytes_of(&i)];
|
||||
let vault_signer_pk = match Pubkey::create_program_address(&seeds, program_id) {
|
||||
Ok(pk) => pk,
|
||||
Err(_) => {
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let vault_signer = AccountInfo::new(
|
||||
bump.alloc(vault_signer_pk),
|
||||
true,
|
||||
false,
|
||||
bump.alloc(1000000),
|
||||
&mut [],
|
||||
&system_program::ID,
|
||||
false,
|
||||
Epoch::default(),
|
||||
);
|
||||
break (market, i, vault_signer);
|
||||
};
|
||||
|
||||
let market = AccountInfo::new(
|
||||
bump.alloc(market_key),
|
||||
false,
|
||||
true,
|
||||
bump.alloc(60_000_000_000),
|
||||
allocate_dex_owned_account(size_of::<MarketState>(), bump),
|
||||
program_id,
|
||||
false,
|
||||
Epoch::default(),
|
||||
);
|
||||
|
||||
let bids = new_dex_owned_account(rng, 1 << 23, program_id, bump);
|
||||
let asks = new_dex_owned_account(rng, 1 << 23, program_id, bump);
|
||||
let req_q = new_dex_owned_account(rng, 640, program_id, bump);
|
||||
|
@ -178,18 +217,8 @@ fn setup_market<'bump, R: Rng>(rng: &mut R, bump: &'bump Bump) -> MarketAccounts
|
|||
let pc_mint = new_token_mint(rng, bump);
|
||||
|
||||
let rent_sysvar = new_rent_sysvar_account(100000, Rent::default(), bump);
|
||||
|
||||
let mut i = 0;
|
||||
let (vault_signer_nonce, vault_signer_pk) = loop {
|
||||
assert!(i < 100);
|
||||
if let Ok(pk) = gen_vault_signer_key(i, &market.key, program_id) {
|
||||
break (i, bump.alloc(pk));
|
||||
}
|
||||
i += 1;
|
||||
};
|
||||
|
||||
let coin_vault = new_token_account(rng, &coin_mint.key, vault_signer_pk, 0, bump);
|
||||
let pc_vault = new_token_account(rng, &pc_mint.key, vault_signer_pk, 0, bump);
|
||||
let coin_vault = new_token_account(rng, &coin_mint.key, vault_signer.key, 0, bump);
|
||||
let pc_vault = new_token_account(rng, &pc_mint.key, vault_signer.key, 0, bump);
|
||||
|
||||
let coin_lot_size = 1_000;
|
||||
let pc_lot_size = 1;
|
||||
|
@ -245,6 +274,124 @@ fn setup_market<'bump, R: Rng>(rng: &mut R, bump: &'bump Bump) -> MarketAccounts
|
|||
coin_mint,
|
||||
pc_mint,
|
||||
rent_sysvar,
|
||||
vault_signer,
|
||||
}
|
||||
}
|
||||
|
||||
fn layer_orders(
|
||||
dex_program_id: &Pubkey,
|
||||
start_price: u64,
|
||||
end_price: u64,
|
||||
price_step: u64,
|
||||
start_size: u64,
|
||||
size_step: u64,
|
||||
side: Side,
|
||||
instruction_accounts: &[AccountInfo],
|
||||
) {
|
||||
assert!(price_step > 0 && size_step > 0);
|
||||
let mut prices = vec![];
|
||||
let mut sizes = vec![];
|
||||
match side {
|
||||
Side::Bid => {
|
||||
assert!(start_price >= end_price);
|
||||
let mut price = start_price;
|
||||
let mut size = start_size;
|
||||
while price >= end_price && price > 0 {
|
||||
prices.push(price);
|
||||
sizes.push(size);
|
||||
price -= price_step;
|
||||
size += size_step;
|
||||
}
|
||||
}
|
||||
Side::Ask => {
|
||||
assert!(start_price <= end_price);
|
||||
let mut price = start_price;
|
||||
let mut size = start_size;
|
||||
while price <= end_price {
|
||||
prices.push(price);
|
||||
sizes.push(size);
|
||||
price += price_step;
|
||||
size += size_step;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (i, (p, s)) in prices.iter().zip(sizes.iter()).enumerate() {
|
||||
let new_order_instruction = MarketInstruction::NewOrderV3(NewOrderInstructionV3 {
|
||||
side,
|
||||
limit_price: NonZeroU64::new(*p).unwrap(),
|
||||
max_coin_qty: NonZeroU64::new(*s).unwrap(),
|
||||
max_native_pc_qty_including_fees: NonZeroU64::new(*s * *p).unwrap(),
|
||||
client_order_id: i as u64,
|
||||
order_type: OrderType::Limit,
|
||||
self_trade_behavior: SelfTradeBehavior::AbortTransaction,
|
||||
limit: 1,
|
||||
max_ts: i64::MAX,
|
||||
});
|
||||
let starting_balance = TokenAccount::new(&instruction_accounts[6])
|
||||
.unwrap()
|
||||
.balance()
|
||||
.unwrap();
|
||||
State::process(
|
||||
dex_program_id,
|
||||
instruction_accounts,
|
||||
&new_order_instruction.pack().clone(),
|
||||
)
|
||||
.unwrap();
|
||||
let owner = instruction_accounts[7].key;
|
||||
let ending_balance = TokenAccount::new(&instruction_accounts[6])
|
||||
.unwrap()
|
||||
.balance()
|
||||
.unwrap();
|
||||
let side_str = match side {
|
||||
Side::Bid => "BUY",
|
||||
Side::Ask => "SELL",
|
||||
};
|
||||
println!(
|
||||
"{} placed {} LIMIT {} @ {}, balance {} -> {}",
|
||||
owner, s, side_str, p, starting_balance, ending_balance
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
struct BBO {
|
||||
bid: u64,
|
||||
ask: u64,
|
||||
nbid: u64,
|
||||
nask: u64,
|
||||
buyer: [u64; 4],
|
||||
seller: [u64; 4],
|
||||
}
|
||||
|
||||
fn get_bbo(
|
||||
program_id: &Pubkey,
|
||||
market: &AccountInfo,
|
||||
bids_a: &AccountInfo,
|
||||
asks_a: &AccountInfo,
|
||||
) -> BBO {
|
||||
let mkt = MarketState::load(market, program_id, false).unwrap();
|
||||
let bids = mkt.load_bids_mut(bids_a).unwrap();
|
||||
let asks = mkt.load_asks_mut(asks_a).unwrap();
|
||||
let (ask, nask, seller) = match asks.find_min() {
|
||||
None => (u64::MAX, 0, [0; 4]),
|
||||
Some(h) => {
|
||||
let bo = asks.get(h).unwrap().as_leaf().unwrap();
|
||||
(bo.price().into(), bo.quantity(), bo.owner())
|
||||
}
|
||||
};
|
||||
let (bid, nbid, buyer) = match bids.find_max() {
|
||||
None => (0, 0, [0; 4]),
|
||||
Some(h) => {
|
||||
let bb = bids.get(h).unwrap().as_leaf().unwrap();
|
||||
(bb.price().into(), bb.quantity(), bb.owner())
|
||||
}
|
||||
};
|
||||
BBO {
|
||||
bid,
|
||||
ask,
|
||||
nbid,
|
||||
nask,
|
||||
buyer,
|
||||
seller,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -413,6 +560,528 @@ fn test_new_order() {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ioc_new_order() {
|
||||
let mut rng = StdRng::seed_from_u64(2);
|
||||
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);
|
||||
// Account with 25 coin orders (coin lot size = 1000)
|
||||
let coin_account =
|
||||
new_token_account(&mut rng, accounts.coin_mint.key, owner.key, 25_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 mut instruction_accounts = 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(),
|
||||
];
|
||||
layer_orders(
|
||||
dex_program_id,
|
||||
10_000,
|
||||
9_000,
|
||||
200,
|
||||
1,
|
||||
2,
|
||||
Side::Bid,
|
||||
instruction_accounts.as_slice(),
|
||||
);
|
||||
instruction_accounts[6] = coin_account.clone();
|
||||
layer_orders(
|
||||
dex_program_id,
|
||||
10_100,
|
||||
11_100,
|
||||
200,
|
||||
1,
|
||||
1,
|
||||
Side::Ask,
|
||||
instruction_accounts.as_slice(),
|
||||
);
|
||||
|
||||
let taker = new_sol_account(&mut rng, 1_000_000_000, &bump);
|
||||
let orders_account_taker =
|
||||
new_dex_owned_account(&mut rng, size_of::<OpenOrders>(), dex_program_id, &bump);
|
||||
let taker_coin_account =
|
||||
new_token_account(&mut rng, accounts.coin_mint.key, taker.key, 6_000, &bump);
|
||||
let taker_pc_account =
|
||||
new_token_account(&mut rng, accounts.pc_mint.key, owner.key, 100_000, &bump);
|
||||
// IOC take out the 10_000 level
|
||||
let instruction_data = MarketInstruction::NewOrderV3(NewOrderInstructionV3 {
|
||||
side: Side::Ask,
|
||||
limit_price: NonZeroU64::new(10_000).unwrap(),
|
||||
max_coin_qty: NonZeroU64::new(1).unwrap(),
|
||||
max_native_pc_qty_including_fees: NonZeroU64::new(1).unwrap(),
|
||||
order_type: OrderType::ImmediateOrCancel,
|
||||
client_order_id: 0xface,
|
||||
self_trade_behavior: SelfTradeBehavior::AbortTransaction,
|
||||
limit: 1,
|
||||
max_ts: i64::MAX,
|
||||
})
|
||||
.pack();
|
||||
let instruction_accounts: &[AccountInfo] = bump_vec![in ≎
|
||||
accounts.market.clone(),
|
||||
orders_account_taker.clone(),
|
||||
accounts.req_q.clone(),
|
||||
accounts.event_q.clone(),
|
||||
accounts.bids.clone(),
|
||||
accounts.asks.clone(),
|
||||
taker_coin_account.clone(),
|
||||
taker.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 ta = TokenAccount::new(&taker_coin_account).unwrap();
|
||||
assert_eq!(ta.balance().unwrap(), 5_000);
|
||||
let ta = TokenAccount::new(&taker_pc_account).unwrap();
|
||||
assert_eq!(ta.balance().unwrap(), 100_000);
|
||||
|
||||
let BBO {
|
||||
bid,
|
||||
ask,
|
||||
nbid,
|
||||
nask,
|
||||
buyer,
|
||||
seller,
|
||||
} = get_bbo(
|
||||
dex_program_id,
|
||||
&accounts.market,
|
||||
&accounts.bids,
|
||||
&accounts.asks,
|
||||
);
|
||||
assert_eq!(bid, 9800);
|
||||
assert_eq!(ask, 10100);
|
||||
assert_eq!(nbid, 3);
|
||||
assert_eq!(nask, 1);
|
||||
assert_eq!(buyer, seller);
|
||||
// IOC take out the 10_000 level
|
||||
let instruction_data = MarketInstruction::NewOrderV3(NewOrderInstructionV3 {
|
||||
side: Side::Ask,
|
||||
limit_price: NonZeroU64::new(9_800).unwrap(),
|
||||
max_coin_qty: NonZeroU64::new(5).unwrap(),
|
||||
max_native_pc_qty_including_fees: NonZeroU64::new(1).unwrap(),
|
||||
order_type: OrderType::ImmediateOrCancel,
|
||||
client_order_id: 0xabcd,
|
||||
self_trade_behavior: SelfTradeBehavior::AbortTransaction,
|
||||
limit: 1,
|
||||
max_ts: i64::MAX,
|
||||
})
|
||||
.pack();
|
||||
let instruction_accounts: &[AccountInfo] = bump_vec![in ≎
|
||||
accounts.market.clone(),
|
||||
orders_account_taker.clone(),
|
||||
accounts.req_q.clone(),
|
||||
accounts.event_q.clone(),
|
||||
accounts.bids.clone(),
|
||||
accounts.asks.clone(),
|
||||
taker_coin_account.clone(),
|
||||
taker.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 BBO {
|
||||
bid,
|
||||
ask,
|
||||
nbid: _,
|
||||
nask: _,
|
||||
buyer,
|
||||
seller,
|
||||
} = get_bbo(
|
||||
dex_program_id,
|
||||
&accounts.market,
|
||||
&accounts.bids,
|
||||
&accounts.asks,
|
||||
);
|
||||
assert_eq!(bid, 9600);
|
||||
if ask == 9800 {
|
||||
println!("UNDEFINED BEHAVIOR: Taker placed a limit order, but specific IOC");
|
||||
}
|
||||
// This check will fail until the bug for processing IOC orders is fixed
|
||||
assert_eq!(ask, 10100);
|
||||
assert_eq!(buyer, seller);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_send_take() {
|
||||
let mut rng = StdRng::seed_from_u64(3);
|
||||
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);
|
||||
// Account with 25 coin orders (coin lot size = 1000)
|
||||
let coin_account =
|
||||
new_token_account(&mut rng, accounts.coin_mint.key, owner.key, 25_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 mut instruction_accounts = 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(),
|
||||
];
|
||||
let market = Market::load(&accounts.market, &dex_program_id, false).unwrap();
|
||||
let pc = market.pc_deposits_total;
|
||||
let pcf = market.pc_fees_accrued;
|
||||
let cdt = market.coin_deposits_total;
|
||||
let cf = market.coin_fees_accrued;
|
||||
println!(
|
||||
"pc_deposits_total: {}, pc_fees_accrued: {}, coin_deposits_total: {}, coin_fees_accrued: {}",
|
||||
pc, pcf, cdt, cf
|
||||
);
|
||||
drop(market);
|
||||
layer_orders(
|
||||
dex_program_id,
|
||||
10_000,
|
||||
9_000,
|
||||
200,
|
||||
1,
|
||||
2,
|
||||
Side::Bid,
|
||||
instruction_accounts.as_slice(),
|
||||
);
|
||||
let market = Market::load(&accounts.market, &dex_program_id, false).unwrap();
|
||||
let pc = market.pc_deposits_total;
|
||||
let pcf = market.pc_fees_accrued;
|
||||
let cdt = market.coin_deposits_total;
|
||||
let cf = market.coin_fees_accrued;
|
||||
println!(
|
||||
"pc_deposits_total: {}, pc_fees_accrued: {}, coin_deposits_total: {}, coin_fees_accrued: {}",
|
||||
pc, pcf, cdt, cf
|
||||
);
|
||||
drop(market);
|
||||
instruction_accounts[6] = coin_account.clone();
|
||||
layer_orders(
|
||||
dex_program_id,
|
||||
10_100,
|
||||
11_100,
|
||||
200,
|
||||
1,
|
||||
1,
|
||||
Side::Ask,
|
||||
instruction_accounts.as_slice(),
|
||||
);
|
||||
let market = Market::load(&accounts.market, &dex_program_id, false).unwrap();
|
||||
let pc = market.pc_deposits_total;
|
||||
let pcf = market.pc_fees_accrued;
|
||||
let cdt = market.coin_deposits_total;
|
||||
let cf = market.coin_fees_accrued;
|
||||
println!(
|
||||
"pc_deposits_total: {}, pc_fees_accrued: {}, coin_deposits_total: {}, coin_fees_accrued: {}",
|
||||
pc, pcf, cdt, cf
|
||||
);
|
||||
drop(market);
|
||||
let mut total_pc_on_book = 0;
|
||||
let mut p = 10_000;
|
||||
let mut s = 1;
|
||||
while p >= 9_000 {
|
||||
total_pc_on_book += p * s;
|
||||
s += 2;
|
||||
p -= 200
|
||||
}
|
||||
|
||||
let taker = new_sol_account(&mut rng, 1_000_000_000, &bump);
|
||||
let taker_coin_account =
|
||||
new_token_account(&mut rng, accounts.coin_mint.key, taker.key, 6_000, &bump);
|
||||
let taker_pc_account =
|
||||
new_token_account(&mut rng, accounts.pc_mint.key, taker.key, 100_000, &bump);
|
||||
|
||||
let instruction_accounts = bump_vec![in ≎
|
||||
accounts.market.clone(),
|
||||
accounts.req_q.clone(),
|
||||
accounts.event_q.clone(),
|
||||
accounts.bids.clone(),
|
||||
accounts.asks.clone(),
|
||||
taker_coin_account.clone(),
|
||||
taker_pc_account.clone(),
|
||||
taker.clone(),
|
||||
accounts.coin_vault.clone(),
|
||||
accounts.pc_vault.clone(),
|
||||
spl_token_program.clone(),
|
||||
accounts.vault_signer.clone(),
|
||||
];
|
||||
|
||||
let starting_balance = TokenAccount::new(&taker_coin_account)
|
||||
.unwrap()
|
||||
.balance()
|
||||
.unwrap();
|
||||
let starting_pc = TokenAccount::new(&taker_pc_account)
|
||||
.unwrap()
|
||||
.balance()
|
||||
.unwrap();
|
||||
let max_coin_qty = 3;
|
||||
let limit_price = 10_299;
|
||||
let max_pc_qty = max_coin_qty * limit_price;
|
||||
let send_take_ix = MarketInstruction::SendTake(SendTakeInstruction {
|
||||
side: Side::Bid,
|
||||
limit_price: NonZeroU64::new(limit_price).unwrap(),
|
||||
max_coin_qty: NonZeroU64::new(max_coin_qty).unwrap(),
|
||||
max_native_pc_qty_including_fees: NonZeroU64::new(max_pc_qty).unwrap(),
|
||||
min_coin_qty: 0,
|
||||
min_native_pc_qty: 0,
|
||||
limit: 50,
|
||||
});
|
||||
State::process(dex_program_id, &instruction_accounts, &send_take_ix.pack()).unwrap();
|
||||
let ending_balance = TokenAccount::new(&taker_coin_account)
|
||||
.unwrap()
|
||||
.balance()
|
||||
.unwrap();
|
||||
let ending_pc = TokenAccount::new(&taker_pc_account)
|
||||
.unwrap()
|
||||
.balance()
|
||||
.unwrap();
|
||||
println!(
|
||||
"{} sends 3 MARKET BUY @ {}, matched {}, paid {}",
|
||||
taker.key,
|
||||
limit_price,
|
||||
ending_balance - starting_balance,
|
||||
starting_pc - ending_pc
|
||||
);
|
||||
let market = Market::load(&accounts.market, &dex_program_id, false).unwrap();
|
||||
let pc = market.pc_deposits_total;
|
||||
let pcf = market.pc_fees_accrued;
|
||||
let cdt = market.coin_deposits_total;
|
||||
let cf = market.coin_fees_accrued;
|
||||
println!(
|
||||
"pc_deposits_total: {}, pc_fees_accrued: {}, coin_deposits_total: {}, coin_fees_accrued: {}",
|
||||
pc, pcf, cdt, cf
|
||||
);
|
||||
drop(market);
|
||||
|
||||
let tca = TokenAccount::new(&taker_coin_account).unwrap();
|
||||
assert_eq!(tca.balance().unwrap(), 7_000);
|
||||
let tpca = TokenAccount::new(&taker_pc_account).unwrap();
|
||||
// There's a default 4bps fee applied, but the fee rounds up always
|
||||
// See fees.rs:taker_fee (line 137)
|
||||
assert_eq!(tpca.balance().unwrap(), 100_000 - 10_100 - 5);
|
||||
let prev_pc_balance = tpca.balance().unwrap();
|
||||
|
||||
let starting_balance = TokenAccount::new(&taker_coin_account)
|
||||
.unwrap()
|
||||
.balance()
|
||||
.unwrap();
|
||||
let starting_pc = TokenAccount::new(&taker_pc_account)
|
||||
.unwrap()
|
||||
.balance()
|
||||
.unwrap();
|
||||
let max_coin_qty = 3;
|
||||
let limit_price = 9999;
|
||||
let max_pc_qty = max_coin_qty * limit_price;
|
||||
let send_take_ix = MarketInstruction::SendTake(SendTakeInstruction {
|
||||
side: Side::Ask,
|
||||
limit_price: NonZeroU64::new(limit_price).unwrap(),
|
||||
max_coin_qty: NonZeroU64::new(max_coin_qty).unwrap(),
|
||||
max_native_pc_qty_including_fees: NonZeroU64::new(max_pc_qty).unwrap(),
|
||||
min_coin_qty: 0,
|
||||
min_native_pc_qty: 0,
|
||||
limit: 1,
|
||||
});
|
||||
State::process(dex_program_id, &instruction_accounts, &send_take_ix.pack()).unwrap();
|
||||
let ending_balance = TokenAccount::new(&taker_coin_account)
|
||||
.unwrap()
|
||||
.balance()
|
||||
.unwrap();
|
||||
let ending_pc = TokenAccount::new(&taker_pc_account)
|
||||
.unwrap()
|
||||
.balance()
|
||||
.unwrap();
|
||||
println!(
|
||||
"{} sends 3 MARKET SELL @ {}, matched {}, received {}",
|
||||
taker.key,
|
||||
limit_price,
|
||||
starting_balance - ending_balance,
|
||||
ending_pc - starting_pc
|
||||
);
|
||||
let market = Market::load(&accounts.market, &dex_program_id, false).unwrap();
|
||||
let pc = market.pc_deposits_total;
|
||||
let pcf = market.pc_fees_accrued;
|
||||
let cdt = market.coin_deposits_total;
|
||||
let cf = market.coin_fees_accrued;
|
||||
println!(
|
||||
"pc_deposits_total: {}, pc_fees_accrued: {}, coin_deposits_total: {}, coin_fees_accrued: {}",
|
||||
pc, pcf, cdt, cf
|
||||
);
|
||||
drop(market);
|
||||
|
||||
let tca = TokenAccount::new(&taker_coin_account).unwrap();
|
||||
assert_eq!(tca.balance().unwrap(), 6_000);
|
||||
let tpca = TokenAccount::new(&taker_pc_account).unwrap();
|
||||
assert_eq!(
|
||||
tpca.balance().unwrap(),
|
||||
prev_pc_balance + 10_000 - 4 /* This time the fee is exactly 4 bps */
|
||||
);
|
||||
let prev_pc_balance = tpca.balance().unwrap();
|
||||
|
||||
let starting_balance = TokenAccount::new(&taker_coin_account)
|
||||
.unwrap()
|
||||
.balance()
|
||||
.unwrap();
|
||||
let starting_pc = TokenAccount::new(&taker_pc_account)
|
||||
.unwrap()
|
||||
.balance()
|
||||
.unwrap();
|
||||
let max_coin_qty = 3;
|
||||
let limit_price = 9999;
|
||||
let max_pc_qty = max_coin_qty * limit_price;
|
||||
let mut send_take_ix = SendTakeInstruction {
|
||||
side: Side::Ask,
|
||||
limit_price: NonZeroU64::new(limit_price).unwrap(),
|
||||
max_coin_qty: NonZeroU64::new(max_coin_qty).unwrap(),
|
||||
max_native_pc_qty_including_fees: NonZeroU64::new(max_pc_qty).unwrap(),
|
||||
min_coin_qty: max_coin_qty,
|
||||
min_native_pc_qty: max_pc_qty,
|
||||
limit: 2,
|
||||
};
|
||||
let send_take = MarketInstruction::SendTake(send_take_ix.clone());
|
||||
assert!(State::process(dex_program_id, &instruction_accounts, &send_take.pack()).is_err());
|
||||
let ending_balance = TokenAccount::new(&taker_coin_account)
|
||||
.unwrap()
|
||||
.balance()
|
||||
.unwrap();
|
||||
let ending_pc = TokenAccount::new(&taker_pc_account)
|
||||
.unwrap()
|
||||
.balance()
|
||||
.unwrap();
|
||||
println!(
|
||||
"{} sends 3 MARKET SELL @ {}, matched {}, received {}",
|
||||
taker.key,
|
||||
limit_price,
|
||||
starting_balance - ending_balance,
|
||||
ending_pc - starting_pc
|
||||
);
|
||||
send_take_ix.limit_price = NonZeroU64::new(9800).unwrap();
|
||||
send_take_ix.min_coin_qty = 1;
|
||||
send_take_ix.min_native_pc_qty = 0;
|
||||
let send_take = MarketInstruction::SendTake(send_take_ix.clone());
|
||||
assert!(!State::process(dex_program_id, &instruction_accounts, &send_take.pack()).is_err());
|
||||
let ending_balance = TokenAccount::new(&taker_coin_account)
|
||||
.unwrap()
|
||||
.balance()
|
||||
.unwrap();
|
||||
let ending_pc = TokenAccount::new(&taker_pc_account)
|
||||
.unwrap()
|
||||
.balance()
|
||||
.unwrap();
|
||||
println!(
|
||||
"{} sends 3 MARKET SELL @ {}, matched {}, received {}",
|
||||
taker.key,
|
||||
send_take_ix.limit_price.get(),
|
||||
starting_balance - ending_balance,
|
||||
ending_pc - starting_pc
|
||||
);
|
||||
let market = Market::load(&accounts.market, &dex_program_id, false).unwrap();
|
||||
let pc = market.pc_deposits_total;
|
||||
let pcf = market.pc_fees_accrued;
|
||||
let cdt = market.coin_deposits_total;
|
||||
let cf = market.coin_fees_accrued;
|
||||
println!(
|
||||
"pc_deposits_total: {}, pc_fees_accrued: {}, coin_deposits_total: {}, coin_fees_accrued: {}",
|
||||
pc, pcf, cdt, cf
|
||||
);
|
||||
drop(market);
|
||||
|
||||
let BBO {
|
||||
bid,
|
||||
ask,
|
||||
nbid,
|
||||
nask,
|
||||
buyer,
|
||||
seller,
|
||||
} = get_bbo(
|
||||
dex_program_id,
|
||||
&accounts.market,
|
||||
&accounts.bids,
|
||||
&accounts.asks,
|
||||
);
|
||||
assert_eq!(bid, 9600);
|
||||
assert_eq!(ask, 10300);
|
||||
assert_eq!(nbid, 5);
|
||||
assert_eq!(nask, 2);
|
||||
assert_eq!(buyer, seller);
|
||||
let tca = TokenAccount::new(&taker_coin_account).unwrap();
|
||||
assert_eq!(tca.balance().unwrap(), 3_000);
|
||||
let tpca = TokenAccount::new(&taker_pc_account).unwrap();
|
||||
assert_eq!(tpca.balance().unwrap(), prev_pc_balance + (3 * 9800) - 12);
|
||||
|
||||
{
|
||||
let crank_accounts = bump_vec![in ≎
|
||||
orders_account.clone(),
|
||||
accounts.market.clone(),
|
||||
accounts.event_q.clone(),
|
||||
coin_account.clone(),
|
||||
pc_account.clone(),
|
||||
]
|
||||
.into_bump_slice_mut();
|
||||
let instruction_data = MarketInstruction::ConsumeEvents(200).pack();
|
||||
State::process(dex_program_id, crank_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();
|
||||
|
||||
// The taker sold a total of 4 coins
|
||||
assert_eq!(identity(open_orders.native_coin_free), 4_000);
|
||||
// The maker places 21 offers (1+2+...+6) and 1 was filled. Then there were 4 sells
|
||||
assert_eq!(identity(open_orders.native_coin_total), 24_000);
|
||||
assert_eq!(identity(open_orders.native_pc_free), 10100);
|
||||
assert_eq!(
|
||||
identity(open_orders.native_pc_total),
|
||||
total_pc_on_book - 10000 - (9800 * 3) + 10100
|
||||
);
|
||||
}
|
||||
let market = Market::load(&accounts.market, &dex_program_id, false).unwrap();
|
||||
let pc = market.pc_deposits_total;
|
||||
let pcf = market.pc_fees_accrued;
|
||||
let cdt = market.coin_deposits_total;
|
||||
let cf = market.coin_fees_accrued;
|
||||
println!(
|
||||
"pc_deposits_total: {}, pc_fees_accrued: {}, coin_deposits_total: {}, coin_fees_accrued: {}",
|
||||
pc, pcf, cdt, cf
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cancel_orders() {
|
||||
let mut rng = StdRng::seed_from_u64(1);
|
||||
|
@ -425,8 +1094,6 @@ fn test_cancel_orders() {
|
|||
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);
|
||||
|
||||
|
@ -520,8 +1187,6 @@ fn test_max_ts_order() {
|
|||
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);
|
||||
|
||||
|
@ -608,8 +1273,6 @@ fn test_replace_orders() {
|
|||
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);
|
||||
|
||||
|
|
Loading…
Reference in New Issue