Tests: PerpLiqBaseToken tests
This commit is contained in:
parent
45025a1145
commit
5572242e6d
|
@ -34,9 +34,6 @@ pub fn perp_liq_base_position(
|
|||
) -> Result<()> {
|
||||
let group_pk = &ctx.accounts.group.key();
|
||||
|
||||
let account_retriever = ScanningAccountRetriever::new(ctx.remaining_accounts, group_pk)
|
||||
.context("create account retriever")?;
|
||||
|
||||
let mut liqor = ctx.accounts.liqor.load_mut()?;
|
||||
// account constraint #1
|
||||
require!(
|
||||
|
@ -50,8 +47,12 @@ pub fn perp_liq_base_position(
|
|||
let mut liqee = ctx.accounts.liqee.load_mut()?;
|
||||
|
||||
// Initial liqee health check
|
||||
let mut liqee_health_cache = new_health_cache(&liqee.borrow(), &account_retriever)
|
||||
.context("create liqee health cache")?;
|
||||
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,
|
||||
|
@ -124,7 +125,7 @@ pub fn perp_liq_base_position(
|
|||
.max(0);
|
||||
let quote_transfer = cm!(-I80F48::from(base_transfer)
|
||||
* price_per_lot
|
||||
* (I80F48::ONE + perp_market.liquidation_fee));
|
||||
* (I80F48::ONE - perp_market.liquidation_fee));
|
||||
|
||||
(base_transfer, quote_transfer) // base > 0, quote < 0
|
||||
} else {
|
||||
|
@ -153,7 +154,7 @@ pub fn perp_liq_base_position(
|
|||
.min(0);
|
||||
let quote_transfer = cm!(-I80F48::from(base_transfer)
|
||||
* price_per_lot
|
||||
* (I80F48::ONE - perp_market.liquidation_fee));
|
||||
* (I80F48::ONE + perp_market.liquidation_fee));
|
||||
|
||||
(base_transfer, quote_transfer) // base < 0, quote > 0
|
||||
};
|
||||
|
@ -182,6 +183,8 @@ pub fn perp_liq_base_position(
|
|||
|
||||
// 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);
|
||||
|
|
|
@ -2678,6 +2678,65 @@ impl ClientInstruction for PerpLiqForceCancelOrdersInstruction {
|
|||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
|
|
|
@ -11,6 +11,8 @@ 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();
|
||||
|
@ -180,3 +182,284 @@ async fn test_liq_perps_force_cancel() -> Result<(), TransportError> {
|
|||
|
||||
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(())
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue