Fix perp liq computation; doc liq fee in token liq
This commit is contained in:
parent
524fe110e3
commit
9757f7a509
|
@ -106,7 +106,8 @@ pub fn perp_liq_base_position(
|
|||
|
||||
// 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 {
|
||||
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"
|
||||
|
@ -114,10 +115,10 @@ pub fn perp_liq_base_position(
|
|||
|
||||
// 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 quote_init_asset_weight = I80F48::ONE;
|
||||
let fee_factor = cm!(I80F48::ONE - perp_market.liquidation_fee);
|
||||
let health_per_lot = cm!(price_per_lot
|
||||
* (quote_asset_weight * (I80F48::ONE - perp_market.liquidation_fee)
|
||||
- perp_market.init_asset_weight));
|
||||
* (-perp_market.init_asset_weight + quote_init_asset_weight * fee_factor));
|
||||
|
||||
// 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)
|
||||
|
@ -130,9 +131,7 @@ pub fn perp_liq_base_position(
|
|||
.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));
|
||||
let quote_transfer = cm!(-I80F48::from(base_transfer) * price_per_lot * fee_factor);
|
||||
|
||||
(base_transfer, quote_transfer) // base > 0, quote < 0
|
||||
} else {
|
||||
|
@ -144,10 +143,10 @@ pub fn perp_liq_base_position(
|
|||
|
||||
// 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 quote_init_liab_weight = I80F48::ONE;
|
||||
let fee_factor = cm!(I80F48::ONE + perp_market.liquidation_fee);
|
||||
let health_per_lot = cm!(price_per_lot
|
||||
* (perp_market.init_liab_weight * (I80F48::ONE + perp_market.liquidation_fee)
|
||||
- quote_liab_weight));
|
||||
* (perp_market.init_liab_weight - quote_init_liab_weight * fee_factor));
|
||||
|
||||
// (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)
|
||||
|
@ -160,9 +159,7 @@ pub fn perp_liq_base_position(
|
|||
.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));
|
||||
let quote_transfer = cm!(-I80F48::from(base_transfer) * price_per_lot * fee_factor);
|
||||
|
||||
(base_transfer, quote_transfer) // base < 0, quote > 0
|
||||
};
|
||||
|
|
|
@ -101,9 +101,18 @@ pub fn token_liq_with_token(
|
|||
let liqee_liab_native = liqee_liab_position.native(liab_bank);
|
||||
require!(liqee_liab_native.is_negative(), MangoError::SomeError);
|
||||
|
||||
// TODO why sum of both tokens liquidation fees? Add comment
|
||||
let fee_factor = I80F48::ONE + asset_bank.liquidation_fee + liab_bank.liquidation_fee;
|
||||
let liab_price_adjusted = liab_price * fee_factor;
|
||||
// Liquidation fees work by giving the liqor more assets than the oracle price would
|
||||
// indicate. Specifically we choose
|
||||
// assets =
|
||||
// liabs * liab_price/asset_price * (1 + liab_liq_fee + asset_liq_fee)
|
||||
// Which means that we use a increased liab price and reduced asset price for the conversion.
|
||||
// It would be more fully correct to use (1+liab_liq_fee)*(1+asset_liq_fee), but for small
|
||||
// fee amounts that is nearly identical.
|
||||
// For simplicity we write
|
||||
// assets = liabs * liab_price / asset_price * fee_factor
|
||||
// assets = liabs * liab_price_adjusted / asset_price
|
||||
let fee_factor = cm!(I80F48::ONE + asset_bank.liquidation_fee + liab_bank.liquidation_fee);
|
||||
let liab_price_adjusted = cm!(liab_price * fee_factor);
|
||||
|
||||
let init_asset_weight = asset_bank.init_asset_weight;
|
||||
let init_liab_weight = liab_bank.init_liab_weight;
|
||||
|
|
|
@ -296,20 +296,6 @@ async fn test_liq_perps_base_position_and_bankruptcy() -> Result<(), TransportEr
|
|||
)
|
||||
.await;
|
||||
|
||||
send_tx(
|
||||
solana,
|
||||
TokenDepositInstruction {
|
||||
amount: 1,
|
||||
account,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[1],
|
||||
token_authority: payer,
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
account
|
||||
};
|
||||
let account_0 = make_account(0).await;
|
||||
|
@ -365,7 +351,7 @@ async fn test_liq_perps_base_position_and_bankruptcy() -> Result<(), TransportEr
|
|||
//
|
||||
// SETUP: Change the oracle to make health go negative for account_0
|
||||
//
|
||||
set_bank_stub_oracle_price(solana, group, base_token, admin, 0.5).await;
|
||||
set_bank_stub_oracle_price(solana, group, base_token, admin, 0.6).await;
|
||||
|
||||
// verify health is bad: can't withdraw
|
||||
assert!(send_tx(
|
||||
|
@ -375,7 +361,7 @@ async fn test_liq_perps_base_position_and_bankruptcy() -> Result<(), TransportEr
|
|||
allow_borrow: false,
|
||||
account: account_0,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[1],
|
||||
token_account: payer_mint_accounts[0],
|
||||
bank_index: 0,
|
||||
}
|
||||
)
|
||||
|
@ -383,7 +369,7 @@ async fn test_liq_perps_base_position_and_bankruptcy() -> Result<(), TransportEr
|
|||
.is_err());
|
||||
|
||||
//
|
||||
// TEST: Liquidate base position
|
||||
// TEST: Liquidate base position with limit
|
||||
//
|
||||
send_tx(
|
||||
solana,
|
||||
|
@ -398,7 +384,7 @@ async fn test_liq_perps_base_position_and_bankruptcy() -> Result<(), TransportEr
|
|||
.await
|
||||
.unwrap();
|
||||
|
||||
let liq_amount = 10.0 * 100.0 * 0.5 * (1.0 - 0.05);
|
||||
let liq_amount = 10.0 * 100.0 * 0.6 * (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(
|
||||
|
@ -414,10 +400,57 @@ async fn test_liq_perps_base_position_and_bankruptcy() -> Result<(), TransportEr
|
|||
0.1
|
||||
));
|
||||
|
||||
//
|
||||
// TEST: Liquidate base position max
|
||||
//
|
||||
send_tx(
|
||||
solana,
|
||||
PerpLiqBasePositionInstruction {
|
||||
liqor,
|
||||
liqor_owner: owner,
|
||||
liqee: account_0,
|
||||
perp_market,
|
||||
max_base_transfer: i64::MAX,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let liq_amount_2 = 4.0 * 100.0 * 0.6 * (1.0 - 0.05);
|
||||
let liqor_data = solana.get_account::<MangoAccount>(liqor).await;
|
||||
assert_eq!(liqor_data.perps[0].base_position_lots(), 10 + 4);
|
||||
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_0).await;
|
||||
assert_eq!(liqee_data.perps[0].base_position_lots(), 6);
|
||||
assert!(assert_equal(
|
||||
liqee_data.perps[0].quote_position_native(),
|
||||
-20.0 * 100.0 + liq_amount + liq_amount_2,
|
||||
0.1
|
||||
));
|
||||
|
||||
// verify health is good again
|
||||
send_tx(
|
||||
solana,
|
||||
TokenWithdrawInstruction {
|
||||
amount: 1,
|
||||
allow_borrow: false,
|
||||
account: account_0,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[0],
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
//
|
||||
// SETUP: Change the oracle to make health go negative for account_1
|
||||
//
|
||||
set_bank_stub_oracle_price(solana, group, base_token, admin, 2.0).await;
|
||||
set_bank_stub_oracle_price(solana, group, base_token, admin, 1.3).await;
|
||||
|
||||
// verify health is bad: can't withdraw
|
||||
assert!(send_tx(
|
||||
|
@ -427,7 +460,7 @@ async fn test_liq_perps_base_position_and_bankruptcy() -> Result<(), TransportEr
|
|||
allow_borrow: false,
|
||||
account: account_1,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[1],
|
||||
token_account: payer_mint_accounts[0],
|
||||
bank_index: 0,
|
||||
}
|
||||
)
|
||||
|
@ -437,6 +470,38 @@ async fn test_liq_perps_base_position_and_bankruptcy() -> Result<(), TransportEr
|
|||
//
|
||||
// TEST: Liquidate base position
|
||||
//
|
||||
send_tx(
|
||||
solana,
|
||||
PerpLiqBasePositionInstruction {
|
||||
liqor,
|
||||
liqor_owner: owner,
|
||||
liqee: account_1,
|
||||
perp_market,
|
||||
max_base_transfer: -10,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let liq_amount_3 = 10.0 * 100.0 * 1.3 * (1.0 + 0.05);
|
||||
let liqor_data = solana.get_account::<MangoAccount>(liqor).await;
|
||||
assert_eq!(liqor_data.perps[0].base_position_lots(), 14 - 10);
|
||||
assert!(assert_equal(
|
||||
liqor_data.perps[0].quote_position_native(),
|
||||
-liq_amount - liq_amount_2 + liq_amount_3,
|
||||
0.1
|
||||
));
|
||||
let liqee_data = solana.get_account::<MangoAccount>(account_1).await;
|
||||
assert_eq!(liqee_data.perps[0].base_position_lots(), -10);
|
||||
assert!(assert_equal(
|
||||
liqee_data.perps[0].quote_position_native(),
|
||||
20.0 * 100.0 - liq_amount_3,
|
||||
0.1
|
||||
));
|
||||
|
||||
//
|
||||
// TEST: Liquidate base position max
|
||||
//
|
||||
send_tx(
|
||||
solana,
|
||||
PerpLiqBasePositionInstruction {
|
||||
|
@ -450,19 +515,71 @@ async fn test_liq_perps_base_position_and_bankruptcy() -> Result<(), TransportEr
|
|||
.await
|
||||
.unwrap();
|
||||
|
||||
let liq_amount_2 = 20.0 * 100.0 * 2.0 * (1.0 + 0.05);
|
||||
let liq_amount_4 = 5.0 * 100.0 * 1.3 * (1.0 + 0.05);
|
||||
let liqor_data = solana.get_account::<MangoAccount>(liqor).await;
|
||||
assert_eq!(liqor_data.perps[0].base_position_lots(), 10 - 20);
|
||||
assert_eq!(liqor_data.perps[0].base_position_lots(), 4 - 5);
|
||||
assert!(assert_equal(
|
||||
liqor_data.perps[0].quote_position_native(),
|
||||
-liq_amount + liq_amount_2,
|
||||
-liq_amount - liq_amount_2 + liq_amount_3 + liq_amount_4,
|
||||
0.1
|
||||
));
|
||||
let liqee_data = solana.get_account::<MangoAccount>(account_1).await;
|
||||
assert_eq!(liqee_data.perps[0].base_position_lots(), -5);
|
||||
assert!(assert_equal(
|
||||
liqee_data.perps[0].quote_position_native(),
|
||||
20.0 * 100.0 - liq_amount_3 - liq_amount_4,
|
||||
0.1
|
||||
));
|
||||
|
||||
// verify health is good again
|
||||
send_tx(
|
||||
solana,
|
||||
TokenWithdrawInstruction {
|
||||
amount: 1,
|
||||
allow_borrow: false,
|
||||
account: account_1,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[0],
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
//
|
||||
// SETUP: liquidate base position to 0, so bankruptcy can be tested
|
||||
//
|
||||
set_bank_stub_oracle_price(solana, group, base_token, admin, 2.0).await;
|
||||
|
||||
//
|
||||
// TEST: Liquidate base position max
|
||||
//
|
||||
send_tx(
|
||||
solana,
|
||||
PerpLiqBasePositionInstruction {
|
||||
liqor,
|
||||
liqor_owner: owner,
|
||||
liqee: account_1,
|
||||
perp_market,
|
||||
max_base_transfer: i64::MIN,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let liq_amount_5 = 5.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(), -1 - 5);
|
||||
assert!(assert_equal(
|
||||
liqor_data.perps[0].quote_position_native(),
|
||||
-liq_amount - liq_amount_2 + liq_amount_3 + liq_amount_4 + liq_amount_5,
|
||||
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(),
|
||||
20.0 * 100.0 - liq_amount_2,
|
||||
20.0 * 100.0 - liq_amount_3 - liq_amount_4 - liq_amount_5,
|
||||
0.1
|
||||
));
|
||||
|
||||
|
@ -482,6 +599,24 @@ async fn test_liq_perps_base_position_and_bankruptcy() -> Result<(), TransportEr
|
|||
.await
|
||||
.is_err());
|
||||
|
||||
//
|
||||
// SETUP: We want pnl settling to cause a negative quote position,
|
||||
// thus we deposit some base token collateral
|
||||
//
|
||||
send_tx(
|
||||
solana,
|
||||
TokenDepositInstruction {
|
||||
amount: 1,
|
||||
account: account_1,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[1],
|
||||
token_authority: payer,
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
//
|
||||
// TEST: Can settle-pnl even though health is negative
|
||||
//
|
||||
|
@ -499,8 +634,9 @@ async fn test_liq_perps_base_position_and_bankruptcy() -> Result<(), TransportEr
|
|||
.await
|
||||
.unwrap();
|
||||
|
||||
let liqee_settle_health_before = 1000.0 + 1.0 * 2.0 * 0.8;
|
||||
let remaining_pnl = 20.0 * 100.0 - liq_amount_2 + liqee_settle_health_before;
|
||||
let liqee_settle_health_before = 999.0 + 1.0 * 2.0 * 0.8;
|
||||
let remaining_pnl =
|
||||
20.0 * 100.0 - liq_amount_3 - liq_amount_4 - liq_amount_5 + liqee_settle_health_before;
|
||||
assert!(remaining_pnl < 0.0);
|
||||
let liqee_data = solana.get_account::<MangoAccount>(account_1).await;
|
||||
assert_eq!(liqee_data.perps[0].base_position_lots(), 0);
|
||||
|
|
Loading…
Reference in New Issue