liq_token_with_token: Correctly adjust health cache for dusting

Previously the health cache adjustments didn't take into account that
positions between 0 and 1 native token would be dusted to 0.

This makes a difference for valuable tokens with few decimals. For
example 1 BTC-native token is currently worth 500k SOL-native tokens.
That means 0.9 BTC-native would easily be enough collateral for a 10k
SOL-native borrow -- but if the 0.9 get dusted, then the whole account
is bankrupt instead.
This commit is contained in:
Christian Kamm 2022-08-09 15:47:54 +02:00
parent 3139b0816a
commit e8479e2e7c
2 changed files with 81 additions and 5 deletions

View File

@ -85,8 +85,8 @@ pub fn liq_token_with_token(
// The main complication here is that we can't keep the liqee_asset_position and liqee_liab_position
// borrows alive at the same time. Possibly adding get_mut_pair() would be helpful.
let (liqee_asset_position, liqee_asset_raw_index) = liqee.token_get(asset_token_index)?;
let liqee_assets_native = liqee_asset_position.native(asset_bank);
require!(liqee_assets_native.is_positive(), MangoError::SomeError);
let liqee_asset_native = liqee_asset_position.native(asset_bank);
require!(liqee_asset_native.is_positive(), MangoError::SomeError);
let (liqee_liab_position, liqee_liab_raw_index) = liqee.token_get(liab_token_index)?;
let liqee_liab_native = liqee_liab_position.native(liab_bank);
@ -115,7 +115,7 @@ pub fn liq_token_with_token(
/ (liab_price * init_liab_weight - init_asset_weight * liab_price_adjusted));
// How much liab can we get at most for the asset balance?
let liab_possible = cm!(liqee_assets_native * asset_price / liab_price_adjusted);
let liab_possible = cm!(liqee_asset_native * asset_price / liab_price_adjusted);
// The amount of liab native tokens we will transfer
let liab_transfer = min(
@ -135,6 +135,7 @@ pub fn liq_token_with_token(
liqor.token_get_mut_or_create(liab_token_index)?;
let liqor_liab_active = liab_bank.withdraw_with_fee(liqor_liab_position, liab_transfer)?;
let liqor_liab_position_indexed = liqor_liab_position.indexed_position;
let liqee_liab_native_after = liqee_liab_position.native(&liab_bank);
let (liqor_asset_position, liqor_asset_raw_index, _) =
liqor.token_get_mut_or_create(asset_token_index)?;
@ -145,10 +146,17 @@ pub fn liq_token_with_token(
let liqee_asset_active =
asset_bank.withdraw_without_fee(liqee_asset_position, asset_transfer)?;
let liqee_asset_position_indexed = liqee_asset_position.indexed_position;
let liqee_assets_native_after = liqee_asset_position.native(&asset_bank);
// Update the health cache
liqee_health_cache.adjust_token_balance(liab_token_index, liab_transfer)?;
liqee_health_cache.adjust_token_balance(asset_token_index, -asset_transfer)?;
liqee_health_cache.adjust_token_balance(
liab_token_index,
cm!(liqee_liab_native_after - liqee_liab_native),
)?;
liqee_health_cache.adjust_token_balance(
asset_token_index,
cm!(liqee_assets_native_after - liqee_asset_native),
)?;
msg!(
"liquidated {} liab for {} asset",

View File

@ -505,5 +505,73 @@ async fn test_liq_tokens_with_token() -> Result<(), TransportError> {
assert!(!liqee.being_liquidated());
assert!(!liqee.is_bankrupt());
//
// TEST: bankruptcy when collateral is dusted
//
// Setup: make collateral really valueable, remove nearly all of it
send_tx(
solana,
StubOracleSetInstruction {
group,
admin,
mint: collateral_token1.mint.pubkey,
payer,
price: "100000.0",
},
)
.await
.unwrap();
send_tx(
solana,
TokenWithdrawInstruction {
amount: (account_position(solana, account, collateral_token1.bank).await) as u64 - 1,
allow_borrow: false,
account,
owner,
token_account: payer_mint_accounts[2],
bank_index: 0,
},
)
.await
.unwrap();
// Setup: reduce collateral value to trigger liquidatability
// We have -93 borrows, so -93*2*1.4 = -260.4 health from that
// And 1-2 collateral, so max 2*0.6*X health; say X=150 for max 180 health
send_tx(
solana,
StubOracleSetInstruction {
group,
admin,
mint: collateral_token1.mint.pubkey,
payer,
price: "150.0",
},
)
.await
.unwrap();
send_tx(
solana,
LiqTokenWithTokenInstruction {
liqee: account,
liqor: vault_account,
liqor_owner: owner,
asset_token_index: collateral_token1.index,
liab_token_index: borrow_token1.index,
max_liab_transfer: I80F48::from_num(10001.0),
asset_bank_index: 0,
liab_bank_index: 0,
},
)
.await
.unwrap();
// Liqee's remaining collateral got dusted, only borrows remain: bankrupt
let liqee = get_mango_account(solana, account).await;
assert_eq!(liqee.token_iter_active().count(), 1);
assert!(liqee.is_bankrupt());
assert!(liqee.being_liquidated());
Ok(())
}