mango-v4/programs/mango-v4/src/instructions/serum3_place_order.rs

747 lines
28 KiB
Rust

use crate::accounts_zerocopy::*;
use crate::error::*;
use crate::health::*;
use crate::i80f48::ClampToInt;
use crate::state::*;
use crate::accounts_ix::*;
use crate::logs::{emit_stack, Serum3OpenOrdersBalanceLogV2, TokenBalanceLog};
use crate::serum3_cpi::{
load_market_state, load_open_orders_ref, OpenOrdersAmounts, OpenOrdersSlim,
};
use crate::util::clock_now;
use anchor_lang::prelude::*;
use fixed::types::I80F48;
use serum_dex::instruction::NewOrderInstructionV3;
#[allow(clippy::too_many_arguments)]
pub fn serum3_place_order(
ctx: Context<Serum3PlaceOrder>,
side: Serum3Side,
limit_price_lots: u64,
max_base_qty: u64,
max_native_quote_qty_including_fees: u64,
self_trade_behavior: Serum3SelfTradeBehavior,
order_type: Serum3OrderType,
client_order_id: u64,
limit: u16,
require_v2: bool,
) -> Result<()> {
// Also required by serum3's place order
require_gt!(limit_price_lots, 0);
let serum_market = ctx.accounts.serum_market.load()?;
require!(
!serum_market.is_reduce_only(),
MangoError::MarketInReduceOnlyMode
);
//
// Validation
//
let receiver_token_index;
let payer_token_index;
{
let account = ctx.accounts.account.load_full()?;
// account constraint #1
require!(
account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()),
MangoError::SomeError
);
// Validate open_orders #2
require!(
account
.serum3_orders(serum_market.market_index)?
.open_orders
== ctx.accounts.open_orders.key(),
MangoError::SomeError
);
// Validate bank and vault #3
let payer_bank = ctx.accounts.payer_bank.load()?;
require_keys_eq!(payer_bank.vault, ctx.accounts.payer_vault.key());
payer_token_index = match side {
Serum3Side::Bid => serum_market.quote_token_index,
Serum3Side::Ask => serum_market.base_token_index,
};
require_eq!(payer_bank.token_index, payer_token_index);
receiver_token_index = match side {
Serum3Side::Bid => serum_market.base_token_index,
Serum3Side::Ask => serum_market.quote_token_index,
};
}
//
// Pre-health computation
//
let mut account = ctx.accounts.account.load_full_mut()?;
let (now_ts, now_slot) = clock_now();
let retriever = new_fixed_order_account_retriever_with_optional_banks(
ctx.remaining_accounts,
&account.borrow(),
now_slot,
)?;
let mut health_cache = new_health_cache_skipping_missing_banks_and_bad_oracles(
&account.borrow(),
&retriever,
now_ts,
)
.context("pre init health")?;
// The payer and receiver token banks/oracles must be passed and be valid
health_cache.token_info_index(payer_token_index)?;
health_cache.token_info_index(receiver_token_index)?;
let pre_health_opt = if !account.fixed.is_in_health_region() {
let pre_init_health = account.check_health_pre(&health_cache)?;
Some(pre_init_health)
} else {
None
};
// Check if the bank for the token whose balance is increased is in reduce-only mode
let receiver_bank_ai;
let receiver_bank_oracle;
let receiver_bank_reduce_only;
{
// The token position already exists, but we need the active_index.
let (_, _, active_index) = account.ensure_token_position(receiver_token_index)?;
let group_key = ctx.accounts.group.key();
let (receiver_bank, oracle) =
retriever.bank_and_oracle(&group_key, active_index, receiver_token_index)?;
receiver_bank_oracle = oracle;
receiver_bank_reduce_only = receiver_bank.are_deposits_reduce_only();
// The fixed_order account retriever can't give us mut references, so use the above
// call to .bank_and_oracle() as validation and then copy out the matching AccountInfo.
receiver_bank_ai = ctx.remaining_accounts[active_index].clone();
// Double-check that we got the right account
let receiver_bank2 = receiver_bank_ai.load::<Bank>()?;
assert_eq!(receiver_bank2.group, group_key);
assert_eq!(receiver_bank2.token_index, receiver_token_index);
}
drop(retriever);
//
// Instruction version checking #4
//
let is_v2_instruction;
{
let group = ctx.accounts.group.load()?;
let v1_available = group.is_ix_enabled(IxGate::Serum3PlaceOrder);
let v2_available = group.is_ix_enabled(IxGate::Serum3PlaceOrderV2);
is_v2_instruction =
require_v2 || !v1_available || (receiver_bank_ai.is_writable && v2_available);
if is_v2_instruction {
require!(v2_available, MangoError::IxIsDisabled);
require_msg_typed!(
receiver_bank_ai.is_writable,
MangoError::HealthAccountBankNotWritable,
"the receiver bank (token index {}) in the health account list must be writable",
receiver_token_index
);
} else {
require!(v1_available, MangoError::IxIsDisabled);
}
}
//
// Before-order tracking
//
let before_vault = ctx.accounts.payer_vault.amount;
let before_oo_free_slots;
let before_had_bids;
let before_had_asks;
let before_oo = {
let oo_ai = &ctx.accounts.open_orders.as_ref();
let open_orders = load_open_orders_ref(oo_ai)?;
before_oo_free_slots = open_orders.free_slot_bits;
before_had_bids = (!open_orders.free_slot_bits & open_orders.is_bid_bits) != 0;
before_had_asks = (!open_orders.free_slot_bits & !open_orders.is_bid_bits) != 0;
OpenOrdersSlim::from_oo(&open_orders)
};
// Provide a readable error message in case the vault doesn't have enough tokens
let base_lot_size;
let quote_lot_size;
{
let market_state = load_market_state(
&ctx.accounts.serum_market_external,
&ctx.accounts.serum_program.key(),
)?;
base_lot_size = market_state.coin_lot_size;
quote_lot_size = market_state.pc_lot_size;
let needed_amount = match side {
Serum3Side::Ask => {
(max_base_qty * base_lot_size).saturating_sub(before_oo.native_base_free())
}
Serum3Side::Bid => {
max_native_quote_qty_including_fees.saturating_sub(before_oo.native_quote_free())
}
};
if before_vault < needed_amount {
return err!(MangoError::InsufficentBankVaultFunds).with_context(|| {
format!(
"bank vault does not have enough tokens, need {} but have {}",
needed_amount, before_vault
)
});
}
}
//
// Apply the order to serum
//
let order = serum_dex::instruction::NewOrderInstructionV3 {
side: u8::try_from(side).unwrap().try_into().unwrap(),
limit_price: limit_price_lots.try_into().unwrap(),
max_coin_qty: max_base_qty.try_into().unwrap(),
max_native_pc_qty_including_fees: max_native_quote_qty_including_fees.try_into().unwrap(),
self_trade_behavior: u8::try_from(self_trade_behavior)
.unwrap()
.try_into()
.unwrap(),
order_type: u8::try_from(order_type).unwrap().try_into().unwrap(),
client_order_id,
limit,
max_ts: i64::MAX,
};
cpi_place_order(ctx.accounts, order)?;
//
// After-order tracking
//
let after_oo_free_slots;
let after_oo = {
let oo_ai = &ctx.accounts.open_orders.as_ref();
let open_orders = load_open_orders_ref(oo_ai)?;
after_oo_free_slots = open_orders.free_slot_bits;
OpenOrdersSlim::from_oo(&open_orders)
};
let oo_difference = OODifference::new(&before_oo, &after_oo);
//
// Track the highest bid and lowest ask, to be able to evaluate worst-case health even
// when they cross the oracle
//
let serum = account.serum3_orders_mut(serum_market.market_index)?;
if !before_had_bids {
// The 0 state means uninitialized/no value
serum.highest_placed_bid_inv = 0.0;
serum.lowest_placed_bid_inv = 0.0;
}
if !before_had_asks {
serum.lowest_placed_ask = 0.0;
serum.highest_placed_ask = 0.0;
}
// in the normal quote per base units
let limit_price = limit_price_lots as f64 * quote_lot_size as f64 / base_lot_size as f64;
let new_order_on_book = after_oo_free_slots != before_oo_free_slots;
if new_order_on_book {
match side {
Serum3Side::Ask => {
serum.lowest_placed_ask = if serum.lowest_placed_ask == 0.0 {
limit_price
} else {
serum.lowest_placed_ask.min(limit_price)
};
serum.highest_placed_ask = if serum.highest_placed_ask == 0.0 {
limit_price
} else {
serum.highest_placed_ask.max(limit_price)
}
}
Serum3Side::Bid => {
// in base per quote units, to avoid a division in health
let limit_price_inv = 1.0 / limit_price;
serum.highest_placed_bid_inv = if serum.highest_placed_bid_inv == 0.0 {
limit_price_inv
} else {
// the highest bid has the lowest _inv value
serum.highest_placed_bid_inv.min(limit_price_inv)
};
serum.lowest_placed_bid_inv = if serum.lowest_placed_bid_inv == 0.0 {
limit_price_inv
} else {
// lowest bid has max _inv value
serum.lowest_placed_bid_inv.max(limit_price_inv)
}
}
}
}
emit_stack(Serum3OpenOrdersBalanceLogV2 {
mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.account.key(),
market_index: serum_market.market_index,
base_token_index: serum_market.base_token_index,
quote_token_index: serum_market.quote_token_index,
base_total: after_oo.native_base_total(),
base_free: after_oo.native_base_free(),
quote_total: after_oo.native_quote_total(),
quote_free: after_oo.native_quote_free(),
referrer_rebates_accrued: after_oo.native_rebates(),
});
ctx.accounts.payer_vault.reload()?;
let after_vault = ctx.accounts.payer_vault.amount;
// Placing an order cannot increase vault balance
require_gte!(before_vault, after_vault);
let mut payer_bank = ctx.accounts.payer_bank.load_mut()?;
// Update the potential token tracking in banks
// (for init weight scaling, deposit limit checks)
if is_v2_instruction {
let mut receiver_bank = receiver_bank_ai.load_mut::<Bank>()?;
let (base_bank, quote_bank) = match side {
Serum3Side::Bid => (&mut receiver_bank, &mut payer_bank),
Serum3Side::Ask => (&mut payer_bank, &mut receiver_bank),
};
update_bank_potential_tokens(serum, base_bank, quote_bank, &after_oo);
} else {
update_bank_potential_tokens_payer_only(serum, &mut payer_bank, &after_oo);
}
// Track position before withdraw happens
let before_position_native = account
.token_position_mut(payer_bank.token_index)?
.0
.native(&payer_bank);
// Charge the difference in vault balance to the user's account
// (must be done before limit checks like deposit limit)
let vault_difference = {
apply_vault_difference(
ctx.accounts.account.key(),
&mut account.borrow_mut(),
serum_market.market_index,
&mut payer_bank,
after_vault,
before_vault,
)?
};
// Deposit limit check, receiver side:
// Placing an order can always increase the receiver bank deposits on fill.
{
let receiver_bank = receiver_bank_ai.load::<Bank>()?;
receiver_bank
.check_deposit_and_oo_limit()
.with_context(|| std::format!("on {}", receiver_bank.name()))?;
}
// Payer bank safety checks like reduce-only, net borrows, vault-to-deposits ratio
let payer_oracle_ref = &AccountInfoRef::borrow(&ctx.accounts.payer_oracle)?;
let payer_bank_oracle =
payer_bank.oracle_price(&OracleAccountInfos::from_reader(payer_oracle_ref), None)?;
let withdrawn_from_vault = I80F48::from(before_vault - after_vault);
if withdrawn_from_vault > before_position_native {
require_msg_typed!(
!payer_bank.are_borrows_reduce_only(),
MangoError::TokenInReduceOnlyMode,
"the payer tokens cannot be borrowed"
);
payer_bank.enforce_max_utilization_on_borrow()?;
payer_bank.check_net_borrows(payer_bank_oracle)?;
// Deposit limit check, payer side:
// The payer bank deposits could increase when cancelling the order later:
// Imagine the account borrowing payer tokens to place the order, repaying the borrows
// and then cancelling the order to create a deposit.
//
// However, if the account only decreases its deposits to place an order it can't
// worsen the situation and should always go through, even if payer deposit limits are
// already exceeded.
payer_bank
.check_deposit_and_oo_limit()
.with_context(|| std::format!("on {}", payer_bank.name()))?;
} else {
payer_bank.enforce_borrows_lte_deposits()?;
}
// Limit order price bands: If the order ends up on the book, ensure
// - a bid isn't too far below oracle
// - an ask isn't too far above oracle
// because placing orders that are guaranteed to never be hit can be bothersome:
// For example placing a very large bid near zero would make the potential_base_tokens
// value go through the roof, reducing available init margin for other users.
let band_threshold = serum_market.oracle_price_band();
if new_order_on_book && band_threshold != f32::MAX {
let (base_oracle, quote_oracle) = match side {
Serum3Side::Bid => (&receiver_bank_oracle, &payer_bank_oracle),
Serum3Side::Ask => (&payer_bank_oracle, &receiver_bank_oracle),
};
let base_oracle_f64 = base_oracle.to_num::<f64>();
let quote_oracle_f64 = quote_oracle.to_num::<f64>();
// this has the same units as base_oracle: USD per BASE; limit_price is in QUOTE per BASE
let limit_price_in_dollar = limit_price * quote_oracle_f64;
let band_factor = 1.0 + band_threshold as f64;
match side {
Serum3Side::Bid => {
require_msg_typed!(
limit_price_in_dollar * band_factor >= base_oracle_f64,
MangoError::Serum3PriceBandExceeded,
"bid price {} must be larger than {} ({}% of oracle)",
limit_price,
base_oracle_f64 / (quote_oracle_f64 * band_factor),
(100.0 / band_factor) as u64,
);
}
Serum3Side::Ask => {
require_msg_typed!(
limit_price_in_dollar <= base_oracle_f64 * band_factor,
MangoError::Serum3PriceBandExceeded,
"ask price {} must be smaller than {} ({}% of oracle)",
limit_price,
base_oracle_f64 * band_factor / quote_oracle_f64,
(100.0 * band_factor) as u64,
);
}
}
}
// Health cache updates for the changed account state
let receiver_bank = receiver_bank_ai.load::<Bank>()?;
// update scaled weights for receiver bank
health_cache.adjust_token_balance(&receiver_bank, I80F48::ZERO)?;
vault_difference.adjust_health_cache_token_balance(&mut health_cache, &payer_bank)?;
let serum_account = account.serum3_orders(serum_market.market_index)?;
oo_difference.recompute_health_cache_serum3_state(
&mut health_cache,
&serum_account,
&after_oo,
)?;
// Check the receiver's reduce only flag.
//
// Note that all orders on the book executing can still cause a net deposit. That's because
// the total serum3 potential amount assumes all reserved amounts convert at the current
// oracle price.
//
// This also requires that all serum3 oos that touch the receiver_token are avaliable in the
// health cache. We make this a general requirement to avoid surprises.
for serum3 in account.active_serum3_orders() {
if serum3.base_token_index == receiver_token_index
|| serum3.quote_token_index == receiver_token_index
{
require_msg!(
health_cache.serum3_infos.iter().any(|s3| s3.market_index == serum3.market_index),
"health cache is missing serum3 info {} involving receiver token {}; passed banks and oracles?",
serum3.market_index, receiver_token_index
);
}
}
if receiver_bank_reduce_only {
let balance = health_cache.token_info(receiver_token_index)?.balance_spot;
let potential =
health_cache.total_serum3_potential(HealthType::Maint, receiver_token_index)?;
require_msg_typed!(
balance + potential < 1,
MangoError::TokenInReduceOnlyMode,
"receiver bank does not accept deposits"
);
}
//
// Health check
//
if let Some(pre_init_health) = pre_health_opt {
account.check_health_post(&health_cache, pre_init_health)?;
}
Ok(())
}
pub struct OODifference {
free_base_change: I80F48,
free_quote_change: I80F48,
}
impl OODifference {
pub fn new(before_oo: &OpenOrdersSlim, after_oo: &OpenOrdersSlim) -> Self {
Self {
free_base_change: I80F48::from(after_oo.native_base_free())
- I80F48::from(before_oo.native_base_free()),
free_quote_change: I80F48::from(after_oo.native_quote_free())
- I80F48::from(before_oo.native_quote_free()),
}
}
pub fn recompute_health_cache_serum3_state(
&self,
health_cache: &mut HealthCache,
serum_account: &Serum3Orders,
open_orders: &OpenOrdersSlim,
) -> Result<()> {
health_cache.recompute_serum3_info(
serum_account,
open_orders,
self.free_base_change,
self.free_quote_change,
)
}
}
pub struct VaultDifference {
token_index: TokenIndex,
native_change: I80F48,
}
impl VaultDifference {
pub fn adjust_health_cache_token_balance(
&self,
health_cache: &mut HealthCache,
bank: &Bank,
) -> Result<()> {
assert_eq!(bank.token_index, self.token_index);
health_cache.adjust_token_balance(bank, self.native_change)?;
Ok(())
}
}
/// Called in apply_settle_changes() and place_order to adjust token positions after
/// changing the vault balances
/// Also logs changes to token balances
fn apply_vault_difference(
account_pk: Pubkey,
account: &mut MangoAccountRefMut,
serum_market_index: Serum3MarketIndex,
bank: &mut Bank,
vault_after: u64,
vault_before: u64,
) -> Result<VaultDifference> {
let needed_change = I80F48::from(vault_after) - I80F48::from(vault_before);
let (position, _) = account.token_position_mut(bank.token_index)?;
let native_before = position.native(bank);
let now_ts = Clock::get()?.unix_timestamp.try_into().unwrap();
if needed_change >= 0 {
bank.deposit(position, needed_change, now_ts)?;
} else {
bank.withdraw_without_fee(position, -needed_change, now_ts)?;
}
let native_after = position.native(bank);
let native_change = native_after - native_before;
// amount of tokens transfered to serum3 reserved that were borrowed
let new_borrows = native_change
.max(native_after)
.min(I80F48::ZERO)
.abs()
.to_num::<u64>();
let indexed_position = position.indexed_position;
let market = account.serum3_orders_mut(serum_market_index).unwrap();
let borrows_without_fee;
if bank.token_index == market.base_token_index {
borrows_without_fee = &mut market.base_borrows_without_fee;
} else if bank.token_index == market.quote_token_index {
borrows_without_fee = &mut market.quote_borrows_without_fee;
} else {
return Err(error_msg!(
"assert failed: apply_vault_difference called with bad token index"
));
};
// Only for place: Add to potential borrow amount
*borrows_without_fee += new_borrows;
// Only for settle/liq_force_cancel: Reduce the potential borrow amounts
if needed_change > 0 {
*borrows_without_fee = (*borrows_without_fee).saturating_sub(needed_change.to_num::<u64>());
}
emit_stack(TokenBalanceLog {
mango_group: bank.group,
mango_account: account_pk,
token_index: bank.token_index,
indexed_position: indexed_position.to_bits(),
deposit_index: bank.deposit_index.to_bits(),
borrow_index: bank.borrow_index.to_bits(),
});
Ok(VaultDifference {
token_index: bank.token_index,
native_change,
})
}
/// Uses the changes in OpenOrders and vaults to adjust the user token position,
/// collect fees and optionally adjusts the HealthCache.
pub fn apply_settle_changes(
group: &Group,
account_pk: Pubkey,
account: &mut MangoAccountRefMut,
base_bank: &mut Bank,
quote_bank: &mut Bank,
serum_market: &Serum3Market,
before_base_vault: u64,
before_quote_vault: u64,
before_oo: &OpenOrdersSlim,
after_base_vault: u64,
after_quote_vault: u64,
after_oo: &OpenOrdersSlim,
health_cache: Option<&mut HealthCache>,
fees_to_dao: bool,
quote_oracle: Option<&AccountInfo>,
) -> Result<()> {
let mut received_fees = 0;
if fees_to_dao {
// Example: rebates go from 100 -> 10. That means we credit 90 in fees.
received_fees = before_oo
.native_rebates()
.saturating_sub(after_oo.native_rebates());
quote_bank.collected_fees_native += I80F48::from(received_fees);
// Credit the buyback_fees at the current value of the quote token.
if let Some(quote_oracle_ai) = quote_oracle {
let clock = Clock::get()?;
let now_ts = clock.unix_timestamp.try_into().unwrap();
let quote_oracle_ref = &AccountInfoRef::borrow(quote_oracle_ai)?;
let quote_oracle_price = quote_bank.oracle_price(
&OracleAccountInfos::from_reader(quote_oracle_ref),
Some(clock.slot),
)?;
let quote_asset_price = quote_oracle_price.min(quote_bank.stable_price());
account
.fixed
.expire_buyback_fees(now_ts, group.buyback_fees_expiry_interval);
let fees_in_usd = I80F48::from(received_fees) * quote_asset_price;
account
.fixed
.accrue_buyback_fees(fees_in_usd.clamp_to_u64());
}
}
// Don't count the referrer rebate fees as part of the vault change that should be
// credited to the user.
let after_quote_vault_adjusted = after_quote_vault - received_fees;
// Settle cannot decrease vault balances
require_gte!(after_base_vault, before_base_vault);
require_gte!(after_quote_vault_adjusted, before_quote_vault);
// Credit the difference in vault balances to the user's account
let base_difference = apply_vault_difference(
account_pk,
account,
serum_market.market_index,
base_bank,
after_base_vault,
before_base_vault,
)?;
let quote_difference = apply_vault_difference(
account_pk,
account,
serum_market.market_index,
quote_bank,
after_quote_vault_adjusted,
before_quote_vault,
)?;
// Tokens were moved from open orders into banks again: also update the tracking
// for potential_serum_tokens on the banks.
{
let serum_orders = account.serum3_orders_mut(serum_market.market_index)?;
update_bank_potential_tokens(serum_orders, base_bank, quote_bank, after_oo);
}
if let Some(health_cache) = health_cache {
base_difference.adjust_health_cache_token_balance(health_cache, &base_bank)?;
quote_difference.adjust_health_cache_token_balance(health_cache, &quote_bank)?;
let serum_account = account.serum3_orders(serum_market.market_index)?;
OODifference::new(&before_oo, &after_oo).recompute_health_cache_serum3_state(
health_cache,
serum_account,
after_oo,
)?;
}
Ok(())
}
fn update_bank_potential_tokens_payer_only(
serum_orders: &mut Serum3Orders,
payer_bank: &mut Bank,
oo: &OpenOrdersSlim,
) {
// Do the tracking for the avaliable bank
if serum_orders.base_token_index == payer_bank.token_index {
let new_base = oo.native_base_total()
+ (oo.native_quote_reserved() as f64 * serum_orders.lowest_placed_bid_inv) as u64;
let old_base = serum_orders.potential_base_tokens;
payer_bank.update_potential_serum_tokens(old_base, new_base);
serum_orders.potential_base_tokens = new_base;
} else {
assert_eq!(serum_orders.quote_token_index, payer_bank.token_index);
let new_quote = oo.native_quote_total()
+ (oo.native_base_reserved() as f64 * serum_orders.highest_placed_ask) as u64;
let old_quote = serum_orders.potential_quote_tokens;
payer_bank.update_potential_serum_tokens(old_quote, new_quote);
serum_orders.potential_quote_tokens = new_quote;
}
}
fn update_bank_potential_tokens(
serum_orders: &mut Serum3Orders,
base_bank: &mut Bank,
quote_bank: &mut Bank,
oo: &OpenOrdersSlim,
) {
assert_eq!(serum_orders.base_token_index, base_bank.token_index);
assert_eq!(serum_orders.quote_token_index, quote_bank.token_index);
// Potential tokens are all tokens on the side, plus reserved on the other side
// converted at favorable price. This creates an overestimation of the potential
// base and quote tokens flowing out of this open orders account.
let new_base = oo.native_base_total()
+ (oo.native_quote_reserved() as f64 * serum_orders.lowest_placed_bid_inv) as u64;
let new_quote = oo.native_quote_total()
+ (oo.native_base_reserved() as f64 * serum_orders.highest_placed_ask) as u64;
let old_base = serum_orders.potential_base_tokens;
let old_quote = serum_orders.potential_quote_tokens;
base_bank.update_potential_serum_tokens(old_base, new_base);
quote_bank.update_potential_serum_tokens(old_quote, new_quote);
serum_orders.potential_base_tokens = new_base;
serum_orders.potential_quote_tokens = new_quote;
}
fn cpi_place_order(ctx: &Serum3PlaceOrder, order: NewOrderInstructionV3) -> Result<()> {
use crate::serum3_cpi;
let group = ctx.group.load()?;
serum3_cpi::PlaceOrder {
program: ctx.serum_program.to_account_info(),
market: ctx.serum_market_external.to_account_info(),
request_queue: ctx.market_request_queue.to_account_info(),
event_queue: ctx.market_event_queue.to_account_info(),
bids: ctx.market_bids.to_account_info(),
asks: ctx.market_asks.to_account_info(),
base_vault: ctx.market_base_vault.to_account_info(),
quote_vault: ctx.market_quote_vault.to_account_info(),
token_program: ctx.token_program.to_account_info(),
open_orders: ctx.open_orders.to_account_info(),
order_payer_token_account: ctx.payer_vault.to_account_info(),
user_authority: ctx.group.to_account_info(),
}
.call(&group, order)
}