From bbca1d876381ee6764f573499552d8fe420dd12b Mon Sep 17 00:00:00 2001 From: Christian Kamm Date: Wed, 13 Jul 2022 14:04:20 +0200 Subject: [PATCH] Liq: basic liq_token_bankruptcy use --- client/src/client.rs | 73 ++++++++++++++++++++++++++++- liquidator/src/liquidate.rs | 91 ++++++++++++++++++++++--------------- 2 files changed, 126 insertions(+), 38 deletions(-) diff --git a/client/src/client.rs b/client/src/client.rs index e9783c3ec..7b2620ced 100644 --- a/client/src/client.rs +++ b/client/src/client.rs @@ -12,7 +12,7 @@ use fixed::types::I80F48; use itertools::Itertools; use mango_v4::instructions::{Serum3OrderType, Serum3SelfTradeBehavior, Serum3Side}; use mango_v4::state::{ - Bank, MangoAccount, MintInfo, PerpMarket, PerpMarketIndex, Serum3Market, TokenIndex, + Bank, Group, MangoAccount, MintInfo, PerpMarket, PerpMarketIndex, Serum3Market, TokenIndex, }; use solana_client::rpc_client::RpcClient; @@ -34,6 +34,7 @@ pub struct MangoClient { pub admin: Keypair, pub mango_account_cache: (Pubkey, MangoAccount), pub group: Pubkey, + pub group_cache: Group, // TODO: future: this may not scale if there's thousands of mints, probably some function // wrapping getMultipleAccounts is needed (or bettew: we provide this data as a service) pub banks_cache: HashMap>, @@ -75,6 +76,8 @@ impl MangoClient { ) .0; + let group_data = program.account::(group)?; + // Mango Account let mut mango_account_tuples = program.accounts::(vec![ RpcFilterType::Memcmp(Memcmp { @@ -238,6 +241,7 @@ impl MangoClient { payer, mango_account_cache, group, + group_cache: group_data, banks_cache, banks_cache_by_token_index, mint_infos_cache, @@ -916,6 +920,73 @@ impl MangoClient { }) .send() } + + pub fn liq_token_bankruptcy( + &self, + liqee: (&Pubkey, &MangoAccount), + liab_token_index: TokenIndex, + max_liab_transfer: I80F48, + ) -> Result { + let quote_token_index = 0; + + let (_, quote_mint_info, _) = self + .mint_infos_cache_by_token_index + .get("e_token_index) + .unwrap(); + let (liab_mint_info_key, liab_mint_info, _) = self + .mint_infos_cache_by_token_index + .get(&liab_token_index) + .unwrap(); + + let bank_remaining_ams = liab_mint_info + .banks() + .iter() + .map(|bank_pubkey| AccountMeta { + pubkey: *bank_pubkey, + is_signer: false, + is_writable: true, + }) + .collect::>(); + + let health_remaining_ams = self + .derive_liquidation_health_check_remaining_account_metas( + liqee.1, + quote_token_index, + liab_token_index, + ) + .unwrap(); + + self.program() + .request() + .instruction(Instruction { + program_id: mango_v4::id(), + accounts: { + let mut ams = anchor_lang::ToAccountMetas::to_account_metas( + &mango_v4::accounts::LiqTokenBankruptcy { + group: self.group(), + liqee: *liqee.0, + liqor: self.mango_account_cache.0, + liqor_owner: self.payer.pubkey(), + liab_mint_info: *liab_mint_info_key, + quote_vault: quote_mint_info.first_vault(), + insurance_vault: self.group_cache.insurance_vault, + token_program: Token::id(), + }, + None, + ); + ams.extend(bank_remaining_ams); + ams.extend(health_remaining_ams); + ams + }, + data: anchor_lang::InstructionData::data( + &mango_v4::instruction::LiqTokenBankruptcy { + liab_token_index, + max_liab_transfer, + }, + ), + }) + .send() + } } fn from_serum_style_pubkey(d: &[u64; 4]) -> Pubkey { diff --git a/liquidator/src/liquidate.rs b/liquidator/src/liquidate.rs index fb7a2ac3f..8a83e59f0 100644 --- a/liquidator/src/liquidate.rs +++ b/liquidator/src/liquidate.rs @@ -121,6 +121,7 @@ pub fn process_account( ) -> anyhow::Result<()> { // TODO: configurable let min_health_ratio = I80F48::from_num(50.0f64); + let quote_token_index = 0; let account = load_mango_account_from_chain::(chain_data, pubkey)?; @@ -129,49 +130,65 @@ pub fn process_account( .expect("always ok") .health(HealthType::Maint); + // find asset and liab tokens + let mut tokens = account + .tokens + .iter_active() + .map(|token| { + let mint_info_pk = mint_infos.get(&token.token_index).expect("always Ok"); + let mint_info = load_mango_account_from_chain::(chain_data, mint_info_pk)?; + let bank = load_mango_account_from_chain::(chain_data, &mint_info.first_bank())?; + let oracle = chain_data.account(&mint_info.oracle)?; + let price = oracle_price( + &KeyedAccountSharedData::new(mint_info.oracle, oracle.clone()), + bank.oracle_config.conf_filter, + bank.mint_decimals, + )?; + Ok((token.token_index, bank, token.native(bank) * price)) + }) + .collect::>>()?; + tokens.sort_by(|a, b| a.2.cmp(&b.2)); + + let get_max_liab_transfer = |source, target| -> anyhow::Result { + let liqor = load_mango_account_from_chain::( + chain_data, + &mango_client.mango_account_cache.0, + )?; + + let health_cache = + new_health_cache_(chain_data, mint_infos, perp_markets, liqor).expect("always ok"); + + let amount = + health_cache.max_swap_source_for_health_ratio(source, target, min_health_ratio)?; + Ok(amount) + }; + // try liquidating - if maint_health.is_negative() { - // find asset and liab tokens - let mut tokens = account - .tokens - .iter_active() - .map(|token| { - let mint_info_pk = mint_infos.get(&token.token_index).expect("always Ok"); - let mint_info = - load_mango_account_from_chain::(chain_data, mint_info_pk)?; - let bank = - load_mango_account_from_chain::(chain_data, &mint_info.first_bank())?; - let oracle = chain_data.account(&mint_info.oracle)?; - let price = oracle_price( - &KeyedAccountSharedData::new(mint_info.oracle, oracle.clone()), - bank.oracle_config.conf_filter, - bank.mint_decimals, - )?; - Ok((token.token_index, bank, token.native(bank) * price)) - }) - .collect::>>()?; - tokens.sort_by(|a, b| a.2.cmp(&b.2)); + if account.is_bankrupt() { + if tokens.is_empty() { + anyhow::bail!("mango account {}, is bankrupt has no active tokens", pubkey); + } + let (liab_token_index, _liab_bank, _liab_price) = tokens.first().unwrap(); + + let max_liab_transfer = get_max_liab_transfer(*liab_token_index, quote_token_index)?; + + let sig = mango_client + .liq_token_bankruptcy((pubkey, account), *liab_token_index, max_liab_transfer) + .context("sending liq_token_bankruptcy")?; + log::info!( + "Liquidated bankruptcy for {}..., maint_health was {}, tx sig {:?}", + &pubkey.to_string()[..3], + maint_health, + sig + ); + } else if maint_health.is_negative() { if tokens.len() < 2 { anyhow::bail!("mango account {}, has less than 2 active tokens", pubkey); } let (asset_token_index, _asset_bank, _asset_price) = tokens.last().unwrap(); let (liab_token_index, _liab_bank, _liab_price) = tokens.first().unwrap(); - let max_liab_transfer = { - let liqor = load_mango_account_from_chain::( - chain_data, - &mango_client.mango_account_cache.0, - )?; - - let health_cache = - new_health_cache_(chain_data, mint_infos, perp_markets, liqor).expect("always ok"); - - health_cache.max_swap_source_for_health_ratio( - *liab_token_index, - *asset_token_index, - min_health_ratio, - )? - }; + let max_liab_transfer = get_max_liab_transfer(*liab_token_index, *asset_token_index)?; // // TODO: log liqor's assets in UI form @@ -189,7 +206,7 @@ pub fn process_account( ) .context("sending liq_token_with_token")?; log::info!( - "Liquidated {}..., maint_health was {}, tx sig {:?}", + "Liquidated token with token for {}..., maint_health was {}, tx sig {:?}", &pubkey.to_string()[..3], maint_health, sig