liquidator: Call liq_perp_bankruptcy

This commit is contained in:
Christian Kamm 2022-09-27 11:36:58 +02:00
parent 12864e15a6
commit 984695e8d0
3 changed files with 117 additions and 28 deletions

View File

@ -315,8 +315,7 @@ impl MangoClient {
pub fn derive_liquidation_health_check_remaining_account_metas(
&self,
liqee: &MangoAccountValue,
asset_token_index: TokenIndex,
liab_token_index: TokenIndex,
writable_banks: &[TokenIndex],
) -> anyhow::Result<Vec<AccountMeta>> {
// figure out all the banks/oracles that need to be passed for the health check
let mut banks = vec![];
@ -331,7 +330,7 @@ impl MangoClient {
for token_index in token_indexes {
let mint_info = self.context.mint_info(token_index);
let writable_bank = token_index == asset_token_index || token_index == liab_token_index;
let writable_bank = writable_banks.iter().contains(&token_index);
banks.push((mint_info.first_bank(), writable_bank));
oracles.push(mint_info.oracle);
}
@ -928,8 +927,7 @@ impl MangoClient {
let perp = self.context.perp(market_index);
let health_remaining_ams = self
.context
.derive_health_check_remaining_account_metas(liqee.1, vec![], false)
.derive_liquidation_health_check_remaining_account_metas(liqee.1, &[])
.unwrap();
self.program()
@ -960,6 +958,57 @@ impl MangoClient {
.map_err(prettify_client_error)
}
pub fn perp_liq_bankruptcy(
&self,
liqee: (&Pubkey, &MangoAccountValue),
market_index: PerpMarketIndex,
max_liab_transfer: u64,
) -> anyhow::Result<Signature> {
let quote_token_index = 0;
let quote_info = self.context.token(quote_token_index);
let group = account_fetcher_fetch_anchor_account::<Group>(
&*self.account_fetcher,
&self.context.group,
)?;
let perp = self.context.perp(market_index);
let health_remaining_ams = self
.derive_liquidation_health_check_remaining_account_metas(liqee.1, &[])
.unwrap();
self.program()
.request()
.instruction(Instruction {
program_id: mango_v4::id(),
accounts: {
let mut ams = anchor_lang::ToAccountMetas::to_account_metas(
&mango_v4::accounts::PerpLiqBankruptcy {
group: self.group(),
perp_market: perp.address,
liqor: self.mango_account_address,
liqor_owner: self.owner(),
liqee: *liqee.0,
quote_bank: quote_info.mint_info.first_bank(),
quote_vault: quote_info.mint_info.first_vault(),
insurance_vault: group.insurance_vault,
token_program: Token::id(),
},
None,
);
ams.extend(health_remaining_ams.into_iter());
ams
},
data: anchor_lang::InstructionData::data(
&mango_v4::instruction::PerpLiqBankruptcy { max_liab_transfer },
),
})
.signer(&self.owner)
.send()
.map_err(prettify_client_error)
}
//
// Liquidation
//
@ -974,8 +1023,7 @@ impl MangoClient {
let health_remaining_ams = self
.derive_liquidation_health_check_remaining_account_metas(
liqee.1,
asset_token_index,
liab_token_index,
&[asset_token_index, liab_token_index],
)
.unwrap();
@ -1030,8 +1078,7 @@ impl MangoClient {
let health_remaining_ams = self
.derive_liquidation_health_check_remaining_account_metas(
liqee.1,
quote_token_index,
liab_token_index,
&[quote_token_index, liab_token_index],
)
.unwrap();

View File

@ -148,7 +148,7 @@ impl<'a> LiquidateHelper<'a> {
Ok(Some(sig))
}
fn liq_perp_base_position(&self) -> anyhow::Result<Option<Signature>> {
fn perp_liq_base_position(&self) -> anyhow::Result<Option<Signature>> {
let mut perp_base_positions = self
.liqee
.active_perp_positions()
@ -225,14 +225,14 @@ impl<'a> LiquidateHelper<'a> {
Ok(Some(sig))
}
fn settle_perp_pnl(&self) -> anyhow::Result<Option<Signature>> {
fn perp_settle_pnl(&self) -> anyhow::Result<Option<Signature>> {
let spot_health = self.health_cache.spot_health(HealthType::Maint);
let mut perp_settleable_pnl = self
.liqee
.active_perp_positions()
.map(|pp| {
.filter_map(|pp| {
if pp.base_position_lots() != 0 {
return Ok(None);
return None;
}
let pnl = pp.quote_position_native();
let settleable_pnl = if pnl > 0 {
@ -240,12 +240,11 @@ impl<'a> LiquidateHelper<'a> {
} else if pnl < 0 && spot_health > 0 {
pnl.max(-spot_health)
} else {
return Ok(None);
return None;
};
Ok(Some((pp.market_index, settleable_pnl)))
Some((pp.market_index, settleable_pnl))
})
.filter_map_ok(|v| v)
.collect::<anyhow::Result<Vec<(PerpMarketIndex, I80F48)>>>()?;
.collect::<Vec<(PerpMarketIndex, I80F48)>>();
// sort by pnl, descending
perp_settleable_pnl.sort_by(|a, b| b.1.cmp(&a.1));
@ -293,6 +292,44 @@ impl<'a> LiquidateHelper<'a> {
return Ok(None);
}
fn perp_liq_bankruptcy(&self) -> anyhow::Result<Option<Signature>> {
if self.health_cache.has_liquidatable_assets() {
return Ok(None);
}
let mut perp_bankruptcies = self
.liqee
.active_perp_positions()
.filter_map(|pp| {
let quote = pp.quote_position_native();
if quote >= 0 {
return None;
}
Some((pp.market_index, quote))
})
.collect::<Vec<(PerpMarketIndex, I80F48)>>();
perp_bankruptcies.sort_by(|a, b| a.1.cmp(&b.1));
if perp_bankruptcies.is_empty() {
return Ok(None);
}
let (perp_market_index, _) = perp_bankruptcies.first().unwrap();
let sig = self.client.perp_liq_bankruptcy(
(self.pubkey, &self.liqee),
*perp_market_index,
// Always use the max amount, since the health effect is always positive
u64::MAX,
)?;
log::info!(
"Liquidated bankruptcy for perp market on account {}, market index {}, maint_health was {}, tx sig {:?}",
self.pubkey,
perp_market_index,
self.maint_health,
sig
);
Ok(Some(sig))
}
fn tokens(&self) -> anyhow::Result<Vec<(TokenIndex, I80F48, I80F48)>> {
let mut tokens = self
.liqee
@ -354,7 +391,7 @@ impl<'a> LiquidateHelper<'a> {
Ok(amount)
}
fn liq_spot(&self) -> anyhow::Result<Option<Signature>> {
fn token_liq(&self) -> anyhow::Result<Option<Signature>> {
if !self.health_cache.has_borrows() || self.health_cache.can_call_spot_bankruptcy() {
return Ok(None);
}
@ -417,7 +454,7 @@ impl<'a> LiquidateHelper<'a> {
Ok(Some(sig))
}
fn bankrupt_spot(&self) -> anyhow::Result<Option<Signature>> {
fn token_liq_bankruptcy(&self) -> anyhow::Result<Option<Signature>> {
if !self.health_cache.can_call_spot_bankruptcy() {
return Ok(None);
}
@ -484,7 +521,7 @@ impl<'a> LiquidateHelper<'a> {
return Ok(txsig);
}
if let Some(txsig) = self.liq_perp_base_position()? {
if let Some(txsig) = self.perp_liq_base_position()? {
return Ok(txsig);
}
@ -493,21 +530,26 @@ impl<'a> LiquidateHelper<'a> {
// It's possible that some positive pnl can't be settled (if there's
// no liquid counterparty) and that some negative pnl can't be settled
// (if the liqee isn't liquid enough).
if let Some(txsig) = self.settle_perp_pnl()? {
if let Some(txsig) = self.perp_settle_pnl()? {
return Ok(txsig);
}
if let Some(txsig) = self.liq_spot()? {
if let Some(txsig) = self.token_liq()? {
return Ok(txsig);
}
// TODO: socialize unsettleable negative pnl
// if let Some(txsig) = self.bankrupt_perp()? {
// return Ok(txsig);
// }
if let Some(txsig) = self.bankrupt_spot()? {
// Socialize/insurance fund unsettleable negative pnl
if let Some(txsig) = self.perp_liq_bankruptcy()? {
return Ok(txsig);
}
// Socialize/insurance fund unliquidatable borrows
if let Some(txsig) = self.token_liq_bankruptcy()? {
return Ok(txsig);
}
// TODO: What about unliquidatable positive perp pnl?
anyhow::bail!(
"Don't know what to do with liquidatable account {}, maint_health was {}",
self.pubkey,

View File

@ -792,7 +792,7 @@ impl HealthCache {
// can use perp_liq_base_position
p.base != 0
// can use perp_settle_pnl
|| p.quote > ONE_NATIVE_USDC_IN_USD
|| p.quote > ONE_NATIVE_USDC_IN_USD // TODO: we're not guaranteed to be able to settle positive perp pnl!
// can use perp_liq_force_cancel_orders
|| p.has_open_orders
});