492 lines
16 KiB
Rust
492 lines
16 KiB
Rust
use anchor_lang::prelude::*;
|
|
use serum_dex::state::ToAlignedBytes;
|
|
|
|
use std::cell::{Ref, RefMut};
|
|
use std::cmp::min;
|
|
use std::convert::identity;
|
|
use std::mem::size_of;
|
|
|
|
use crate::accounts_zerocopy::*;
|
|
use crate::error::*;
|
|
use crate::state::*;
|
|
|
|
/// Serum padding is "serum" + data + "padding"
|
|
fn strip_dex_padding(data: &[u8]) -> Result<&[u8]> {
|
|
require!(data.len() >= 12, MangoError::SomeError);
|
|
Ok(&data[5..data.len() - 7])
|
|
}
|
|
|
|
fn strip_dex_padding_ref<'a>(acc: &'a AccountInfo) -> Result<Ref<'a, [u8]>> {
|
|
require!(acc.data_len() >= 12, MangoError::SomeError);
|
|
Ok(Ref::map(acc.try_borrow_data()?, |data| {
|
|
&data[5..data.len() - 7]
|
|
}))
|
|
}
|
|
|
|
fn strip_dex_padding_ref_mut<'a>(acc: &'a AccountInfo) -> Result<RefMut<'a, [u8]>> {
|
|
require!(acc.data_len() >= 12, MangoError::SomeError);
|
|
Ok(RefMut::map(acc.try_borrow_mut_data()?, |data| {
|
|
let len = data.len();
|
|
&mut data[5..len - 7]
|
|
}))
|
|
}
|
|
|
|
#[inline]
|
|
pub fn remove_slop_mut<T: bytemuck::Pod>(bytes: &mut [u8]) -> &mut [T] {
|
|
let slop = bytes.len() % size_of::<T>();
|
|
let new_len = bytes.len() - slop;
|
|
bytemuck::cast_slice_mut(&mut bytes[..new_len])
|
|
}
|
|
|
|
fn strip_data_header_mut<H: bytemuck::Pod, D: bytemuck::Pod>(
|
|
orig_data: RefMut<[u8]>,
|
|
) -> Result<(RefMut<H>, RefMut<[D]>)> {
|
|
Ok(RefMut::map_split(orig_data, |data| {
|
|
let (header_bytes, inner_bytes) = data.split_at_mut(size_of::<H>());
|
|
let header = bytemuck::try_from_bytes_mut(header_bytes).unwrap();
|
|
let inner = remove_slop_mut(inner_bytes);
|
|
(header, inner)
|
|
}))
|
|
}
|
|
|
|
pub fn load_market_state<'a>(
|
|
market_account: &'a AccountInfo,
|
|
program_id: &Pubkey,
|
|
) -> Result<Ref<'a, serum_dex::state::MarketState>> {
|
|
require!(market_account.owner == program_id, MangoError::SomeError);
|
|
let state: Ref<serum_dex::state::MarketState> =
|
|
Ref::map(strip_dex_padding_ref(market_account)?, |data| {
|
|
bytemuck::from_bytes(data)
|
|
});
|
|
state
|
|
.check_flags(false)
|
|
.map_err(|_| error!(MangoError::SomeError))?;
|
|
Ok(state)
|
|
}
|
|
|
|
/// Copied over from serum dex
|
|
#[derive(Copy, Clone, bytemuck::Zeroable, bytemuck::Pod)]
|
|
#[repr(C, packed)]
|
|
pub struct OrderBookStateHeader {
|
|
pub account_flags: u64, // Initialized, (Bids or Asks)
|
|
}
|
|
|
|
pub fn load_bids_mut<'a>(
|
|
sm: &serum_dex::state::MarketState,
|
|
bids: &'a AccountInfo,
|
|
) -> Result<RefMut<'a, serum_dex::critbit::Slab>> {
|
|
require!(
|
|
bids.key.to_aligned_bytes() == identity(sm.bids),
|
|
MangoError::SomeError
|
|
);
|
|
let orig_data = strip_dex_padding_ref_mut(bids)?;
|
|
let (header, buf) = strip_data_header_mut::<OrderBookStateHeader, u8>(orig_data)?;
|
|
require!(
|
|
header.account_flags
|
|
== serum_dex::state::AccountFlag::Initialized as u64
|
|
+ serum_dex::state::AccountFlag::Bids as u64,
|
|
MangoError::SomeError
|
|
);
|
|
Ok(RefMut::map(buf, serum_dex::critbit::Slab::new))
|
|
}
|
|
|
|
pub fn load_asks_mut<'a>(
|
|
sm: &serum_dex::state::MarketState,
|
|
asks: &'a AccountInfo,
|
|
) -> Result<RefMut<'a, serum_dex::critbit::Slab>> {
|
|
require!(
|
|
asks.key.to_aligned_bytes() == identity(sm.asks),
|
|
MangoError::SomeError
|
|
);
|
|
let orig_data = strip_dex_padding_ref_mut(asks)?;
|
|
let (header, buf) = strip_data_header_mut::<OrderBookStateHeader, u8>(orig_data)?;
|
|
require!(
|
|
header.account_flags
|
|
== serum_dex::state::AccountFlag::Initialized as u64
|
|
+ serum_dex::state::AccountFlag::Asks as u64,
|
|
MangoError::SomeError
|
|
);
|
|
Ok(RefMut::map(buf, serum_dex::critbit::Slab::new))
|
|
}
|
|
|
|
pub fn load_open_orders_ref<'a>(
|
|
acc: &'a AccountInfo,
|
|
) -> Result<Ref<'a, serum_dex::state::OpenOrders>> {
|
|
Ok(Ref::map(strip_dex_padding_ref(acc)?, bytemuck::from_bytes))
|
|
}
|
|
|
|
pub fn load_open_orders(acc: &impl AccountReader) -> Result<&serum_dex::state::OpenOrders> {
|
|
load_open_orders_bytes(acc.data())
|
|
}
|
|
|
|
pub fn load_open_orders_bytes(bytes: &[u8]) -> Result<&serum_dex::state::OpenOrders> {
|
|
Ok(bytemuck::from_bytes(strip_dex_padding(bytes)?))
|
|
}
|
|
|
|
pub fn pubkey_from_u64_array(d: [u64; 4]) -> Pubkey {
|
|
let b: [u8; 32] = bytemuck::cast(d);
|
|
Pubkey::from(b)
|
|
}
|
|
|
|
pub struct InitOpenOrders<'info> {
|
|
/// CHECK: cpi
|
|
pub program: AccountInfo<'info>,
|
|
/// CHECK: cpi
|
|
pub market: AccountInfo<'info>,
|
|
/// CHECK: cpi
|
|
pub open_orders: AccountInfo<'info>,
|
|
/// CHECK: cpi
|
|
pub open_orders_authority: AccountInfo<'info>,
|
|
/// CHECK: cpi
|
|
pub rent: AccountInfo<'info>,
|
|
}
|
|
|
|
impl<'info> InitOpenOrders<'info> {
|
|
pub fn call(self, group: &Group) -> Result<()> {
|
|
let data = serum_dex::instruction::MarketInstruction::InitOpenOrders.pack();
|
|
let instruction = solana_program::instruction::Instruction {
|
|
program_id: *self.program.key,
|
|
data,
|
|
accounts: vec![
|
|
AccountMeta::new(*self.open_orders.key, false),
|
|
AccountMeta::new_readonly(*self.open_orders_authority.key, true),
|
|
AccountMeta::new_readonly(*self.market.key, false),
|
|
AccountMeta::new_readonly(*self.rent.key, false),
|
|
],
|
|
};
|
|
|
|
let account_infos = [
|
|
self.program,
|
|
self.open_orders,
|
|
self.open_orders_authority,
|
|
self.market,
|
|
self.rent,
|
|
];
|
|
|
|
let seeds = group_seeds!(group);
|
|
solana_program::program::invoke_signed_unchecked(&instruction, &account_infos, &[seeds])?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
pub struct CloseOpenOrders<'info> {
|
|
/// CHECK: cpi
|
|
pub program: AccountInfo<'info>,
|
|
/// CHECK: cpi
|
|
pub market: AccountInfo<'info>,
|
|
/// CHECK: cpi
|
|
pub open_orders: AccountInfo<'info>,
|
|
/// CHECK: cpi
|
|
pub open_orders_authority: AccountInfo<'info>,
|
|
/// CHECK: cpi
|
|
pub sol_destination: AccountInfo<'info>,
|
|
}
|
|
|
|
impl<'info> CloseOpenOrders<'info> {
|
|
pub fn call(self, group: &Group) -> Result<()> {
|
|
let data = serum_dex::instruction::MarketInstruction::CloseOpenOrders.pack();
|
|
let instruction = solana_program::instruction::Instruction {
|
|
program_id: *self.program.key,
|
|
data,
|
|
accounts: vec![
|
|
AccountMeta::new(*self.open_orders.key, false),
|
|
AccountMeta::new_readonly(*self.open_orders_authority.key, true),
|
|
AccountMeta::new(*self.sol_destination.key, false),
|
|
AccountMeta::new_readonly(*self.market.key, false),
|
|
],
|
|
};
|
|
|
|
let account_infos = [
|
|
self.program,
|
|
self.open_orders,
|
|
self.open_orders_authority,
|
|
self.sol_destination,
|
|
self.market,
|
|
];
|
|
|
|
let seeds = group_seeds!(group);
|
|
solana_program::program::invoke_signed_unchecked(&instruction, &account_infos, &[seeds])?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
pub struct SettleFunds<'info> {
|
|
/// CHECK: cpi
|
|
pub program: AccountInfo<'info>,
|
|
/// CHECK: cpi
|
|
pub market: AccountInfo<'info>,
|
|
/// CHECK: cpi
|
|
pub open_orders: AccountInfo<'info>,
|
|
/// CHECK: cpi
|
|
pub open_orders_authority: AccountInfo<'info>,
|
|
/// CHECK: cpi
|
|
pub base_vault: AccountInfo<'info>,
|
|
/// CHECK: cpi
|
|
pub quote_vault: AccountInfo<'info>,
|
|
/// CHECK: cpi
|
|
pub user_base_wallet: AccountInfo<'info>,
|
|
/// CHECK: cpi
|
|
pub user_quote_wallet: AccountInfo<'info>,
|
|
/// CHECK: cpi
|
|
pub vault_signer: AccountInfo<'info>,
|
|
/// CHECK: cpi
|
|
pub token_program: AccountInfo<'info>,
|
|
/// CHECK: cpi
|
|
pub rebates_quote_wallet: AccountInfo<'info>,
|
|
}
|
|
|
|
impl<'a> SettleFunds<'a> {
|
|
pub fn call(self, group: &Group) -> Result<()> {
|
|
let data = serum_dex::instruction::MarketInstruction::SettleFunds.pack();
|
|
let instruction = solana_program::instruction::Instruction {
|
|
program_id: *self.program.key,
|
|
data,
|
|
accounts: vec![
|
|
AccountMeta::new(*self.market.key, false),
|
|
AccountMeta::new(*self.open_orders.key, false),
|
|
AccountMeta::new_readonly(*self.open_orders_authority.key, true),
|
|
AccountMeta::new(*self.base_vault.key, false),
|
|
AccountMeta::new(*self.quote_vault.key, false),
|
|
AccountMeta::new(*self.user_base_wallet.key, false),
|
|
AccountMeta::new(*self.user_quote_wallet.key, false),
|
|
AccountMeta::new_readonly(*self.vault_signer.key, false),
|
|
AccountMeta::new_readonly(*self.token_program.key, false),
|
|
AccountMeta::new(*self.rebates_quote_wallet.key, false),
|
|
],
|
|
};
|
|
|
|
let account_infos = [
|
|
self.program,
|
|
self.market,
|
|
self.open_orders,
|
|
self.open_orders_authority,
|
|
self.base_vault,
|
|
self.quote_vault,
|
|
self.user_base_wallet,
|
|
self.user_quote_wallet,
|
|
self.vault_signer,
|
|
self.token_program,
|
|
self.rebates_quote_wallet,
|
|
];
|
|
|
|
let seeds = group_seeds!(group);
|
|
solana_program::program::invoke_signed_unchecked(&instruction, &account_infos, &[seeds])?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
pub struct PlaceOrder<'info> {
|
|
/// CHECK: cpi
|
|
pub program: AccountInfo<'info>,
|
|
/// CHECK: cpi
|
|
pub market: AccountInfo<'info>,
|
|
/// CHECK: cpi
|
|
pub request_queue: AccountInfo<'info>,
|
|
/// CHECK: cpi
|
|
pub event_queue: AccountInfo<'info>,
|
|
/// CHECK: cpi
|
|
pub bids: AccountInfo<'info>,
|
|
/// CHECK: cpi
|
|
pub asks: AccountInfo<'info>,
|
|
/// CHECK: cpi
|
|
pub base_vault: AccountInfo<'info>,
|
|
/// CHECK: cpi
|
|
pub quote_vault: AccountInfo<'info>,
|
|
/// CHECK: cpi
|
|
pub token_program: AccountInfo<'info>,
|
|
|
|
/// CHECK: cpi
|
|
pub open_orders: AccountInfo<'info>,
|
|
/// CHECK: cpi
|
|
pub order_payer_token_account: AccountInfo<'info>,
|
|
/// must cover the open_orders and the order_payer_token_account
|
|
/// CHECK: cpi
|
|
pub user_authority: AccountInfo<'info>,
|
|
}
|
|
|
|
impl<'a> PlaceOrder<'a> {
|
|
pub fn call(
|
|
self,
|
|
group: &Group,
|
|
order: serum_dex::instruction::NewOrderInstructionV3,
|
|
) -> Result<()> {
|
|
let data = serum_dex::instruction::MarketInstruction::NewOrderV3(order).pack();
|
|
let instruction = solana_program::instruction::Instruction {
|
|
program_id: *self.program.key,
|
|
data,
|
|
accounts: vec![
|
|
AccountMeta::new(*self.market.key, false),
|
|
AccountMeta::new(*self.open_orders.key, false),
|
|
AccountMeta::new(*self.request_queue.key, false),
|
|
AccountMeta::new(*self.event_queue.key, false),
|
|
AccountMeta::new(*self.bids.key, false),
|
|
AccountMeta::new(*self.asks.key, false),
|
|
AccountMeta::new(*self.order_payer_token_account.key, false),
|
|
AccountMeta::new_readonly(*self.user_authority.key, true),
|
|
AccountMeta::new(*self.base_vault.key, false),
|
|
AccountMeta::new(*self.quote_vault.key, false),
|
|
AccountMeta::new_readonly(*self.token_program.key, false),
|
|
AccountMeta::new_readonly(*self.user_authority.key, false),
|
|
],
|
|
};
|
|
let account_infos = [
|
|
self.program,
|
|
self.market,
|
|
self.open_orders,
|
|
self.request_queue,
|
|
self.event_queue,
|
|
self.bids,
|
|
self.asks,
|
|
self.order_payer_token_account,
|
|
self.user_authority.clone(),
|
|
self.base_vault,
|
|
self.quote_vault,
|
|
self.token_program,
|
|
self.user_authority,
|
|
];
|
|
|
|
let seeds = group_seeds!(group);
|
|
solana_program::program::invoke_signed_unchecked(&instruction, &account_infos, &[seeds])?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
pub struct CancelOrder<'info> {
|
|
/// CHECK: cpi
|
|
pub program: AccountInfo<'info>,
|
|
/// CHECK: cpi
|
|
pub market: AccountInfo<'info>,
|
|
/// CHECK: cpi
|
|
pub event_queue: AccountInfo<'info>,
|
|
/// CHECK: cpi
|
|
pub bids: AccountInfo<'info>,
|
|
/// CHECK: cpi
|
|
pub asks: AccountInfo<'info>,
|
|
|
|
/// CHECK: cpi
|
|
pub open_orders: AccountInfo<'info>,
|
|
/// CHECK: cpi
|
|
pub open_orders_authority: AccountInfo<'info>,
|
|
}
|
|
|
|
impl<'a> CancelOrder<'a> {
|
|
pub fn cancel_one(
|
|
self,
|
|
group: &Group,
|
|
order: serum_dex::instruction::CancelOrderInstructionV2,
|
|
) -> Result<()> {
|
|
let data = serum_dex::instruction::MarketInstruction::CancelOrderV2(order).pack();
|
|
let instruction = solana_program::instruction::Instruction {
|
|
program_id: *self.program.key,
|
|
data,
|
|
accounts: vec![
|
|
AccountMeta::new(*self.market.key, false),
|
|
AccountMeta::new(*self.bids.key, false),
|
|
AccountMeta::new(*self.asks.key, false),
|
|
AccountMeta::new(*self.open_orders.key, false),
|
|
AccountMeta::new_readonly(*self.open_orders_authority.key, true),
|
|
AccountMeta::new(*self.event_queue.key, false),
|
|
],
|
|
};
|
|
let account_infos = [
|
|
self.program,
|
|
self.market,
|
|
self.bids,
|
|
self.asks,
|
|
self.open_orders,
|
|
self.open_orders_authority,
|
|
self.event_queue,
|
|
];
|
|
|
|
let seeds = group_seeds!(group);
|
|
solana_program::program::invoke_signed_unchecked(&instruction, &account_infos, &[seeds])?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn cancel_all(self, group: &Group, mut limit: u8) -> Result<()> {
|
|
// find all cancels by scanning open_orders/bids/asks
|
|
let mut cancels = vec![];
|
|
{
|
|
let open_orders = load_open_orders_ref(&self.open_orders)?;
|
|
let market = load_market_state(&self.market, self.program.key)?;
|
|
let bids = load_bids_mut(&market, &self.bids)?;
|
|
let asks = load_asks_mut(&market, &self.asks)?;
|
|
|
|
limit = min(limit, open_orders.free_slot_bits.count_zeros() as u8);
|
|
if limit == 0 {
|
|
return Ok(());
|
|
}
|
|
for j in 0..128 {
|
|
let slot_mask = 1u128 << j;
|
|
if open_orders.free_slot_bits & slot_mask != 0 {
|
|
// means slot is free
|
|
continue;
|
|
}
|
|
let order_id = open_orders.orders[j];
|
|
|
|
// free_slot_bits is only updated when the event queue is processed,
|
|
// that means we need to scan bids/asks to see if the order is still alive
|
|
let side = if open_orders.is_bid_bits & slot_mask != 0 {
|
|
match bids.find_by_key(order_id) {
|
|
None => continue,
|
|
Some(_) => serum_dex::matching::Side::Bid,
|
|
}
|
|
} else {
|
|
match asks.find_by_key(order_id) {
|
|
None => continue,
|
|
Some(_) => serum_dex::matching::Side::Ask,
|
|
}
|
|
};
|
|
|
|
let cancel_instruction =
|
|
serum_dex::instruction::CancelOrderInstructionV2 { side, order_id };
|
|
|
|
cancels.push(cancel_instruction);
|
|
|
|
limit -= 1;
|
|
if limit == 0 {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
let mut instruction = solana_program::instruction::Instruction {
|
|
program_id: *self.program.key,
|
|
data: vec![],
|
|
accounts: vec![
|
|
AccountMeta::new(*self.market.key, false),
|
|
AccountMeta::new(*self.bids.key, false),
|
|
AccountMeta::new(*self.asks.key, false),
|
|
AccountMeta::new(*self.open_orders.key, false),
|
|
AccountMeta::new_readonly(*self.open_orders_authority.key, true),
|
|
AccountMeta::new(*self.event_queue.key, false),
|
|
],
|
|
};
|
|
let account_infos = [
|
|
self.program,
|
|
self.market,
|
|
self.bids,
|
|
self.asks,
|
|
self.open_orders,
|
|
self.open_orders_authority,
|
|
self.event_queue,
|
|
];
|
|
let seeds = group_seeds!(group);
|
|
|
|
for cancel in cancels.into_iter() {
|
|
instruction.data =
|
|
serum_dex::instruction::MarketInstruction::CancelOrderV2(cancel).pack();
|
|
solana_program::program::invoke_signed_unchecked(
|
|
&instruction,
|
|
&account_infos,
|
|
&[seeds],
|
|
)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|