Serum: serum3_liq_force_cancel_orders instruction
Still needs a test.
This commit is contained in:
parent
27f882a333
commit
34d14ef267
|
@ -204,7 +204,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e49a20ac8743546bc76a247ff0a81ceb0cee7653b087ccb330bf3553d907d522"
|
checksum = "e49a20ac8743546bc76a247ff0a81ceb0cee7653b087ccb330bf3553d907d522"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anchor-lang",
|
"anchor-lang",
|
||||||
"serum_dex",
|
|
||||||
"solana-program",
|
"solana-program",
|
||||||
"spl-associated-token-account",
|
"spl-associated-token-account",
|
||||||
"spl-token",
|
"spl-token",
|
||||||
|
@ -1563,6 +1562,7 @@ dependencies = [
|
||||||
"num_enum",
|
"num_enum",
|
||||||
"pyth-client",
|
"pyth-client",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serum_dex",
|
||||||
"solana-logger",
|
"solana-logger",
|
||||||
"solana-program",
|
"solana-program",
|
||||||
"solana-program-test",
|
"solana-program-test",
|
||||||
|
@ -2400,8 +2400,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serum_dex"
|
name = "serum_dex"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/blockworks-foundation/serum-dex.git#3104f424ee38a415418a1cdef67970771f832857"
|
||||||
checksum = "02705854bae4622e552346c8edd43ab90c7425da35d63d2c689f39238f8d8b25"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayref",
|
"arrayref",
|
||||||
"bincode",
|
"bincode",
|
||||||
|
|
|
@ -21,7 +21,7 @@ test-bpf = []
|
||||||
# todo: when to fix, when to use caret? need a regular chore to bump dependencies
|
# todo: when to fix, when to use caret? need a regular chore to bump dependencies
|
||||||
# note: possibly need init-if-needed feature
|
# note: possibly need init-if-needed feature
|
||||||
anchor-lang = { version = "0.22.0", features = [] }
|
anchor-lang = { version = "0.22.0", features = [] }
|
||||||
anchor-spl = { version = "0.22.0", features = ["dex"] }
|
anchor-spl = { version = "0.22.0", features = [] }
|
||||||
bytemuck = "^1.7.2"
|
bytemuck = "^1.7.2"
|
||||||
# todo: higher versions don't work
|
# todo: higher versions don't work
|
||||||
fixed = { version = "=1.11.0", features = ["serde", "borsh"] }
|
fixed = { version = "=1.11.0", features = ["serde", "borsh"] }
|
||||||
|
@ -30,7 +30,7 @@ pyth-client = {version = "0.5.0", features = ["no-entrypoint"]}
|
||||||
serde = "^1.0"
|
serde = "^1.0"
|
||||||
solana-program = "1.9.5"
|
solana-program = "1.9.5"
|
||||||
static_assertions = "1.1"
|
static_assertions = "1.1"
|
||||||
#serum_dex = { version = "0.4.0", git = "https://github.com/blockworks-foundation/serum-dex.git", default-features=false, features = ["no-entrypoint", "program"] }
|
serum_dex = { version = "0.4.0", git = "https://github.com/blockworks-foundation/serum-dex.git", default-features=false, features = ["no-entrypoint", "program"] }
|
||||||
checked_math = { path = "../../lib/checked_math" }
|
checked_math = { path = "../../lib/checked_math" }
|
||||||
arrayref = "0.3.6"
|
arrayref = "0.3.6"
|
||||||
num_enum = "0.5.1"
|
num_enum = "0.5.1"
|
||||||
|
|
|
@ -8,6 +8,7 @@ pub use place_perp_order::*;
|
||||||
pub use register_token::*;
|
pub use register_token::*;
|
||||||
pub use serum3_cancel_order::*;
|
pub use serum3_cancel_order::*;
|
||||||
pub use serum3_create_open_orders::*;
|
pub use serum3_create_open_orders::*;
|
||||||
|
pub use serum3_liq_force_cancel_orders::*;
|
||||||
pub use serum3_place_order::*;
|
pub use serum3_place_order::*;
|
||||||
pub use serum3_register_market::*;
|
pub use serum3_register_market::*;
|
||||||
pub use serum3_settle_funds::*;
|
pub use serum3_settle_funds::*;
|
||||||
|
@ -24,6 +25,7 @@ mod place_perp_order;
|
||||||
mod register_token;
|
mod register_token;
|
||||||
mod serum3_cancel_order;
|
mod serum3_cancel_order;
|
||||||
mod serum3_create_open_orders;
|
mod serum3_create_open_orders;
|
||||||
|
mod serum3_liq_force_cancel_orders;
|
||||||
mod serum3_place_order;
|
mod serum3_place_order;
|
||||||
mod serum3_register_market;
|
mod serum3_register_market;
|
||||||
mod serum3_settle_funds;
|
mod serum3_settle_funds;
|
||||||
|
|
|
@ -4,8 +4,6 @@ use borsh::{BorshDeserialize, BorshSerialize};
|
||||||
use num_enum::TryFromPrimitive;
|
use num_enum::TryFromPrimitive;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
use anchor_spl::dex;
|
|
||||||
use dex::serum_dex;
|
|
||||||
use serum_dex::matching::Side;
|
use serum_dex::matching::Side;
|
||||||
|
|
||||||
use crate::error::*;
|
use crate::error::*;
|
||||||
|
@ -112,7 +110,7 @@ pub fn serum3_cancel_order(
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Settle
|
// Cancel
|
||||||
//
|
//
|
||||||
cpi_cancel_order(&ctx.accounts, order)?;
|
cpi_cancel_order(&ctx.accounts, order)?;
|
||||||
|
|
||||||
|
@ -132,5 +130,5 @@ fn cpi_cancel_order(ctx: &Serum3CancelOrder, order: CancelOrderInstructionData)
|
||||||
open_orders: ctx.open_orders.to_account_info(),
|
open_orders: ctx.open_orders.to_account_info(),
|
||||||
open_orders_authority: ctx.group.to_account_info(),
|
open_orders_authority: ctx.group.to_account_info(),
|
||||||
}
|
}
|
||||||
.call(&group, order.0)
|
.cancel_one(&group, order.0)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
use anchor_lang::prelude::*;
|
use anchor_lang::prelude::*;
|
||||||
use anchor_spl::dex;
|
|
||||||
use dex::serum_dex;
|
|
||||||
|
|
||||||
use crate::state::*;
|
use crate::state::*;
|
||||||
|
|
||||||
|
@ -49,26 +47,9 @@ pub struct Serum3CreateOpenOrders<'info> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn serum3_create_open_orders(ctx: Context<Serum3CreateOpenOrders>) -> Result<()> {
|
pub fn serum3_create_open_orders(ctx: Context<Serum3CreateOpenOrders>) -> Result<()> {
|
||||||
let serum_market = ctx.accounts.serum_market.load()?;
|
cpi_init_open_orders(&ctx.accounts)?;
|
||||||
let context = CpiContext::new(
|
|
||||||
ctx.accounts.serum_program.to_account_info(),
|
|
||||||
dex::InitOpenOrders {
|
|
||||||
open_orders: ctx.accounts.open_orders.to_account_info(),
|
|
||||||
// The open order authority must be the same as the authority on
|
|
||||||
// the vault accounts, because serum's placeorder doesn't distinguish
|
|
||||||
// the two authorities.
|
|
||||||
authority: ctx.accounts.group.to_account_info(),
|
|
||||||
market: ctx.accounts.serum_market_external.to_account_info(),
|
|
||||||
rent: ctx.accounts.rent.to_account_info(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
let group = ctx.accounts.group.load()?;
|
|
||||||
let seeds = group_seeds!(group);
|
|
||||||
// TODO: Anchor's code _forces_ anchor_spl::dex::id() as a program id.
|
|
||||||
// Are we ok with that? that would mean storing serum_program is not
|
|
||||||
// necessary.
|
|
||||||
dex::init_open_orders(context.with_signer(&[seeds]))?;
|
|
||||||
|
|
||||||
|
let serum_market = ctx.accounts.serum_market.load()?;
|
||||||
let mut account = ctx.accounts.account.load_mut()?;
|
let mut account = ctx.accounts.account.load_mut()?;
|
||||||
let serum_account = account
|
let serum_account = account
|
||||||
.serum3_account_map
|
.serum3_account_map
|
||||||
|
@ -91,3 +72,16 @@ pub fn serum3_create_open_orders(ctx: Context<Serum3CreateOpenOrders>) -> Result
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn cpi_init_open_orders(ctx: &Serum3CreateOpenOrders) -> Result<()> {
|
||||||
|
use crate::serum3_cpi;
|
||||||
|
let group = ctx.group.load()?;
|
||||||
|
serum3_cpi::InitOpenOrders {
|
||||||
|
program: ctx.serum_program.to_account_info(),
|
||||||
|
market: ctx.serum_market_external.to_account_info(),
|
||||||
|
open_orders: ctx.open_orders.to_account_info(),
|
||||||
|
open_orders_authority: ctx.group.to_account_info(),
|
||||||
|
rent: ctx.rent.to_account_info(),
|
||||||
|
}
|
||||||
|
.call(&group)
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,180 @@
|
||||||
|
use anchor_lang::prelude::*;
|
||||||
|
use anchor_spl::token::{Token, TokenAccount};
|
||||||
|
|
||||||
|
use crate::error::*;
|
||||||
|
use crate::state::*;
|
||||||
|
|
||||||
|
#[derive(Accounts)]
|
||||||
|
pub struct Serum3LiqForceCancelOrders<'info> {
|
||||||
|
pub group: AccountLoader<'info, Group>,
|
||||||
|
|
||||||
|
#[account(
|
||||||
|
mut,
|
||||||
|
has_one = group,
|
||||||
|
)]
|
||||||
|
pub account: AccountLoader<'info, MangoAccount>,
|
||||||
|
|
||||||
|
// Validated inline
|
||||||
|
#[account(mut)]
|
||||||
|
pub open_orders: UncheckedAccount<'info>,
|
||||||
|
|
||||||
|
#[account(
|
||||||
|
has_one = group,
|
||||||
|
has_one = serum_program,
|
||||||
|
has_one = serum_market_external,
|
||||||
|
)]
|
||||||
|
pub serum_market: AccountLoader<'info, Serum3Market>,
|
||||||
|
pub serum_program: UncheckedAccount<'info>,
|
||||||
|
#[account(mut)]
|
||||||
|
pub serum_market_external: UncheckedAccount<'info>,
|
||||||
|
|
||||||
|
// These accounts are forwarded directly to the serum cpi call
|
||||||
|
// and are validated there.
|
||||||
|
#[account(mut)]
|
||||||
|
pub market_bids: UncheckedAccount<'info>,
|
||||||
|
#[account(mut)]
|
||||||
|
pub market_asks: UncheckedAccount<'info>,
|
||||||
|
#[account(mut)]
|
||||||
|
pub market_event_queue: UncheckedAccount<'info>,
|
||||||
|
#[account(mut)]
|
||||||
|
pub market_base_vault: UncheckedAccount<'info>,
|
||||||
|
#[account(mut)]
|
||||||
|
pub market_quote_vault: UncheckedAccount<'info>,
|
||||||
|
pub market_vault_signer: UncheckedAccount<'info>,
|
||||||
|
|
||||||
|
// token_index and bank.vault == vault is validated inline
|
||||||
|
#[account(mut, has_one = group)]
|
||||||
|
pub quote_bank: AccountLoader<'info, Bank>,
|
||||||
|
#[account(mut)]
|
||||||
|
pub quote_vault: Box<Account<'info, TokenAccount>>,
|
||||||
|
#[account(mut, has_one = group)]
|
||||||
|
pub base_bank: AccountLoader<'info, Bank>,
|
||||||
|
#[account(mut)]
|
||||||
|
pub base_vault: Box<Account<'info, TokenAccount>>,
|
||||||
|
|
||||||
|
pub token_program: Program<'info, Token>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn serum3_liq_force_cancel_orders(
|
||||||
|
ctx: Context<Serum3LiqForceCancelOrders>,
|
||||||
|
limit: u8,
|
||||||
|
) -> Result<()> {
|
||||||
|
//
|
||||||
|
// Validation
|
||||||
|
//
|
||||||
|
{
|
||||||
|
let account = ctx.accounts.account.load()?;
|
||||||
|
let serum_market = ctx.accounts.serum_market.load()?;
|
||||||
|
|
||||||
|
// Validate open_orders
|
||||||
|
require!(
|
||||||
|
account
|
||||||
|
.serum3_account_map
|
||||||
|
.find(serum_market.market_index)
|
||||||
|
.ok_or_else(|| error!(MangoError::SomeError))?
|
||||||
|
.open_orders
|
||||||
|
== ctx.accounts.open_orders.key(),
|
||||||
|
MangoError::SomeError
|
||||||
|
);
|
||||||
|
|
||||||
|
// Validate banks and vaults
|
||||||
|
let quote_bank = ctx.accounts.quote_bank.load()?;
|
||||||
|
require!(
|
||||||
|
quote_bank.vault == ctx.accounts.quote_vault.key(),
|
||||||
|
MangoError::SomeError
|
||||||
|
);
|
||||||
|
require!(
|
||||||
|
quote_bank.token_index == serum_market.quote_token_index,
|
||||||
|
MangoError::SomeError
|
||||||
|
);
|
||||||
|
let base_bank = ctx.accounts.base_bank.load()?;
|
||||||
|
require!(
|
||||||
|
base_bank.vault == ctx.accounts.base_vault.key(),
|
||||||
|
MangoError::SomeError
|
||||||
|
);
|
||||||
|
require!(
|
||||||
|
base_bank.token_index == serum_market.base_token_index,
|
||||||
|
MangoError::SomeError
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: do the correct health / being_liquidated check
|
||||||
|
{
|
||||||
|
let account = ctx.accounts.account.load()?;
|
||||||
|
let health = compute_health(&account, &ctx.remaining_accounts)?;
|
||||||
|
msg!("health: {}", health);
|
||||||
|
require!(health < 0, MangoError::SomeError);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Before-settle tracking
|
||||||
|
//
|
||||||
|
let before_base_vault = ctx.accounts.base_vault.amount;
|
||||||
|
let before_quote_vault = ctx.accounts.quote_vault.amount;
|
||||||
|
|
||||||
|
//
|
||||||
|
// Cancel all and settle
|
||||||
|
//
|
||||||
|
cpi_cancel_all_orders(&ctx.accounts, limit)?;
|
||||||
|
cpi_settle_funds(&ctx.accounts)?;
|
||||||
|
|
||||||
|
//
|
||||||
|
// After-settle tracking
|
||||||
|
//
|
||||||
|
ctx.accounts.base_vault.reload()?;
|
||||||
|
ctx.accounts.quote_vault.reload()?;
|
||||||
|
let after_base_vault = ctx.accounts.base_vault.amount;
|
||||||
|
let after_quote_vault = ctx.accounts.quote_vault.amount;
|
||||||
|
|
||||||
|
// Charge the difference in vault balances to the user's account
|
||||||
|
{
|
||||||
|
let mut account = ctx.accounts.account.load_mut()?;
|
||||||
|
|
||||||
|
let mut base_bank = ctx.accounts.base_bank.load_mut()?;
|
||||||
|
let base_position = account.token_account_map.get_mut(base_bank.token_index)?;
|
||||||
|
base_bank.change(base_position, (after_base_vault - before_base_vault) as i64)?;
|
||||||
|
|
||||||
|
let mut quote_bank = ctx.accounts.quote_bank.load_mut()?;
|
||||||
|
let quote_position = account.token_account_map.get_mut(quote_bank.token_index)?;
|
||||||
|
quote_bank.change(
|
||||||
|
quote_position,
|
||||||
|
(after_quote_vault - before_quote_vault) as i64,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cpi_cancel_all_orders(ctx: &Serum3LiqForceCancelOrders, limit: u8) -> Result<()> {
|
||||||
|
use crate::serum3_cpi;
|
||||||
|
let group = ctx.group.load()?;
|
||||||
|
serum3_cpi::CancelOrder {
|
||||||
|
program: ctx.serum_program.to_account_info(),
|
||||||
|
market: ctx.serum_market_external.to_account_info(),
|
||||||
|
bids: ctx.market_bids.to_account_info(),
|
||||||
|
asks: ctx.market_asks.to_account_info(),
|
||||||
|
event_queue: ctx.market_event_queue.to_account_info(),
|
||||||
|
|
||||||
|
open_orders: ctx.open_orders.to_account_info(),
|
||||||
|
open_orders_authority: ctx.group.to_account_info(),
|
||||||
|
}
|
||||||
|
.cancel_all(&group, limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cpi_settle_funds(ctx: &Serum3LiqForceCancelOrders) -> Result<()> {
|
||||||
|
use crate::serum3_cpi;
|
||||||
|
let group = ctx.group.load()?;
|
||||||
|
serum3_cpi::SettleFunds {
|
||||||
|
program: ctx.serum_program.to_account_info(),
|
||||||
|
market: ctx.serum_market_external.to_account_info(),
|
||||||
|
open_orders: ctx.open_orders.to_account_info(),
|
||||||
|
open_orders_authority: ctx.group.to_account_info(),
|
||||||
|
base_vault: ctx.market_base_vault.to_account_info(),
|
||||||
|
quote_vault: ctx.market_quote_vault.to_account_info(),
|
||||||
|
user_base_wallet: ctx.base_vault.to_account_info(),
|
||||||
|
user_quote_wallet: ctx.quote_vault.to_account_info(),
|
||||||
|
vault_signer: ctx.market_vault_signer.to_account_info(),
|
||||||
|
token_program: ctx.token_program.to_account_info(),
|
||||||
|
}
|
||||||
|
.call(&group)
|
||||||
|
}
|
|
@ -3,13 +3,10 @@ use anchor_spl::token::{Token, TokenAccount};
|
||||||
use arrayref::array_refs;
|
use arrayref::array_refs;
|
||||||
use borsh::{BorshDeserialize, BorshSerialize};
|
use borsh::{BorshDeserialize, BorshSerialize};
|
||||||
use num_enum::TryFromPrimitive;
|
use num_enum::TryFromPrimitive;
|
||||||
|
use serum_dex::matching::Side;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::num::NonZeroU64;
|
use std::num::NonZeroU64;
|
||||||
|
|
||||||
use anchor_spl::dex;
|
|
||||||
use dex::serum_dex;
|
|
||||||
use serum_dex::matching::Side;
|
|
||||||
|
|
||||||
use crate::error::*;
|
use crate::error::*;
|
||||||
use crate::state::*;
|
use crate::state::*;
|
||||||
|
|
||||||
|
|
|
@ -114,6 +114,13 @@ pub mod mango_v4 {
|
||||||
instructions::serum3_settle_funds(ctx)
|
instructions::serum3_settle_funds(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn serum3_liq_force_cancel_orders(
|
||||||
|
ctx: Context<Serum3LiqForceCancelOrders>,
|
||||||
|
limit: u8,
|
||||||
|
) -> Result<()> {
|
||||||
|
instructions::serum3_liq_force_cancel_orders(ctx, limit)
|
||||||
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Perps
|
/// Perps
|
||||||
///
|
///
|
||||||
|
|
|
@ -1,8 +1,149 @@
|
||||||
use anchor_lang::prelude::*;
|
use anchor_lang::prelude::*;
|
||||||
use anchor_spl::dex::serum_dex;
|
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::error::*;
|
||||||
use crate::state::*;
|
use crate::state::*;
|
||||||
|
|
||||||
|
fn strip_dex_padding<'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() - 12]
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn strip_dex_padding_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 - 12]
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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<'a, H: bytemuck::Pod, D: bytemuck::Pod>(
|
||||||
|
orig_data: RefMut<'a, [u8]>,
|
||||||
|
) -> Result<(RefMut<'a, H>, RefMut<'a, [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(market_account)?, |data| {
|
||||||
|
bytemuck::from_bytes(data)
|
||||||
|
});
|
||||||
|
state
|
||||||
|
.check_flags()
|
||||||
|
.map_err(|_| error!(MangoError::SomeError))?;
|
||||||
|
Ok(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Copied over from serum dex
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
#[repr(C, packed)]
|
||||||
|
pub struct OrderBookStateHeader {
|
||||||
|
pub account_flags: u64, // Initialized, (Bids or Asks)
|
||||||
|
}
|
||||||
|
unsafe impl bytemuck::Zeroable for OrderBookStateHeader {}
|
||||||
|
unsafe impl bytemuck::Pod for OrderBookStateHeader {}
|
||||||
|
|
||||||
|
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_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_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<'a>(acc: &'a AccountInfo) -> Result<Ref<'a, serum_dex::state::OpenOrders>> {
|
||||||
|
Ok(Ref::map(strip_dex_padding(acc)?, bytemuck::from_bytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct InitOpenOrders<'info> {
|
||||||
|
pub program: AccountInfo<'info>,
|
||||||
|
pub market: AccountInfo<'info>,
|
||||||
|
pub open_orders: AccountInfo<'info>,
|
||||||
|
pub open_orders_authority: AccountInfo<'info>,
|
||||||
|
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 SettleFunds<'info> {
|
pub struct SettleFunds<'info> {
|
||||||
pub program: AccountInfo<'info>,
|
pub program: AccountInfo<'info>,
|
||||||
pub market: AccountInfo<'info>,
|
pub market: AccountInfo<'info>,
|
||||||
|
@ -134,7 +275,7 @@ pub struct CancelOrder<'info> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> CancelOrder<'a> {
|
impl<'a> CancelOrder<'a> {
|
||||||
pub fn call(
|
pub fn cancel_one(
|
||||||
self,
|
self,
|
||||||
group: &Group,
|
group: &Group,
|
||||||
order: serum_dex::instruction::CancelOrderInstructionV2,
|
order: serum_dex::instruction::CancelOrderInstructionV2,
|
||||||
|
@ -167,4 +308,88 @@ impl<'a> CancelOrder<'a> {
|
||||||
|
|
||||||
Ok(())
|
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(&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(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
use anchor_lang::prelude::*;
|
use anchor_lang::prelude::*;
|
||||||
use anchor_spl::dex::serum_dex;
|
|
||||||
use fixed::types::I80F48;
|
use fixed::types::I80F48;
|
||||||
use std::cell::Ref;
|
use std::cell::Ref;
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
use anchor_lang::prelude::*;
|
use anchor_lang::prelude::*;
|
||||||
use anchor_lang::solana_program::sysvar::{self, SysvarId};
|
use anchor_lang::solana_program::sysvar::{self, SysvarId};
|
||||||
use anchor_spl::dex::serum_dex;
|
|
||||||
use anchor_spl::token::{Token, TokenAccount};
|
use anchor_spl::token::{Token, TokenAccount};
|
||||||
use fixed::types::I80F48;
|
use fixed::types::I80F48;
|
||||||
use solana_program::instruction::Instruction;
|
use solana_program::instruction::Instruction;
|
||||||
|
@ -764,7 +763,7 @@ impl<'keypair> ClientInstruction for Serum3PlaceOrderInstruction<'keypair> {
|
||||||
let program_id = mango_v4::id();
|
let program_id = mango_v4::id();
|
||||||
let instruction = Self::Instruction {
|
let instruction = Self::Instruction {
|
||||||
order: mango_v4::instructions::NewOrderInstructionData(
|
order: mango_v4::instructions::NewOrderInstructionData(
|
||||||
anchor_spl::dex::serum_dex::instruction::NewOrderInstructionV3 {
|
serum_dex::instruction::NewOrderInstructionV3 {
|
||||||
side: self.side.try_into().unwrap(),
|
side: self.side.try_into().unwrap(),
|
||||||
limit_price: self.limit_price.try_into().unwrap(),
|
limit_price: self.limit_price.try_into().unwrap(),
|
||||||
max_coin_qty: self.max_base_qty.try_into().unwrap(),
|
max_coin_qty: self.max_base_qty.try_into().unwrap(),
|
||||||
|
@ -872,7 +871,7 @@ impl<'keypair> ClientInstruction for Serum3CancelOrderInstruction<'keypair> {
|
||||||
let program_id = mango_v4::id();
|
let program_id = mango_v4::id();
|
||||||
let instruction = Self::Instruction {
|
let instruction = Self::Instruction {
|
||||||
order: mango_v4::instructions::CancelOrderInstructionData(
|
order: mango_v4::instructions::CancelOrderInstructionData(
|
||||||
anchor_spl::dex::serum_dex::instruction::CancelOrderInstructionV2 {
|
serum_dex::instruction::CancelOrderInstructionV2 {
|
||||||
side: self.side.try_into().unwrap(),
|
side: self.side.try_into().unwrap(),
|
||||||
order_id: self.order_id,
|
order_id: self.order_id,
|
||||||
},
|
},
|
||||||
|
@ -996,6 +995,85 @@ impl<'keypair> ClientInstruction for Serum3SettleFundsInstruction<'keypair> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct Serum3LiqForceCancelOrdersInstruction {
|
||||||
|
pub account: Pubkey,
|
||||||
|
pub serum_market: Pubkey,
|
||||||
|
pub limit: u8,
|
||||||
|
}
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl ClientInstruction for Serum3LiqForceCancelOrdersInstruction {
|
||||||
|
type Accounts = mango_v4::accounts::Serum3LiqForceCancelOrders;
|
||||||
|
type Instruction = mango_v4::instruction::Serum3LiqForceCancelOrders;
|
||||||
|
async fn to_instruction(
|
||||||
|
&self,
|
||||||
|
account_loader: impl ClientAccountLoader + 'async_trait,
|
||||||
|
) -> (Self::Accounts, instruction::Instruction) {
|
||||||
|
let program_id = mango_v4::id();
|
||||||
|
let instruction = Self::Instruction { limit: self.limit };
|
||||||
|
|
||||||
|
let account: MangoAccount = account_loader.load(&self.account).await.unwrap();
|
||||||
|
let serum_market: Serum3Market = account_loader.load(&self.serum_market).await.unwrap();
|
||||||
|
let open_orders = account
|
||||||
|
.serum3_account_map
|
||||||
|
.find(serum_market.market_index)
|
||||||
|
.unwrap()
|
||||||
|
.open_orders;
|
||||||
|
let quote_info =
|
||||||
|
get_mint_info_by_token_index(&account_loader, &account, serum_market.quote_token_index)
|
||||||
|
.await;
|
||||||
|
let base_info =
|
||||||
|
get_mint_info_by_token_index(&account_loader, &account, serum_market.base_token_index)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let market_external_bytes = account_loader
|
||||||
|
.load_bytes(&serum_market.serum_market_external)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let market_external: &serum_dex::state::MarketState = bytemuck::from_bytes(
|
||||||
|
&market_external_bytes[5..5 + std::mem::size_of::<serum_dex::state::MarketState>()],
|
||||||
|
);
|
||||||
|
// unpack the data, to avoid unaligned references
|
||||||
|
let bids = market_external.bids;
|
||||||
|
let asks = market_external.asks;
|
||||||
|
let event_q = market_external.event_q;
|
||||||
|
let coin_vault = market_external.coin_vault;
|
||||||
|
let pc_vault = market_external.pc_vault;
|
||||||
|
let vault_signer = serum_dex::state::gen_vault_signer_key(
|
||||||
|
market_external.vault_signer_nonce,
|
||||||
|
&serum_market.serum_market_external,
|
||||||
|
&serum_market.serum_program,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let accounts = Self::Accounts {
|
||||||
|
group: account.group,
|
||||||
|
account: self.account,
|
||||||
|
open_orders,
|
||||||
|
quote_bank: quote_info.bank,
|
||||||
|
quote_vault: quote_info.vault,
|
||||||
|
base_bank: base_info.bank,
|
||||||
|
base_vault: base_info.vault,
|
||||||
|
serum_market: self.serum_market,
|
||||||
|
serum_program: serum_market.serum_program,
|
||||||
|
serum_market_external: serum_market.serum_market_external,
|
||||||
|
market_bids: from_serum_style_pubkey(&bids),
|
||||||
|
market_asks: from_serum_style_pubkey(&asks),
|
||||||
|
market_event_queue: from_serum_style_pubkey(&event_q),
|
||||||
|
market_base_vault: from_serum_style_pubkey(&coin_vault),
|
||||||
|
market_quote_vault: from_serum_style_pubkey(&pc_vault),
|
||||||
|
market_vault_signer: vault_signer,
|
||||||
|
token_program: Token::id(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let instruction = make_instruction(program_id, &accounts, instruction);
|
||||||
|
(accounts, instruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signers(&self) -> Vec<&Keypair> {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct CreatePerpMarketInstruction<'keypair> {
|
pub struct CreatePerpMarketInstruction<'keypair> {
|
||||||
pub group: Pubkey,
|
pub group: Pubkey,
|
||||||
pub mint: Pubkey,
|
pub mint: Pubkey,
|
||||||
|
|
|
@ -215,7 +215,7 @@ impl TestContextBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_serum_program(&mut self) -> Pubkey {
|
pub fn add_serum_program(&mut self) -> Pubkey {
|
||||||
let serum_program_id = anchor_spl::dex::id();
|
let serum_program_id = Pubkey::new_unique();
|
||||||
self.test.add_program("serum_dex", serum_program_id, None);
|
self.test.add_program("serum_dex", serum_program_id, None);
|
||||||
serum_program_id
|
serum_program_id
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use std::{mem, sync::Arc};
|
use std::{mem, sync::Arc};
|
||||||
|
|
||||||
use anchor_spl::dex::serum_dex;
|
|
||||||
use bytemuck::from_bytes;
|
use bytemuck::from_bytes;
|
||||||
use solana_sdk::pubkey::Pubkey;
|
use solana_sdk::pubkey::Pubkey;
|
||||||
use solana_sdk::{
|
use solana_sdk::{
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
#![cfg(feature = "test-bpf")]
|
#![cfg(feature = "test-bpf")]
|
||||||
|
|
||||||
use anchor_spl::dex::serum_dex;
|
|
||||||
use solana_program_test::*;
|
use solana_program_test::*;
|
||||||
use solana_sdk::{signature::Keypair, transport::TransportError};
|
use solana_sdk::{signature::Keypair, transport::TransportError};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue