OpenBook place order: respect bank reduce_only flags (#569)
This commit is contained in:
parent
6f47ad92d6
commit
0da1b6728b
|
@ -149,6 +149,32 @@ pub struct Serum3Info {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Serum3Info {
|
impl Serum3Info {
|
||||||
|
#[inline(always)]
|
||||||
|
fn all_reserved_as_base(
|
||||||
|
&self,
|
||||||
|
health_type: HealthType,
|
||||||
|
quote_info: &TokenInfo,
|
||||||
|
base_info: &TokenInfo,
|
||||||
|
) -> I80F48 {
|
||||||
|
let quote_asset = quote_info.prices.asset(health_type);
|
||||||
|
let base_liab = base_info.prices.liab(health_type);
|
||||||
|
// OPTIMIZATION: These divisions can be extremely expensive (up to 5k CU each)
|
||||||
|
self.reserved_base + self.reserved_quote * quote_asset / base_liab
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn all_reserved_as_quote(
|
||||||
|
&self,
|
||||||
|
health_type: HealthType,
|
||||||
|
quote_info: &TokenInfo,
|
||||||
|
base_info: &TokenInfo,
|
||||||
|
) -> I80F48 {
|
||||||
|
let base_asset = base_info.prices.asset(health_type);
|
||||||
|
let quote_liab = quote_info.prices.liab(health_type);
|
||||||
|
// OPTIMIZATION: These divisions can be extremely expensive (up to 5k CU each)
|
||||||
|
self.reserved_quote + self.reserved_base * base_asset / quote_liab
|
||||||
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn health_contribution(
|
fn health_contribution(
|
||||||
&self,
|
&self,
|
||||||
|
@ -600,23 +626,15 @@ impl HealthCache {
|
||||||
let mut serum3_reserved = Vec::with_capacity(self.serum3_infos.len());
|
let mut serum3_reserved = Vec::with_capacity(self.serum3_infos.len());
|
||||||
|
|
||||||
for info in self.serum3_infos.iter() {
|
for info in self.serum3_infos.iter() {
|
||||||
let quote = &self.token_infos[info.quote_index];
|
let quote_info = &self.token_infos[info.quote_index];
|
||||||
let base = &self.token_infos[info.base_index];
|
let base_info = &self.token_infos[info.base_index];
|
||||||
|
|
||||||
let reserved_base = info.reserved_base;
|
let all_reserved_as_base =
|
||||||
let reserved_quote = info.reserved_quote;
|
info.all_reserved_as_base(health_type, quote_info, base_info);
|
||||||
|
let all_reserved_as_quote =
|
||||||
let quote_asset = quote.prices.asset(health_type);
|
info.all_reserved_as_quote(health_type, quote_info, base_info);
|
||||||
let base_liab = base.prices.liab(health_type);
|
|
||||||
// OPTIMIZATION: These divisions can be extremely expensive (up to 5k CU each)
|
|
||||||
let all_reserved_as_base = reserved_base + reserved_quote * quote_asset / base_liab;
|
|
||||||
|
|
||||||
let base_asset = base.prices.asset(health_type);
|
|
||||||
let quote_liab = quote.prices.liab(health_type);
|
|
||||||
let all_reserved_as_quote = reserved_quote + reserved_base * base_asset / quote_liab;
|
|
||||||
|
|
||||||
let base_max_reserved = &mut token_max_reserved[info.base_index];
|
let base_max_reserved = &mut token_max_reserved[info.base_index];
|
||||||
// note: () does not work with mutable references
|
|
||||||
*base_max_reserved += all_reserved_as_base;
|
*base_max_reserved += all_reserved_as_base;
|
||||||
let quote_max_reserved = &mut token_max_reserved[info.quote_index];
|
let quote_max_reserved = &mut token_max_reserved[info.quote_index];
|
||||||
*quote_max_reserved += all_reserved_as_quote;
|
*quote_max_reserved += all_reserved_as_quote;
|
||||||
|
@ -688,6 +706,36 @@ impl HealthCache {
|
||||||
}
|
}
|
||||||
health
|
health
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn total_serum3_potential(
|
||||||
|
&self,
|
||||||
|
health_type: HealthType,
|
||||||
|
token_index: TokenIndex,
|
||||||
|
) -> Result<I80F48> {
|
||||||
|
let target_token_info_index = self.token_info_index(token_index)?;
|
||||||
|
let total_reserved = self
|
||||||
|
.serum3_infos
|
||||||
|
.iter()
|
||||||
|
.filter_map(|info| {
|
||||||
|
if info.quote_index == target_token_info_index {
|
||||||
|
Some(info.all_reserved_as_quote(
|
||||||
|
health_type,
|
||||||
|
&self.token_infos[info.quote_index],
|
||||||
|
&self.token_infos[info.base_index],
|
||||||
|
))
|
||||||
|
} else if info.base_index == target_token_info_index {
|
||||||
|
Some(info.all_reserved_as_base(
|
||||||
|
health_type,
|
||||||
|
&self.token_infos[info.quote_index],
|
||||||
|
&self.token_infos[info.base_index],
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.sum();
|
||||||
|
Ok(total_reserved)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn find_token_info_index(infos: &[TokenInfo], token_index: TokenIndex) -> Result<usize> {
|
pub(crate) fn find_token_info_index(infos: &[TokenInfo], token_index: TokenIndex) -> Result<usize> {
|
||||||
|
|
|
@ -113,6 +113,7 @@ pub fn serum3_place_order(
|
||||||
//
|
//
|
||||||
// Validation
|
// Validation
|
||||||
//
|
//
|
||||||
|
let receiver_token_index;
|
||||||
{
|
{
|
||||||
let account = ctx.accounts.account.load_full()?;
|
let account = ctx.accounts.account.load_full()?;
|
||||||
// account constraint #1
|
// account constraint #1
|
||||||
|
@ -138,23 +139,40 @@ pub fn serum3_place_order(
|
||||||
Serum3Side::Ask => serum_market.base_token_index,
|
Serum3Side::Ask => serum_market.base_token_index,
|
||||||
};
|
};
|
||||||
require_eq!(payer_bank.token_index, payer_token_index);
|
require_eq!(payer_bank.token_index, payer_token_index);
|
||||||
|
|
||||||
|
receiver_token_index = match side {
|
||||||
|
Serum3Side::Bid => serum_market.base_token_index,
|
||||||
|
Serum3Side::Ask => serum_market.quote_token_index,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Pre-health computation
|
// Pre-health computation
|
||||||
//
|
//
|
||||||
let mut account = ctx.accounts.account.load_full_mut()?;
|
let mut account = ctx.accounts.account.load_full_mut()?;
|
||||||
|
let retriever = new_fixed_order_account_retriever(ctx.remaining_accounts, &account.borrow())?;
|
||||||
|
let mut health_cache =
|
||||||
|
new_health_cache(&account.borrow(), &retriever).context("pre-withdraw init health")?;
|
||||||
let pre_health_opt = if !account.fixed.is_in_health_region() {
|
let pre_health_opt = if !account.fixed.is_in_health_region() {
|
||||||
let retriever =
|
|
||||||
new_fixed_order_account_retriever(ctx.remaining_accounts, &account.borrow())?;
|
|
||||||
let health_cache =
|
|
||||||
new_health_cache(&account.borrow(), &retriever).context("pre-withdraw init health")?;
|
|
||||||
let pre_init_health = account.check_health_pre(&health_cache)?;
|
let pre_init_health = account.check_health_pre(&health_cache)?;
|
||||||
Some((health_cache, pre_init_health))
|
Some(pre_init_health)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Check if the bank for the token whose balance is increased is in reduce-only mode
|
||||||
|
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()
|
||||||
|
};
|
||||||
|
|
||||||
|
drop(retriever);
|
||||||
|
|
||||||
//
|
//
|
||||||
// Before-order tracking
|
// Before-order tracking
|
||||||
//
|
//
|
||||||
|
@ -212,30 +230,29 @@ pub fn serum3_place_order(
|
||||||
};
|
};
|
||||||
cpi_place_order(ctx.accounts, order)?;
|
cpi_place_order(ctx.accounts, order)?;
|
||||||
|
|
||||||
let oo_difference = {
|
|
||||||
let oo_ai = &ctx.accounts.open_orders.as_ref();
|
|
||||||
let open_orders = load_open_orders_ref(oo_ai)?;
|
|
||||||
let after_oo = OpenOrdersSlim::from_oo(&open_orders);
|
|
||||||
|
|
||||||
emit!(Serum3OpenOrdersBalanceLogV2 {
|
|
||||||
mango_group: ctx.accounts.group.key(),
|
|
||||||
mango_account: ctx.accounts.account.key(),
|
|
||||||
market_index: serum_market.market_index,
|
|
||||||
base_token_index: serum_market.base_token_index,
|
|
||||||
quote_token_index: serum_market.quote_token_index,
|
|
||||||
base_total: after_oo.native_coin_total,
|
|
||||||
base_free: after_oo.native_coin_free,
|
|
||||||
quote_total: after_oo.native_pc_total,
|
|
||||||
quote_free: after_oo.native_pc_free,
|
|
||||||
referrer_rebates_accrued: after_oo.referrer_rebates_accrued,
|
|
||||||
});
|
|
||||||
|
|
||||||
OODifference::new(&before_oo, &after_oo)
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// After-order tracking
|
// After-order tracking
|
||||||
//
|
//
|
||||||
|
let after_oo = {
|
||||||
|
let oo_ai = &ctx.accounts.open_orders.as_ref();
|
||||||
|
let open_orders = load_open_orders_ref(oo_ai)?;
|
||||||
|
OpenOrdersSlim::from_oo(&open_orders)
|
||||||
|
};
|
||||||
|
let oo_difference = OODifference::new(&before_oo, &after_oo);
|
||||||
|
|
||||||
|
emit!(Serum3OpenOrdersBalanceLogV2 {
|
||||||
|
mango_group: ctx.accounts.group.key(),
|
||||||
|
mango_account: ctx.accounts.account.key(),
|
||||||
|
market_index: serum_market.market_index,
|
||||||
|
base_token_index: serum_market.base_token_index,
|
||||||
|
quote_token_index: serum_market.quote_token_index,
|
||||||
|
base_total: after_oo.native_coin_total,
|
||||||
|
base_free: after_oo.native_coin_free,
|
||||||
|
quote_total: after_oo.native_pc_total,
|
||||||
|
quote_free: after_oo.native_pc_free,
|
||||||
|
referrer_rebates_accrued: after_oo.referrer_rebates_accrued,
|
||||||
|
});
|
||||||
|
|
||||||
ctx.accounts.payer_vault.reload()?;
|
ctx.accounts.payer_vault.reload()?;
|
||||||
let after_vault = ctx.accounts.payer_vault.amount;
|
let after_vault = ctx.accounts.payer_vault.amount;
|
||||||
|
|
||||||
|
@ -264,23 +281,45 @@ pub fn serum3_place_order(
|
||||||
};
|
};
|
||||||
|
|
||||||
if withdrawn_from_vault > position_native {
|
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 =
|
let oracle_price =
|
||||||
payer_bank.oracle_price(&AccountInfoRef::borrow(&ctx.accounts.payer_oracle)?, None)?;
|
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.enforce_min_vault_to_deposits_ratio((*ctx.accounts.payer_vault).as_ref())?;
|
||||||
payer_bank.check_net_borrows(oracle_price)?;
|
payer_bank.check_net_borrows(oracle_price)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vault_difference.adjust_health_cache_token_balance(&mut health_cache, &payer_bank)?;
|
||||||
|
oo_difference.adjust_health_cache_serum3_state(&mut health_cache, &serum_market)?;
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
if receiver_bank_reduce_only {
|
||||||
|
let balance = health_cache
|
||||||
|
.token_info(receiver_token_index)?
|
||||||
|
.balance_native;
|
||||||
|
let potential =
|
||||||
|
health_cache.total_serum3_potential(HealthType::Maint, receiver_token_index)?;
|
||||||
|
require_msg_typed!(
|
||||||
|
balance + potential < 1,
|
||||||
|
MangoError::TokenInReduceOnlyMode,
|
||||||
|
"receiver bank does not accept deposits"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Health check
|
// Health check
|
||||||
//
|
//
|
||||||
if let Some((mut health_cache, pre_init_health)) = pre_health_opt {
|
if let Some(pre_init_health) = pre_health_opt {
|
||||||
vault_difference.adjust_health_cache_token_balance(&mut health_cache, &payer_bank)?;
|
|
||||||
oo_difference.adjust_health_cache_serum3_state(&mut health_cache, &serum_market)?;
|
|
||||||
account.check_health_post(&health_cache, pre_init_health)?;
|
account.check_health_post(&health_cache, pre_init_health)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: enforce min_vault_to_deposits_ratio
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ use mango_v4::accounts_ix::{Serum3OrderType, Serum3SelfTradeBehavior, Serum3Side
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_health_wrap() -> Result<(), TransportError> {
|
async fn test_health_wrap() -> Result<(), TransportError> {
|
||||||
let mut test_builder = TestContextBuilder::new();
|
let mut test_builder = TestContextBuilder::new();
|
||||||
test_builder.test().set_compute_max_units(135000);
|
test_builder.test().set_compute_max_units(140000);
|
||||||
let context = test_builder.start_default().await;
|
let context = test_builder.start_default().await;
|
||||||
let solana = &context.solana.clone();
|
let solana = &context.solana.clone();
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
#![allow(dead_code)]
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use mango_v4::accounts_ix::{Serum3OrderType, Serum3SelfTradeBehavior, Serum3Side};
|
use mango_v4::accounts_ix::{Serum3OrderType, Serum3SelfTradeBehavior, Serum3Side};
|
||||||
|
@ -34,15 +35,25 @@ impl SerumOrderPlacer {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn bid(&mut self, limit_price: f64, max_base: u64) -> Option<(u128, u64)> {
|
async fn try_bid(
|
||||||
|
&mut self,
|
||||||
|
limit_price: f64,
|
||||||
|
max_base: u64,
|
||||||
|
taker: bool,
|
||||||
|
) -> Result<mango_v4::accounts::Serum3PlaceOrder, TransportError> {
|
||||||
let client_order_id = self.inc_client_order_id();
|
let client_order_id = self.inc_client_order_id();
|
||||||
|
let fees = if taker { 0.0004 } else { 0.0 };
|
||||||
send_tx(
|
send_tx(
|
||||||
&self.solana,
|
&self.solana,
|
||||||
Serum3PlaceOrderInstruction {
|
Serum3PlaceOrderInstruction {
|
||||||
side: Serum3Side::Bid,
|
side: Serum3Side::Bid,
|
||||||
limit_price: (limit_price * 100.0 / 10.0) as u64, // in quote_lot (10) per base lot (100)
|
limit_price: (limit_price * 100.0 / 10.0) as u64, // in quote_lot (10) per base lot (100)
|
||||||
max_base_qty: max_base / 100, // in base lot (100)
|
max_base_qty: max_base / 100, // in base lot (100)
|
||||||
max_native_quote_qty_including_fees: (limit_price * (max_base as f64)) as u64,
|
// 4 bps taker fees added in
|
||||||
|
max_native_quote_qty_including_fees: (limit_price
|
||||||
|
* (max_base as f64)
|
||||||
|
* (1.0 + fees))
|
||||||
|
.ceil() as u64,
|
||||||
self_trade_behavior: Serum3SelfTradeBehavior::AbortTransaction,
|
self_trade_behavior: Serum3SelfTradeBehavior::AbortTransaction,
|
||||||
order_type: Serum3OrderType::Limit,
|
order_type: Serum3OrderType::Limit,
|
||||||
client_order_id,
|
client_order_id,
|
||||||
|
@ -53,12 +64,25 @@ impl SerumOrderPlacer {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
}
|
||||||
self.find_order_id_for_client_order_id(client_order_id)
|
|
||||||
|
async fn bid_maker(&mut self, limit_price: f64, max_base: u64) -> Option<(u128, u64)> {
|
||||||
|
self.try_bid(limit_price, max_base, false).await.unwrap();
|
||||||
|
self.find_order_id_for_client_order_id(self.next_client_order_id - 1)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn ask(&mut self, limit_price: f64, max_base: u64) -> Option<(u128, u64)> {
|
async fn bid_taker(&mut self, limit_price: f64, max_base: u64) -> Option<(u128, u64)> {
|
||||||
|
self.try_bid(limit_price, max_base, true).await.unwrap();
|
||||||
|
self.find_order_id_for_client_order_id(self.next_client_order_id - 1)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn try_ask(
|
||||||
|
&mut self,
|
||||||
|
limit_price: f64,
|
||||||
|
max_base: u64,
|
||||||
|
) -> Result<mango_v4::accounts::Serum3PlaceOrder, TransportError> {
|
||||||
let client_order_id = self.inc_client_order_id();
|
let client_order_id = self.inc_client_order_id();
|
||||||
send_tx(
|
send_tx(
|
||||||
&self.solana,
|
&self.solana,
|
||||||
|
@ -77,8 +101,11 @@ impl SerumOrderPlacer {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
}
|
||||||
self.find_order_id_for_client_order_id(client_order_id)
|
|
||||||
|
async fn ask(&mut self, limit_price: f64, max_base: u64) -> Option<(u128, u64)> {
|
||||||
|
self.try_ask(limit_price, max_base).await.unwrap();
|
||||||
|
self.find_order_id_for_client_order_id(self.next_client_order_id - 1)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -262,7 +289,7 @@ async fn test_serum_basics() -> Result<(), TransportError> {
|
||||||
//
|
//
|
||||||
// TEST: Place an order
|
// TEST: Place an order
|
||||||
//
|
//
|
||||||
let (order_id, _) = order_placer.bid(1.0, 100).await.unwrap();
|
let (order_id, _) = order_placer.bid_maker(1.0, 100).await.unwrap();
|
||||||
check_prev_instruction_post_health(&solana, account).await;
|
check_prev_instruction_post_health(&solana, account).await;
|
||||||
|
|
||||||
let native0 = account_position(solana, account, base_token.bank).await;
|
let native0 = account_position(solana, account, base_token.bank).await;
|
||||||
|
@ -362,7 +389,7 @@ async fn test_serum_loan_origination_fees() -> Result<(), TransportError> {
|
||||||
// TEST: Placing and canceling an order does not take loan origination fees even if borrows are needed
|
// TEST: Placing and canceling an order does not take loan origination fees even if borrows are needed
|
||||||
//
|
//
|
||||||
{
|
{
|
||||||
let (bid_order_id, _) = order_placer.bid(1.0, 200000).await.unwrap();
|
let (bid_order_id, _) = order_placer.bid_maker(1.0, 200000).await.unwrap();
|
||||||
let (ask_order_id, _) = order_placer.ask(2.0, 200000).await.unwrap();
|
let (ask_order_id, _) = order_placer.ask(2.0, 200000).await.unwrap();
|
||||||
|
|
||||||
let o = order_placer.mango_serum_orders().await;
|
let o = order_placer.mango_serum_orders().await;
|
||||||
|
@ -377,7 +404,7 @@ async fn test_serum_loan_origination_fees() -> Result<(), TransportError> {
|
||||||
assert_eq!(o.quote_borrows_without_fee, 19999);
|
assert_eq!(o.quote_borrows_without_fee, 19999);
|
||||||
|
|
||||||
// placing new, slightly larger orders increases the borrow_without_fee amount only by a small amount
|
// placing new, slightly larger orders increases the borrow_without_fee amount only by a small amount
|
||||||
let (bid_order_id, _) = order_placer.bid(1.0, 210000).await.unwrap();
|
let (bid_order_id, _) = order_placer.bid_maker(1.0, 210000).await.unwrap();
|
||||||
let (ask_order_id, _) = order_placer.ask(2.0, 300000).await.unwrap();
|
let (ask_order_id, _) = order_placer.ask(2.0, 300000).await.unwrap();
|
||||||
|
|
||||||
let o = order_placer.mango_serum_orders().await;
|
let o = order_placer.mango_serum_orders().await;
|
||||||
|
@ -429,7 +456,10 @@ async fn test_serum_loan_origination_fees() -> Result<(), TransportError> {
|
||||||
.collected_fees_native;
|
.collected_fees_native;
|
||||||
|
|
||||||
// account2 has an order on the book
|
// account2 has an order on the book
|
||||||
order_placer2.bid(1.0, bid_amount as u64).await.unwrap();
|
order_placer2
|
||||||
|
.bid_maker(1.0, bid_amount as u64)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
// account takes
|
// account takes
|
||||||
order_placer.ask(1.0, ask_amount as u64).await.unwrap();
|
order_placer.ask(1.0, ask_amount as u64).await.unwrap();
|
||||||
|
@ -566,7 +596,7 @@ async fn test_serum_settle_v1() -> Result<(), TransportError> {
|
||||||
let base2_start = account_position(solana, account2, base_bank).await;
|
let base2_start = account_position(solana, account2, base_bank).await;
|
||||||
|
|
||||||
// account2 has an order on the book, account takes
|
// account2 has an order on the book, account takes
|
||||||
order_placer2.bid(1.0, amount as u64).await.unwrap();
|
order_placer2.bid_maker(1.0, amount as u64).await.unwrap();
|
||||||
order_placer.ask(1.0, amount as u64).await.unwrap();
|
order_placer.ask(1.0, amount as u64).await.unwrap();
|
||||||
|
|
||||||
context
|
context
|
||||||
|
@ -663,7 +693,7 @@ async fn test_serum_settle_v2_to_dao() -> Result<(), TransportError> {
|
||||||
let base2_start = account_position(solana, account2, base_bank).await;
|
let base2_start = account_position(solana, account2, base_bank).await;
|
||||||
|
|
||||||
// account2 has an order on the book, account takes
|
// account2 has an order on the book, account takes
|
||||||
order_placer2.bid(1.0, amount as u64).await.unwrap();
|
order_placer2.bid_maker(1.0, amount as u64).await.unwrap();
|
||||||
order_placer.ask(1.0, amount as u64).await.unwrap();
|
order_placer.ask(1.0, amount as u64).await.unwrap();
|
||||||
|
|
||||||
context
|
context
|
||||||
|
@ -756,7 +786,7 @@ async fn test_serum_settle_v2_to_account() -> Result<(), TransportError> {
|
||||||
let base2_start = account_position(solana, account2, base_bank).await;
|
let base2_start = account_position(solana, account2, base_bank).await;
|
||||||
|
|
||||||
// account2 has an order on the book, account takes
|
// account2 has an order on the book, account takes
|
||||||
order_placer2.bid(1.0, amount as u64).await.unwrap();
|
order_placer2.bid_maker(1.0, amount as u64).await.unwrap();
|
||||||
order_placer.ask(1.0, amount as u64).await.unwrap();
|
order_placer.ask(1.0, amount as u64).await.unwrap();
|
||||||
|
|
||||||
context
|
context
|
||||||
|
@ -805,6 +835,173 @@ async fn test_serum_settle_v2_to_account() -> Result<(), TransportError> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_serum_reduce_only_borrows() -> Result<(), TransportError> {
|
||||||
|
let mut test_builder = TestContextBuilder::new();
|
||||||
|
test_builder.test().set_compute_max_units(95_000); // Serum3PlaceOrder needs 92.8k
|
||||||
|
let context = test_builder.start_default().await;
|
||||||
|
let solana = &context.solana.clone();
|
||||||
|
|
||||||
|
//
|
||||||
|
// SETUP: Create a group, accounts, market etc
|
||||||
|
//
|
||||||
|
let deposit_amount = 1000;
|
||||||
|
let CommonSetup {
|
||||||
|
group_with_tokens,
|
||||||
|
base_token,
|
||||||
|
mut order_placer,
|
||||||
|
..
|
||||||
|
} = common_setup(&context, deposit_amount).await;
|
||||||
|
|
||||||
|
send_tx(
|
||||||
|
solana,
|
||||||
|
TokenMakeReduceOnly {
|
||||||
|
group: group_with_tokens.group,
|
||||||
|
admin: group_with_tokens.admin,
|
||||||
|
mint: base_token.mint.pubkey,
|
||||||
|
reduce_only: 2,
|
||||||
|
force_close: false,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
//
|
||||||
|
// TEST: Cannot borrow tokens when bank is reduce only
|
||||||
|
//
|
||||||
|
|
||||||
|
let err = order_placer.try_ask(1.0, 1100).await;
|
||||||
|
assert_mango_error(&err, MangoError::TokenInReduceOnlyMode.into(), "".into());
|
||||||
|
|
||||||
|
order_placer.try_ask(0.5, 500).await.unwrap();
|
||||||
|
|
||||||
|
let err = order_placer.try_ask(1.0, 600).await;
|
||||||
|
assert_mango_error(&err, MangoError::TokenInReduceOnlyMode.into(), "".into());
|
||||||
|
|
||||||
|
order_placer.try_ask(2.0, 500).await.unwrap();
|
||||||
|
|
||||||
|
let err = order_placer.try_ask(1.0, 100).await;
|
||||||
|
assert_mango_error(&err, MangoError::TokenInReduceOnlyMode.into(), "".into());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_serum_reduce_only_deposits1() -> Result<(), TransportError> {
|
||||||
|
let mut test_builder = TestContextBuilder::new();
|
||||||
|
test_builder.test().set_compute_max_units(95_000); // Serum3PlaceOrder needs 92.8k
|
||||||
|
let context = test_builder.start_default().await;
|
||||||
|
let solana = &context.solana.clone();
|
||||||
|
|
||||||
|
//
|
||||||
|
// SETUP: Create a group, accounts, market etc
|
||||||
|
//
|
||||||
|
let deposit_amount = 1000;
|
||||||
|
let CommonSetup {
|
||||||
|
group_with_tokens,
|
||||||
|
base_token,
|
||||||
|
mut order_placer,
|
||||||
|
mut order_placer2,
|
||||||
|
..
|
||||||
|
} = common_setup(&context, deposit_amount).await;
|
||||||
|
|
||||||
|
send_tx(
|
||||||
|
solana,
|
||||||
|
TokenMakeReduceOnly {
|
||||||
|
group: group_with_tokens.group,
|
||||||
|
admin: group_with_tokens.admin,
|
||||||
|
mint: base_token.mint.pubkey,
|
||||||
|
reduce_only: 1,
|
||||||
|
force_close: false,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
//
|
||||||
|
// TEST: Cannot buy tokens when deposits are already >0
|
||||||
|
//
|
||||||
|
|
||||||
|
// fails to place on the book
|
||||||
|
let err = order_placer.try_bid(1.0, 1000, false).await;
|
||||||
|
assert_mango_error(&err, MangoError::TokenInReduceOnlyMode.into(), "".into());
|
||||||
|
|
||||||
|
// also fails as a taker order
|
||||||
|
order_placer2.ask(1.0, 500).await.unwrap();
|
||||||
|
let err = order_placer.try_bid(1.0, 100, true).await;
|
||||||
|
assert_mango_error(&err, MangoError::TokenInReduceOnlyMode.into(), "".into());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_serum_reduce_only_deposits2() -> Result<(), TransportError> {
|
||||||
|
let mut test_builder = TestContextBuilder::new();
|
||||||
|
test_builder.test().set_compute_max_units(95_000); // Serum3PlaceOrder needs 92.8k
|
||||||
|
let context = test_builder.start_default().await;
|
||||||
|
let solana = &context.solana.clone();
|
||||||
|
|
||||||
|
//
|
||||||
|
// SETUP: Create a group, accounts, market etc
|
||||||
|
//
|
||||||
|
let deposit_amount = 1000;
|
||||||
|
let CommonSetup {
|
||||||
|
group_with_tokens,
|
||||||
|
base_token,
|
||||||
|
mut order_placer,
|
||||||
|
mut order_placer2,
|
||||||
|
..
|
||||||
|
} = common_setup(&context, deposit_amount).await;
|
||||||
|
|
||||||
|
// Give account some base token borrows (-500)
|
||||||
|
send_tx(
|
||||||
|
solana,
|
||||||
|
TokenWithdrawInstruction {
|
||||||
|
amount: 1500,
|
||||||
|
allow_borrow: true,
|
||||||
|
account: order_placer.account,
|
||||||
|
owner: order_placer.owner,
|
||||||
|
token_account: context.users[0].token_accounts[1],
|
||||||
|
bank_index: 0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
//
|
||||||
|
// TEST: Cannot buy tokens when deposits are already >0
|
||||||
|
//
|
||||||
|
send_tx(
|
||||||
|
solana,
|
||||||
|
TokenMakeReduceOnly {
|
||||||
|
group: group_with_tokens.group,
|
||||||
|
admin: group_with_tokens.admin,
|
||||||
|
mint: base_token.mint.pubkey,
|
||||||
|
reduce_only: 1,
|
||||||
|
force_close: false,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// cannot place a large order on the book that would deposit too much
|
||||||
|
let err = order_placer.try_bid(1.0, 600, false).await;
|
||||||
|
assert_mango_error(&err, MangoError::TokenInReduceOnlyMode.into(), "".into());
|
||||||
|
|
||||||
|
// a small order is fine
|
||||||
|
order_placer.try_bid(1.0, 100, false).await.unwrap();
|
||||||
|
|
||||||
|
// taking some is fine too
|
||||||
|
order_placer2.ask(1.0, 800).await.unwrap();
|
||||||
|
order_placer.try_bid(1.0, 100, true).await.unwrap();
|
||||||
|
|
||||||
|
// the limit for orders is reduced now, 100 received, 100 on the book
|
||||||
|
let err = order_placer.try_bid(1.0, 400, true).await;
|
||||||
|
assert_mango_error(&err, MangoError::TokenInReduceOnlyMode.into(), "".into());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
struct CommonSetup {
|
struct CommonSetup {
|
||||||
group_with_tokens: GroupWithTokens,
|
group_with_tokens: GroupWithTokens,
|
||||||
serum_market_cookie: SpotMarketCookie,
|
serum_market_cookie: SpotMarketCookie,
|
||||||
|
|
Loading…
Reference in New Issue