Remove is_bankrupt

Instead, check for any liquidatable assets in liq_token_bankruptcy.

Bankrupt accounts may use the same operations as any other
negative-health account.
This commit is contained in:
Christian Kamm 2022-08-11 18:15:47 +02:00
parent 52660f1476
commit 92a37f23ed
26 changed files with 65 additions and 120 deletions

View File

@ -108,11 +108,12 @@ pub fn maybe_liquidate_account(
let quote_token_index = 0;
let account = account_fetcher.fetch_mango_account(pubkey)?;
let maint_health = new_health_cache_(&mango_client.context, account_fetcher, &account)
.expect("always ok")
.health(HealthType::Maint);
let health_cache =
new_health_cache_(&mango_client.context, account_fetcher, &account).expect("always ok");
let maint_health = health_cache.health(HealthType::Maint);
let is_bankrupt = !health_cache.has_liquidatable_assets();
if maint_health >= 0 && !account.is_bankrupt() {
if maint_health >= 0 && !is_bankrupt {
return Ok(false);
}
@ -121,16 +122,17 @@ pub fn maybe_liquidate_account(
pubkey,
account.fixed.owner,
maint_health,
account.is_bankrupt(),
is_bankrupt,
);
// Fetch a fresh account and re-compute
// This is -- unfortunately -- needed because the websocket streams seem to not
// be great at providing timely updates to the account data.
let account = account_fetcher.fetch_fresh_mango_account(pubkey)?;
let maint_health = new_health_cache_(&mango_client.context, account_fetcher, &account)
.expect("always ok")
.health(HealthType::Maint);
let health_cache =
new_health_cache_(&mango_client.context, account_fetcher, &account).expect("always ok");
let maint_health = health_cache.health(HealthType::Maint);
let is_bankrupt = !health_cache.has_liquidatable_assets();
// find asset and liab tokens
let mut tokens = account
@ -172,7 +174,7 @@ pub fn maybe_liquidate_account(
};
// try liquidating
let txsig = if account.is_bankrupt() {
let txsig = if is_bankrupt {
if tokens.is_empty() {
anyhow::bail!("mango account {}, is bankrupt has no active tokens", pubkey);
}

View File

@ -33,7 +33,6 @@ pub fn account_close(ctx: Context<AccountClose>) -> Result<()> {
// don't perform checks if group is just testing
if group.testing == 0 {
require!(!account.fixed.being_liquidated(), MangoError::SomeError);
require!(!account.fixed.is_bankrupt(), MangoError::SomeError);
require_eq!(account.fixed.delegate, Pubkey::default());
for ele in account.token_iter() {
require_eq!(ele.is_active(), false);

View File

@ -48,7 +48,6 @@ pub fn account_create(
account.fixed.bump = *ctx.bumps.get("account").ok_or(MangoError::SomeError)?;
account.fixed.delegate = Pubkey::default();
account.fixed.set_being_liquidated(false);
account.fixed.set_bankrupt(false);
account.expand_dynamic_content(token_count, serum3_count, perp_count, perp_oo_count)?;

View File

@ -184,8 +184,6 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>(
let mut account = ctx.accounts.account.load_mut()?;
let group = account.fixed.group;
require!(!account.fixed.is_bankrupt(), MangoError::IsBankrupt);
// Find index at which vaults start
let vaults_index = ctx
.remaining_accounts

View File

@ -90,23 +90,27 @@ pub fn liq_token_bankruptcy(
.is_owner_or_delegate(ctx.accounts.liqor_owner.key()),
MangoError::SomeError
);
require!(!liqor.fixed.is_bankrupt(), MangoError::IsBankrupt);
let mut liqee = ctx.accounts.liqee.load_mut()?;
require!(liqee.fixed.is_bankrupt(), MangoError::IsBankrupt);
let liab_bank = bank_ais[0].load::<Bank>()?;
let liab_deposit_index = liab_bank.deposit_index;
let (liqee_liab, liqee_raw_token_index) = liqee.token_get_mut(liab_token_index)?;
let mut remaining_liab_loss = -liqee_liab.native(&liab_bank);
require_gt!(remaining_liab_loss, I80F48::ZERO);
drop(liab_bank);
let mut account_retriever = ScanningAccountRetriever::new(health_ais, group_pk)?;
// find insurance transfer amount
let mut liqee = ctx.accounts.liqee.load_mut()?;
let mut liqee_health_cache = new_health_cache(&liqee.borrow(), &account_retriever)
.context("create liqee health cache")?;
require!(
!liqee_health_cache.has_liquidatable_assets(),
MangoError::IsNotBankrupt
);
liqee.fixed.set_being_liquidated(true);
let (liab_bank, liab_price, opt_quote_bank_and_price) =
account_retriever.banks_mut_and_oracles(liab_token_index, QUOTE_TOKEN_INDEX)?;
let liab_deposit_index = liab_bank.deposit_index;
let (liqee_liab, liqee_raw_token_index) = liqee.token_get_mut(liab_token_index)?;
let initial_liab_native = liqee_liab.native(&liab_bank);
let mut remaining_liab_loss = -initial_liab_native;
require_gt!(remaining_liab_loss, I80F48::ZERO);
// find insurance transfer amount
let liab_fee_factor = if liab_token_index == QUOTE_TOKEN_INDEX {
I80F48::ONE
} else {
@ -226,14 +230,16 @@ pub fn liq_token_bankruptcy(
liqee_liab_active = false;
}
// If the account has no more borrows then it's no longer bankrupt
// and should (always?) no longer be liquidated.
let account_retriever = ScanningAccountRetriever::new(health_ais, group_pk)?;
let liqee_health_cache = new_health_cache(&liqee.borrow(), &account_retriever)?;
liqee.fixed.set_bankrupt(liqee_health_cache.has_borrows());
if !liqee.is_bankrupt() && liqee_health_cache.health(HealthType::Init) >= 0 {
liqee.fixed.set_being_liquidated(false);
}
let liab_bank = bank_ais[0].load::<Bank>()?;
let end_liab_native = liqee_liab.native(&liab_bank);
liqee_health_cache
.adjust_token_balance(liab_token_index, cm!(end_liab_native - initial_liab_native))?;
// Check liqee health again
let liqee_init_health = liqee_health_cache.health(HealthType::Init);
liqee
.fixed
.maybe_recover_from_being_liquidated(liqee_init_health);
if !liqee_liab_active {
liqee.token_deactivate(liqee_raw_token_index);

View File

@ -47,10 +47,8 @@ pub fn liq_token_with_token(
.is_owner_or_delegate(ctx.accounts.liqor_owner.key()),
MangoError::SomeError
);
require!(!liqor.fixed.is_bankrupt(), MangoError::IsBankrupt);
let mut liqee = ctx.accounts.liqee.load_mut()?;
require!(!liqee.fixed.is_bankrupt(), MangoError::IsBankrupt);
// Initial liqee health check
let mut liqee_health_cache = new_health_cache(&liqee.borrow(), &account_retriever)
@ -236,18 +234,10 @@ pub fn liq_token_with_token(
}
// Check liqee health again
let maint_health = liqee_health_cache.health(HealthType::Maint);
if maint_health < I80F48::ZERO {
liqee
.fixed
.set_bankrupt(!liqee_health_cache.has_liquidatable_assets());
} else {
let init_health = liqee_health_cache.health(HealthType::Init);
// this is equivalent to one native USDC or 1e-6 USDC
// This is used as threshold to flip flag instead of 0 because of dust issues
liqee.fixed.set_being_liquidated(init_health < -I80F48::ONE);
}
let liqee_init_health = liqee_health_cache.health(HealthType::Init);
liqee
.fixed
.maybe_recover_from_being_liquidated(liqee_init_health);
// Check liqor's health
let liqor_health = compute_health(&liqor.borrow(), HealthType::Init, &account_retriever)

View File

@ -31,8 +31,6 @@ pub fn perp_cancel_all_orders(ctx: Context<PerpCancelAllOrders>, limit: u8) -> R
MangoError::SomeError
);
require!(!account.fixed.is_bankrupt(), MangoError::IsBankrupt);
let mut perp_market = ctx.accounts.perp_market.load_mut()?;
let bids = ctx.accounts.bids.load_mut()?;
let asks = ctx.accounts.asks.load_mut()?;

View File

@ -36,8 +36,6 @@ pub fn perp_cancel_all_orders_by_side(
MangoError::SomeError
);
require!(!account.fixed.is_bankrupt(), MangoError::IsBankrupt);
let mut perp_market = ctx.accounts.perp_market.load_mut()?;
let bids = ctx.accounts.bids.load_mut()?;
let asks = ctx.accounts.asks.load_mut()?;

View File

@ -31,8 +31,6 @@ pub fn perp_cancel_order(ctx: Context<PerpCancelOrder>, order_id: i128) -> Resul
MangoError::SomeError
);
require!(!account.fixed.is_bankrupt(), MangoError::IsBankrupt);
let perp_market = ctx.accounts.perp_market.load_mut()?;
let bids = ctx.accounts.bids.load_mut()?;
let asks = ctx.accounts.asks.load_mut()?;

View File

@ -34,8 +34,6 @@ pub fn perp_cancel_order_by_client_order_id(
MangoError::SomeError
);
require!(!account.fixed.is_bankrupt(), MangoError::IsBankrupt);
let perp_market = ctx.accounts.perp_market.load_mut()?;
let bids = ctx.accounts.bids.load_mut()?;
let asks = ctx.accounts.asks.load_mut()?;

View File

@ -81,7 +81,6 @@ pub fn perp_place_order(
MangoError::SomeError
);
require!(!account.fixed.is_bankrupt(), MangoError::IsBankrupt);
let account_pk = ctx.accounts.account.key();
{

View File

@ -51,8 +51,6 @@ pub fn serum3_cancel_all_orders(ctx: Context<Serum3CancelAllOrders>, limit: u8)
MangoError::SomeError
);
require!(!account.fixed.is_bankrupt(), MangoError::IsBankrupt);
let serum_market = ctx.accounts.serum_market.load()?;
// Validate open_orders

View File

@ -64,8 +64,6 @@ pub fn serum3_cancel_order(
MangoError::SomeError
);
require!(!account.fixed.is_bankrupt(), MangoError::IsBankrupt);
// Validate open_orders
require!(
account

View File

@ -41,8 +41,6 @@ pub fn serum3_close_open_orders(ctx: Context<Serum3CloseOpenOrders>) -> Result<(
MangoError::SomeError
);
require!(!account.fixed.is_bankrupt(), MangoError::IsBankrupt);
let serum_market = ctx.accounts.serum_market.load()?;
// Validate open_orders

View File

@ -53,8 +53,6 @@ pub fn serum3_create_open_orders(ctx: Context<Serum3CreateOpenOrders>) -> Result
MangoError::SomeError
);
require!(!account.fixed.is_bankrupt(), MangoError::IsBankrupt);
let serum_account = account.serum3_create(serum_market.market_index)?;
serum_account.open_orders = ctx.accounts.open_orders.key();
serum_account.base_token_index = serum_market.base_token_index;

View File

@ -70,7 +70,6 @@ pub fn serum3_liq_force_cancel_orders(
//
{
let account = ctx.accounts.account.load()?;
require!(!account.fixed.is_bankrupt(), MangoError::IsBankrupt);
let serum_market = ctx.accounts.serum_market.load()?;
// Validate open_orders

View File

@ -168,7 +168,6 @@ pub fn serum3_place_order(
account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()),
MangoError::SomeError
);
require!(!account.fixed.is_bankrupt(), MangoError::IsBankrupt);
// Validate open_orders
require!(

View File

@ -78,8 +78,6 @@ pub fn serum3_settle_funds(ctx: Context<Serum3SettleFunds>) -> Result<()> {
MangoError::SomeError
);
require!(!account.fixed.is_bankrupt(), MangoError::IsBankrupt);
// Validate open_orders
require!(
account

View File

@ -58,7 +58,6 @@ pub fn token_deposit(ctx: Context<TokenDeposit>, amount: u64) -> Result<()> {
// Get the account's position for that token index
let mut account = ctx.accounts.account.load_mut()?;
require!(!account.fixed.is_bankrupt(), MangoError::IsBankrupt);
let (position, raw_token_index, active_token_index) =
account.token_get_mut_or_create(token_index)?;
@ -94,12 +93,11 @@ pub fn token_deposit(ctx: Context<TokenDeposit>, amount: u64) -> Result<()> {
//
// Health computation
// TODO: This will be used to disable is_bankrupt or being_liquidated
// when health recovers sufficiently
//
let health = compute_health(&account.borrow(), HealthType::Init, &retriever)
.context("post-deposit init health")?;
msg!("health: {}", health);
account.fixed.maybe_recover_from_being_liquidated(health);
//
// Deactivate the position only after the health check because the user passed in

View File

@ -61,7 +61,6 @@ pub fn token_withdraw(ctx: Context<TokenWithdraw>, amount: u64, allow_borrow: bo
// Get the account's position for that token index
let mut account = ctx.accounts.account.load_mut()?;
require!(!account.fixed.is_bankrupt(), MangoError::IsBankrupt);
let (position, raw_token_index, active_token_index) =
account.token_get_mut_or_create(token_index)?;

View File

@ -16,7 +16,7 @@ use crate::util::checked_math as cm;
use super::MangoAccountRef;
const BANKRUPTCY_DUST_THRESHOLD: I80F48 = I80F48!(0.000001);
const ONE_NATIVE_USDC_IN_USD: I80F48 = I80F48!(0.000001);
/// This trait abstracts how to find accounts needed for the health computation.
///
@ -565,13 +565,14 @@ impl HealthCache {
}
pub fn has_liquidatable_assets(&self) -> bool {
let spot_liquidatable = self.token_infos.iter().any(|ti| {
ti.balance > BANKRUPTCY_DUST_THRESHOLD || ti.serum3_max_reserved.is_positive()
});
let spot_liquidatable = self
.token_infos
.iter()
.any(|ti| ti.balance.is_positive() || ti.serum3_max_reserved.is_positive());
let perp_liquidatable = self
.perp_infos
.iter()
.any(|p| p.base != 0 || p.quote > BANKRUPTCY_DUST_THRESHOLD);
.any(|p| p.base != 0 || p.quote > ONE_NATIVE_USDC_IN_USD);
spot_liquidatable || perp_liquidatable
}

View File

@ -54,8 +54,7 @@ pub struct MangoAccount {
/// This account cannot open new positions or borrow until `init_health >= 0`
being_liquidated: u8,
/// This account cannot do anything except go through `resolve_bankruptcy`
is_bankrupt: u8,
padding5: u8,
pub bump: u8,
@ -97,7 +96,7 @@ impl Default for MangoAccount {
owner: Pubkey::default(),
delegate: Pubkey::default(),
being_liquidated: 0,
is_bankrupt: 0,
padding5: 0,
account_num: 0,
bump: 0,
padding: Default::default(),
@ -193,7 +192,7 @@ pub struct MangoAccountFixed {
pub delegate: Pubkey,
pub account_num: u32,
being_liquidated: u8,
is_bankrupt: u8,
padding2: u8,
pub bump: u8,
pub padding: [u8; 1],
pub net_deposits: f32,
@ -214,14 +213,6 @@ impl MangoAccountFixed {
self.owner == ix_signer || self.delegate == ix_signer
}
pub fn is_bankrupt(&self) -> bool {
self.is_bankrupt != 0
}
pub fn set_bankrupt(&mut self, b: bool) {
self.is_bankrupt = if b { 1 } else { 0 };
}
pub fn being_liquidated(&self) -> bool {
self.being_liquidated != 0
}
@ -229,6 +220,14 @@ impl MangoAccountFixed {
pub fn set_being_liquidated(&mut self, b: bool) {
self.being_liquidated = if b { 1 } else { 0 };
}
pub fn maybe_recover_from_being_liquidated(&mut self, init_health: I80F48) {
// This is used as threshold to flip flag instead of 0 because of dust issues
let one_native_usdc = I80F48::ONE;
if self.being_liquidated() && init_health > -one_native_usdc {
self.set_being_liquidated(false);
}
}
}
impl DynamicAccountType for MangoAccount {
@ -522,10 +521,6 @@ impl<
self.fixed().being_liquidated()
}
pub fn is_bankrupt(&self) -> bool {
self.fixed().is_bankrupt()
}
pub fn borrow(&self) -> DynamicAccountRef<MangoAccount> {
DynamicAccount {
header: self.header(),

View File

@ -220,7 +220,6 @@ async fn test_bankrupt_tokens_socialize_loss() -> Result<(), TransportError> {
);
let liqee = get_mango_account(solana, account).await;
assert!(liqee.being_liquidated());
assert!(!liqee.is_bankrupt());
// eat collateral2, leaving the account bankrupt
send_tx(
@ -246,7 +245,6 @@ async fn test_bankrupt_tokens_socialize_loss() -> Result<(), TransportError> {
);
let liqee = get_mango_account(solana, account).await;
assert!(liqee.being_liquidated());
assert!(liqee.is_bankrupt());
//
// TEST: socialize loss on borrow1 and 2
@ -272,7 +270,6 @@ async fn test_bankrupt_tokens_socialize_loss() -> Result<(), TransportError> {
);
let liqee = get_mango_account(solana, account).await;
assert!(liqee.being_liquidated());
assert!(liqee.is_bankrupt());
assert!(account_position_closed(solana, account, borrow_token1.bank).await);
// both bank's borrows were completely wiped: no one else borrowed
let borrow1_bank0: Bank = solana.get_account(borrow_token1.bank).await;
@ -299,7 +296,6 @@ async fn test_bankrupt_tokens_socialize_loss() -> Result<(), TransportError> {
);
let liqee = get_mango_account(solana, account).await;
assert!(!liqee.being_liquidated());
assert!(!liqee.is_bankrupt());
assert!(account_position_closed(solana, account, borrow_token2.bank).await);
Ok(())
@ -531,7 +527,6 @@ async fn test_bankrupt_tokens_insurance_fund() -> Result<(), TransportError> {
assert!(account_position_closed(solana, account, collateral_token1.bank).await);
let liqee = get_mango_account(solana, account).await;
assert!(liqee.being_liquidated());
assert!(!liqee.is_bankrupt());
// eat collateral2, leaving the account bankrupt
send_tx(
@ -552,7 +547,6 @@ async fn test_bankrupt_tokens_insurance_fund() -> Result<(), TransportError> {
assert!(account_position_closed(solana, account, collateral_token2.bank).await,);
let liqee = get_mango_account(solana, account).await;
assert!(liqee.being_liquidated());
assert!(liqee.is_bankrupt());
//
// TEST: use the insurance fund to liquidate borrow1 and borrow2
@ -577,7 +571,6 @@ async fn test_bankrupt_tokens_insurance_fund() -> Result<(), TransportError> {
.unwrap();
let liqee = get_mango_account(solana, account).await;
assert!(liqee.being_liquidated());
assert!(liqee.is_bankrupt());
assert!(account_position_closed(solana, account, borrow_token1.bank).await);
assert_eq!(
solana.token_account_balance(insurance_vault).await,
@ -610,7 +603,6 @@ async fn test_bankrupt_tokens_insurance_fund() -> Result<(), TransportError> {
.unwrap();
let liqee = get_mango_account(solana, account).await;
assert!(liqee.being_liquidated());
assert!(liqee.is_bankrupt());
assert!(account_position_closed(solana, account, borrow_token1.bank).await);
assert_eq!(
account_position(solana, account, borrow_token2.bank).await,
@ -645,7 +637,6 @@ async fn test_bankrupt_tokens_insurance_fund() -> Result<(), TransportError> {
.unwrap();
let liqee = get_mango_account(solana, account).await;
assert!(!liqee.being_liquidated());
assert!(!liqee.is_bankrupt());
assert!(account_position_closed(solana, account, borrow_token1.bank).await);
assert!(account_position_closed(solana, account, borrow_token2.bank).await);
assert_eq!(solana.token_account_balance(insurance_vault).await, 0);

View File

@ -406,7 +406,6 @@ async fn test_liq_tokens_with_token() -> Result<(), TransportError> {
assert!(account_position_closed(solana, account, collateral_token2.bank).await,);
let liqee = get_mango_account(solana, account).await;
assert!(liqee.being_liquidated());
assert!(!liqee.is_bankrupt());
//
// TEST: liquidate the remaining borrow2 against collateral1,
@ -436,7 +435,6 @@ async fn test_liq_tokens_with_token() -> Result<(), TransportError> {
);
let liqee = get_mango_account(solana, account).await;
assert!(liqee.being_liquidated());
assert!(!liqee.is_bankrupt());
//
// TEST: liquidate borrow1 with collateral1, but place a limit
@ -468,7 +466,6 @@ async fn test_liq_tokens_with_token() -> Result<(), TransportError> {
);
let liqee = get_mango_account(solana, account).await;
assert!(liqee.being_liquidated());
assert!(!liqee.is_bankrupt());
//
// TEST: liquidate borrow1 with collateral1, making the account healthy again
@ -503,7 +500,6 @@ async fn test_liq_tokens_with_token() -> Result<(), TransportError> {
);
let liqee = get_mango_account(solana, account).await;
assert!(!liqee.being_liquidated());
assert!(!liqee.is_bankrupt());
//
// TEST: bankruptcy when collateral is dusted
@ -567,11 +563,12 @@ async fn test_liq_tokens_with_token() -> Result<(), TransportError> {
.await
.unwrap();
// Liqee's remaining collateral got dusted, only borrows remain: bankrupt
// Liqee's remaining collateral got dusted, only borrows remain
// but the borrow amount is so tiny, that being_liquidated is already switched off
let liqee = get_mango_account(solana, account).await;
assert_eq!(liqee.token_iter_active().count(), 1);
assert!(liqee.is_bankrupt());
assert!(liqee.being_liquidated());
assert!(account_position_f64(solana, account, borrow_token1.bank).await > -1.0);
assert!(!liqee.being_liquidated());
Ok(())
}

View File

@ -27,7 +27,6 @@ export class MangoAccount {
name: number[];
delegate: PublicKey;
beingLiquidated: number;
isBankrupt: number;
accountNum: number;
bump: number;
netDeposits: number;
@ -46,7 +45,6 @@ export class MangoAccount {
obj.name,
obj.delegate,
obj.beingLiquidated,
obj.isBankrupt,
obj.accountNum,
obj.bump,
obj.netDeposits,
@ -67,7 +65,6 @@ export class MangoAccount {
name: number[],
public delegate: PublicKey,
beingLiquidated: number,
isBankrupt: number,
public accountNum: number,
bump: number,
netDeposits: number,

View File

@ -3007,10 +3007,7 @@ export type MangoV4 = {
"type": "u8"
},
{
"name": "isBankrupt",
"docs": [
"This account cannot do anything except go through `resolve_bankruptcy`"
],
"name": "padding5",
"type": "u8"
},
{
@ -8054,10 +8051,7 @@ export const IDL: MangoV4 = {
"type": "u8"
},
{
"name": "isBankrupt",
"docs": [
"This account cannot do anything except go through `resolve_bankruptcy`"
],
"name": "padding5",
"type": "u8"
},
{