Tests: PerpLiqBaseToken tests

This commit is contained in:
Christian Kamm 2022-09-09 10:47:55 +02:00
parent 45025a1145
commit 5572242e6d
3 changed files with 352 additions and 7 deletions

View File

@ -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);

View File

@ -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 {

View File

@ -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(())
}