Openbook token tracking and price bands (#805)
- track min bid, max ask
- track maximal token outflow from oo
- add serum3_place_order_v2 with mutable receiver bank
- placing openbook orders is restricted to a certain distance from the
oracle
(cherry picked from commit 81501837a9
)
This commit is contained in:
parent
99790a01e4
commit
f533d65a58
200
mango_v4.json
200
mango_v4.json
|
@ -2740,6 +2740,161 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "serum3PlaceOrderV2",
|
||||
"accounts": [
|
||||
{
|
||||
"name": "group",
|
||||
"isMut": false,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "account",
|
||||
"isMut": true,
|
||||
"isSigner": false,
|
||||
"relations": [
|
||||
"group"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "owner",
|
||||
"isMut": false,
|
||||
"isSigner": true
|
||||
},
|
||||
{
|
||||
"name": "openOrders",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "serumMarket",
|
||||
"isMut": false,
|
||||
"isSigner": false,
|
||||
"relations": [
|
||||
"group",
|
||||
"serum_program",
|
||||
"serum_market_external"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "serumProgram",
|
||||
"isMut": false,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "serumMarketExternal",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "marketBids",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "marketAsks",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "marketEventQueue",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "marketRequestQueue",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "marketBaseVault",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "marketQuoteVault",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "marketVaultSigner",
|
||||
"isMut": false,
|
||||
"isSigner": false,
|
||||
"docs": [
|
||||
"needed for the automatic settle_funds call"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "payerBank",
|
||||
"isMut": true,
|
||||
"isSigner": false,
|
||||
"docs": [
|
||||
"The bank that pays for the order, if necessary"
|
||||
],
|
||||
"relations": [
|
||||
"group"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "payerVault",
|
||||
"isMut": true,
|
||||
"isSigner": false,
|
||||
"docs": [
|
||||
"The bank vault that pays for the order, if necessary"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "payerOracle",
|
||||
"isMut": false,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "tokenProgram",
|
||||
"isMut": false,
|
||||
"isSigner": false
|
||||
}
|
||||
],
|
||||
"args": [
|
||||
{
|
||||
"name": "side",
|
||||
"type": {
|
||||
"defined": "Serum3Side"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "limitPrice",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "maxBaseQty",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "maxNativeQuoteQtyIncludingFees",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "selfTradeBehavior",
|
||||
"type": {
|
||||
"defined": "Serum3SelfTradeBehavior"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "orderType",
|
||||
"type": {
|
||||
"defined": "Serum3OrderType"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "clientOrderId",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "limit",
|
||||
"type": "u16"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "serum3CancelOrder",
|
||||
"accounts": [
|
||||
|
@ -7108,7 +7263,13 @@
|
|||
"type": "f64"
|
||||
},
|
||||
{
|
||||
"name": "depositsInSerum",
|
||||
"name": "potentialSerumTokens",
|
||||
"docs": [
|
||||
"Largest amount of tokens that might be added the the bank based on",
|
||||
"serum open order execution.",
|
||||
"",
|
||||
"Can be negative with multiple banks, then it'd need to be balanced in the keeper."
|
||||
],
|
||||
"type": "i64"
|
||||
},
|
||||
{
|
||||
|
@ -8680,26 +8841,41 @@
|
|||
"type": "f64"
|
||||
},
|
||||
{
|
||||
"name": "baseDepositsReserved",
|
||||
"name": "potentialBaseTokens",
|
||||
"docs": [
|
||||
"Tracks the amount of deposits that flowed into the serum open orders account.",
|
||||
"An overestimate of the amount of tokens that might flow out of the open orders account.",
|
||||
"",
|
||||
"The bank still considers these amounts user deposits (see deposits_in_serum)",
|
||||
"and they need to be deducted from there when they flow back into the bank",
|
||||
"as real tokens."
|
||||
"The bank still considers these amounts user deposits (see Bank::potential_serum_tokens)",
|
||||
"and that value needs to be updated in conjunction with these numbers.",
|
||||
"",
|
||||
"This estimation is based on the amount of tokens in the open orders account",
|
||||
"(see update_bank_potential_tokens() in serum3_place_order and settle)"
|
||||
],
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "quoteDepositsReserved",
|
||||
"name": "potentialQuoteTokens",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "lowestPlacedBidInv",
|
||||
"docs": [
|
||||
"Track lowest bid/highest ask, same way as for highest bid/lowest ask.",
|
||||
"",
|
||||
"0 is a special \"unset\" state."
|
||||
],
|
||||
"type": "f64"
|
||||
},
|
||||
{
|
||||
"name": "highestPlacedAsk",
|
||||
"type": "f64"
|
||||
},
|
||||
{
|
||||
"name": "reserved",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
32
|
||||
16
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -10362,6 +10538,9 @@
|
|||
},
|
||||
{
|
||||
"name": "TokenConditionalSwapCreateLinearAuction"
|
||||
},
|
||||
{
|
||||
"name": "Serum3PlaceOrderV2"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -13283,6 +13462,11 @@
|
|||
"code": 6059,
|
||||
"name": "TokenConditionalSwapTypeNotStartable",
|
||||
"msg": "token conditional swap type cannot be started"
|
||||
},
|
||||
{
|
||||
"code": 6060,
|
||||
"name": "HealthAccountBankNotWritable",
|
||||
"msg": "a bank in the health account list should be writable but is not"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -31,11 +31,10 @@ pub enum Serum3Side {
|
|||
Ask = 1,
|
||||
}
|
||||
|
||||
// Used for Serum3PlaceOrder v1 and v2
|
||||
#[derive(Accounts)]
|
||||
pub struct Serum3PlaceOrder<'info> {
|
||||
#[account(
|
||||
constraint = group.load()?.is_ix_enabled(IxGate::Serum3PlaceOrder) @ MangoError::IxIsDisabled,
|
||||
)]
|
||||
// ix gate check is done at #4
|
||||
pub group: AccountLoader<'info, Group>,
|
||||
|
||||
#[account(
|
||||
|
|
|
@ -125,6 +125,10 @@ pub enum MangoError {
|
|||
TokenConditionalSwapTooSmallForStartIncentive,
|
||||
#[msg("token conditional swap type cannot be started")]
|
||||
TokenConditionalSwapTypeNotStartable,
|
||||
#[msg("a bank in the health account list should be writable but is not")]
|
||||
HealthAccountBankNotWritable,
|
||||
#[msg("the market does not allow limit orders too far from the current oracle value")]
|
||||
Serum3PriceBandExceeded,
|
||||
}
|
||||
|
||||
impl MangoError {
|
||||
|
|
|
@ -1462,7 +1462,7 @@ mod tests {
|
|||
borrows: u64,
|
||||
deposit_weight_scale_start_quote: u64,
|
||||
borrow_weight_scale_start_quote: u64,
|
||||
deposits_in_serum: i64,
|
||||
potential_serum_tokens: u64,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
@ -1518,7 +1518,7 @@ mod tests {
|
|||
let bank = bank.data();
|
||||
bank.indexed_deposits = I80F48::from(settings.deposits) / bank.deposit_index;
|
||||
bank.indexed_borrows = I80F48::from(settings.borrows) / bank.borrow_index;
|
||||
bank.deposits_in_serum = settings.deposits_in_serum;
|
||||
bank.potential_serum_tokens = settings.potential_serum_tokens;
|
||||
if settings.deposit_weight_scale_start_quote > 0 {
|
||||
bank.deposit_weight_scale_start_quote =
|
||||
settings.deposit_weight_scale_start_quote as f64;
|
||||
|
@ -1834,7 +1834,7 @@ mod tests {
|
|||
..Default::default()
|
||||
},
|
||||
TestHealth1Case {
|
||||
// 17, deposits_in_serum counts for deposit weight scaling
|
||||
// 17, potential_serum_tokens counts for deposit weight scaling
|
||||
token1: 100,
|
||||
token2: 100,
|
||||
token3: 100,
|
||||
|
@ -1847,13 +1847,13 @@ mod tests {
|
|||
BankSettings {
|
||||
deposits: 100,
|
||||
deposit_weight_scale_start_quote: 100 * 5,
|
||||
deposits_in_serum: 100,
|
||||
potential_serum_tokens: 100,
|
||||
..BankSettings::default()
|
||||
},
|
||||
BankSettings {
|
||||
deposits: 600,
|
||||
deposit_weight_scale_start_quote: 500 * 10,
|
||||
deposits_in_serum: 100,
|
||||
potential_serum_tokens: 100,
|
||||
..BankSettings::default()
|
||||
},
|
||||
],
|
||||
|
|
|
@ -23,6 +23,7 @@ pub fn health_region_begin<'key, 'accounts, 'remaining, 'info>(
|
|||
crate::instruction::Serum3CancelAllOrders::discriminator(),
|
||||
crate::instruction::Serum3CancelOrder::discriminator(),
|
||||
crate::instruction::Serum3PlaceOrder::discriminator(),
|
||||
crate::instruction::Serum3PlaceOrderV2::discriminator(),
|
||||
crate::instruction::Serum3SettleFunds::discriminator(),
|
||||
crate::instruction::Serum3SettleFundsV2::discriminator(),
|
||||
];
|
||||
|
|
|
@ -94,6 +94,7 @@ pub fn ix_gate_set(ctx: Context<IxGateSet>, ix_gate: u128) -> Result<()> {
|
|||
ix_gate,
|
||||
IxGate::TokenConditionalSwapCreateLinearAuction,
|
||||
);
|
||||
log_if_changed(&group, ix_gate, IxGate::Serum3PlaceOrderV2);
|
||||
|
||||
group.ix_gate = ix_gate;
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ pub fn serum3_edit_market(
|
|||
reduce_only_opt: Option<bool>,
|
||||
force_close_opt: Option<bool>,
|
||||
name_opt: Option<String>,
|
||||
oracle_price_band_opt: Option<f32>,
|
||||
) -> Result<()> {
|
||||
let mut serum3_market = ctx.accounts.market.load_mut()?;
|
||||
|
||||
|
@ -46,6 +47,16 @@ pub fn serum3_edit_market(
|
|||
require_group_admin = true;
|
||||
};
|
||||
|
||||
if let Some(oracle_price_band) = oracle_price_band_opt {
|
||||
msg!(
|
||||
"Oracle price band: old - {:?}, new - {:?}",
|
||||
serum3_market.oracle_price_band,
|
||||
oracle_price_band
|
||||
);
|
||||
serum3_market.oracle_price_band = oracle_price_band;
|
||||
require_group_admin = true;
|
||||
};
|
||||
|
||||
if require_group_admin {
|
||||
require!(
|
||||
group.admin == ctx.accounts.admin.key(),
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::accounts_zerocopy::AccountInfoRef;
|
||||
use crate::accounts_zerocopy::*;
|
||||
use crate::error::*;
|
||||
use crate::health::*;
|
||||
use crate::i80f48::ClampToInt;
|
||||
|
@ -25,6 +25,7 @@ pub fn serum3_place_order(
|
|||
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);
|
||||
|
@ -87,18 +88,52 @@ pub fn serum3_place_order(
|
|||
};
|
||||
|
||||
// Check if the bank for the token whose balance is increased is in reduce-only mode
|
||||
let receiver_bank_reduce_only = {
|
||||
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 = retriever
|
||||
.bank_and_oracle(&group_key, active_index, receiver_token_index)?
|
||||
.0;
|
||||
receiver_bank.are_deposits_reduce_only()
|
||||
};
|
||||
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
|
||||
//
|
||||
|
@ -185,33 +220,45 @@ pub fn serum3_place_order(
|
|||
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 => {
|
||||
// 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;
|
||||
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 =
|
||||
base_lot_size as f64 / (limit_price_lots as f64 * quote_lot_size as f64);
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -237,6 +284,18 @@ pub fn serum3_place_order(
|
|||
|
||||
let mut payer_bank = ctx.accounts.payer_bank.load_mut()?;
|
||||
|
||||
// Update the tracking in banks
|
||||
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);
|
||||
}
|
||||
|
||||
// Enforce min vault to deposits ratio
|
||||
let withdrawn_from_vault = I80F48::from(before_vault - after_vault);
|
||||
let position_native = account
|
||||
|
@ -256,20 +315,65 @@ pub fn serum3_place_order(
|
|||
)?
|
||||
};
|
||||
|
||||
// Payer bank safety checks like reduce-only, net borrows, vault-to-deposits ratio
|
||||
let payer_bank_oracle =
|
||||
payer_bank.oracle_price(&AccountInfoRef::borrow(&ctx.accounts.payer_oracle)?, None)?;
|
||||
if withdrawn_from_vault > position_native {
|
||||
require_msg_typed!(
|
||||
!payer_bank.are_borrows_reduce_only(),
|
||||
MangoError::TokenInReduceOnlyMode,
|
||||
"the payer tokens cannot be borrowed"
|
||||
);
|
||||
let oracle_price =
|
||||
payer_bank.oracle_price(&AccountInfoRef::borrow(&ctx.accounts.payer_oracle)?, None)?;
|
||||
payer_bank.enforce_min_vault_to_deposits_ratio((*ctx.accounts.payer_vault).as_ref())?;
|
||||
payer_bank.check_net_borrows(oracle_price)?;
|
||||
payer_bank.check_net_borrows(payer_bank_oracle)?;
|
||||
}
|
||||
|
||||
vault_difference.adjust_health_cache_token_balance(&mut health_cache, &payer_bank)?;
|
||||
// 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,
|
||||
|
@ -379,33 +483,22 @@ fn apply_vault_difference(
|
|||
.min(I80F48::ZERO)
|
||||
.abs()
|
||||
.to_num::<u64>();
|
||||
// amount of tokens transferred to serum3 reserved that were taken from deposits
|
||||
let used_deposits = native_before
|
||||
.min(-needed_change)
|
||||
.max(I80F48::ZERO)
|
||||
.to_num::<u64>();
|
||||
|
||||
let indexed_position = position.indexed_position;
|
||||
let market = account.serum3_orders_mut(serum_market_index).unwrap();
|
||||
let borrows_without_fee;
|
||||
let deposits_reserved;
|
||||
if bank.token_index == market.base_token_index {
|
||||
borrows_without_fee = &mut market.base_borrows_without_fee;
|
||||
deposits_reserved = &mut market.base_deposits_reserved;
|
||||
} else if bank.token_index == market.quote_token_index {
|
||||
borrows_without_fee = &mut market.quote_borrows_without_fee;
|
||||
deposits_reserved = &mut market.quote_deposits_reserved;
|
||||
} else {
|
||||
return Err(error_msg!(
|
||||
"assert failed: apply_vault_difference called with bad token index"
|
||||
));
|
||||
};
|
||||
|
||||
// Only for place: Add to potential borrow amount and reserved deposits
|
||||
// Only for place: Add to potential borrow amount
|
||||
*borrows_without_fee += new_borrows;
|
||||
*deposits_reserved += used_deposits;
|
||||
let used_deposits_signed: i64 = used_deposits.try_into().unwrap();
|
||||
bank.deposits_in_serum += used_deposits_signed;
|
||||
|
||||
// Only for settle/liq_force_cancel: Reduce the potential borrow amounts
|
||||
if needed_change > 0 {
|
||||
|
@ -499,25 +592,10 @@ pub fn apply_settle_changes(
|
|||
)?;
|
||||
|
||||
// Tokens were moved from open orders into banks again: also update the tracking
|
||||
// for deposits_in_serum on the banks.
|
||||
// for potential_serum_tokens on the banks.
|
||||
{
|
||||
let serum_orders = account.serum3_orders_mut(serum_market.market_index)?;
|
||||
|
||||
let after_base_reserved = after_oo.native_base_reserved();
|
||||
if after_base_reserved < serum_orders.base_deposits_reserved {
|
||||
let diff = serum_orders.base_deposits_reserved - after_base_reserved;
|
||||
serum_orders.base_deposits_reserved = after_base_reserved;
|
||||
let diff_signed: i64 = diff.try_into().unwrap();
|
||||
base_bank.deposits_in_serum -= diff_signed;
|
||||
}
|
||||
|
||||
let after_quote_reserved = after_oo.native_quote_reserved();
|
||||
if after_quote_reserved < serum_orders.quote_deposits_reserved {
|
||||
let diff = serum_orders.quote_deposits_reserved - after_quote_reserved;
|
||||
serum_orders.quote_deposits_reserved = after_quote_reserved;
|
||||
let diff_signed: i64 = diff.try_into().unwrap();
|
||||
quote_bank.deposits_in_serum -= diff_signed;
|
||||
}
|
||||
update_bank_potential_tokens(serum_orders, base_bank, quote_bank, after_oo);
|
||||
}
|
||||
|
||||
if let Some(health_cache) = health_cache {
|
||||
|
@ -535,6 +613,58 @@ pub fn apply_settle_changes(
|
|||
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;
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ pub fn serum3_register_market(
|
|||
ctx: Context<Serum3RegisterMarket>,
|
||||
market_index: Serum3MarketIndex,
|
||||
name: String,
|
||||
oracle_price_band: f32,
|
||||
) -> Result<()> {
|
||||
// TODO: must guard against accidentally using the same market_index twice!
|
||||
|
||||
|
@ -44,6 +45,7 @@ pub fn serum3_register_market(
|
|||
market_index,
|
||||
bump: *ctx.bumps.get("serum_market").ok_or(MangoError::SomeError)?,
|
||||
padding2: Default::default(),
|
||||
oracle_price_band,
|
||||
registration_time: Clock::get()?.unix_timestamp.try_into().unwrap(),
|
||||
reserved: [0; 128],
|
||||
};
|
||||
|
|
|
@ -113,7 +113,7 @@ pub fn token_register(
|
|||
flash_loan_swap_fee_rate: flash_loan_swap_fee_rate,
|
||||
interest_target_utilization,
|
||||
interest_curve_scaling: interest_curve_scaling.into(),
|
||||
deposits_in_serum: 0,
|
||||
potential_serum_tokens: 0,
|
||||
maint_weight_shift_start: 0,
|
||||
maint_weight_shift_end: 0,
|
||||
maint_weight_shift_duration_inv: I80F48::ZERO,
|
||||
|
|
|
@ -96,7 +96,7 @@ pub fn token_register_trustless(
|
|||
flash_loan_swap_fee_rate: 0.0,
|
||||
interest_target_utilization: 0.5,
|
||||
interest_curve_scaling: 4.0,
|
||||
deposits_in_serum: 0,
|
||||
potential_serum_tokens: 0,
|
||||
maint_weight_shift_start: 0,
|
||||
maint_weight_shift_end: 0,
|
||||
maint_weight_shift_duration_inv: I80F48::ZERO,
|
||||
|
|
|
@ -560,9 +560,10 @@ pub mod mango_v4 {
|
|||
ctx: Context<Serum3RegisterMarket>,
|
||||
market_index: Serum3MarketIndex,
|
||||
name: String,
|
||||
oracle_price_band: f32,
|
||||
) -> Result<()> {
|
||||
#[cfg(feature = "enable-gpl")]
|
||||
instructions::serum3_register_market(ctx, market_index, name)?;
|
||||
instructions::serum3_register_market(ctx, market_index, name, oracle_price_band)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -571,9 +572,16 @@ pub mod mango_v4 {
|
|||
reduce_only_opt: Option<bool>,
|
||||
force_close_opt: Option<bool>,
|
||||
name_opt: Option<String>,
|
||||
oracle_price_band_opt: Option<f32>,
|
||||
) -> Result<()> {
|
||||
#[cfg(feature = "enable-gpl")]
|
||||
instructions::serum3_edit_market(ctx, reduce_only_opt, force_close_opt, name_opt)?;
|
||||
instructions::serum3_edit_market(
|
||||
ctx,
|
||||
reduce_only_opt,
|
||||
force_close_opt,
|
||||
name_opt,
|
||||
oracle_price_band_opt,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -623,6 +631,35 @@ pub mod mango_v4 {
|
|||
order_type,
|
||||
client_order_id,
|
||||
limit,
|
||||
false,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn serum3_place_order_v2(
|
||||
ctx: Context<Serum3PlaceOrder>,
|
||||
side: Serum3Side,
|
||||
limit_price: 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,
|
||||
) -> Result<()> {
|
||||
#[cfg(feature = "enable-gpl")]
|
||||
instructions::serum3_place_order(
|
||||
ctx,
|
||||
side,
|
||||
limit_price,
|
||||
max_base_qty,
|
||||
max_native_quote_qty_including_fees,
|
||||
self_trade_behavior,
|
||||
order_type,
|
||||
client_order_id,
|
||||
limit,
|
||||
true,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -157,9 +157,9 @@ pub struct Bank {
|
|||
/// Except when first migrating to having this field, then 0.0
|
||||
pub interest_curve_scaling: f64,
|
||||
|
||||
// user deposits that were moved into serum open orders
|
||||
// can be negative due to multibank, then it'd need to be balanced in the keeper
|
||||
pub deposits_in_serum: i64,
|
||||
/// Largest amount of tokens that might be added the the bank based on
|
||||
/// serum open order execution.
|
||||
pub potential_serum_tokens: u64,
|
||||
|
||||
pub maint_weight_shift_start: u64,
|
||||
pub maint_weight_shift_end: u64,
|
||||
|
@ -238,7 +238,7 @@ impl Bank {
|
|||
flash_loan_approved_amount: 0,
|
||||
flash_loan_token_account_initial: u64::MAX,
|
||||
net_borrows_in_window: 0,
|
||||
deposits_in_serum: 0,
|
||||
potential_serum_tokens: 0,
|
||||
bump,
|
||||
bank_num,
|
||||
|
||||
|
@ -969,7 +969,8 @@ impl Bank {
|
|||
if self.deposit_weight_scale_start_quote == f64::MAX {
|
||||
return self.init_asset_weight;
|
||||
}
|
||||
let all_deposits = self.native_deposits().to_num::<f64>() + self.deposits_in_serum as f64;
|
||||
let all_deposits =
|
||||
self.native_deposits().to_num::<f64>() + self.potential_serum_tokens as f64;
|
||||
let deposits_quote = all_deposits * price.to_num::<f64>();
|
||||
if deposits_quote <= self.deposit_weight_scale_start_quote {
|
||||
self.init_asset_weight
|
||||
|
@ -998,6 +999,16 @@ impl Bank {
|
|||
self.init_liab_weight * I80F48::from_num(scale)
|
||||
}
|
||||
}
|
||||
|
||||
/// Grows potential_serum_tokens if new > old, shrinks it otherwise
|
||||
#[inline(always)]
|
||||
pub fn update_potential_serum_tokens(&mut self, old: u64, new: u64) {
|
||||
if new >= old {
|
||||
self.potential_serum_tokens += new - old;
|
||||
} else {
|
||||
self.potential_serum_tokens = self.potential_serum_tokens.saturating_sub(old - new);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
|
|
|
@ -220,6 +220,7 @@ pub enum IxGate {
|
|||
TokenConditionalSwapStart = 68,
|
||||
TokenConditionalSwapCreatePremiumAuction = 69,
|
||||
TokenConditionalSwapCreateLinearAuction = 70,
|
||||
Serum3PlaceOrderV2 = 71,
|
||||
// NOTE: Adding new variants requires matching changes in ts and the ix_gate_set instruction.
|
||||
}
|
||||
|
||||
|
|
|
@ -145,20 +145,28 @@ pub struct Serum3Orders {
|
|||
pub highest_placed_bid_inv: f64,
|
||||
pub lowest_placed_ask: f64,
|
||||
|
||||
/// Tracks the amount of deposits that flowed into the serum open orders account.
|
||||
/// An overestimate of the amount of tokens that might flow out of the open orders account.
|
||||
///
|
||||
/// The bank still considers these amounts user deposits (see deposits_in_serum)
|
||||
/// and they need to be deducted from there when they flow back into the bank
|
||||
/// as real tokens.
|
||||
pub base_deposits_reserved: u64,
|
||||
pub quote_deposits_reserved: u64,
|
||||
/// The bank still considers these amounts user deposits (see Bank::potential_serum_tokens)
|
||||
/// and that value needs to be updated in conjunction with these numbers.
|
||||
///
|
||||
/// This estimation is based on the amount of tokens in the open orders account
|
||||
/// (see update_bank_potential_tokens() in serum3_place_order and settle)
|
||||
pub potential_base_tokens: u64,
|
||||
pub potential_quote_tokens: u64,
|
||||
|
||||
/// Track lowest bid/highest ask, same way as for highest bid/lowest ask.
|
||||
///
|
||||
/// 0 is a special "unset" state.
|
||||
pub lowest_placed_bid_inv: f64,
|
||||
pub highest_placed_ask: f64,
|
||||
|
||||
#[derivative(Debug = "ignore")]
|
||||
pub reserved: [u8; 32],
|
||||
pub reserved: [u8; 16],
|
||||
}
|
||||
const_assert_eq!(
|
||||
size_of::<Serum3Orders>(),
|
||||
32 + 8 * 2 + 2 * 3 + 2 + 4 * 8 + 32
|
||||
32 + 8 * 2 + 2 * 3 + 2 + 6 * 8 + 16
|
||||
);
|
||||
const_assert_eq!(size_of::<Serum3Orders>(), 120);
|
||||
const_assert_eq!(size_of::<Serum3Orders>() % 8, 0);
|
||||
|
@ -185,9 +193,11 @@ impl Default for Serum3Orders {
|
|||
quote_borrows_without_fee: 0,
|
||||
highest_placed_bid_inv: 0.0,
|
||||
lowest_placed_ask: 0.0,
|
||||
base_deposits_reserved: 0,
|
||||
quote_deposits_reserved: 0,
|
||||
reserved: [0; 32],
|
||||
potential_base_tokens: 0,
|
||||
potential_quote_tokens: 0,
|
||||
lowest_placed_bid_inv: 0.0,
|
||||
highest_placed_ask: 0.0,
|
||||
reserved: [0; 16],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,13 @@ pub struct Serum3Market {
|
|||
|
||||
pub bump: u8,
|
||||
|
||||
pub padding2: [u8; 5],
|
||||
pub padding2: [u8; 1],
|
||||
|
||||
/// Limit orders must be <= oracle * (1+band) and >= oracle / (1+band)
|
||||
///
|
||||
/// Zero value is the default due to migration and disables the limit,
|
||||
/// same as f32::MAX.
|
||||
pub oracle_price_band: f32,
|
||||
|
||||
pub registration_time: u64,
|
||||
|
||||
|
@ -34,7 +40,7 @@ pub struct Serum3Market {
|
|||
}
|
||||
const_assert_eq!(
|
||||
size_of::<Serum3Market>(),
|
||||
32 + 2 + 2 + 1 + 3 + 16 + 2 * 32 + 2 + 1 + 5 + 8 + 128
|
||||
32 + 2 + 2 + 1 + 3 + 16 + 2 * 32 + 2 + 1 + 1 + 4 + 8 + 128
|
||||
);
|
||||
const_assert_eq!(size_of::<Serum3Market>(), 264);
|
||||
const_assert_eq!(size_of::<Serum3Market>() % 8, 0);
|
||||
|
@ -53,6 +59,14 @@ impl Serum3Market {
|
|||
pub fn is_force_close(&self) -> bool {
|
||||
self.force_close == 1
|
||||
}
|
||||
|
||||
pub fn oracle_price_band(&self) -> f32 {
|
||||
if self.oracle_price_band == 0.0 {
|
||||
f32::MAX // default disabled
|
||||
} else {
|
||||
self.oracle_price_band
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[account(zero_copy)]
|
||||
|
|
|
@ -309,13 +309,13 @@ async fn test_serum_basics() -> Result<(), TransportError> {
|
|||
//
|
||||
// TEST: Place an order
|
||||
//
|
||||
let (order_id, _) = order_placer.bid_maker(1.0, 100).await.unwrap();
|
||||
let (order_id, _) = order_placer.bid_maker(0.9, 100).await.unwrap();
|
||||
check_prev_instruction_post_health(&solana, account).await;
|
||||
|
||||
let native0 = account_position(solana, account, base_token.bank).await;
|
||||
let native1 = account_position(solana, account, quote_token.bank).await;
|
||||
assert_eq!(native0, 1000);
|
||||
assert_eq!(native1, 900);
|
||||
assert_eq!(native1, 910);
|
||||
|
||||
let account_data = get_mango_account(solana, account).await;
|
||||
assert_eq!(
|
||||
|
@ -342,13 +342,13 @@ async fn test_serum_basics() -> Result<(), TransportError> {
|
|||
let serum_orders = account_data.serum3_orders_by_raw_index(0).unwrap();
|
||||
assert_eq!(serum_orders.base_borrows_without_fee, 0);
|
||||
assert_eq!(serum_orders.quote_borrows_without_fee, 0);
|
||||
assert_eq!(serum_orders.base_deposits_reserved, 0);
|
||||
assert_eq!(serum_orders.quote_deposits_reserved, 100);
|
||||
assert_eq!(serum_orders.potential_base_tokens, 100);
|
||||
assert_eq!(serum_orders.potential_quote_tokens, 90);
|
||||
|
||||
let base_bank = solana.get_account::<Bank>(base_token.bank).await;
|
||||
assert_eq!(base_bank.deposits_in_serum, 0);
|
||||
assert_eq!(base_bank.potential_serum_tokens, 100);
|
||||
let quote_bank = solana.get_account::<Bank>(quote_token.bank).await;
|
||||
assert_eq!(quote_bank.deposits_in_serum, 100);
|
||||
assert_eq!(quote_bank.potential_serum_tokens, 90);
|
||||
|
||||
assert!(order_id != 0);
|
||||
|
||||
|
@ -371,13 +371,13 @@ async fn test_serum_basics() -> Result<(), TransportError> {
|
|||
let serum_orders = account_data.serum3_orders_by_raw_index(0).unwrap();
|
||||
assert_eq!(serum_orders.base_borrows_without_fee, 0);
|
||||
assert_eq!(serum_orders.quote_borrows_without_fee, 0);
|
||||
assert_eq!(serum_orders.base_deposits_reserved, 0);
|
||||
assert_eq!(serum_orders.quote_deposits_reserved, 0);
|
||||
assert_eq!(serum_orders.potential_base_tokens, 0);
|
||||
assert_eq!(serum_orders.potential_quote_tokens, 0);
|
||||
|
||||
let base_bank = solana.get_account::<Bank>(base_token.bank).await;
|
||||
assert_eq!(base_bank.deposits_in_serum, 0);
|
||||
assert_eq!(base_bank.potential_serum_tokens, 0);
|
||||
let quote_bank = solana.get_account::<Bank>(quote_token.bank).await;
|
||||
assert_eq!(quote_bank.deposits_in_serum, 0);
|
||||
assert_eq!(quote_bank.potential_serum_tokens, 0);
|
||||
|
||||
// Process events such that the OutEvent deactivates the closed order on open_orders
|
||||
context
|
||||
|
@ -1150,89 +1150,72 @@ async fn test_serum_track_bid_ask() -> Result<(), TransportError> {
|
|||
// TEST: highest bid/lowest ask updating
|
||||
//
|
||||
|
||||
assert_eq!(
|
||||
order_placer
|
||||
.mango_serum_orders()
|
||||
.await
|
||||
.highest_placed_bid_inv,
|
||||
0.0
|
||||
);
|
||||
assert_eq!(
|
||||
order_placer.mango_serum_orders().await.lowest_placed_ask,
|
||||
0.0
|
||||
);
|
||||
let srm = order_placer.mango_serum_orders().await;
|
||||
assert_eq!(srm.highest_placed_bid_inv, 0.0);
|
||||
assert_eq!(srm.lowest_placed_bid_inv, 0.0);
|
||||
assert_eq!(srm.highest_placed_ask, 0.0);
|
||||
assert_eq!(srm.lowest_placed_ask, 0.0);
|
||||
|
||||
order_placer.bid_maker(10.0, 100).await.unwrap();
|
||||
assert_eq!(
|
||||
order_placer
|
||||
.mango_serum_orders()
|
||||
.await
|
||||
.highest_placed_bid_inv,
|
||||
1.0 / 10.0
|
||||
);
|
||||
|
||||
let srm = order_placer.mango_serum_orders().await;
|
||||
assert_eq!(srm.highest_placed_bid_inv, 1.0 / 10.0);
|
||||
assert_eq!(srm.lowest_placed_bid_inv, 1.0 / 10.0);
|
||||
assert_eq!(srm.highest_placed_ask, 0.0);
|
||||
assert_eq!(srm.lowest_placed_ask, 0.0);
|
||||
|
||||
order_placer.bid_maker(9.0, 100).await.unwrap();
|
||||
assert_eq!(
|
||||
order_placer
|
||||
.mango_serum_orders()
|
||||
.await
|
||||
.highest_placed_bid_inv,
|
||||
1.0 / 10.0
|
||||
);
|
||||
|
||||
let srm = order_placer.mango_serum_orders().await;
|
||||
assert_eq!(srm.highest_placed_bid_inv, 1.0 / 10.0);
|
||||
assert_eq!(srm.lowest_placed_bid_inv, 1.0 / 9.0);
|
||||
assert_eq!(srm.highest_placed_ask, 0.0);
|
||||
assert_eq!(srm.lowest_placed_ask, 0.0);
|
||||
|
||||
order_placer.bid_maker(11.0, 100).await.unwrap();
|
||||
assert_eq!(
|
||||
order_placer
|
||||
.mango_serum_orders()
|
||||
.await
|
||||
.highest_placed_bid_inv,
|
||||
1.0 / 11.0
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
order_placer.mango_serum_orders().await.lowest_placed_ask,
|
||||
0.0
|
||||
);
|
||||
let srm = order_placer.mango_serum_orders().await;
|
||||
assert_eq!(srm.highest_placed_bid_inv, 1.0 / 11.0);
|
||||
assert_eq!(srm.lowest_placed_bid_inv, 1.0 / 9.0);
|
||||
assert_eq!(srm.highest_placed_ask, 0.0);
|
||||
assert_eq!(srm.lowest_placed_ask, 0.0);
|
||||
|
||||
order_placer.ask(20.0, 100).await.unwrap();
|
||||
assert_eq!(
|
||||
order_placer.mango_serum_orders().await.lowest_placed_ask,
|
||||
20.0
|
||||
);
|
||||
order_placer.ask(19.0, 100).await.unwrap();
|
||||
assert_eq!(
|
||||
order_placer.mango_serum_orders().await.lowest_placed_ask,
|
||||
19.0
|
||||
);
|
||||
order_placer.ask(21.0, 100).await.unwrap();
|
||||
assert_eq!(
|
||||
order_placer.mango_serum_orders().await.lowest_placed_ask,
|
||||
19.0
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
order_placer
|
||||
.mango_serum_orders()
|
||||
.await
|
||||
.highest_placed_bid_inv,
|
||||
1.0 / 11.0
|
||||
);
|
||||
let srm = order_placer.mango_serum_orders().await;
|
||||
assert_eq!(srm.highest_placed_bid_inv, 1.0 / 11.0);
|
||||
assert_eq!(srm.lowest_placed_bid_inv, 1.0 / 9.0);
|
||||
assert_eq!(srm.highest_placed_ask, 20.0);
|
||||
assert_eq!(srm.lowest_placed_ask, 20.0);
|
||||
|
||||
order_placer.ask(19.0, 100).await.unwrap();
|
||||
|
||||
let srm = order_placer.mango_serum_orders().await;
|
||||
assert_eq!(srm.highest_placed_bid_inv, 1.0 / 11.0);
|
||||
assert_eq!(srm.lowest_placed_bid_inv, 1.0 / 9.0);
|
||||
assert_eq!(srm.highest_placed_ask, 20.0);
|
||||
assert_eq!(srm.lowest_placed_ask, 19.0);
|
||||
|
||||
order_placer.ask(21.0, 100).await.unwrap();
|
||||
|
||||
let srm = order_placer.mango_serum_orders().await;
|
||||
assert_eq!(srm.highest_placed_bid_inv, 1.0 / 11.0);
|
||||
assert_eq!(srm.lowest_placed_bid_inv, 1.0 / 9.0);
|
||||
assert_eq!(srm.highest_placed_ask, 21.0);
|
||||
assert_eq!(srm.lowest_placed_ask, 19.0);
|
||||
|
||||
//
|
||||
// TEST: cancellation allows for resets
|
||||
//
|
||||
|
||||
order_placer.cancel_all().await;
|
||||
assert_eq!(
|
||||
order_placer.mango_serum_orders().await.lowest_placed_ask,
|
||||
19.0
|
||||
);
|
||||
assert_eq!(
|
||||
order_placer
|
||||
.mango_serum_orders()
|
||||
.await
|
||||
.highest_placed_bid_inv,
|
||||
1.0 / 11.0
|
||||
);
|
||||
|
||||
// no immediate change
|
||||
let srm = order_placer.mango_serum_orders().await;
|
||||
assert_eq!(srm.highest_placed_bid_inv, 1.0 / 11.0);
|
||||
assert_eq!(srm.lowest_placed_bid_inv, 1.0 / 9.0);
|
||||
assert_eq!(srm.highest_placed_ask, 21.0);
|
||||
assert_eq!(srm.lowest_placed_ask, 19.0);
|
||||
|
||||
// Process events such that the OutEvent deactivates the closed order on open_orders
|
||||
context
|
||||
|
@ -1242,36 +1225,36 @@ async fn test_serum_track_bid_ask() -> Result<(), TransportError> {
|
|||
|
||||
// takes new value for bid, resets ask
|
||||
order_placer.bid_maker(1.0, 100).await.unwrap();
|
||||
assert_eq!(
|
||||
order_placer.mango_serum_orders().await.lowest_placed_ask,
|
||||
0.0
|
||||
);
|
||||
assert_eq!(
|
||||
order_placer
|
||||
.mango_serum_orders()
|
||||
.await
|
||||
.highest_placed_bid_inv,
|
||||
1.0
|
||||
);
|
||||
|
||||
let srm = order_placer.mango_serum_orders().await;
|
||||
assert_eq!(srm.highest_placed_bid_inv, 1.0);
|
||||
assert_eq!(srm.lowest_placed_bid_inv, 1.0);
|
||||
assert_eq!(srm.highest_placed_ask, 0.0);
|
||||
assert_eq!(srm.lowest_placed_ask, 0.0);
|
||||
|
||||
//
|
||||
// TEST: can reset even when there's still an order on the other side
|
||||
//
|
||||
let (oid, _) = order_placer.ask(10.0, 100).await.unwrap();
|
||||
assert_eq!(
|
||||
order_placer.mango_serum_orders().await.lowest_placed_ask,
|
||||
10.0
|
||||
);
|
||||
|
||||
let srm = order_placer.mango_serum_orders().await;
|
||||
assert_eq!(srm.highest_placed_bid_inv, 1.0);
|
||||
assert_eq!(srm.lowest_placed_bid_inv, 1.0);
|
||||
assert_eq!(srm.highest_placed_ask, 10.0);
|
||||
assert_eq!(srm.lowest_placed_ask, 10.0);
|
||||
|
||||
order_placer.cancel(oid).await;
|
||||
context
|
||||
.serum
|
||||
.consume_spot_events(&serum_market_cookie, &[order_placer.open_orders])
|
||||
.await;
|
||||
order_placer.ask(9.0, 100).await.unwrap();
|
||||
assert_eq!(
|
||||
order_placer.mango_serum_orders().await.lowest_placed_ask,
|
||||
9.0
|
||||
);
|
||||
|
||||
let srm = order_placer.mango_serum_orders().await;
|
||||
assert_eq!(srm.highest_placed_bid_inv, 1.0);
|
||||
assert_eq!(srm.lowest_placed_bid_inv, 1.0);
|
||||
assert_eq!(srm.highest_placed_ask, 9.0);
|
||||
assert_eq!(srm.lowest_placed_ask, 9.0);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1305,10 +1288,10 @@ async fn test_serum_track_reserved_deposits() -> Result<(), TransportError> {
|
|||
let base_bank = solana.get_account::<Bank>(base_bank).await;
|
||||
let quote_bank = solana.get_account::<Bank>(quote_bank).await;
|
||||
(
|
||||
orders.base_deposits_reserved,
|
||||
base_bank.deposits_in_serum,
|
||||
orders.quote_deposits_reserved,
|
||||
quote_bank.deposits_in_serum,
|
||||
orders.potential_base_tokens,
|
||||
base_bank.potential_serum_tokens,
|
||||
orders.potential_quote_tokens,
|
||||
quote_bank.potential_serum_tokens,
|
||||
)
|
||||
};
|
||||
|
||||
|
@ -1317,9 +1300,14 @@ async fn test_serum_track_reserved_deposits() -> Result<(), TransportError> {
|
|||
//
|
||||
|
||||
order_placer.bid_maker(0.8, 2000).await.unwrap();
|
||||
order_placer.ask(1.2, 2000).await.unwrap();
|
||||
assert_eq!(get_vals(solana).await, (2000, 2000, 1600, 1600));
|
||||
|
||||
order_placer.ask(1.2, 2000).await.unwrap();
|
||||
assert_eq!(
|
||||
get_vals(solana).await,
|
||||
(2 * 2000, 2 * 2000, 1600 + 2400, 1600 + 2400)
|
||||
);
|
||||
|
||||
//
|
||||
// TEST: match partially on both sides, increasing the on-bank reserved amounts
|
||||
// because order_placer2 puts funds into the serum oo
|
||||
|
@ -1333,9 +1321,12 @@ async fn test_serum_track_reserved_deposits() -> Result<(), TransportError> {
|
|||
&[order_placer.open_orders, order_placer2.open_orders],
|
||||
)
|
||||
.await;
|
||||
assert_eq!(get_vals(solana).await, (2000, 2000, 1600, 2801));
|
||||
// taker order directly converted to base, no change to quote
|
||||
assert_eq!(get_vals(solana).await, (4000, 4000 + 1000, 4000, 4000));
|
||||
|
||||
// takes out 1000 base
|
||||
order_placer2.settle_v2(false).await;
|
||||
assert_eq!(get_vals(solana).await, (2000, 2000, 1600, 1600));
|
||||
assert_eq!(get_vals(solana).await, (4000, 4000, 4000, 4000));
|
||||
|
||||
order_placer2.ask(0.8, 1000).await.unwrap();
|
||||
context
|
||||
|
@ -1345,16 +1336,19 @@ async fn test_serum_track_reserved_deposits() -> Result<(), TransportError> {
|
|||
&[order_placer.open_orders, order_placer2.open_orders],
|
||||
)
|
||||
.await;
|
||||
assert_eq!(get_vals(solana).await, (2000, 3000, 1600, 1600));
|
||||
// taker order directly converted to quote
|
||||
assert_eq!(get_vals(solana).await, (4000, 4000, 4000, 4000 + 799));
|
||||
|
||||
order_placer2.settle_v2(false).await;
|
||||
assert_eq!(get_vals(solana).await, (2000, 2000, 1600, 1600));
|
||||
assert_eq!(get_vals(solana).await, (4000, 4000, 4000, 4000));
|
||||
|
||||
//
|
||||
// TEST: Settlement updates the values
|
||||
//
|
||||
|
||||
order_placer.settle_v2(false).await;
|
||||
assert_eq!(get_vals(solana).await, (1000, 1000, 800, 800));
|
||||
// remaining is bid 1000 @ 0.8; ask 1000 @ 1.2
|
||||
assert_eq!(get_vals(solana).await, (2000, 2000, 2000, 2000));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1481,6 +1475,82 @@ async fn test_serum_compute() -> Result<(), TransportError> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_serum_bands() -> Result<(), TransportError> {
|
||||
let mut test_builder = TestContextBuilder::new();
|
||||
test_builder.test().set_compute_max_units(150_000); // Serum3PlaceOrder needs lots
|
||||
let context = test_builder.start_default().await;
|
||||
let solana = &context.solana.clone();
|
||||
|
||||
//
|
||||
// SETUP: Create a group, accounts, market etc
|
||||
//
|
||||
let deposit_amount = 10000;
|
||||
let CommonSetup {
|
||||
group_with_tokens,
|
||||
mut order_placer,
|
||||
quote_token,
|
||||
base_token,
|
||||
..
|
||||
} = common_setup(&context, deposit_amount).await;
|
||||
|
||||
//
|
||||
// SETUP: Set oracle price for market to 100
|
||||
//
|
||||
set_bank_stub_oracle_price(
|
||||
solana,
|
||||
group_with_tokens.group,
|
||||
&base_token,
|
||||
group_with_tokens.admin,
|
||||
200.0,
|
||||
)
|
||||
.await;
|
||||
set_bank_stub_oracle_price(
|
||||
solana,
|
||||
group_with_tokens.group,
|
||||
"e_token,
|
||||
group_with_tokens.admin,
|
||||
2.0,
|
||||
)
|
||||
.await;
|
||||
|
||||
//
|
||||
// TEST: can place way over/under oracle
|
||||
//
|
||||
|
||||
order_placer.bid_maker(1.0, 100).await.unwrap();
|
||||
order_placer.ask(200.0, 100).await.unwrap();
|
||||
order_placer.cancel_all().await;
|
||||
|
||||
//
|
||||
// TEST: Can't when bands are enabled
|
||||
//
|
||||
send_tx(
|
||||
solana,
|
||||
Serum3EditMarketInstruction {
|
||||
group: group_with_tokens.group,
|
||||
admin: group_with_tokens.admin,
|
||||
market: order_placer.serum_market,
|
||||
options: mango_v4::instruction::Serum3EditMarket {
|
||||
oracle_price_band_opt: Some(0.5),
|
||||
..serum3_edit_market_instruction_default()
|
||||
},
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let r = order_placer.try_bid(65.0, 100, false).await;
|
||||
assert!(r.is_err());
|
||||
let r = order_placer.try_ask(151.0, 100).await;
|
||||
assert!(r.is_err());
|
||||
|
||||
order_placer.try_bid(67.0, 100, false).await.unwrap();
|
||||
order_placer.try_ask(149.0, 100).await.unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct CommonSetup {
|
||||
group_with_tokens: GroupWithTokens,
|
||||
serum_market_cookie: SpotMarketCookie,
|
||||
|
|
|
@ -2275,6 +2275,7 @@ impl ClientInstruction for Serum3RegisterMarketInstruction {
|
|||
let instruction = Self::Instruction {
|
||||
market_index: self.market_index,
|
||||
name: "UUU/usdc".to_string(),
|
||||
oracle_price_band: f32::MAX,
|
||||
};
|
||||
|
||||
let serum_market = Pubkey::find_program_address(
|
||||
|
@ -2319,6 +2320,46 @@ impl ClientInstruction for Serum3RegisterMarketInstruction {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn serum3_edit_market_instruction_default() -> mango_v4::instruction::Serum3EditMarket {
|
||||
mango_v4::instruction::Serum3EditMarket {
|
||||
reduce_only_opt: None,
|
||||
force_close_opt: None,
|
||||
name_opt: None,
|
||||
oracle_price_band_opt: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Serum3EditMarketInstruction {
|
||||
pub group: Pubkey,
|
||||
pub admin: TestKeypair,
|
||||
pub market: Pubkey,
|
||||
pub options: mango_v4::instruction::Serum3EditMarket,
|
||||
}
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl ClientInstruction for Serum3EditMarketInstruction {
|
||||
type Accounts = mango_v4::accounts::Serum3EditMarket;
|
||||
type Instruction = mango_v4::instruction::Serum3EditMarket;
|
||||
async fn to_instruction(
|
||||
&self,
|
||||
_account_loader: impl ClientAccountLoader + 'async_trait,
|
||||
) -> (Self::Accounts, instruction::Instruction) {
|
||||
let program_id = mango_v4::id();
|
||||
|
||||
let accounts = Self::Accounts {
|
||||
group: self.group,
|
||||
admin: self.admin.pubkey(),
|
||||
market: self.market,
|
||||
};
|
||||
|
||||
let instruction = make_instruction(program_id, &accounts, &self.options);
|
||||
(accounts, instruction)
|
||||
}
|
||||
|
||||
fn signers(&self) -> Vec<TestKeypair> {
|
||||
vec![self.admin]
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Serum3DeregisterMarketInstruction {
|
||||
pub group: Pubkey,
|
||||
pub admin: TestKeypair,
|
||||
|
@ -2493,7 +2534,7 @@ pub struct Serum3PlaceOrderInstruction {
|
|||
#[async_trait::async_trait(?Send)]
|
||||
impl ClientInstruction for Serum3PlaceOrderInstruction {
|
||||
type Accounts = mango_v4::accounts::Serum3PlaceOrder;
|
||||
type Instruction = mango_v4::instruction::Serum3PlaceOrder;
|
||||
type Instruction = mango_v4::instruction::Serum3PlaceOrderV2;
|
||||
async fn to_instruction(
|
||||
&self,
|
||||
account_loader: impl ClientAccountLoader + 'async_trait,
|
||||
|
@ -2547,7 +2588,7 @@ impl ClientInstruction for Serum3PlaceOrderInstruction {
|
|||
)
|
||||
.unwrap();
|
||||
|
||||
let health_check_metas = derive_health_check_remaining_account_metas(
|
||||
let mut health_check_metas = derive_health_check_remaining_account_metas(
|
||||
&account_loader,
|
||||
&account,
|
||||
None,
|
||||
|
@ -2556,11 +2597,17 @@ impl ClientInstruction for Serum3PlaceOrderInstruction {
|
|||
)
|
||||
.await;
|
||||
|
||||
let payer_info = &match self.side {
|
||||
Serum3Side::Bid => "e_info,
|
||||
Serum3Side::Ask => &base_info,
|
||||
let (payer_info, receiver_info) = &match self.side {
|
||||
Serum3Side::Bid => ("e_info, &base_info),
|
||||
Serum3Side::Ask => (&base_info, "e_info),
|
||||
};
|
||||
|
||||
let receiver_active_index = account
|
||||
.active_token_positions()
|
||||
.position(|tp| tp.token_index == receiver_info.token_index)
|
||||
.unwrap();
|
||||
health_check_metas[receiver_active_index].is_writable = true;
|
||||
|
||||
let accounts = Self::Accounts {
|
||||
group: account.fixed.group,
|
||||
account: self.account,
|
||||
|
|
|
@ -1849,6 +1849,32 @@ export class MangoClient {
|
|||
orderType: Serum3OrderType,
|
||||
clientOrderId: number,
|
||||
limit: number,
|
||||
): Promise<TransactionInstruction[]> {
|
||||
return await this.serum3PlaceOrderV2Ix(
|
||||
group,
|
||||
mangoAccount,
|
||||
externalMarketPk,
|
||||
side,
|
||||
price,
|
||||
size,
|
||||
selfTradeBehavior,
|
||||
orderType,
|
||||
clientOrderId,
|
||||
limit,
|
||||
);
|
||||
}
|
||||
|
||||
public async serum3PlaceOrderV1Ix(
|
||||
group: Group,
|
||||
mangoAccount: MangoAccount,
|
||||
externalMarketPk: PublicKey,
|
||||
side: Serum3Side,
|
||||
price: number,
|
||||
size: number,
|
||||
selfTradeBehavior: Serum3SelfTradeBehavior,
|
||||
orderType: Serum3OrderType,
|
||||
clientOrderId: number,
|
||||
limit: number,
|
||||
): Promise<TransactionInstruction[]> {
|
||||
const ixs: TransactionInstruction[] = [];
|
||||
const serum3Market = group.serum3MarketsMapByExternal.get(
|
||||
|
@ -1962,6 +1988,143 @@ export class MangoClient {
|
|||
return ixs;
|
||||
}
|
||||
|
||||
public async serum3PlaceOrderV2Ix(
|
||||
group: Group,
|
||||
mangoAccount: MangoAccount,
|
||||
externalMarketPk: PublicKey,
|
||||
side: Serum3Side,
|
||||
price: number,
|
||||
size: number,
|
||||
selfTradeBehavior: Serum3SelfTradeBehavior,
|
||||
orderType: Serum3OrderType,
|
||||
clientOrderId: number,
|
||||
limit: number,
|
||||
): Promise<TransactionInstruction[]> {
|
||||
const ixs: TransactionInstruction[] = [];
|
||||
const serum3Market = group.serum3MarketsMapByExternal.get(
|
||||
externalMarketPk.toBase58(),
|
||||
)!;
|
||||
|
||||
let openOrderPk: PublicKey | undefined = undefined;
|
||||
const banks: Bank[] = [];
|
||||
const openOrdersForMarket: [Serum3Market, PublicKey][] = [];
|
||||
if (!mangoAccount.getSerum3Account(serum3Market.marketIndex)) {
|
||||
const ix = await this.serum3CreateOpenOrdersIx(
|
||||
group,
|
||||
mangoAccount,
|
||||
serum3Market.serumMarketExternal,
|
||||
);
|
||||
ixs.push(ix);
|
||||
openOrderPk = await serum3Market.findOoPda(
|
||||
this.program.programId,
|
||||
mangoAccount.publicKey,
|
||||
);
|
||||
openOrdersForMarket.push([serum3Market, openOrderPk]);
|
||||
const baseTokenIndex = serum3Market.baseTokenIndex;
|
||||
const quoteTokenIndex = serum3Market.quoteTokenIndex;
|
||||
// only include banks if no deposit has been previously made for same token
|
||||
banks.push(group.getFirstBankByTokenIndex(quoteTokenIndex));
|
||||
banks.push(group.getFirstBankByTokenIndex(baseTokenIndex));
|
||||
}
|
||||
|
||||
const healthRemainingAccounts: PublicKey[] =
|
||||
this.buildHealthRemainingAccounts(
|
||||
group,
|
||||
[mangoAccount],
|
||||
banks,
|
||||
[],
|
||||
openOrdersForMarket,
|
||||
);
|
||||
|
||||
const serum3MarketExternal = group.serum3ExternalMarketsMap.get(
|
||||
externalMarketPk.toBase58(),
|
||||
)!;
|
||||
const serum3MarketExternalVaultSigner =
|
||||
await generateSerum3MarketExternalVaultSignerAddress(
|
||||
this.cluster,
|
||||
serum3Market,
|
||||
serum3MarketExternal,
|
||||
);
|
||||
|
||||
const limitPrice = serum3MarketExternal.priceNumberToLots(price);
|
||||
const maxBaseQuantity = serum3MarketExternal.baseSizeNumberToLots(size);
|
||||
const isTaker = orderType !== Serum3OrderType.postOnly;
|
||||
const maxQuoteQuantity = new BN(
|
||||
Math.ceil(
|
||||
serum3MarketExternal.decoded.quoteLotSize.toNumber() *
|
||||
(1 + Math.max(serum3Market.getFeeRates(isTaker), 0)) *
|
||||
serum3MarketExternal.baseSizeNumberToLots(size).toNumber() *
|
||||
serum3MarketExternal.priceNumberToLots(price).toNumber(),
|
||||
),
|
||||
);
|
||||
|
||||
const payerTokenIndex = ((): TokenIndex => {
|
||||
if (side == Serum3Side.bid) {
|
||||
return serum3Market.quoteTokenIndex;
|
||||
} else {
|
||||
return serum3Market.baseTokenIndex;
|
||||
}
|
||||
})();
|
||||
|
||||
const receiverTokenIndex = ((): TokenIndex => {
|
||||
if (side == Serum3Side.bid) {
|
||||
return serum3Market.baseTokenIndex;
|
||||
} else {
|
||||
return serum3Market.quoteTokenIndex;
|
||||
}
|
||||
})();
|
||||
|
||||
const payerBank = group.getFirstBankByTokenIndex(payerTokenIndex);
|
||||
const receiverBank = group.getFirstBankByTokenIndex(receiverTokenIndex);
|
||||
const ix = await this.program.methods
|
||||
.serum3PlaceOrderV2(
|
||||
side,
|
||||
limitPrice,
|
||||
maxBaseQuantity,
|
||||
maxQuoteQuantity,
|
||||
selfTradeBehavior,
|
||||
orderType,
|
||||
new BN(clientOrderId),
|
||||
limit,
|
||||
)
|
||||
.accounts({
|
||||
group: group.publicKey,
|
||||
account: mangoAccount.publicKey,
|
||||
owner: (this.program.provider as AnchorProvider).wallet.publicKey,
|
||||
openOrders:
|
||||
openOrderPk ||
|
||||
mangoAccount.getSerum3Account(serum3Market.marketIndex)?.openOrders,
|
||||
serumMarket: serum3Market.publicKey,
|
||||
serumProgram: OPENBOOK_PROGRAM_ID[this.cluster],
|
||||
serumMarketExternal: serum3Market.serumMarketExternal,
|
||||
marketBids: serum3MarketExternal.bidsAddress,
|
||||
marketAsks: serum3MarketExternal.asksAddress,
|
||||
marketEventQueue: serum3MarketExternal.decoded.eventQueue,
|
||||
marketRequestQueue: serum3MarketExternal.decoded.requestQueue,
|
||||
marketBaseVault: serum3MarketExternal.decoded.baseVault,
|
||||
marketQuoteVault: serum3MarketExternal.decoded.quoteVault,
|
||||
marketVaultSigner: serum3MarketExternalVaultSigner,
|
||||
payerBank: payerBank.publicKey,
|
||||
payerVault: payerBank.vault,
|
||||
payerOracle: payerBank.oracle,
|
||||
})
|
||||
.remainingAccounts(
|
||||
healthRemainingAccounts.map(
|
||||
(pk) =>
|
||||
({
|
||||
pubkey: pk,
|
||||
isWritable: receiverBank.publicKey.equals(pk) ? true : false,
|
||||
isSigner: false,
|
||||
} as AccountMeta),
|
||||
),
|
||||
)
|
||||
.instruction();
|
||||
|
||||
ixs.push(ix);
|
||||
|
||||
return ixs;
|
||||
}
|
||||
|
||||
public async serum3PlaceOrder(
|
||||
group: Group,
|
||||
mangoAccount: MangoAccount,
|
||||
|
@ -1974,7 +2137,7 @@ export class MangoClient {
|
|||
clientOrderId: number,
|
||||
limit: number,
|
||||
): Promise<MangoSignatureStatus> {
|
||||
const placeOrderIxs = await this.serum3PlaceOrderIx(
|
||||
const placeOrderIxs = await this.serum3PlaceOrderV2Ix(
|
||||
group,
|
||||
mangoAccount,
|
||||
externalMarketPk,
|
||||
|
@ -4520,7 +4683,7 @@ export class MangoClient {
|
|||
orderId,
|
||||
),
|
||||
this.serum3SettleFundsV2Ix(group, mangoAccount, externalMarketPk),
|
||||
this.serum3PlaceOrderIx(
|
||||
this.serum3PlaceOrderV2Ix(
|
||||
group,
|
||||
mangoAccount,
|
||||
externalMarketPk,
|
||||
|
|
|
@ -282,6 +282,7 @@ export interface IxGateParams {
|
|||
TokenConditionalSwapStart: boolean;
|
||||
TokenConditionalSwapCreatePremiumAuction: boolean;
|
||||
TokenConditionalSwapCreateLinearAuction: boolean;
|
||||
Serum3PlaceOrderV2: boolean;
|
||||
}
|
||||
|
||||
// Default with all ixs enabled, use with buildIxGate
|
||||
|
@ -360,6 +361,7 @@ export const TrueIxGateParams: IxGateParams = {
|
|||
TokenConditionalSwapStart: true,
|
||||
TokenConditionalSwapCreatePremiumAuction: true,
|
||||
TokenConditionalSwapCreateLinearAuction: true,
|
||||
Serum3PlaceOrderV2: true,
|
||||
};
|
||||
|
||||
// build ix gate e.g. buildIxGate(Builder(TrueIxGateParams).TokenDeposit(false).build()).toNumber(),
|
||||
|
@ -448,6 +450,7 @@ export function buildIxGate(p: IxGateParams): BN {
|
|||
toggleIx(ixGate, p, 'TokenConditionalSwapStart', 68);
|
||||
toggleIx(ixGate, p, 'TokenConditionalSwapCreatePremiumAuction', 69);
|
||||
toggleIx(ixGate, p, 'TokenConditionalSwapCreateLinearAuction', 70);
|
||||
toggleIx(ixGate, p, 'Serum3PlaceOrderV2', 71);
|
||||
|
||||
return ixGate;
|
||||
}
|
||||
|
|
|
@ -2740,6 +2740,161 @@ export type MangoV4 = {
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "serum3PlaceOrderV2",
|
||||
"accounts": [
|
||||
{
|
||||
"name": "group",
|
||||
"isMut": false,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "account",
|
||||
"isMut": true,
|
||||
"isSigner": false,
|
||||
"relations": [
|
||||
"group"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "owner",
|
||||
"isMut": false,
|
||||
"isSigner": true
|
||||
},
|
||||
{
|
||||
"name": "openOrders",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "serumMarket",
|
||||
"isMut": false,
|
||||
"isSigner": false,
|
||||
"relations": [
|
||||
"group",
|
||||
"serum_program",
|
||||
"serum_market_external"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "serumProgram",
|
||||
"isMut": false,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "serumMarketExternal",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "marketBids",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "marketAsks",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "marketEventQueue",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "marketRequestQueue",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "marketBaseVault",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "marketQuoteVault",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "marketVaultSigner",
|
||||
"isMut": false,
|
||||
"isSigner": false,
|
||||
"docs": [
|
||||
"needed for the automatic settle_funds call"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "payerBank",
|
||||
"isMut": true,
|
||||
"isSigner": false,
|
||||
"docs": [
|
||||
"The bank that pays for the order, if necessary"
|
||||
],
|
||||
"relations": [
|
||||
"group"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "payerVault",
|
||||
"isMut": true,
|
||||
"isSigner": false,
|
||||
"docs": [
|
||||
"The bank vault that pays for the order, if necessary"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "payerOracle",
|
||||
"isMut": false,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "tokenProgram",
|
||||
"isMut": false,
|
||||
"isSigner": false
|
||||
}
|
||||
],
|
||||
"args": [
|
||||
{
|
||||
"name": "side",
|
||||
"type": {
|
||||
"defined": "Serum3Side"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "limitPrice",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "maxBaseQty",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "maxNativeQuoteQtyIncludingFees",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "selfTradeBehavior",
|
||||
"type": {
|
||||
"defined": "Serum3SelfTradeBehavior"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "orderType",
|
||||
"type": {
|
||||
"defined": "Serum3OrderType"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "clientOrderId",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "limit",
|
||||
"type": "u16"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "serum3CancelOrder",
|
||||
"accounts": [
|
||||
|
@ -7108,7 +7263,13 @@ export type MangoV4 = {
|
|||
"type": "f64"
|
||||
},
|
||||
{
|
||||
"name": "depositsInSerum",
|
||||
"name": "potentialSerumTokens",
|
||||
"docs": [
|
||||
"Largest amount of tokens that might be added the the bank based on",
|
||||
"serum open order execution.",
|
||||
"",
|
||||
"Can be negative with multiple banks, then it'd need to be balanced in the keeper."
|
||||
],
|
||||
"type": "i64"
|
||||
},
|
||||
{
|
||||
|
@ -8680,26 +8841,41 @@ export type MangoV4 = {
|
|||
"type": "f64"
|
||||
},
|
||||
{
|
||||
"name": "baseDepositsReserved",
|
||||
"name": "potentialBaseTokens",
|
||||
"docs": [
|
||||
"Tracks the amount of deposits that flowed into the serum open orders account.",
|
||||
"An overestimate of the amount of tokens that might flow out of the open orders account.",
|
||||
"",
|
||||
"The bank still considers these amounts user deposits (see deposits_in_serum)",
|
||||
"and they need to be deducted from there when they flow back into the bank",
|
||||
"as real tokens."
|
||||
"The bank still considers these amounts user deposits (see Bank::potential_serum_tokens)",
|
||||
"and that value needs to be updated in conjunction with these numbers.",
|
||||
"",
|
||||
"This estimation is based on the amount of tokens in the open orders account",
|
||||
"(see update_bank_potential_tokens() in serum3_place_order and settle)"
|
||||
],
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "quoteDepositsReserved",
|
||||
"name": "potentialQuoteTokens",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "lowestPlacedBidInv",
|
||||
"docs": [
|
||||
"Track lowest bid/highest ask, same way as for highest bid/lowest ask.",
|
||||
"",
|
||||
"0 is a special \"unset\" state."
|
||||
],
|
||||
"type": "f64"
|
||||
},
|
||||
{
|
||||
"name": "highestPlacedAsk",
|
||||
"type": "f64"
|
||||
},
|
||||
{
|
||||
"name": "reserved",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
32
|
||||
16
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -10362,6 +10538,9 @@ export type MangoV4 = {
|
|||
},
|
||||
{
|
||||
"name": "TokenConditionalSwapCreateLinearAuction"
|
||||
},
|
||||
{
|
||||
"name": "Serum3PlaceOrderV2"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -13283,6 +13462,11 @@ export type MangoV4 = {
|
|||
"code": 6059,
|
||||
"name": "TokenConditionalSwapTypeNotStartable",
|
||||
"msg": "token conditional swap type cannot be started"
|
||||
},
|
||||
{
|
||||
"code": 6060,
|
||||
"name": "HealthAccountBankNotWritable",
|
||||
"msg": "a bank in the health account list should be writable but is not"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
@ -16029,6 +16213,161 @@ export const IDL: MangoV4 = {
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "serum3PlaceOrderV2",
|
||||
"accounts": [
|
||||
{
|
||||
"name": "group",
|
||||
"isMut": false,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "account",
|
||||
"isMut": true,
|
||||
"isSigner": false,
|
||||
"relations": [
|
||||
"group"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "owner",
|
||||
"isMut": false,
|
||||
"isSigner": true
|
||||
},
|
||||
{
|
||||
"name": "openOrders",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "serumMarket",
|
||||
"isMut": false,
|
||||
"isSigner": false,
|
||||
"relations": [
|
||||
"group",
|
||||
"serum_program",
|
||||
"serum_market_external"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "serumProgram",
|
||||
"isMut": false,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "serumMarketExternal",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "marketBids",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "marketAsks",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "marketEventQueue",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "marketRequestQueue",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "marketBaseVault",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "marketQuoteVault",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "marketVaultSigner",
|
||||
"isMut": false,
|
||||
"isSigner": false,
|
||||
"docs": [
|
||||
"needed for the automatic settle_funds call"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "payerBank",
|
||||
"isMut": true,
|
||||
"isSigner": false,
|
||||
"docs": [
|
||||
"The bank that pays for the order, if necessary"
|
||||
],
|
||||
"relations": [
|
||||
"group"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "payerVault",
|
||||
"isMut": true,
|
||||
"isSigner": false,
|
||||
"docs": [
|
||||
"The bank vault that pays for the order, if necessary"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "payerOracle",
|
||||
"isMut": false,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "tokenProgram",
|
||||
"isMut": false,
|
||||
"isSigner": false
|
||||
}
|
||||
],
|
||||
"args": [
|
||||
{
|
||||
"name": "side",
|
||||
"type": {
|
||||
"defined": "Serum3Side"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "limitPrice",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "maxBaseQty",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "maxNativeQuoteQtyIncludingFees",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "selfTradeBehavior",
|
||||
"type": {
|
||||
"defined": "Serum3SelfTradeBehavior"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "orderType",
|
||||
"type": {
|
||||
"defined": "Serum3OrderType"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "clientOrderId",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "limit",
|
||||
"type": "u16"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "serum3CancelOrder",
|
||||
"accounts": [
|
||||
|
@ -20397,7 +20736,13 @@ export const IDL: MangoV4 = {
|
|||
"type": "f64"
|
||||
},
|
||||
{
|
||||
"name": "depositsInSerum",
|
||||
"name": "potentialSerumTokens",
|
||||
"docs": [
|
||||
"Largest amount of tokens that might be added the the bank based on",
|
||||
"serum open order execution.",
|
||||
"",
|
||||
"Can be negative with multiple banks, then it'd need to be balanced in the keeper."
|
||||
],
|
||||
"type": "i64"
|
||||
},
|
||||
{
|
||||
|
@ -21969,26 +22314,41 @@ export const IDL: MangoV4 = {
|
|||
"type": "f64"
|
||||
},
|
||||
{
|
||||
"name": "baseDepositsReserved",
|
||||
"name": "potentialBaseTokens",
|
||||
"docs": [
|
||||
"Tracks the amount of deposits that flowed into the serum open orders account.",
|
||||
"An overestimate of the amount of tokens that might flow out of the open orders account.",
|
||||
"",
|
||||
"The bank still considers these amounts user deposits (see deposits_in_serum)",
|
||||
"and they need to be deducted from there when they flow back into the bank",
|
||||
"as real tokens."
|
||||
"The bank still considers these amounts user deposits (see Bank::potential_serum_tokens)",
|
||||
"and that value needs to be updated in conjunction with these numbers.",
|
||||
"",
|
||||
"This estimation is based on the amount of tokens in the open orders account",
|
||||
"(see update_bank_potential_tokens() in serum3_place_order and settle)"
|
||||
],
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "quoteDepositsReserved",
|
||||
"name": "potentialQuoteTokens",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "lowestPlacedBidInv",
|
||||
"docs": [
|
||||
"Track lowest bid/highest ask, same way as for highest bid/lowest ask.",
|
||||
"",
|
||||
"0 is a special \"unset\" state."
|
||||
],
|
||||
"type": "f64"
|
||||
},
|
||||
{
|
||||
"name": "highestPlacedAsk",
|
||||
"type": "f64"
|
||||
},
|
||||
{
|
||||
"name": "reserved",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
32
|
||||
16
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -23651,6 +24011,9 @@ export const IDL: MangoV4 = {
|
|||
},
|
||||
{
|
||||
"name": "TokenConditionalSwapCreateLinearAuction"
|
||||
},
|
||||
{
|
||||
"name": "Serum3PlaceOrderV2"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -26572,6 +26935,11 @@ export const IDL: MangoV4 = {
|
|||
"code": 6059,
|
||||
"name": "TokenConditionalSwapTypeNotStartable",
|
||||
"msg": "token conditional swap type cannot be started"
|
||||
},
|
||||
{
|
||||
"code": 6060,
|
||||
"name": "HealthAccountBankNotWritable",
|
||||
"msg": "a bank in the health account list should be writable but is not"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue