2023-12-05 06:39:24 -08:00
|
|
|
use crate::accounts_zerocopy::*;
|
2022-07-19 02:36:23 -07:00
|
|
|
use crate::error::*;
|
2022-12-08 04:12:43 -08:00
|
|
|
use crate::health::*;
|
2023-03-20 03:18:11 -07:00
|
|
|
use crate::i80f48::ClampToInt;
|
2022-12-08 04:12:43 -08:00
|
|
|
use crate::state::*;
|
2022-06-21 02:45:38 -07:00
|
|
|
|
2023-02-14 23:42:07 -08:00
|
|
|
use crate::accounts_ix::*;
|
2023-11-22 07:00:47 -08:00
|
|
|
use crate::logs::{emit_stack, Serum3OpenOrdersBalanceLogV2, TokenBalanceLog};
|
2023-09-13 00:35:10 -07:00
|
|
|
use crate::serum3_cpi::{
|
|
|
|
load_market_state, load_open_orders_ref, OpenOrdersAmounts, OpenOrdersSlim,
|
|
|
|
};
|
2024-03-04 06:49:14 -08:00
|
|
|
use crate::util::clock_now;
|
2022-03-12 05:01:56 -08:00
|
|
|
use anchor_lang::prelude::*;
|
2023-02-24 02:56:33 -08:00
|
|
|
|
2022-03-26 11:34:44 -07:00
|
|
|
use fixed::types::I80F48;
|
2022-04-01 23:59:07 -07:00
|
|
|
use serum_dex::instruction::NewOrderInstructionV3;
|
2022-03-19 04:11:56 -07:00
|
|
|
|
2022-04-01 23:59:07 -07:00
|
|
|
#[allow(clippy::too_many_arguments)]
|
2022-03-18 05:42:20 -07:00
|
|
|
pub fn serum3_place_order(
|
|
|
|
ctx: Context<Serum3PlaceOrder>,
|
2022-04-01 23:59:07 -07:00
|
|
|
side: Serum3Side,
|
2023-09-13 00:35:10 -07:00
|
|
|
limit_price_lots: u64,
|
2022-04-01 23:59:07 -07:00
|
|
|
max_base_qty: u64,
|
|
|
|
max_native_quote_qty_including_fees: u64,
|
|
|
|
self_trade_behavior: Serum3SelfTradeBehavior,
|
|
|
|
order_type: Serum3OrderType,
|
|
|
|
client_order_id: u64,
|
|
|
|
limit: u16,
|
2023-12-05 06:39:24 -08:00
|
|
|
require_v2: bool,
|
2022-03-12 05:01:56 -08:00
|
|
|
) -> Result<()> {
|
2023-09-13 00:35:10 -07:00
|
|
|
// Also required by serum3's place order
|
|
|
|
require_gt!(limit_price_lots, 0);
|
|
|
|
|
2022-06-21 02:45:38 -07:00
|
|
|
let serum_market = ctx.accounts.serum_market.load()?;
|
2023-01-04 00:24:40 -08:00
|
|
|
require!(
|
|
|
|
!serum_market.is_reduce_only(),
|
|
|
|
MangoError::MarketInReduceOnlyMode
|
|
|
|
);
|
2022-06-21 02:45:38 -07:00
|
|
|
|
2022-03-15 06:44:47 -07:00
|
|
|
//
|
|
|
|
// Validation
|
|
|
|
//
|
2023-05-03 23:02:28 -07:00
|
|
|
let receiver_token_index;
|
2024-03-04 06:49:14 -08:00
|
|
|
let payer_token_index;
|
2022-03-15 06:44:47 -07:00
|
|
|
{
|
2022-12-29 02:48:46 -08:00
|
|
|
let account = ctx.accounts.account.load_full()?;
|
2022-08-25 09:24:11 -07:00
|
|
|
// account constraint #1
|
2022-07-25 07:07:53 -07:00
|
|
|
require!(
|
|
|
|
account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()),
|
|
|
|
MangoError::SomeError
|
|
|
|
);
|
2022-03-14 13:53:30 -07:00
|
|
|
|
2022-08-25 09:24:11 -07:00
|
|
|
// Validate open_orders #2
|
2022-03-15 06:44:47 -07:00
|
|
|
require!(
|
|
|
|
account
|
2022-08-30 03:47:15 -07:00
|
|
|
.serum3_orders(serum_market.market_index)?
|
2022-03-15 06:44:47 -07:00
|
|
|
.open_orders
|
|
|
|
== ctx.accounts.open_orders.key(),
|
|
|
|
MangoError::SomeError
|
|
|
|
);
|
|
|
|
|
2022-08-30 00:55:19 -07:00
|
|
|
// Validate bank and vault #3
|
|
|
|
let payer_bank = ctx.accounts.payer_bank.load()?;
|
|
|
|
require_keys_eq!(payer_bank.vault, ctx.accounts.payer_vault.key());
|
2024-03-04 06:49:14 -08:00
|
|
|
payer_token_index = match side {
|
2022-08-30 00:55:19 -07:00
|
|
|
Serum3Side::Bid => serum_market.quote_token_index,
|
|
|
|
Serum3Side::Ask => serum_market.base_token_index,
|
|
|
|
};
|
|
|
|
require_eq!(payer_bank.token_index, payer_token_index);
|
2023-05-03 23:02:28 -07:00
|
|
|
|
|
|
|
receiver_token_index = match side {
|
|
|
|
Serum3Side::Bid => serum_market.base_token_index,
|
|
|
|
Serum3Side::Ask => serum_market.quote_token_index,
|
|
|
|
};
|
2022-03-15 06:44:47 -07:00
|
|
|
}
|
|
|
|
|
2022-08-26 03:45:32 -07:00
|
|
|
//
|
|
|
|
// Pre-health computation
|
|
|
|
//
|
2022-12-29 02:48:46 -08:00
|
|
|
let mut account = ctx.accounts.account.load_full_mut()?;
|
2024-03-04 06:49:14 -08:00
|
|
|
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)?;
|
|
|
|
|
2022-08-26 03:45:32 -07:00
|
|
|
let pre_health_opt = if !account.fixed.is_in_health_region() {
|
2023-02-10 00:00:36 -08:00
|
|
|
let pre_init_health = account.check_health_pre(&health_cache)?;
|
2023-05-03 23:02:28 -07:00
|
|
|
Some(pre_init_health)
|
2022-08-26 03:45:32 -07:00
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
|
2023-05-03 23:02:28 -07:00
|
|
|
// Check if the bank for the token whose balance is increased is in reduce-only mode
|
2023-12-05 06:39:24 -08:00
|
|
|
let receiver_bank_ai;
|
|
|
|
let receiver_bank_oracle;
|
|
|
|
let receiver_bank_reduce_only;
|
|
|
|
{
|
2023-05-03 23:02:28 -07:00
|
|
|
// 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();
|
2023-12-05 06:39:24 -08:00
|
|
|
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);
|
|
|
|
}
|
2023-05-03 23:02:28 -07:00
|
|
|
|
|
|
|
drop(retriever);
|
|
|
|
|
2023-12-05 06:39:24 -08:00
|
|
|
//
|
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-15 06:44:47 -07:00
|
|
|
//
|
|
|
|
// Before-order tracking
|
|
|
|
//
|
|
|
|
|
2022-08-30 00:55:19 -07:00
|
|
|
let before_vault = ctx.accounts.payer_vault.amount;
|
2022-03-14 13:53:30 -07:00
|
|
|
|
2023-09-13 00:35:10 -07:00
|
|
|
let before_oo_free_slots;
|
|
|
|
let before_had_bids;
|
|
|
|
let before_had_asks;
|
2022-08-26 03:45:32 -07:00
|
|
|
let before_oo = {
|
|
|
|
let oo_ai = &ctx.accounts.open_orders.as_ref();
|
|
|
|
let open_orders = load_open_orders_ref(oo_ai)?;
|
2023-09-13 00:35:10 -07:00
|
|
|
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;
|
2022-08-26 03:45:32 -07:00
|
|
|
OpenOrdersSlim::from_oo(&open_orders)
|
|
|
|
};
|
|
|
|
|
2022-07-19 02:36:23 -07:00
|
|
|
// Provide a readable error message in case the vault doesn't have enough tokens
|
2023-09-13 00:35:10 -07:00
|
|
|
let base_lot_size;
|
|
|
|
let quote_lot_size;
|
2022-08-30 01:41:59 -07:00
|
|
|
{
|
2023-09-13 00:35:10 -07:00
|
|
|
let market_state = load_market_state(
|
2022-08-30 01:41:59 -07:00
|
|
|
&ctx.accounts.serum_market_external,
|
|
|
|
&ctx.accounts.serum_program.key(),
|
2023-09-13 00:35:10 -07:00
|
|
|
)?;
|
|
|
|
base_lot_size = market_state.coin_lot_size;
|
|
|
|
quote_lot_size = market_state.pc_lot_size;
|
2022-08-30 01:41:59 -07:00
|
|
|
|
|
|
|
let needed_amount = match side {
|
|
|
|
Serum3Side::Ask => {
|
2023-02-24 02:56:33 -08:00
|
|
|
(max_base_qty * base_lot_size).saturating_sub(before_oo.native_base_free())
|
2022-08-30 01:41:59 -07:00
|
|
|
}
|
|
|
|
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
|
|
|
|
)
|
|
|
|
});
|
2022-08-30 00:55:19 -07:00
|
|
|
}
|
2022-07-19 02:36:23 -07:00
|
|
|
}
|
|
|
|
|
2022-08-23 05:10:38 -07:00
|
|
|
//
|
2022-08-26 03:45:32 -07:00
|
|
|
// Apply the order to serum
|
2022-03-14 13:53:30 -07:00
|
|
|
//
|
2022-04-01 23:59:07 -07:00
|
|
|
let order = serum_dex::instruction::NewOrderInstructionV3 {
|
2022-04-02 01:02:09 -07:00
|
|
|
side: u8::try_from(side).unwrap().try_into().unwrap(),
|
2023-09-13 00:35:10 -07:00
|
|
|
limit_price: limit_price_lots.try_into().unwrap(),
|
2022-04-01 23:59:07 -07:00
|
|
|
max_coin_qty: max_base_qty.try_into().unwrap(),
|
|
|
|
max_native_pc_qty_including_fees: max_native_quote_qty_including_fees.try_into().unwrap(),
|
2022-04-02 01:02:09 -07:00
|
|
|
self_trade_behavior: u8::try_from(self_trade_behavior)
|
2022-04-02 00:52:40 -07:00
|
|
|
.unwrap()
|
|
|
|
.try_into()
|
|
|
|
.unwrap(),
|
2022-04-02 01:02:09 -07:00
|
|
|
order_type: u8::try_from(order_type).unwrap().try_into().unwrap(),
|
2022-04-01 23:59:07 -07:00
|
|
|
client_order_id,
|
|
|
|
limit,
|
2022-11-16 02:50:40 -08:00
|
|
|
max_ts: i64::MAX,
|
2022-04-01 23:59:07 -07:00
|
|
|
};
|
2022-03-23 01:33:51 -07:00
|
|
|
cpi_place_order(ctx.accounts, order)?;
|
2022-06-21 02:45:38 -07:00
|
|
|
|
2023-05-03 23:02:28 -07:00
|
|
|
//
|
|
|
|
// After-order tracking
|
|
|
|
//
|
2023-09-13 00:35:10 -07:00
|
|
|
let after_oo_free_slots;
|
2023-05-03 23:02:28 -07:00
|
|
|
let after_oo = {
|
2022-06-21 02:45:38 -07:00
|
|
|
let oo_ai = &ctx.accounts.open_orders.as_ref();
|
|
|
|
let open_orders = load_open_orders_ref(oo_ai)?;
|
2023-09-13 00:35:10 -07:00
|
|
|
after_oo_free_slots = open_orders.free_slot_bits;
|
2023-05-03 23:02:28 -07:00
|
|
|
OpenOrdersSlim::from_oo(&open_orders)
|
2022-08-23 05:10:38 -07:00
|
|
|
};
|
2023-05-03 23:02:28 -07:00
|
|
|
let oo_difference = OODifference::new(&before_oo, &after_oo);
|
|
|
|
|
2023-09-13 00:35:10 -07:00
|
|
|
//
|
|
|
|
// 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;
|
2023-12-05 06:39:24 -08:00
|
|
|
serum.lowest_placed_bid_inv = 0.0;
|
2023-09-13 00:35:10 -07:00
|
|
|
}
|
|
|
|
if !before_had_asks {
|
|
|
|
serum.lowest_placed_ask = 0.0;
|
2023-12-05 06:39:24 -08:00
|
|
|
serum.highest_placed_ask = 0.0;
|
2023-09-13 00:35:10 -07:00
|
|
|
}
|
2023-12-05 06:39:24 -08:00
|
|
|
// 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;
|
|
|
|
|
2023-09-13 00:35:10 -07:00
|
|
|
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)
|
|
|
|
};
|
2023-12-05 06:39:24 -08:00
|
|
|
serum.highest_placed_ask = if serum.highest_placed_ask == 0.0 {
|
|
|
|
limit_price
|
|
|
|
} else {
|
|
|
|
serum.highest_placed_ask.max(limit_price)
|
|
|
|
}
|
2023-09-13 00:35:10 -07:00
|
|
|
}
|
|
|
|
Serum3Side::Bid => {
|
|
|
|
// in base per quote units, to avoid a division in health
|
2023-12-05 06:39:24 -08:00
|
|
|
let limit_price_inv = 1.0 / limit_price;
|
2023-09-13 00:35:10 -07:00
|
|
|
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)
|
|
|
|
};
|
2023-12-05 06:39:24 -08:00
|
|
|
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)
|
|
|
|
}
|
2023-09-13 00:35:10 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-22 07:00:47 -08:00
|
|
|
emit_stack(Serum3OpenOrdersBalanceLogV2 {
|
2023-05-03 23:02:28 -07:00
|
|
|
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,
|
2023-09-13 00:35:10 -07:00
|
|
|
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(),
|
2023-05-03 23:02:28 -07:00
|
|
|
});
|
2022-03-15 06:44:47 -07:00
|
|
|
|
2022-08-30 00:55:19 -07:00
|
|
|
ctx.accounts.payer_vault.reload()?;
|
|
|
|
let after_vault = ctx.accounts.payer_vault.amount;
|
2022-03-15 06:44:47 -07:00
|
|
|
|
2022-08-30 00:55:19 -07:00
|
|
|
// Placing an order cannot increase vault balance
|
|
|
|
require_gte!(before_vault, after_vault);
|
2022-08-26 03:45:32 -07:00
|
|
|
|
2022-11-29 00:47:03 -08:00
|
|
|
let mut payer_bank = ctx.accounts.payer_bank.load_mut()?;
|
2022-12-15 09:27:22 -08:00
|
|
|
|
2023-12-05 06:43:38 -08:00
|
|
|
// Update the potential token tracking in banks
|
|
|
|
// (for init weight scaling, deposit limit checks)
|
2023-12-05 06:39:24 -08:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2023-12-05 06:43:38 -08:00
|
|
|
// Track position before withdraw happens
|
|
|
|
let before_position_native = account
|
2022-12-15 09:27:22 -08:00
|
|
|
.token_position_mut(payer_bank.token_index)?
|
|
|
|
.0
|
|
|
|
.native(&payer_bank);
|
|
|
|
|
|
|
|
// Charge the difference in vault balance to the user's account
|
2023-12-05 06:43:38 -08:00
|
|
|
// (must be done before limit checks like deposit limit)
|
2022-08-23 05:10:38 -07:00
|
|
|
let vault_difference = {
|
2022-07-07 04:15:49 -07:00
|
|
|
apply_vault_difference(
|
2022-09-23 10:42:43 -07:00
|
|
|
ctx.accounts.account.key(),
|
2022-07-25 07:07:53 -07:00
|
|
|
&mut account.borrow_mut(),
|
2022-08-26 03:45:32 -07:00
|
|
|
serum_market.market_index,
|
2022-08-30 00:55:19 -07:00
|
|
|
&mut payer_bank,
|
|
|
|
after_vault,
|
|
|
|
before_vault,
|
2022-07-07 04:15:49 -07:00
|
|
|
)?
|
|
|
|
};
|
2022-03-14 07:26:26 -07:00
|
|
|
|
2024-02-05 04:06:06 -08:00
|
|
|
// Deposit limit check, receiver side:
|
|
|
|
// Placing an order can always increase the receiver bank deposits on fill.
|
2023-12-05 06:43:38 -08:00
|
|
|
{
|
|
|
|
let receiver_bank = receiver_bank_ai.load::<Bank>()?;
|
|
|
|
receiver_bank
|
|
|
|
.check_deposit_and_oo_limit()
|
|
|
|
.with_context(|| std::format!("on {}", receiver_bank.name()))?;
|
|
|
|
}
|
|
|
|
|
2023-12-05 06:39:24 -08:00
|
|
|
// Payer bank safety checks like reduce-only, net borrows, vault-to-deposits ratio
|
2024-01-04 09:29:54 -08:00
|
|
|
let payer_oracle_ref = &AccountInfoRef::borrow(&ctx.accounts.payer_oracle)?;
|
2023-12-05 06:39:24 -08:00
|
|
|
let payer_bank_oracle =
|
2024-01-04 09:29:54 -08:00
|
|
|
payer_bank.oracle_price(&OracleAccountInfos::from_reader(payer_oracle_ref), None)?;
|
2023-12-05 06:43:38 -08:00
|
|
|
let withdrawn_from_vault = I80F48::from(before_vault - after_vault);
|
|
|
|
if withdrawn_from_vault > before_position_native {
|
2023-05-03 23:02:28 -07:00
|
|
|
require_msg_typed!(
|
|
|
|
!payer_bank.are_borrows_reduce_only(),
|
|
|
|
MangoError::TokenInReduceOnlyMode,
|
|
|
|
"the payer tokens cannot be borrowed"
|
|
|
|
);
|
2023-12-30 08:45:35 -08:00
|
|
|
payer_bank.enforce_max_utilization_on_borrow()?;
|
2023-12-05 06:39:24 -08:00
|
|
|
payer_bank.check_net_borrows(payer_bank_oracle)?;
|
2024-02-05 04:06:06 -08:00
|
|
|
|
|
|
|
// 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()))?;
|
2023-12-30 08:45:35 -08:00
|
|
|
} else {
|
|
|
|
payer_bank.enforce_borrows_lte_deposits()?;
|
2023-04-12 23:56:33 -07:00
|
|
|
}
|
|
|
|
|
2023-12-05 06:39:24 -08:00
|
|
|
// 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,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-09-13 00:35:10 -07:00
|
|
|
|
2023-12-05 06:39:24 -08:00
|
|
|
// 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)?;
|
2023-09-13 00:35:10 -07:00
|
|
|
let serum_account = account.serum3_orders(serum_market.market_index)?;
|
|
|
|
oo_difference.recompute_health_cache_serum3_state(
|
|
|
|
&mut health_cache,
|
|
|
|
&serum_account,
|
|
|
|
&after_oo,
|
|
|
|
)?;
|
2023-05-03 23:02:28 -07:00
|
|
|
|
|
|
|
// 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.
|
2024-03-04 06:49:14 -08:00
|
|
|
//
|
|
|
|
// 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
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2023-05-03 23:02:28 -07:00
|
|
|
if receiver_bank_reduce_only {
|
2023-05-17 06:50:05 -07:00
|
|
|
let balance = health_cache.token_info(receiver_token_index)?.balance_spot;
|
2023-05-03 23:02:28 -07:00
|
|
|
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"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-03-15 06:44:47 -07:00
|
|
|
//
|
|
|
|
// Health check
|
|
|
|
//
|
2023-05-03 23:02:28 -07:00
|
|
|
if let Some(pre_init_health) = pre_health_opt {
|
2023-02-10 00:00:36 -08:00
|
|
|
account.check_health_post(&health_cache, pre_init_health)?;
|
2022-08-19 18:50:54 -07:00
|
|
|
}
|
|
|
|
|
2022-03-15 06:44:47 -07:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2022-08-23 05:10:38 -07:00
|
|
|
pub struct OODifference {
|
|
|
|
free_base_change: I80F48,
|
|
|
|
free_quote_change: I80F48,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl OODifference {
|
|
|
|
pub fn new(before_oo: &OpenOrdersSlim, after_oo: &OpenOrdersSlim) -> Self {
|
|
|
|
Self {
|
2023-02-24 02:56:33 -08:00
|
|
|
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()),
|
2022-08-23 05:10:38 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-13 00:35:10 -07:00
|
|
|
pub fn recompute_health_cache_serum3_state(
|
2022-08-23 05:10:38 -07:00
|
|
|
&self,
|
|
|
|
health_cache: &mut HealthCache,
|
2023-09-13 00:35:10 -07:00
|
|
|
serum_account: &Serum3Orders,
|
|
|
|
open_orders: &OpenOrdersSlim,
|
2022-08-23 05:10:38 -07:00
|
|
|
) -> Result<()> {
|
2023-09-13 00:35:10 -07:00
|
|
|
health_cache.recompute_serum3_info(
|
|
|
|
serum_account,
|
|
|
|
open_orders,
|
2022-08-23 05:10:38 -07:00
|
|
|
self.free_base_change,
|
|
|
|
self.free_quote_change,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-26 03:45:32 -07:00
|
|
|
pub struct VaultDifference {
|
2022-08-30 00:55:19 -07:00
|
|
|
token_index: TokenIndex,
|
|
|
|
native_change: I80F48,
|
2022-07-07 04:15:49 -07:00
|
|
|
}
|
|
|
|
|
2022-08-26 03:45:32 -07:00
|
|
|
impl VaultDifference {
|
2023-02-22 03:04:21 -08:00
|
|
|
pub fn adjust_health_cache_token_balance(
|
|
|
|
&self,
|
|
|
|
health_cache: &mut HealthCache,
|
|
|
|
bank: &Bank,
|
|
|
|
) -> Result<()> {
|
2022-11-29 00:47:03 -08:00
|
|
|
assert_eq!(bank.token_index, self.token_index);
|
|
|
|
health_cache.adjust_token_balance(bank, self.native_change)?;
|
2022-08-23 05:10:38 -07:00
|
|
|
Ok(())
|
|
|
|
}
|
2022-07-07 04:15:49 -07:00
|
|
|
}
|
|
|
|
|
2023-02-22 03:04:21 -08:00
|
|
|
/// Called in apply_settle_changes() and place_order to adjust token positions after
|
2022-08-26 03:45:32 -07:00
|
|
|
/// changing the vault balances
|
2022-09-23 10:42:43 -07:00
|
|
|
/// Also logs changes to token balances
|
2023-02-22 03:04:21 -08:00
|
|
|
fn apply_vault_difference(
|
2022-09-23 10:42:43 -07:00
|
|
|
account_pk: Pubkey,
|
2022-07-25 07:07:53 -07:00
|
|
|
account: &mut MangoAccountRefMut,
|
2022-08-26 03:45:32 -07:00
|
|
|
serum_market_index: Serum3MarketIndex,
|
2022-08-30 00:55:19 -07:00
|
|
|
bank: &mut Bank,
|
|
|
|
vault_after: u64,
|
|
|
|
vault_before: u64,
|
2022-08-26 03:45:32 -07:00
|
|
|
) -> Result<VaultDifference> {
|
2023-02-24 02:56:33 -08:00
|
|
|
let needed_change = I80F48::from(vault_after) - I80F48::from(vault_before);
|
2022-08-30 00:55:19 -07:00
|
|
|
|
|
|
|
let (position, _) = account.token_position_mut(bank.token_index)?;
|
2022-08-30 04:46:39 -07:00
|
|
|
let native_before = position.native(bank);
|
2022-12-02 03:24:11 -08:00
|
|
|
let now_ts = Clock::get()?.unix_timestamp.try_into().unwrap();
|
|
|
|
if needed_change >= 0 {
|
|
|
|
bank.deposit(position, needed_change, now_ts)?;
|
|
|
|
} else {
|
2023-04-12 23:56:33 -07:00
|
|
|
bank.withdraw_without_fee(position, -needed_change, now_ts)?;
|
2022-12-02 03:24:11 -08:00
|
|
|
}
|
2022-08-30 04:46:39 -07:00
|
|
|
let native_after = position.native(bank);
|
2023-02-24 02:56:33 -08:00
|
|
|
let native_change = native_after - native_before;
|
2023-11-02 02:40:31 -07:00
|
|
|
// amount of tokens transfered to serum3 reserved that were borrowed
|
2022-08-30 00:55:19 -07:00
|
|
|
let new_borrows = native_change
|
|
|
|
.max(native_after)
|
2022-08-26 03:45:32 -07:00
|
|
|
.min(I80F48::ZERO)
|
|
|
|
.abs()
|
|
|
|
.to_num::<u64>();
|
|
|
|
|
2022-09-23 10:42:43 -07:00
|
|
|
let indexed_position = position.indexed_position;
|
2022-08-26 03:45:32 -07:00
|
|
|
let market = account.serum3_orders_mut(serum_market_index).unwrap();
|
2023-11-02 02:40:31 -07:00
|
|
|
let borrows_without_fee;
|
|
|
|
if bank.token_index == market.base_token_index {
|
|
|
|
borrows_without_fee = &mut market.base_borrows_without_fee;
|
2022-08-30 00:55:19 -07:00
|
|
|
} else if bank.token_index == market.quote_token_index {
|
2023-11-02 02:40:31 -07:00
|
|
|
borrows_without_fee = &mut market.quote_borrows_without_fee;
|
2022-08-30 00:55:19 -07:00
|
|
|
} else {
|
|
|
|
return Err(error_msg!(
|
|
|
|
"assert failed: apply_vault_difference called with bad token index"
|
|
|
|
));
|
|
|
|
};
|
2022-08-26 03:45:32 -07:00
|
|
|
|
2023-12-05 06:39:24 -08:00
|
|
|
// Only for place: Add to potential borrow amount
|
2023-11-02 02:40:31 -07:00
|
|
|
*borrows_without_fee += new_borrows;
|
2022-08-26 03:45:32 -07:00
|
|
|
|
|
|
|
// Only for settle/liq_force_cancel: Reduce the potential borrow amounts
|
2022-08-30 00:55:19 -07:00
|
|
|
if needed_change > 0 {
|
|
|
|
*borrows_without_fee = (*borrows_without_fee).saturating_sub(needed_change.to_num::<u64>());
|
2022-08-26 03:45:32 -07:00
|
|
|
}
|
2022-08-23 05:10:38 -07:00
|
|
|
|
2023-11-22 07:00:47 -08:00
|
|
|
emit_stack(TokenBalanceLog {
|
2022-09-23 10:42:43 -07:00
|
|
|
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(),
|
|
|
|
});
|
|
|
|
|
2022-08-26 03:45:32 -07:00
|
|
|
Ok(VaultDifference {
|
2022-08-30 00:55:19 -07:00
|
|
|
token_index: bank.token_index,
|
|
|
|
native_change,
|
2022-08-23 05:10:38 -07:00
|
|
|
})
|
2022-05-14 01:47:40 -07:00
|
|
|
}
|
|
|
|
|
2023-02-22 03:04:21 -08:00
|
|
|
/// 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(
|
2023-02-28 00:48:15 -08:00
|
|
|
group: &Group,
|
2023-02-22 03:04:21 -08:00
|
|
|
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>,
|
2023-03-03 05:04:45 -08:00
|
|
|
fees_to_dao: bool,
|
2023-03-20 03:18:11 -07:00
|
|
|
quote_oracle: Option<&AccountInfo>,
|
2023-02-22 03:04:21 -08:00
|
|
|
) -> Result<()> {
|
2023-03-03 05:04:45 -08:00
|
|
|
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);
|
|
|
|
|
2023-03-20 03:18:11 -07:00
|
|
|
// 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();
|
|
|
|
|
2024-01-04 09:29:54 -08:00
|
|
|
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),
|
|
|
|
)?;
|
2023-03-20 03:18:11 -07:00
|
|
|
let quote_asset_price = quote_oracle_price.min(quote_bank.stable_price());
|
2023-03-03 05:04:45 -08:00
|
|
|
account
|
|
|
|
.fixed
|
|
|
|
.expire_buyback_fees(now_ts, group.buyback_fees_expiry_interval);
|
2023-03-20 03:18:11 -07:00
|
|
|
let fees_in_usd = I80F48::from(received_fees) * quote_asset_price;
|
|
|
|
account
|
|
|
|
.fixed
|
|
|
|
.accrue_buyback_fees(fees_in_usd.clamp_to_u64());
|
2023-03-03 05:04:45 -08:00
|
|
|
}
|
2023-02-28 00:48:15 -08:00
|
|
|
}
|
|
|
|
|
2023-02-22 03:04:21 -08:00
|
|
|
// 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,
|
|
|
|
)?;
|
|
|
|
|
2023-11-02 02:40:31 -07:00
|
|
|
// Tokens were moved from open orders into banks again: also update the tracking
|
2023-12-05 06:39:24 -08:00
|
|
|
// for potential_serum_tokens on the banks.
|
2023-11-02 02:40:31 -07:00
|
|
|
{
|
|
|
|
let serum_orders = account.serum3_orders_mut(serum_market.market_index)?;
|
2023-12-05 06:39:24 -08:00
|
|
|
update_bank_potential_tokens(serum_orders, base_bank, quote_bank, after_oo);
|
2023-11-02 02:40:31 -07:00
|
|
|
}
|
|
|
|
|
2023-02-22 03:04:21 -08:00
|
|
|
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, "e_bank)?;
|
|
|
|
|
2023-09-13 00:35:10 -07:00
|
|
|
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,
|
|
|
|
)?;
|
2023-02-22 03:04:21 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2023-12-05 06:39:24 -08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2022-04-01 23:59:07 -07:00
|
|
|
fn cpi_place_order(ctx: &Serum3PlaceOrder, order: NewOrderInstructionV3) -> Result<()> {
|
2022-03-18 07:58:39 -07:00
|
|
|
use crate::serum3_cpi;
|
|
|
|
|
|
|
|
let group = ctx.group.load()?;
|
2022-03-19 04:11:56 -07:00
|
|
|
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(),
|
2022-08-30 00:55:19 -07:00
|
|
|
order_payer_token_account: ctx.payer_vault.to_account_info(),
|
2022-03-19 04:11:56 -07:00
|
|
|
user_authority: ctx.group.to_account_info(),
|
|
|
|
}
|
2022-04-01 23:59:07 -07:00
|
|
|
.call(&group, order)
|
2022-03-18 02:38:38 -07:00
|
|
|
}
|