Merge pull request #223 from blockworks-foundation/ckamm/perp-liq
Perp liquidation: force cancel orders, liq base position
This commit is contained in:
commit
b579096264
|
@ -49,6 +49,8 @@ pub enum MangoError {
|
|||
PerpPositionDoesNotExist,
|
||||
#[msg("max settle amount must be greater than zero")]
|
||||
MaxSettleAmountMustBeGreaterThanZero,
|
||||
#[msg("the perp position has open orders or unprocessed fill events")]
|
||||
HasOpenPerpOrders,
|
||||
}
|
||||
|
||||
pub trait Contextable {
|
||||
|
|
|
@ -20,6 +20,8 @@ pub use perp_consume_events::*;
|
|||
pub use perp_create_market::*;
|
||||
pub use perp_deactivate_position::*;
|
||||
pub use perp_edit_market::*;
|
||||
pub use perp_liq_base_position::*;
|
||||
pub use perp_liq_force_cancel_orders::*;
|
||||
pub use perp_place_order::*;
|
||||
pub use perp_settle_fees::*;
|
||||
pub use perp_settle_pnl::*;
|
||||
|
@ -67,6 +69,8 @@ mod perp_consume_events;
|
|||
mod perp_create_market;
|
||||
mod perp_deactivate_position;
|
||||
mod perp_edit_market;
|
||||
mod perp_liq_base_position;
|
||||
mod perp_liq_force_cancel_orders;
|
||||
mod perp_place_order;
|
||||
mod perp_settle_fees;
|
||||
mod perp_settle_pnl;
|
||||
|
|
|
@ -0,0 +1,194 @@
|
|||
use anchor_lang::prelude::*;
|
||||
use checked_math as cm;
|
||||
use fixed::types::I80F48;
|
||||
|
||||
use crate::accounts_zerocopy::*;
|
||||
use crate::error::*;
|
||||
use crate::state::*;
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct PerpLiqBasePosition<'info> {
|
||||
pub group: AccountLoader<'info, Group>,
|
||||
|
||||
#[account(mut, has_one = group, has_one = oracle)]
|
||||
pub perp_market: AccountLoader<'info, PerpMarket>,
|
||||
|
||||
/// CHECK: Oracle can have different account types, constrained by address in perp_market
|
||||
pub oracle: UncheckedAccount<'info>,
|
||||
|
||||
#[account(
|
||||
mut,
|
||||
has_one = group
|
||||
// liqor_owner is checked at #1
|
||||
)]
|
||||
pub liqor: AccountLoaderDynamic<'info, MangoAccount>,
|
||||
pub liqor_owner: Signer<'info>,
|
||||
|
||||
#[account(mut, has_one = group)]
|
||||
pub liqee: AccountLoaderDynamic<'info, MangoAccount>,
|
||||
}
|
||||
|
||||
pub fn perp_liq_base_position(
|
||||
ctx: Context<PerpLiqBasePosition>,
|
||||
max_base_transfer: i64,
|
||||
) -> Result<()> {
|
||||
let group_pk = &ctx.accounts.group.key();
|
||||
|
||||
let mut liqor = ctx.accounts.liqor.load_mut()?;
|
||||
// account constraint #1
|
||||
require!(
|
||||
liqor
|
||||
.fixed
|
||||
.is_owner_or_delegate(ctx.accounts.liqor_owner.key()),
|
||||
MangoError::SomeError
|
||||
);
|
||||
require!(!liqor.fixed.being_liquidated(), MangoError::BeingLiquidated);
|
||||
|
||||
let mut liqee = ctx.accounts.liqee.load_mut()?;
|
||||
|
||||
// Initial liqee health check
|
||||
let mut liqee_health_cache = {
|
||||
let account_retriever = ScanningAccountRetriever::new(ctx.remaining_accounts, group_pk)
|
||||
.context("create account retriever")?;
|
||||
new_health_cache(&liqee.borrow(), &account_retriever)
|
||||
.context("create liqee health cache")?
|
||||
};
|
||||
let liqee_init_health = liqee_health_cache.health(HealthType::Init);
|
||||
|
||||
// Once maint_health falls below 0, we want to start liquidating,
|
||||
// we want to allow liquidation to continue until init_health is positive,
|
||||
// to prevent constant oscillation between the two states
|
||||
if liqee.being_liquidated() {
|
||||
if liqee
|
||||
.fixed
|
||||
.maybe_recover_from_being_liquidated(liqee_init_health)
|
||||
{
|
||||
msg!("Liqee init_health above zero");
|
||||
return Ok(());
|
||||
}
|
||||
} else {
|
||||
let maint_health = liqee_health_cache.health(HealthType::Maint);
|
||||
require!(
|
||||
maint_health < I80F48::ZERO,
|
||||
MangoError::HealthMustBeNegative
|
||||
);
|
||||
liqee.fixed.set_being_liquidated(true);
|
||||
}
|
||||
|
||||
let mut perp_market = ctx.accounts.perp_market.load_mut()?;
|
||||
let perp_market_index = perp_market.perp_market_index;
|
||||
let base_lot_size = I80F48::from(perp_market.base_lot_size);
|
||||
|
||||
// Get oracle price for market. Price is validated inside
|
||||
let oracle_price =
|
||||
perp_market.oracle_price(&AccountInfoRef::borrow(ctx.accounts.oracle.as_ref())?)?;
|
||||
let price_per_lot = cm!(base_lot_size * oracle_price);
|
||||
|
||||
// Fetch perp positions for accounts, creating for the liqor if needed
|
||||
let liqee_perp_position = liqee.perp_position_mut(perp_market_index)?;
|
||||
let liqor_perp_position = liqor.ensure_perp_position(perp_market_index)?.0;
|
||||
let liqee_base_lots = liqee_perp_position.base_position_lots();
|
||||
|
||||
require!(
|
||||
!liqee_perp_position.has_open_orders(),
|
||||
MangoError::HasOpenPerpOrders
|
||||
);
|
||||
|
||||
// Settle funding
|
||||
liqee_perp_position.settle_funding(&perp_market);
|
||||
liqor_perp_position.settle_funding(&perp_market);
|
||||
|
||||
// Take over the liqee's base in exchange for quote
|
||||
require_msg!(liqee_base_lots != 0, "liqee base position is zero");
|
||||
let (base_transfer, quote_transfer) = if liqee_base_lots > 0 {
|
||||
require_msg!(
|
||||
max_base_transfer > 0,
|
||||
"max_base_transfer must be positive when liqee's base_position is positive"
|
||||
);
|
||||
|
||||
// health gets reduced by `base * price * perp_init_asset_weight`
|
||||
// and increased by `base * price * (1 - liq_fee) * quote_init_asset_weight`
|
||||
let quote_asset_weight = I80F48::ONE;
|
||||
let health_per_lot = cm!(price_per_lot
|
||||
* (quote_asset_weight - perp_market.init_asset_weight - perp_market.liquidation_fee));
|
||||
|
||||
// number of lots to transfer to bring health to zero, rounded up
|
||||
let base_transfer_for_zero: i64 = cm!(-liqee_init_health / health_per_lot)
|
||||
.checked_ceil()
|
||||
.unwrap()
|
||||
.checked_to_num()
|
||||
.unwrap();
|
||||
|
||||
let base_transfer = base_transfer_for_zero
|
||||
.min(liqee_base_lots)
|
||||
.min(max_base_transfer)
|
||||
.max(0);
|
||||
let quote_transfer = cm!(-I80F48::from(base_transfer)
|
||||
* price_per_lot
|
||||
* (I80F48::ONE - perp_market.liquidation_fee));
|
||||
|
||||
(base_transfer, quote_transfer) // base > 0, quote < 0
|
||||
} else {
|
||||
// liqee_base_lots < 0
|
||||
require_msg!(
|
||||
max_base_transfer < 0,
|
||||
"max_base_transfer must be negative when liqee's base_position is positive"
|
||||
);
|
||||
|
||||
// health gets increased by `base * price * perp_init_liab_weight`
|
||||
// and reduced by `base * price * (1 + liq_fee) * quote_init_liab_weight`
|
||||
let quote_liab_weight = I80F48::ONE;
|
||||
let health_per_lot = cm!(price_per_lot
|
||||
* (perp_market.init_liab_weight - quote_liab_weight + perp_market.liquidation_fee));
|
||||
|
||||
// (negative) number of lots to transfer to bring health to zero, rounded away from zero
|
||||
let base_transfer_for_zero: i64 = cm!(liqee_init_health / health_per_lot)
|
||||
.checked_floor()
|
||||
.unwrap()
|
||||
.checked_to_num()
|
||||
.unwrap();
|
||||
|
||||
let base_transfer = base_transfer_for_zero
|
||||
.max(liqee_base_lots)
|
||||
.max(max_base_transfer)
|
||||
.min(0);
|
||||
let quote_transfer = cm!(-I80F48::from(base_transfer)
|
||||
* price_per_lot
|
||||
* (I80F48::ONE + perp_market.liquidation_fee));
|
||||
|
||||
(base_transfer, quote_transfer) // base < 0, quote > 0
|
||||
};
|
||||
|
||||
// Execute the transfer. This is essentially a forced trade and updates the
|
||||
// liqee and liqors entry and break even prices.
|
||||
liqee_perp_position.change_base_and_quote_positions(
|
||||
&mut perp_market,
|
||||
-base_transfer,
|
||||
-quote_transfer,
|
||||
);
|
||||
liqor_perp_position.change_base_and_quote_positions(
|
||||
&mut perp_market,
|
||||
base_transfer,
|
||||
quote_transfer,
|
||||
);
|
||||
|
||||
// Check liqee health again
|
||||
liqee_health_cache.recompute_perp_info(liqee_perp_position, &perp_market)?;
|
||||
let liqee_init_health = liqee_health_cache.health(HealthType::Init);
|
||||
liqee
|
||||
.fixed
|
||||
.maybe_recover_from_being_liquidated(liqee_init_health);
|
||||
|
||||
drop(perp_market);
|
||||
|
||||
// Check liqor's health
|
||||
if !liqor.fixed.is_in_health_region() {
|
||||
let account_retriever = ScanningAccountRetriever::new(ctx.remaining_accounts, group_pk)
|
||||
.context("create account retriever end")?;
|
||||
let liqor_health = compute_health(&liqor.borrow(), HealthType::Init, &account_retriever)
|
||||
.context("compute liqor health")?;
|
||||
require!(liqor_health >= 0, MangoError::HealthMustBePositive);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
use anchor_lang::prelude::*;
|
||||
use fixed::types::I80F48;
|
||||
|
||||
use crate::error::*;
|
||||
use crate::state::*;
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct PerpLiqForceCancelOrders<'info> {
|
||||
pub group: AccountLoader<'info, Group>,
|
||||
|
||||
#[account(mut, has_one = group)]
|
||||
pub account: AccountLoaderDynamic<'info, MangoAccount>,
|
||||
|
||||
#[account(
|
||||
mut,
|
||||
has_one = group,
|
||||
has_one = bids,
|
||||
has_one = asks
|
||||
)]
|
||||
pub perp_market: AccountLoader<'info, PerpMarket>,
|
||||
#[account(mut)]
|
||||
pub asks: AccountLoader<'info, BookSide>,
|
||||
#[account(mut)]
|
||||
pub bids: AccountLoader<'info, BookSide>,
|
||||
}
|
||||
|
||||
pub fn perp_liq_force_cancel_orders(
|
||||
ctx: Context<PerpLiqForceCancelOrders>,
|
||||
limit: u8,
|
||||
) -> Result<()> {
|
||||
let mut account = ctx.accounts.account.load_mut()?;
|
||||
|
||||
//
|
||||
// Check liqee health if liquidation is allowed
|
||||
//
|
||||
let mut health_cache = {
|
||||
let retriever =
|
||||
new_fixed_order_account_retriever(ctx.remaining_accounts, &account.borrow())?;
|
||||
let health_cache =
|
||||
new_health_cache(&account.borrow(), &retriever).context("create health cache")?;
|
||||
|
||||
if account.being_liquidated() {
|
||||
let init_health = health_cache.health(HealthType::Init);
|
||||
if account
|
||||
.fixed
|
||||
.maybe_recover_from_being_liquidated(init_health)
|
||||
{
|
||||
msg!("Liqee init_health above zero");
|
||||
return Ok(());
|
||||
}
|
||||
} else {
|
||||
let maint_health = health_cache.health(HealthType::Maint);
|
||||
require!(
|
||||
maint_health < I80F48::ZERO,
|
||||
MangoError::HealthMustBeNegative
|
||||
);
|
||||
account.fixed.set_being_liquidated(true);
|
||||
}
|
||||
|
||||
health_cache
|
||||
};
|
||||
|
||||
//
|
||||
// Cancel orders
|
||||
//
|
||||
{
|
||||
let mut perp_market = ctx.accounts.perp_market.load_mut()?;
|
||||
let bids = ctx.accounts.bids.load_mut()?;
|
||||
let asks = ctx.accounts.asks.load_mut()?;
|
||||
let mut book = Book::new(bids, asks);
|
||||
|
||||
book.cancel_all_orders(&mut account.borrow_mut(), &mut perp_market, limit, None)?;
|
||||
|
||||
let perp_position = account.perp_position(perp_market.perp_market_index)?;
|
||||
health_cache.recompute_perp_info(perp_position, &perp_market)?;
|
||||
}
|
||||
|
||||
//
|
||||
// Health check at the end
|
||||
//
|
||||
let init_health = health_cache.health(HealthType::Init);
|
||||
account
|
||||
.fixed
|
||||
.maybe_recover_from_being_liquidated(init_health);
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -506,12 +506,26 @@ pub mod mango_v4 {
|
|||
pub fn perp_settle_fees(ctx: Context<PerpSettleFees>, max_settle_amount: u64) -> Result<()> {
|
||||
instructions::perp_settle_fees(ctx, max_settle_amount)
|
||||
}
|
||||
|
||||
pub fn perp_liq_base_position(
|
||||
ctx: Context<PerpLiqBasePosition>,
|
||||
max_base_transfer: i64,
|
||||
) -> Result<()> {
|
||||
instructions::perp_liq_base_position(ctx, max_base_transfer)
|
||||
}
|
||||
|
||||
pub fn perp_liq_force_cancel_orders(
|
||||
ctx: Context<PerpLiqForceCancelOrders>,
|
||||
limit: u8,
|
||||
) -> Result<()> {
|
||||
instructions::perp_liq_force_cancel_orders(ctx, limit)
|
||||
}
|
||||
|
||||
// TODO
|
||||
|
||||
// perp_force_cancel_order
|
||||
|
||||
// liquidate_token_and_perp
|
||||
// liquidate_perp_and_perp
|
||||
|
||||
// settle_* - settle_funds
|
||||
|
||||
|
|
|
@ -326,6 +326,14 @@ impl PerpPosition {
|
|||
cm!(self.quote_position_native += quote_change_native);
|
||||
}
|
||||
|
||||
/// Does the perp position have any open orders or fill events?
|
||||
pub fn has_open_orders(&self) -> bool {
|
||||
self.asks_base_lots != 0
|
||||
|| self.bids_base_lots != 0
|
||||
|| self.taker_base_lots != 0
|
||||
|| self.taker_quote_lots != 0
|
||||
}
|
||||
|
||||
/// Calculate the average entry price of the position
|
||||
pub fn avg_entry_price(&self) -> I80F48 {
|
||||
if self.base_position_lots == 0 {
|
||||
|
|
|
@ -2097,6 +2097,7 @@ impl ClientInstruction for LiqTokenBankruptcyInstruction {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct PerpCreateMarketInstruction {
|
||||
pub group: Pubkey,
|
||||
pub admin: TestKeypair,
|
||||
|
@ -2118,6 +2119,28 @@ pub struct PerpCreateMarketInstruction {
|
|||
pub maker_fee: f32,
|
||||
pub taker_fee: f32,
|
||||
}
|
||||
impl PerpCreateMarketInstruction {
|
||||
pub async fn with_new_book_and_queue(
|
||||
solana: &SolanaCookie,
|
||||
base: &crate::mango_setup::Token,
|
||||
) -> Self {
|
||||
PerpCreateMarketInstruction {
|
||||
asks: solana
|
||||
.create_account_for_type::<BookSide>(&mango_v4::id())
|
||||
.await,
|
||||
bids: solana
|
||||
.create_account_for_type::<BookSide>(&mango_v4::id())
|
||||
.await,
|
||||
event_queue: solana
|
||||
.create_account_for_type::<EventQueue>(&mango_v4::id())
|
||||
.await,
|
||||
oracle: base.oracle,
|
||||
base_token_index: base.index,
|
||||
base_token_decimals: base.mint.decimals,
|
||||
..PerpCreateMarketInstruction::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl ClientInstruction for PerpCreateMarketInstruction {
|
||||
type Accounts = mango_v4::accounts::PerpCreateMarket;
|
||||
|
@ -2254,13 +2277,8 @@ impl ClientInstruction for PerpDeactivatePositionInstruction {
|
|||
}
|
||||
|
||||
pub struct PerpPlaceOrderInstruction {
|
||||
pub group: Pubkey,
|
||||
pub account: Pubkey,
|
||||
pub perp_market: Pubkey,
|
||||
pub asks: Pubkey,
|
||||
pub bids: Pubkey,
|
||||
pub event_queue: Pubkey,
|
||||
pub oracle: Pubkey,
|
||||
pub owner: TestKeypair,
|
||||
pub side: Side,
|
||||
pub price_lots: i64,
|
||||
|
@ -2287,16 +2305,6 @@ impl ClientInstruction for PerpPlaceOrderInstruction {
|
|||
expiry_timestamp: 0,
|
||||
limit: 1,
|
||||
};
|
||||
let accounts = Self::Accounts {
|
||||
group: self.group,
|
||||
account: self.account,
|
||||
perp_market: self.perp_market,
|
||||
asks: self.asks,
|
||||
bids: self.bids,
|
||||
event_queue: self.event_queue,
|
||||
oracle: self.oracle,
|
||||
owner: self.owner.pubkey(),
|
||||
};
|
||||
|
||||
let perp_market: PerpMarket = account_loader.load(&self.perp_market).await.unwrap();
|
||||
let account = account_loader
|
||||
|
@ -2312,6 +2320,16 @@ impl ClientInstruction for PerpPlaceOrderInstruction {
|
|||
)
|
||||
.await;
|
||||
|
||||
let accounts = Self::Accounts {
|
||||
group: account.fixed.group,
|
||||
account: self.account,
|
||||
perp_market: self.perp_market,
|
||||
asks: perp_market.asks,
|
||||
bids: perp_market.bids,
|
||||
event_queue: perp_market.event_queue,
|
||||
oracle: perp_market.oracle,
|
||||
owner: self.owner.pubkey(),
|
||||
};
|
||||
let mut instruction = make_instruction(program_id, &accounts, instruction);
|
||||
instruction.accounts.extend(health_check_metas);
|
||||
|
||||
|
@ -2402,11 +2420,8 @@ impl ClientInstruction for PerpCancelOrderByClientOrderIdInstruction {
|
|||
}
|
||||
|
||||
pub struct PerpCancelAllOrdersInstruction {
|
||||
pub group: Pubkey,
|
||||
pub account: Pubkey,
|
||||
pub perp_market: Pubkey,
|
||||
pub asks: Pubkey,
|
||||
pub bids: Pubkey,
|
||||
pub owner: TestKeypair,
|
||||
}
|
||||
#[async_trait::async_trait(?Send)]
|
||||
|
@ -2415,16 +2430,17 @@ impl ClientInstruction for PerpCancelAllOrdersInstruction {
|
|||
type Instruction = mango_v4::instruction::PerpCancelAllOrders;
|
||||
async fn to_instruction(
|
||||
&self,
|
||||
_loader: impl ClientAccountLoader + 'async_trait,
|
||||
account_loader: impl ClientAccountLoader + 'async_trait,
|
||||
) -> (Self::Accounts, instruction::Instruction) {
|
||||
let program_id = mango_v4::id();
|
||||
let instruction = Self::Instruction { limit: 5 };
|
||||
let perp_market: PerpMarket = account_loader.load(&self.perp_market).await.unwrap();
|
||||
let accounts = Self::Accounts {
|
||||
group: self.group,
|
||||
group: perp_market.group,
|
||||
account: self.account,
|
||||
perp_market: self.perp_market,
|
||||
asks: self.asks,
|
||||
bids: self.bids,
|
||||
asks: perp_market.asks,
|
||||
bids: perp_market.bids,
|
||||
owner: self.owner.pubkey(),
|
||||
};
|
||||
|
||||
|
@ -2438,9 +2454,7 @@ impl ClientInstruction for PerpCancelAllOrdersInstruction {
|
|||
}
|
||||
|
||||
pub struct PerpConsumeEventsInstruction {
|
||||
pub group: Pubkey,
|
||||
pub perp_market: Pubkey,
|
||||
pub event_queue: Pubkey,
|
||||
pub mango_accounts: Vec<Pubkey>,
|
||||
}
|
||||
#[async_trait::async_trait(?Send)]
|
||||
|
@ -2449,14 +2463,16 @@ impl ClientInstruction for PerpConsumeEventsInstruction {
|
|||
type Instruction = mango_v4::instruction::PerpConsumeEvents;
|
||||
async fn to_instruction(
|
||||
&self,
|
||||
_loader: impl ClientAccountLoader + 'async_trait,
|
||||
account_loader: impl ClientAccountLoader + 'async_trait,
|
||||
) -> (Self::Accounts, instruction::Instruction) {
|
||||
let program_id = mango_v4::id();
|
||||
let instruction = Self::Instruction { limit: 10 };
|
||||
|
||||
let perp_market: PerpMarket = account_loader.load(&self.perp_market).await.unwrap();
|
||||
let accounts = Self::Accounts {
|
||||
group: self.group,
|
||||
group: perp_market.group,
|
||||
perp_market: self.perp_market,
|
||||
event_queue: self.event_queue,
|
||||
event_queue: perp_market.event_queue,
|
||||
};
|
||||
|
||||
let mut instruction = make_instruction(program_id, &accounts, instruction);
|
||||
|
@ -2511,11 +2527,9 @@ impl ClientInstruction for PerpUpdateFundingInstruction {
|
|||
}
|
||||
|
||||
pub struct PerpSettlePnlInstruction {
|
||||
pub group: Pubkey,
|
||||
pub account_a: Pubkey,
|
||||
pub account_b: Pubkey,
|
||||
pub perp_market: Pubkey,
|
||||
pub oracle: Pubkey,
|
||||
pub quote_bank: Pubkey,
|
||||
pub max_settle_amount: u64,
|
||||
}
|
||||
|
@ -2531,14 +2545,6 @@ impl ClientInstruction for PerpSettlePnlInstruction {
|
|||
let instruction = Self::Instruction {
|
||||
max_settle_amount: self.max_settle_amount,
|
||||
};
|
||||
let accounts = Self::Accounts {
|
||||
group: self.group,
|
||||
perp_market: self.perp_market,
|
||||
account_a: self.account_a,
|
||||
account_b: self.account_b,
|
||||
oracle: self.oracle,
|
||||
quote_bank: self.quote_bank,
|
||||
};
|
||||
|
||||
let perp_market: PerpMarket = account_loader.load(&self.perp_market).await.unwrap();
|
||||
let account_b = account_loader
|
||||
|
@ -2554,6 +2560,15 @@ impl ClientInstruction for PerpSettlePnlInstruction {
|
|||
)
|
||||
.await;
|
||||
|
||||
let accounts = Self::Accounts {
|
||||
group: perp_market.group,
|
||||
perp_market: self.perp_market,
|
||||
account_a: self.account_a,
|
||||
account_b: self.account_b,
|
||||
oracle: perp_market.oracle,
|
||||
quote_bank: self.quote_bank,
|
||||
};
|
||||
|
||||
let mut instruction = make_instruction(program_id, &accounts, instruction);
|
||||
instruction.accounts.extend(health_check_metas);
|
||||
|
||||
|
@ -2566,10 +2581,8 @@ impl ClientInstruction for PerpSettlePnlInstruction {
|
|||
}
|
||||
|
||||
pub struct PerpSettleFeesInstruction {
|
||||
pub group: Pubkey,
|
||||
pub account: Pubkey,
|
||||
pub perp_market: Pubkey,
|
||||
pub oracle: Pubkey,
|
||||
pub quote_bank: Pubkey,
|
||||
pub max_settle_amount: u64,
|
||||
}
|
||||
|
@ -2585,13 +2598,6 @@ impl ClientInstruction for PerpSettleFeesInstruction {
|
|||
let instruction = Self::Instruction {
|
||||
max_settle_amount: self.max_settle_amount,
|
||||
};
|
||||
let accounts = Self::Accounts {
|
||||
group: self.group,
|
||||
perp_market: self.perp_market,
|
||||
account: self.account,
|
||||
oracle: self.oracle,
|
||||
quote_bank: self.quote_bank,
|
||||
};
|
||||
|
||||
let perp_market: PerpMarket = account_loader.load(&self.perp_market).await.unwrap();
|
||||
let account = account_loader
|
||||
|
@ -2607,6 +2613,13 @@ impl ClientInstruction for PerpSettleFeesInstruction {
|
|||
)
|
||||
.await;
|
||||
|
||||
let accounts = Self::Accounts {
|
||||
group: perp_market.group,
|
||||
perp_market: self.perp_market,
|
||||
account: self.account,
|
||||
oracle: perp_market.oracle,
|
||||
quote_bank: self.quote_bank,
|
||||
};
|
||||
let mut instruction = make_instruction(program_id, &accounts, instruction);
|
||||
instruction.accounts.extend(health_check_metas);
|
||||
|
||||
|
@ -2618,6 +2631,112 @@ impl ClientInstruction for PerpSettleFeesInstruction {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct PerpLiqForceCancelOrdersInstruction {
|
||||
pub account: Pubkey,
|
||||
pub perp_market: Pubkey,
|
||||
}
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl ClientInstruction for PerpLiqForceCancelOrdersInstruction {
|
||||
type Accounts = mango_v4::accounts::PerpLiqForceCancelOrders;
|
||||
type Instruction = mango_v4::instruction::PerpLiqForceCancelOrders;
|
||||
async fn to_instruction(
|
||||
&self,
|
||||
account_loader: impl ClientAccountLoader + 'async_trait,
|
||||
) -> (Self::Accounts, instruction::Instruction) {
|
||||
let program_id = mango_v4::id();
|
||||
let instruction = Self::Instruction { limit: 10 };
|
||||
|
||||
let perp_market: PerpMarket = account_loader.load(&self.perp_market).await.unwrap();
|
||||
let account = account_loader
|
||||
.load_mango_account(&self.account)
|
||||
.await
|
||||
.unwrap();
|
||||
let health_check_metas = derive_health_check_remaining_account_metas(
|
||||
&account_loader,
|
||||
&account,
|
||||
None,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
||||
let accounts = Self::Accounts {
|
||||
group: account.fixed.group,
|
||||
perp_market: self.perp_market,
|
||||
account: self.account,
|
||||
bids: perp_market.bids,
|
||||
asks: perp_market.asks,
|
||||
};
|
||||
let mut instruction = make_instruction(program_id, &accounts, instruction);
|
||||
instruction.accounts.extend(health_check_metas);
|
||||
|
||||
(accounts, instruction)
|
||||
}
|
||||
|
||||
fn signers(&self) -> Vec<TestKeypair> {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PerpLiqBasePositionInstruction {
|
||||
pub liqor: Pubkey,
|
||||
pub liqor_owner: TestKeypair,
|
||||
pub liqee: Pubkey,
|
||||
pub perp_market: Pubkey,
|
||||
pub max_base_transfer: i64,
|
||||
}
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl ClientInstruction for PerpLiqBasePositionInstruction {
|
||||
type Accounts = mango_v4::accounts::PerpLiqBasePosition;
|
||||
type Instruction = mango_v4::instruction::PerpLiqBasePosition;
|
||||
async fn to_instruction(
|
||||
&self,
|
||||
account_loader: impl ClientAccountLoader + 'async_trait,
|
||||
) -> (Self::Accounts, instruction::Instruction) {
|
||||
let program_id = mango_v4::id();
|
||||
let instruction = Self::Instruction {
|
||||
max_base_transfer: self.max_base_transfer,
|
||||
};
|
||||
|
||||
let perp_market: PerpMarket = account_loader.load(&self.perp_market).await.unwrap();
|
||||
let liqor = account_loader
|
||||
.load_mango_account(&self.liqor)
|
||||
.await
|
||||
.unwrap();
|
||||
let liqee = account_loader
|
||||
.load_mango_account(&self.liqee)
|
||||
.await
|
||||
.unwrap();
|
||||
let health_check_metas = derive_liquidation_remaining_account_metas(
|
||||
&account_loader,
|
||||
&liqee,
|
||||
&liqor,
|
||||
TokenIndex::MAX,
|
||||
0,
|
||||
TokenIndex::MAX,
|
||||
0,
|
||||
)
|
||||
.await;
|
||||
|
||||
let accounts = Self::Accounts {
|
||||
group: liqor.fixed.group,
|
||||
perp_market: self.perp_market,
|
||||
oracle: perp_market.oracle,
|
||||
liqor: self.liqor,
|
||||
liqor_owner: self.liqor_owner.pubkey(),
|
||||
liqee: self.liqee,
|
||||
};
|
||||
let mut instruction = make_instruction(program_id, &accounts, instruction);
|
||||
instruction.accounts.extend(health_check_metas);
|
||||
|
||||
(accounts, instruction)
|
||||
}
|
||||
|
||||
fn signers(&self) -> Vec<TestKeypair> {
|
||||
vec![self.liqor_owner]
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BenchmarkInstruction {}
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl ClientInstruction for BenchmarkInstruction {
|
||||
|
|
|
@ -197,7 +197,6 @@ async fn test_health_compute_perp() -> Result<(), TransportError> {
|
|||
//
|
||||
// SETUP: Create perp markets
|
||||
//
|
||||
let quote_token = &tokens[0];
|
||||
let mut perp_markets = vec![];
|
||||
for (perp_market_index, token) in tokens[1..].iter().enumerate() {
|
||||
let mango_v4::accounts::PerpCreateMarket {
|
||||
|
@ -211,25 +210,8 @@ async fn test_health_compute_perp() -> Result<(), TransportError> {
|
|||
PerpCreateMarketInstruction {
|
||||
group,
|
||||
admin,
|
||||
oracle: token.oracle,
|
||||
asks: context
|
||||
.solana
|
||||
.create_account_for_type::<BookSide>(&mango_v4::id())
|
||||
.await,
|
||||
bids: context
|
||||
.solana
|
||||
.create_account_for_type::<BookSide>(&mango_v4::id())
|
||||
.await,
|
||||
event_queue: {
|
||||
context
|
||||
.solana
|
||||
.create_account_for_type::<EventQueue>(&mango_v4::id())
|
||||
.await
|
||||
},
|
||||
payer,
|
||||
perp_market_index: perp_market_index as PerpMarketIndex,
|
||||
base_token_index: quote_token.index,
|
||||
base_token_decimals: quote_token.mint.decimals,
|
||||
quote_lot_size: 10,
|
||||
base_lot_size: 100,
|
||||
maint_asset_weight: 0.975,
|
||||
|
@ -239,6 +221,11 @@ async fn test_health_compute_perp() -> Result<(), TransportError> {
|
|||
liquidation_fee: 0.012,
|
||||
maker_fee: 0.0002,
|
||||
taker_fee: 0.000,
|
||||
// HACK: Currently the base_token_index token needs to be active on the account.
|
||||
// Using token[0] for each market allows us to have multiple perp positions with
|
||||
// just a single token position.
|
||||
base_token_index: tokens[0].index,
|
||||
..PerpCreateMarketInstruction::with_new_book_and_queue(&solana, &token).await
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
@ -255,18 +242,13 @@ async fn test_health_compute_perp() -> Result<(), TransportError> {
|
|||
//
|
||||
// TEST: Create a perp order for each market
|
||||
//
|
||||
for (i, &(perp_market, asks, bids, event_queue)) in perp_markets.iter().enumerate() {
|
||||
for (i, &(perp_market, _asks, _bids, _event_queue)) in perp_markets.iter().enumerate() {
|
||||
println!("adding market {}", i);
|
||||
send_tx(
|
||||
solana,
|
||||
PerpPlaceOrderInstruction {
|
||||
group,
|
||||
account,
|
||||
perp_market,
|
||||
asks,
|
||||
bids,
|
||||
event_queue,
|
||||
oracle: tokens[i + 1].oracle,
|
||||
owner,
|
||||
side: Side::Bid,
|
||||
price_lots,
|
||||
|
|
|
@ -0,0 +1,465 @@
|
|||
#![cfg(feature = "test-bpf")]
|
||||
|
||||
use fixed::types::I80F48;
|
||||
use solana_program_test::*;
|
||||
use solana_sdk::transport::TransportError;
|
||||
|
||||
use mango_v4::state::*;
|
||||
use program_test::*;
|
||||
|
||||
use mango_setup::*;
|
||||
|
||||
mod program_test;
|
||||
|
||||
use utils::assert_equal_fixed_f64 as assert_equal;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_liq_perps_force_cancel() -> Result<(), TransportError> {
|
||||
let test_builder = TestContextBuilder::new();
|
||||
let context = test_builder.start_default().await;
|
||||
let solana = &context.solana.clone();
|
||||
|
||||
let admin = TestKeypair::new();
|
||||
let owner = context.users[0].key;
|
||||
let payer = context.users[1].key;
|
||||
let mints = &context.mints[0..2];
|
||||
let payer_mint_accounts = &context.users[1].token_accounts[0..2];
|
||||
|
||||
//
|
||||
// SETUP: Create a group and an account to fill the vaults
|
||||
//
|
||||
|
||||
let GroupWithTokens { group, tokens, .. } = GroupWithTokensConfig {
|
||||
admin,
|
||||
payer,
|
||||
mints,
|
||||
}
|
||||
.create(solana)
|
||||
.await;
|
||||
//let quote_token = &tokens[0];
|
||||
let base_token = &tokens[1];
|
||||
|
||||
// deposit some funds, to the vaults aren't empty
|
||||
create_funded_account(&solana, group, owner, 0, &context.users[1], mints, 10000, 0).await;
|
||||
|
||||
//
|
||||
// TEST: Create a perp market
|
||||
//
|
||||
let mango_v4::accounts::PerpCreateMarket { perp_market, .. } = send_tx(
|
||||
solana,
|
||||
PerpCreateMarketInstruction {
|
||||
group,
|
||||
admin,
|
||||
payer,
|
||||
perp_market_index: 0,
|
||||
quote_lot_size: 10,
|
||||
base_lot_size: 100,
|
||||
maint_asset_weight: 0.8,
|
||||
init_asset_weight: 0.6,
|
||||
maint_liab_weight: 1.2,
|
||||
init_liab_weight: 1.4,
|
||||
liquidation_fee: 0.05,
|
||||
maker_fee: 0.0,
|
||||
taker_fee: 0.0,
|
||||
..PerpCreateMarketInstruction::with_new_book_and_queue(&solana, base_token).await
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let price_lots = {
|
||||
let perp_market = solana.get_account::<PerpMarket>(perp_market).await;
|
||||
perp_market.native_price_to_lot(I80F48::ONE)
|
||||
};
|
||||
|
||||
//
|
||||
// SETUP: Make an account and deposit some quote and base
|
||||
//
|
||||
let deposit_amount = 1000;
|
||||
let account = create_funded_account(
|
||||
&solana,
|
||||
group,
|
||||
owner,
|
||||
1,
|
||||
&context.users[1],
|
||||
&mints[0..1],
|
||||
deposit_amount,
|
||||
0,
|
||||
)
|
||||
.await;
|
||||
|
||||
send_tx(
|
||||
solana,
|
||||
TokenDepositInstruction {
|
||||
amount: 1,
|
||||
account,
|
||||
token_account: payer_mint_accounts[1],
|
||||
token_authority: payer,
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
//
|
||||
// SETUP: Place a perp order
|
||||
//
|
||||
send_tx(
|
||||
solana,
|
||||
PerpPlaceOrderInstruction {
|
||||
account,
|
||||
perp_market,
|
||||
owner,
|
||||
side: Side::Ask,
|
||||
price_lots,
|
||||
// health was 1000 * 0.6 = 600; this order is -14*100*(1.4-1) = -560
|
||||
max_base_lots: 14,
|
||||
max_quote_lots: i64::MAX,
|
||||
client_order_id: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
//
|
||||
// SETUP: Change the oracle to make health go negative
|
||||
//
|
||||
send_tx(
|
||||
solana,
|
||||
StubOracleSetInstruction {
|
||||
group,
|
||||
admin,
|
||||
mint: base_token.mint.pubkey,
|
||||
payer,
|
||||
price: "10.0",
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// verify health is bad: can't withdraw
|
||||
assert!(send_tx(
|
||||
solana,
|
||||
TokenWithdrawInstruction {
|
||||
amount: 1,
|
||||
allow_borrow: false,
|
||||
account,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[1],
|
||||
bank_index: 0,
|
||||
}
|
||||
)
|
||||
.await
|
||||
.is_err());
|
||||
|
||||
//
|
||||
// TEST: force cancel orders, making the account healthy again
|
||||
//
|
||||
send_tx(
|
||||
solana,
|
||||
PerpLiqForceCancelOrdersInstruction {
|
||||
account,
|
||||
perp_market,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// can withdraw again
|
||||
send_tx(
|
||||
solana,
|
||||
TokenWithdrawInstruction {
|
||||
amount: 1,
|
||||
allow_borrow: false,
|
||||
account,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[1],
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_liq_perps_base_position() -> Result<(), TransportError> {
|
||||
let test_builder = TestContextBuilder::new();
|
||||
let context = test_builder.start_default().await;
|
||||
let solana = &context.solana.clone();
|
||||
|
||||
let admin = TestKeypair::new();
|
||||
let owner = context.users[0].key;
|
||||
let payer = context.users[1].key;
|
||||
let mints = &context.mints[0..2];
|
||||
let payer_mint_accounts = &context.users[1].token_accounts[0..2];
|
||||
|
||||
//
|
||||
// SETUP: Create a group and an account to fill the vaults
|
||||
//
|
||||
|
||||
let GroupWithTokens { group, tokens, .. } = GroupWithTokensConfig {
|
||||
admin,
|
||||
payer,
|
||||
mints,
|
||||
}
|
||||
.create(solana)
|
||||
.await;
|
||||
//let quote_token = &tokens[0];
|
||||
let base_token = &tokens[1];
|
||||
|
||||
// deposit some funds, to the vaults aren't empty
|
||||
let liqor = create_funded_account(
|
||||
&solana,
|
||||
group,
|
||||
owner,
|
||||
250,
|
||||
&context.users[1],
|
||||
mints,
|
||||
10000,
|
||||
0,
|
||||
)
|
||||
.await;
|
||||
|
||||
//
|
||||
// TEST: Create a perp market
|
||||
//
|
||||
let mango_v4::accounts::PerpCreateMarket { perp_market, .. } = send_tx(
|
||||
solana,
|
||||
PerpCreateMarketInstruction {
|
||||
group,
|
||||
admin,
|
||||
payer,
|
||||
perp_market_index: 0,
|
||||
quote_lot_size: 10,
|
||||
base_lot_size: 100,
|
||||
maint_asset_weight: 0.8,
|
||||
init_asset_weight: 0.6,
|
||||
maint_liab_weight: 1.2,
|
||||
init_liab_weight: 1.4,
|
||||
liquidation_fee: 0.05,
|
||||
maker_fee: 0.0,
|
||||
taker_fee: 0.0,
|
||||
..PerpCreateMarketInstruction::with_new_book_and_queue(&solana, base_token).await
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let price_lots = {
|
||||
let perp_market = solana.get_account::<PerpMarket>(perp_market).await;
|
||||
perp_market.native_price_to_lot(I80F48::ONE)
|
||||
};
|
||||
|
||||
//
|
||||
// SETUP: Make an two accounts and deposit some quote and base
|
||||
//
|
||||
let context_ref = &context;
|
||||
let make_account = |idx: u32| async move {
|
||||
let deposit_amount = 1000;
|
||||
let account = create_funded_account(
|
||||
&solana,
|
||||
group,
|
||||
owner,
|
||||
idx,
|
||||
&context_ref.users[1],
|
||||
&mints[0..1],
|
||||
deposit_amount,
|
||||
0,
|
||||
)
|
||||
.await;
|
||||
|
||||
send_tx(
|
||||
solana,
|
||||
TokenDepositInstruction {
|
||||
amount: 1,
|
||||
account,
|
||||
token_account: payer_mint_accounts[1],
|
||||
token_authority: payer,
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
account
|
||||
};
|
||||
let account_0 = make_account(0).await;
|
||||
let account_1 = make_account(1).await;
|
||||
|
||||
//
|
||||
// SETUP: Trade perps between accounts
|
||||
//
|
||||
// health was 1000 * 0.6 = 600 before
|
||||
// after this order it is -14*100*(1.4-1) = -560 for the short
|
||||
// and 14*100*(0.6-1) = -560 for the long
|
||||
//
|
||||
send_tx(
|
||||
solana,
|
||||
PerpPlaceOrderInstruction {
|
||||
account: account_0,
|
||||
perp_market,
|
||||
owner,
|
||||
side: Side::Bid,
|
||||
price_lots,
|
||||
max_base_lots: 14,
|
||||
max_quote_lots: i64::MAX,
|
||||
client_order_id: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
send_tx(
|
||||
solana,
|
||||
PerpPlaceOrderInstruction {
|
||||
account: account_1,
|
||||
perp_market,
|
||||
owner,
|
||||
side: Side::Ask,
|
||||
price_lots,
|
||||
max_base_lots: 14,
|
||||
max_quote_lots: i64::MAX,
|
||||
client_order_id: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
send_tx(
|
||||
solana,
|
||||
PerpConsumeEventsInstruction {
|
||||
perp_market,
|
||||
mango_accounts: vec![account_0, account_1],
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
//
|
||||
// SETUP: Change the oracle to make health go negative for account_0
|
||||
//
|
||||
send_tx(
|
||||
solana,
|
||||
StubOracleSetInstruction {
|
||||
group,
|
||||
admin,
|
||||
mint: base_token.mint.pubkey,
|
||||
payer,
|
||||
price: "0.5",
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// verify health is bad: can't withdraw
|
||||
assert!(send_tx(
|
||||
solana,
|
||||
TokenWithdrawInstruction {
|
||||
amount: 1,
|
||||
allow_borrow: false,
|
||||
account: account_0,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[1],
|
||||
bank_index: 0,
|
||||
}
|
||||
)
|
||||
.await
|
||||
.is_err());
|
||||
|
||||
//
|
||||
// TEST: Liquidate base position
|
||||
//
|
||||
send_tx(
|
||||
solana,
|
||||
PerpLiqBasePositionInstruction {
|
||||
liqor,
|
||||
liqor_owner: owner,
|
||||
liqee: account_0,
|
||||
perp_market,
|
||||
max_base_transfer: 10,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let liq_amount = 10.0 * 100.0 * 0.5 * (1.0 - 0.05);
|
||||
let liqor_data = solana.get_account::<MangoAccount>(liqor).await;
|
||||
assert_eq!(liqor_data.perps[0].base_position_lots(), 10);
|
||||
assert!(assert_equal(
|
||||
liqor_data.perps[0].quote_position_native(),
|
||||
-liq_amount,
|
||||
0.1
|
||||
));
|
||||
let liqee_data = solana.get_account::<MangoAccount>(account_0).await;
|
||||
assert_eq!(liqee_data.perps[0].base_position_lots(), 4);
|
||||
assert!(assert_equal(
|
||||
liqee_data.perps[0].quote_position_native(),
|
||||
-14.0 * 100.0 + liq_amount,
|
||||
0.1
|
||||
));
|
||||
|
||||
//
|
||||
// SETUP: Change the oracle to make health go negative for account_1
|
||||
//
|
||||
send_tx(
|
||||
solana,
|
||||
StubOracleSetInstruction {
|
||||
group,
|
||||
admin,
|
||||
mint: base_token.mint.pubkey,
|
||||
payer,
|
||||
price: "2.0",
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// verify health is bad: can't withdraw
|
||||
assert!(send_tx(
|
||||
solana,
|
||||
TokenWithdrawInstruction {
|
||||
amount: 1,
|
||||
allow_borrow: false,
|
||||
account: account_1,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[1],
|
||||
bank_index: 0,
|
||||
}
|
||||
)
|
||||
.await
|
||||
.is_err());
|
||||
|
||||
//
|
||||
// TEST: Liquidate base position
|
||||
//
|
||||
send_tx(
|
||||
solana,
|
||||
PerpLiqBasePositionInstruction {
|
||||
liqor,
|
||||
liqor_owner: owner,
|
||||
liqee: account_1,
|
||||
perp_market,
|
||||
max_base_transfer: i64::MIN,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let liq_amount_2 = 14.0 * 100.0 * 2.0 * (1.0 + 0.05);
|
||||
let liqor_data = solana.get_account::<MangoAccount>(liqor).await;
|
||||
assert_eq!(liqor_data.perps[0].base_position_lots(), 10 - 14);
|
||||
assert!(assert_equal(
|
||||
liqor_data.perps[0].quote_position_native(),
|
||||
-liq_amount + liq_amount_2,
|
||||
0.1
|
||||
));
|
||||
let liqee_data = solana.get_account::<MangoAccount>(account_1).await;
|
||||
assert_eq!(liqee_data.perps[0].base_position_lots(), 0);
|
||||
assert!(assert_equal(
|
||||
liqee_data.perps[0].quote_position_native(),
|
||||
14.0 * 100.0 - liq_amount_2,
|
||||
0.1
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -72,25 +72,8 @@ async fn test_perp() -> Result<(), TransportError> {
|
|||
PerpCreateMarketInstruction {
|
||||
group,
|
||||
admin,
|
||||
oracle: tokens[0].oracle,
|
||||
asks: context
|
||||
.solana
|
||||
.create_account_for_type::<BookSide>(&mango_v4::id())
|
||||
.await,
|
||||
bids: context
|
||||
.solana
|
||||
.create_account_for_type::<BookSide>(&mango_v4::id())
|
||||
.await,
|
||||
event_queue: {
|
||||
context
|
||||
.solana
|
||||
.create_account_for_type::<EventQueue>(&mango_v4::id())
|
||||
.await
|
||||
},
|
||||
payer,
|
||||
perp_market_index: 0,
|
||||
base_token_index: tokens[0].index,
|
||||
base_token_decimals: tokens[0].mint.decimals,
|
||||
quote_lot_size: 10,
|
||||
base_lot_size: 100,
|
||||
maint_asset_weight: 0.975,
|
||||
|
@ -100,6 +83,7 @@ async fn test_perp() -> Result<(), TransportError> {
|
|||
liquidation_fee: 0.012,
|
||||
maker_fee: -0.0001,
|
||||
taker_fee: 0.0002,
|
||||
..PerpCreateMarketInstruction::with_new_book_and_queue(&solana, &tokens[0]).await
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
@ -116,13 +100,8 @@ async fn test_perp() -> Result<(), TransportError> {
|
|||
send_tx(
|
||||
solana,
|
||||
PerpPlaceOrderInstruction {
|
||||
group,
|
||||
account: account_0,
|
||||
perp_market,
|
||||
asks,
|
||||
bids,
|
||||
event_queue,
|
||||
oracle: tokens[0].oracle,
|
||||
owner,
|
||||
side: Side::Bid,
|
||||
price_lots,
|
||||
|
@ -163,13 +142,8 @@ async fn test_perp() -> Result<(), TransportError> {
|
|||
send_tx(
|
||||
solana,
|
||||
PerpPlaceOrderInstruction {
|
||||
group,
|
||||
account: account_0,
|
||||
perp_market,
|
||||
asks,
|
||||
bids,
|
||||
event_queue,
|
||||
oracle: tokens[0].oracle,
|
||||
owner,
|
||||
side: Side::Bid,
|
||||
price_lots,
|
||||
|
@ -205,13 +179,8 @@ async fn test_perp() -> Result<(), TransportError> {
|
|||
send_tx(
|
||||
solana,
|
||||
PerpPlaceOrderInstruction {
|
||||
group,
|
||||
account: account_0,
|
||||
perp_market,
|
||||
asks,
|
||||
bids,
|
||||
event_queue,
|
||||
oracle: tokens[0].oracle,
|
||||
owner,
|
||||
side: Side::Bid,
|
||||
price_lots,
|
||||
|
@ -226,13 +195,8 @@ async fn test_perp() -> Result<(), TransportError> {
|
|||
send_tx(
|
||||
solana,
|
||||
PerpPlaceOrderInstruction {
|
||||
group,
|
||||
account: account_0,
|
||||
perp_market,
|
||||
asks,
|
||||
bids,
|
||||
event_queue,
|
||||
oracle: tokens[0].oracle,
|
||||
owner,
|
||||
side: Side::Bid,
|
||||
price_lots,
|
||||
|
@ -247,13 +211,8 @@ async fn test_perp() -> Result<(), TransportError> {
|
|||
send_tx(
|
||||
solana,
|
||||
PerpPlaceOrderInstruction {
|
||||
group,
|
||||
account: account_0,
|
||||
perp_market,
|
||||
asks,
|
||||
bids,
|
||||
event_queue,
|
||||
oracle: tokens[0].oracle,
|
||||
owner,
|
||||
side: Side::Bid,
|
||||
price_lots,
|
||||
|
@ -269,11 +228,8 @@ async fn test_perp() -> Result<(), TransportError> {
|
|||
send_tx(
|
||||
solana,
|
||||
PerpCancelAllOrdersInstruction {
|
||||
group,
|
||||
account: account_0,
|
||||
perp_market,
|
||||
asks,
|
||||
bids,
|
||||
owner,
|
||||
},
|
||||
)
|
||||
|
@ -288,13 +244,8 @@ async fn test_perp() -> Result<(), TransportError> {
|
|||
send_tx(
|
||||
solana,
|
||||
PerpPlaceOrderInstruction {
|
||||
group,
|
||||
account: account_0,
|
||||
perp_market,
|
||||
asks,
|
||||
bids,
|
||||
event_queue,
|
||||
oracle: tokens[0].oracle,
|
||||
owner,
|
||||
side: Side::Bid,
|
||||
price_lots,
|
||||
|
@ -310,13 +261,8 @@ async fn test_perp() -> Result<(), TransportError> {
|
|||
send_tx(
|
||||
solana,
|
||||
PerpPlaceOrderInstruction {
|
||||
group,
|
||||
account: account_1,
|
||||
perp_market,
|
||||
asks,
|
||||
bids,
|
||||
event_queue,
|
||||
oracle: tokens[0].oracle,
|
||||
owner,
|
||||
side: Side::Ask,
|
||||
price_lots,
|
||||
|
@ -332,9 +278,7 @@ async fn test_perp() -> Result<(), TransportError> {
|
|||
send_tx(
|
||||
solana,
|
||||
PerpConsumeEventsInstruction {
|
||||
group,
|
||||
perp_market,
|
||||
event_queue,
|
||||
mango_accounts: vec![account_0, account_1],
|
||||
},
|
||||
)
|
||||
|
@ -378,13 +322,8 @@ async fn test_perp() -> Result<(), TransportError> {
|
|||
send_tx(
|
||||
solana,
|
||||
PerpPlaceOrderInstruction {
|
||||
group,
|
||||
account: account_0,
|
||||
perp_market,
|
||||
asks,
|
||||
bids,
|
||||
event_queue,
|
||||
oracle: tokens[0].oracle,
|
||||
owner,
|
||||
side: Side::Ask,
|
||||
price_lots,
|
||||
|
@ -400,13 +339,8 @@ async fn test_perp() -> Result<(), TransportError> {
|
|||
send_tx(
|
||||
solana,
|
||||
PerpPlaceOrderInstruction {
|
||||
group,
|
||||
account: account_1,
|
||||
perp_market,
|
||||
asks,
|
||||
bids,
|
||||
event_queue,
|
||||
oracle: tokens[0].oracle,
|
||||
owner,
|
||||
side: Side::Bid,
|
||||
price_lots,
|
||||
|
@ -422,9 +356,7 @@ async fn test_perp() -> Result<(), TransportError> {
|
|||
send_tx(
|
||||
solana,
|
||||
PerpConsumeEventsInstruction {
|
||||
group,
|
||||
perp_market,
|
||||
event_queue,
|
||||
mango_accounts: vec![account_0, account_1],
|
||||
},
|
||||
)
|
||||
|
@ -451,11 +383,9 @@ async fn test_perp() -> Result<(), TransportError> {
|
|||
send_tx(
|
||||
solana,
|
||||
PerpSettlePnlInstruction {
|
||||
group,
|
||||
account_a: account_0,
|
||||
account_b: account_1,
|
||||
perp_market,
|
||||
oracle: tokens[0].oracle,
|
||||
quote_bank: tokens[0].bank,
|
||||
max_settle_amount: u64::MAX,
|
||||
},
|
||||
|
@ -465,10 +395,8 @@ async fn test_perp() -> Result<(), TransportError> {
|
|||
send_tx(
|
||||
solana,
|
||||
PerpSettleFeesInstruction {
|
||||
group,
|
||||
account: account_1,
|
||||
perp_market,
|
||||
oracle: tokens[0].oracle,
|
||||
quote_bank: tokens[0].bank,
|
||||
max_settle_amount: u64::MAX,
|
||||
},
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
#![cfg(all(feature = "test-bpf"))]
|
||||
|
||||
use anchor_lang::prelude::ErrorCode;
|
||||
use fixed::types::I80F48;
|
||||
use mango_v4::{error::MangoError, state::*};
|
||||
use program_test::*;
|
||||
|
@ -134,36 +133,13 @@ async fn test_perp_settle_pnl() -> Result<(), TransportError> {
|
|||
//
|
||||
// TEST: Create a perp market
|
||||
//
|
||||
let mango_v4::accounts::PerpCreateMarket {
|
||||
perp_market,
|
||||
asks,
|
||||
bids,
|
||||
event_queue,
|
||||
..
|
||||
} = send_tx(
|
||||
let mango_v4::accounts::PerpCreateMarket { perp_market, .. } = send_tx(
|
||||
solana,
|
||||
PerpCreateMarketInstruction {
|
||||
group,
|
||||
admin,
|
||||
oracle: tokens[0].oracle,
|
||||
asks: context
|
||||
.solana
|
||||
.create_account_for_type::<BookSide>(&mango_v4::id())
|
||||
.await,
|
||||
bids: context
|
||||
.solana
|
||||
.create_account_for_type::<BookSide>(&mango_v4::id())
|
||||
.await,
|
||||
event_queue: {
|
||||
context
|
||||
.solana
|
||||
.create_account_for_type::<EventQueue>(&mango_v4::id())
|
||||
.await
|
||||
},
|
||||
payer,
|
||||
perp_market_index: 0,
|
||||
base_token_index: tokens[0].index,
|
||||
base_token_decimals: tokens[0].mint.decimals,
|
||||
quote_lot_size: 10,
|
||||
base_lot_size: 100,
|
||||
maint_asset_weight: 0.975,
|
||||
|
@ -173,6 +149,7 @@ async fn test_perp_settle_pnl() -> Result<(), TransportError> {
|
|||
liquidation_fee: 0.012,
|
||||
maker_fee: 0.0002,
|
||||
taker_fee: 0.000,
|
||||
..PerpCreateMarketInstruction::with_new_book_and_queue(&solana, &tokens[0]).await
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
@ -189,25 +166,8 @@ async fn test_perp_settle_pnl() -> Result<(), TransportError> {
|
|||
PerpCreateMarketInstruction {
|
||||
group,
|
||||
admin,
|
||||
oracle: tokens[1].oracle,
|
||||
asks: context
|
||||
.solana
|
||||
.create_account_for_type::<BookSide>(&mango_v4::id())
|
||||
.await,
|
||||
bids: context
|
||||
.solana
|
||||
.create_account_for_type::<BookSide>(&mango_v4::id())
|
||||
.await,
|
||||
event_queue: {
|
||||
context
|
||||
.solana
|
||||
.create_account_for_type::<EventQueue>(&mango_v4::id())
|
||||
.await
|
||||
},
|
||||
payer,
|
||||
perp_market_index: 1,
|
||||
base_token_index: tokens[1].index,
|
||||
base_token_decimals: tokens[1].mint.decimals,
|
||||
quote_lot_size: 10,
|
||||
base_lot_size: 100,
|
||||
maint_asset_weight: 0.975,
|
||||
|
@ -217,6 +177,7 @@ async fn test_perp_settle_pnl() -> Result<(), TransportError> {
|
|||
liquidation_fee: 0.012,
|
||||
maker_fee: 0.0002,
|
||||
taker_fee: 0.000,
|
||||
..PerpCreateMarketInstruction::with_new_book_and_queue(&solana, &tokens[1]).await
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
@ -247,13 +208,8 @@ async fn test_perp_settle_pnl() -> Result<(), TransportError> {
|
|||
send_tx(
|
||||
solana,
|
||||
PerpPlaceOrderInstruction {
|
||||
group,
|
||||
account: account_0,
|
||||
perp_market,
|
||||
asks,
|
||||
bids,
|
||||
event_queue,
|
||||
oracle: tokens[0].oracle,
|
||||
owner,
|
||||
side: Side::Bid,
|
||||
price_lots,
|
||||
|
@ -268,13 +224,8 @@ async fn test_perp_settle_pnl() -> Result<(), TransportError> {
|
|||
send_tx(
|
||||
solana,
|
||||
PerpPlaceOrderInstruction {
|
||||
group,
|
||||
account: account_1,
|
||||
perp_market,
|
||||
asks,
|
||||
bids,
|
||||
event_queue,
|
||||
oracle: tokens[0].oracle,
|
||||
owner,
|
||||
side: Side::Ask,
|
||||
price_lots,
|
||||
|
@ -289,9 +240,7 @@ async fn test_perp_settle_pnl() -> Result<(), TransportError> {
|
|||
send_tx(
|
||||
solana,
|
||||
PerpConsumeEventsInstruction {
|
||||
group,
|
||||
perp_market,
|
||||
event_queue,
|
||||
mango_accounts: vec![account_0, account_1],
|
||||
},
|
||||
)
|
||||
|
@ -315,11 +264,9 @@ async fn test_perp_settle_pnl() -> Result<(), TransportError> {
|
|||
let result = send_tx(
|
||||
solana,
|
||||
PerpSettlePnlInstruction {
|
||||
group,
|
||||
account_a: account_1,
|
||||
account_b: account_0,
|
||||
perp_market,
|
||||
oracle: tokens[0].oracle,
|
||||
quote_bank: tokens[1].bank,
|
||||
max_settle_amount: u64::MAX,
|
||||
},
|
||||
|
@ -332,36 +279,13 @@ async fn test_perp_settle_pnl() -> Result<(), TransportError> {
|
|||
"Bank must be valid for quote currency".to_string(),
|
||||
);
|
||||
|
||||
// Oracle must be valid for the perp market
|
||||
let result = send_tx(
|
||||
solana,
|
||||
PerpSettlePnlInstruction {
|
||||
group,
|
||||
account_a: account_1,
|
||||
account_b: account_0,
|
||||
perp_market,
|
||||
oracle: tokens[1].oracle, // Using oracle for token 1 not 0
|
||||
quote_bank: tokens[0].bank,
|
||||
max_settle_amount: u64::MAX,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_mango_error(
|
||||
&result,
|
||||
ErrorCode::ConstraintHasOne.into(),
|
||||
"Oracle must be valid for perp market".to_string(),
|
||||
);
|
||||
|
||||
// Cannot settle with yourself
|
||||
let result = send_tx(
|
||||
solana,
|
||||
PerpSettlePnlInstruction {
|
||||
group,
|
||||
account_a: account_0,
|
||||
account_b: account_0,
|
||||
perp_market,
|
||||
oracle: tokens[0].oracle,
|
||||
quote_bank: tokens[0].bank,
|
||||
max_settle_amount: u64::MAX,
|
||||
},
|
||||
|
@ -378,11 +302,9 @@ async fn test_perp_settle_pnl() -> Result<(), TransportError> {
|
|||
let result = send_tx(
|
||||
solana,
|
||||
PerpSettlePnlInstruction {
|
||||
group,
|
||||
account_a: account_0,
|
||||
account_b: account_1,
|
||||
perp_market: perp_market_2,
|
||||
oracle: tokens[1].oracle,
|
||||
quote_bank: tokens[0].bank,
|
||||
max_settle_amount: u64::MAX,
|
||||
},
|
||||
|
@ -399,11 +321,9 @@ async fn test_perp_settle_pnl() -> Result<(), TransportError> {
|
|||
let result = send_tx(
|
||||
solana,
|
||||
PerpSettlePnlInstruction {
|
||||
group,
|
||||
account_a: account_0,
|
||||
account_b: account_1,
|
||||
perp_market: perp_market,
|
||||
oracle: tokens[0].oracle,
|
||||
quote_bank: tokens[0].bank,
|
||||
max_settle_amount: 0,
|
||||
},
|
||||
|
@ -452,11 +372,9 @@ async fn test_perp_settle_pnl() -> Result<(), TransportError> {
|
|||
let result = send_tx(
|
||||
solana,
|
||||
PerpSettlePnlInstruction {
|
||||
group,
|
||||
account_a: account_1,
|
||||
account_b: account_0,
|
||||
perp_market,
|
||||
oracle: tokens[0].oracle,
|
||||
quote_bank: tokens[0].bank,
|
||||
max_settle_amount: u64::MAX,
|
||||
},
|
||||
|
@ -472,11 +390,9 @@ async fn test_perp_settle_pnl() -> Result<(), TransportError> {
|
|||
let result = send_tx(
|
||||
solana,
|
||||
PerpSettlePnlInstruction {
|
||||
group,
|
||||
account_a: account_0,
|
||||
account_b: account_1,
|
||||
perp_market,
|
||||
oracle: tokens[0].oracle,
|
||||
quote_bank: tokens[0].bank,
|
||||
max_settle_amount: u64::MAX,
|
||||
},
|
||||
|
@ -525,11 +441,9 @@ async fn test_perp_settle_pnl() -> Result<(), TransportError> {
|
|||
send_tx(
|
||||
solana,
|
||||
PerpSettlePnlInstruction {
|
||||
group,
|
||||
account_a: account_0,
|
||||
account_b: account_1,
|
||||
perp_market,
|
||||
oracle: tokens[0].oracle,
|
||||
quote_bank: tokens[0].bank,
|
||||
max_settle_amount: partial_settle_amount,
|
||||
},
|
||||
|
@ -590,11 +504,9 @@ async fn test_perp_settle_pnl() -> Result<(), TransportError> {
|
|||
send_tx(
|
||||
solana,
|
||||
PerpSettlePnlInstruction {
|
||||
group,
|
||||
account_a: account_0,
|
||||
account_b: account_1,
|
||||
perp_market,
|
||||
oracle: tokens[0].oracle,
|
||||
quote_bank: tokens[0].bank,
|
||||
max_settle_amount: u64::MAX,
|
||||
},
|
||||
|
@ -685,11 +597,9 @@ async fn test_perp_settle_pnl() -> Result<(), TransportError> {
|
|||
send_tx(
|
||||
solana,
|
||||
PerpSettlePnlInstruction {
|
||||
group,
|
||||
account_a: account_1,
|
||||
account_b: account_0,
|
||||
perp_market,
|
||||
oracle: tokens[0].oracle,
|
||||
quote_bank: tokens[0].bank,
|
||||
max_settle_amount: u64::MAX,
|
||||
},
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
#![cfg(all(feature = "test-bpf"))]
|
||||
|
||||
use anchor_lang::prelude::ErrorCode;
|
||||
use fixed::types::I80F48;
|
||||
use mango_v4::{error::MangoError, state::*};
|
||||
use program_test::*;
|
||||
|
@ -134,36 +133,13 @@ async fn test_perp_settle_fees() -> Result<(), TransportError> {
|
|||
//
|
||||
// TEST: Create a perp market
|
||||
//
|
||||
let mango_v4::accounts::PerpCreateMarket {
|
||||
perp_market,
|
||||
asks,
|
||||
bids,
|
||||
event_queue,
|
||||
..
|
||||
} = send_tx(
|
||||
let mango_v4::accounts::PerpCreateMarket { perp_market, .. } = send_tx(
|
||||
solana,
|
||||
PerpCreateMarketInstruction {
|
||||
group,
|
||||
admin,
|
||||
oracle: tokens[0].oracle,
|
||||
asks: context
|
||||
.solana
|
||||
.create_account_for_type::<BookSide>(&mango_v4::id())
|
||||
.await,
|
||||
bids: context
|
||||
.solana
|
||||
.create_account_for_type::<BookSide>(&mango_v4::id())
|
||||
.await,
|
||||
event_queue: {
|
||||
context
|
||||
.solana
|
||||
.create_account_for_type::<EventQueue>(&mango_v4::id())
|
||||
.await
|
||||
},
|
||||
payer,
|
||||
perp_market_index: 0,
|
||||
base_token_index: tokens[0].index,
|
||||
base_token_decimals: tokens[0].mint.decimals,
|
||||
quote_lot_size: 10,
|
||||
base_lot_size: 100,
|
||||
maint_asset_weight: 0.975,
|
||||
|
@ -173,6 +149,7 @@ async fn test_perp_settle_fees() -> Result<(), TransportError> {
|
|||
liquidation_fee: 0.012,
|
||||
maker_fee: 0.0002,
|
||||
taker_fee: 0.000,
|
||||
..PerpCreateMarketInstruction::with_new_book_and_queue(&solana, &tokens[0]).await
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
@ -189,25 +166,8 @@ async fn test_perp_settle_fees() -> Result<(), TransportError> {
|
|||
PerpCreateMarketInstruction {
|
||||
group,
|
||||
admin,
|
||||
oracle: tokens[1].oracle,
|
||||
asks: context
|
||||
.solana
|
||||
.create_account_for_type::<BookSide>(&mango_v4::id())
|
||||
.await,
|
||||
bids: context
|
||||
.solana
|
||||
.create_account_for_type::<BookSide>(&mango_v4::id())
|
||||
.await,
|
||||
event_queue: {
|
||||
context
|
||||
.solana
|
||||
.create_account_for_type::<EventQueue>(&mango_v4::id())
|
||||
.await
|
||||
},
|
||||
payer,
|
||||
perp_market_index: 1,
|
||||
base_token_index: tokens[1].index,
|
||||
base_token_decimals: tokens[1].mint.decimals,
|
||||
quote_lot_size: 10,
|
||||
base_lot_size: 100,
|
||||
maint_asset_weight: 0.975,
|
||||
|
@ -217,6 +177,7 @@ async fn test_perp_settle_fees() -> Result<(), TransportError> {
|
|||
liquidation_fee: 0.012,
|
||||
maker_fee: 0.0002,
|
||||
taker_fee: 0.000,
|
||||
..PerpCreateMarketInstruction::with_new_book_and_queue(&solana, &tokens[1]).await
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
@ -247,13 +208,8 @@ async fn test_perp_settle_fees() -> Result<(), TransportError> {
|
|||
send_tx(
|
||||
solana,
|
||||
PerpPlaceOrderInstruction {
|
||||
group,
|
||||
account: account_0,
|
||||
perp_market,
|
||||
asks,
|
||||
bids,
|
||||
event_queue,
|
||||
oracle: tokens[0].oracle,
|
||||
owner,
|
||||
side: Side::Bid,
|
||||
price_lots,
|
||||
|
@ -268,13 +224,8 @@ async fn test_perp_settle_fees() -> Result<(), TransportError> {
|
|||
send_tx(
|
||||
solana,
|
||||
PerpPlaceOrderInstruction {
|
||||
group,
|
||||
account: account_1,
|
||||
perp_market,
|
||||
asks,
|
||||
bids,
|
||||
event_queue,
|
||||
oracle: tokens[0].oracle,
|
||||
owner,
|
||||
side: Side::Ask,
|
||||
price_lots,
|
||||
|
@ -289,9 +240,7 @@ async fn test_perp_settle_fees() -> Result<(), TransportError> {
|
|||
send_tx(
|
||||
solana,
|
||||
PerpConsumeEventsInstruction {
|
||||
group,
|
||||
perp_market,
|
||||
event_queue,
|
||||
mango_accounts: vec![account_0, account_1],
|
||||
},
|
||||
)
|
||||
|
@ -313,10 +262,8 @@ async fn test_perp_settle_fees() -> Result<(), TransportError> {
|
|||
let result = send_tx(
|
||||
solana,
|
||||
PerpSettleFeesInstruction {
|
||||
group,
|
||||
account: account_0,
|
||||
perp_market,
|
||||
oracle: tokens[0].oracle,
|
||||
quote_bank: tokens[1].bank,
|
||||
max_settle_amount: u64::MAX,
|
||||
},
|
||||
|
@ -329,34 +276,12 @@ async fn test_perp_settle_fees() -> Result<(), TransportError> {
|
|||
"Bank must be valid for quote currency".to_string(),
|
||||
);
|
||||
|
||||
// Oracle must be valid for the perp market
|
||||
let result = send_tx(
|
||||
solana,
|
||||
PerpSettleFeesInstruction {
|
||||
group,
|
||||
account: account_0,
|
||||
perp_market,
|
||||
oracle: tokens[1].oracle, // Using oracle for token 1 not 0
|
||||
quote_bank: tokens[0].bank,
|
||||
max_settle_amount: u64::MAX,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_mango_error(
|
||||
&result,
|
||||
ErrorCode::ConstraintHasOne.into(),
|
||||
"Oracle must be valid for perp market".to_string(),
|
||||
);
|
||||
|
||||
// Cannot settle position that does not exist
|
||||
let result = send_tx(
|
||||
solana,
|
||||
PerpSettleFeesInstruction {
|
||||
group,
|
||||
account: account_1,
|
||||
perp_market: perp_market_2,
|
||||
oracle: tokens[1].oracle,
|
||||
quote_bank: tokens[0].bank,
|
||||
max_settle_amount: u64::MAX,
|
||||
},
|
||||
|
@ -373,10 +298,8 @@ async fn test_perp_settle_fees() -> Result<(), TransportError> {
|
|||
let result = send_tx(
|
||||
solana,
|
||||
PerpSettleFeesInstruction {
|
||||
group,
|
||||
account: account_1,
|
||||
perp_market: perp_market,
|
||||
oracle: tokens[0].oracle,
|
||||
quote_bank: tokens[0].bank,
|
||||
max_settle_amount: 0,
|
||||
},
|
||||
|
@ -423,10 +346,8 @@ async fn test_perp_settle_fees() -> Result<(), TransportError> {
|
|||
let result = send_tx(
|
||||
solana,
|
||||
PerpSettleFeesInstruction {
|
||||
group,
|
||||
account: account_0,
|
||||
perp_market,
|
||||
oracle: tokens[0].oracle,
|
||||
quote_bank: tokens[0].bank,
|
||||
max_settle_amount: u64::MAX,
|
||||
},
|
||||
|
@ -509,10 +430,8 @@ async fn test_perp_settle_fees() -> Result<(), TransportError> {
|
|||
send_tx(
|
||||
solana,
|
||||
PerpSettleFeesInstruction {
|
||||
group,
|
||||
account: account_1,
|
||||
perp_market,
|
||||
oracle: tokens[0].oracle,
|
||||
quote_bank: tokens[0].bank,
|
||||
max_settle_amount: partial_settle_amount,
|
||||
},
|
||||
|
@ -565,10 +484,8 @@ async fn test_perp_settle_fees() -> Result<(), TransportError> {
|
|||
send_tx(
|
||||
solana,
|
||||
PerpSettleFeesInstruction {
|
||||
group,
|
||||
account: account_1,
|
||||
perp_market,
|
||||
oracle: tokens[0].oracle,
|
||||
quote_bank: tokens[0].bank,
|
||||
max_settle_amount: u64::MAX,
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue