liquidator: Call liq_perp_bankruptcy
This commit is contained in:
parent
12864e15a6
commit
984695e8d0
|
@ -315,8 +315,7 @@ impl MangoClient {
|
||||||
pub fn derive_liquidation_health_check_remaining_account_metas(
|
pub fn derive_liquidation_health_check_remaining_account_metas(
|
||||||
&self,
|
&self,
|
||||||
liqee: &MangoAccountValue,
|
liqee: &MangoAccountValue,
|
||||||
asset_token_index: TokenIndex,
|
writable_banks: &[TokenIndex],
|
||||||
liab_token_index: TokenIndex,
|
|
||||||
) -> anyhow::Result<Vec<AccountMeta>> {
|
) -> anyhow::Result<Vec<AccountMeta>> {
|
||||||
// figure out all the banks/oracles that need to be passed for the health check
|
// figure out all the banks/oracles that need to be passed for the health check
|
||||||
let mut banks = vec![];
|
let mut banks = vec![];
|
||||||
|
@ -331,7 +330,7 @@ impl MangoClient {
|
||||||
|
|
||||||
for token_index in token_indexes {
|
for token_index in token_indexes {
|
||||||
let mint_info = self.context.mint_info(token_index);
|
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));
|
banks.push((mint_info.first_bank(), writable_bank));
|
||||||
oracles.push(mint_info.oracle);
|
oracles.push(mint_info.oracle);
|
||||||
}
|
}
|
||||||
|
@ -928,8 +927,7 @@ impl MangoClient {
|
||||||
let perp = self.context.perp(market_index);
|
let perp = self.context.perp(market_index);
|
||||||
|
|
||||||
let health_remaining_ams = self
|
let health_remaining_ams = self
|
||||||
.context
|
.derive_liquidation_health_check_remaining_account_metas(liqee.1, &[])
|
||||||
.derive_health_check_remaining_account_metas(liqee.1, vec![], false)
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
self.program()
|
self.program()
|
||||||
|
@ -960,6 +958,57 @@ impl MangoClient {
|
||||||
.map_err(prettify_client_error)
|
.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
|
// Liquidation
|
||||||
//
|
//
|
||||||
|
@ -974,8 +1023,7 @@ impl MangoClient {
|
||||||
let health_remaining_ams = self
|
let health_remaining_ams = self
|
||||||
.derive_liquidation_health_check_remaining_account_metas(
|
.derive_liquidation_health_check_remaining_account_metas(
|
||||||
liqee.1,
|
liqee.1,
|
||||||
asset_token_index,
|
&[asset_token_index, liab_token_index],
|
||||||
liab_token_index,
|
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -1030,8 +1078,7 @@ impl MangoClient {
|
||||||
let health_remaining_ams = self
|
let health_remaining_ams = self
|
||||||
.derive_liquidation_health_check_remaining_account_metas(
|
.derive_liquidation_health_check_remaining_account_metas(
|
||||||
liqee.1,
|
liqee.1,
|
||||||
quote_token_index,
|
&[quote_token_index, liab_token_index],
|
||||||
liab_token_index,
|
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|
|
@ -148,7 +148,7 @@ impl<'a> LiquidateHelper<'a> {
|
||||||
Ok(Some(sig))
|
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
|
let mut perp_base_positions = self
|
||||||
.liqee
|
.liqee
|
||||||
.active_perp_positions()
|
.active_perp_positions()
|
||||||
|
@ -225,14 +225,14 @@ impl<'a> LiquidateHelper<'a> {
|
||||||
Ok(Some(sig))
|
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 spot_health = self.health_cache.spot_health(HealthType::Maint);
|
||||||
let mut perp_settleable_pnl = self
|
let mut perp_settleable_pnl = self
|
||||||
.liqee
|
.liqee
|
||||||
.active_perp_positions()
|
.active_perp_positions()
|
||||||
.map(|pp| {
|
.filter_map(|pp| {
|
||||||
if pp.base_position_lots() != 0 {
|
if pp.base_position_lots() != 0 {
|
||||||
return Ok(None);
|
return None;
|
||||||
}
|
}
|
||||||
let pnl = pp.quote_position_native();
|
let pnl = pp.quote_position_native();
|
||||||
let settleable_pnl = if pnl > 0 {
|
let settleable_pnl = if pnl > 0 {
|
||||||
|
@ -240,12 +240,11 @@ impl<'a> LiquidateHelper<'a> {
|
||||||
} else if pnl < 0 && spot_health > 0 {
|
} else if pnl < 0 && spot_health > 0 {
|
||||||
pnl.max(-spot_health)
|
pnl.max(-spot_health)
|
||||||
} else {
|
} 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::<Vec<(PerpMarketIndex, I80F48)>>();
|
||||||
.collect::<anyhow::Result<Vec<(PerpMarketIndex, I80F48)>>>()?;
|
|
||||||
// sort by pnl, descending
|
// sort by pnl, descending
|
||||||
perp_settleable_pnl.sort_by(|a, b| b.1.cmp(&a.1));
|
perp_settleable_pnl.sort_by(|a, b| b.1.cmp(&a.1));
|
||||||
|
|
||||||
|
@ -293,6 +292,44 @@ impl<'a> LiquidateHelper<'a> {
|
||||||
return Ok(None);
|
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)>> {
|
fn tokens(&self) -> anyhow::Result<Vec<(TokenIndex, I80F48, I80F48)>> {
|
||||||
let mut tokens = self
|
let mut tokens = self
|
||||||
.liqee
|
.liqee
|
||||||
|
@ -354,7 +391,7 @@ impl<'a> LiquidateHelper<'a> {
|
||||||
Ok(amount)
|
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() {
|
if !self.health_cache.has_borrows() || self.health_cache.can_call_spot_bankruptcy() {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
@ -417,7 +454,7 @@ impl<'a> LiquidateHelper<'a> {
|
||||||
Ok(Some(sig))
|
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() {
|
if !self.health_cache.can_call_spot_bankruptcy() {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
@ -484,7 +521,7 @@ impl<'a> LiquidateHelper<'a> {
|
||||||
return Ok(txsig);
|
return Ok(txsig);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(txsig) = self.liq_perp_base_position()? {
|
if let Some(txsig) = self.perp_liq_base_position()? {
|
||||||
return Ok(txsig);
|
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
|
// 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
|
// no liquid counterparty) and that some negative pnl can't be settled
|
||||||
// (if the liqee isn't liquid enough).
|
// (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);
|
return Ok(txsig);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(txsig) = self.liq_spot()? {
|
if let Some(txsig) = self.token_liq()? {
|
||||||
return Ok(txsig);
|
return Ok(txsig);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: socialize unsettleable negative pnl
|
// Socialize/insurance fund unsettleable negative pnl
|
||||||
// if let Some(txsig) = self.bankrupt_perp()? {
|
if let Some(txsig) = self.perp_liq_bankruptcy()? {
|
||||||
// return Ok(txsig);
|
|
||||||
// }
|
|
||||||
if let Some(txsig) = self.bankrupt_spot()? {
|
|
||||||
return Ok(txsig);
|
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!(
|
anyhow::bail!(
|
||||||
"Don't know what to do with liquidatable account {}, maint_health was {}",
|
"Don't know what to do with liquidatable account {}, maint_health was {}",
|
||||||
self.pubkey,
|
self.pubkey,
|
||||||
|
|
|
@ -792,7 +792,7 @@ impl HealthCache {
|
||||||
// can use perp_liq_base_position
|
// can use perp_liq_base_position
|
||||||
p.base != 0
|
p.base != 0
|
||||||
// can use perp_settle_pnl
|
// 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
|
// can use perp_liq_force_cancel_orders
|
||||||
|| p.has_open_orders
|
|| p.has_open_orders
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue