mango-v4/liquidator/src/liquidate.rs

182 lines
6.5 KiB
Rust
Raw Normal View History

use crate::account_shared_data::KeyedAccountSharedData;
use crate::ChainDataAccountFetcher;
use client::{AccountFetcher, MangoClient, MangoGroupContext};
use mango_v4::state::{
new_health_cache, oracle_price, Bank, FixedOrderAccountRetriever, HealthCache, HealthType,
MangoAccount, TokenIndex,
};
use {anyhow::Context, fixed::types::I80F48, solana_sdk::pubkey::Pubkey};
pub fn new_health_cache_(
context: &MangoGroupContext,
account_fetcher: &ChainDataAccountFetcher,
account: &MangoAccount,
) -> anyhow::Result<HealthCache> {
let active_token_len = account.tokens.iter_active().count();
let active_perp_len = account.perps.iter_active_accounts().count();
let metas = context.derive_health_check_remaining_account_metas(account, None, false)?;
let accounts = metas
.iter()
.map(|meta| {
Ok(KeyedAccountSharedData::new(
meta.pubkey,
account_fetcher.fetch_raw(&meta.pubkey)?,
))
})
.collect::<anyhow::Result<Vec<_>>>()?;
let retriever = FixedOrderAccountRetriever {
ais: accounts,
n_banks: active_token_len,
begin_perp: active_token_len * 2,
begin_serum3: active_token_len * 2 + active_perp_len,
};
new_health_cache(account, &retriever).context("make health cache")
}
#[allow(clippy::too_many_arguments)]
2022-07-13 04:02:25 -07:00
pub fn process_account(
mango_client: &MangoClient,
account_fetcher: &ChainDataAccountFetcher,
2022-07-13 04:02:25 -07:00
pubkey: &Pubkey,
) -> anyhow::Result<()> {
// TODO: configurable
let min_health_ratio = I80F48::from_num(50.0f64);
2022-07-13 05:04:20 -07:00
let quote_token_index = 0;
let account = account_fetcher.fetch::<MangoAccount>(pubkey)?;
let maint_health = new_health_cache_(&mango_client.context, account_fetcher, &account)
.expect("always ok")
.health(HealthType::Maint);
if maint_health >= 0 && !account.is_bankrupt() {
return Ok(());
}
2022-07-13 04:02:25 -07:00
log::trace!(
"possible candidate: {}, with owner: {}, maint health: {}, bankrupt: {}",
pubkey,
account.owner,
maint_health,
account.is_bankrupt(),
);
// Fetch a fresh account and re-compute
2022-07-18 09:16:58 -07:00
// 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::<MangoAccount>(pubkey)?;
let maint_health = new_health_cache_(&mango_client.context, account_fetcher, &account)
2022-07-13 04:02:25 -07:00
.expect("always ok")
.health(HealthType::Maint);
2022-07-13 05:04:20 -07:00
// find asset and liab tokens
let mut tokens = account
.tokens
.iter_active()
.map(|token_position| {
let token = mango_client.context.token(token_position.token_index);
let bank = account_fetcher.fetch::<Bank>(&token.mint_info.first_bank())?;
let oracle = account_fetcher.fetch_raw_account(token.mint_info.oracle)?;
2022-07-13 05:04:20 -07:00
let price = oracle_price(
&KeyedAccountSharedData::new(token.mint_info.oracle, oracle.into()),
2022-07-13 05:04:20 -07:00
bank.oracle_config.conf_filter,
bank.mint_decimals,
)?;
Ok((
token_position.token_index,
bank,
token_position.native(&bank) * price,
))
2022-07-13 05:04:20 -07:00
})
.collect::<anyhow::Result<Vec<(TokenIndex, Bank, I80F48)>>>()?;
2022-07-13 05:04:20 -07:00
tokens.sort_by(|a, b| a.2.cmp(&b.2));
let get_max_liab_transfer = |source, target| -> anyhow::Result<I80F48> {
let mut liqor = account_fetcher
.fetch_fresh::<MangoAccount>(&mango_client.mango_account_address)
.context("getting liquidator account")?
.clone();
2022-07-13 05:04:20 -07:00
// Ensure the tokens are activated, so they appear in the health cache and
// max_swap_source() will work.
liqor.tokens.get_mut_or_create(source)?;
liqor.tokens.get_mut_or_create(target)?;
2022-07-13 05:04:20 -07:00
let health_cache =
new_health_cache_(&mango_client.context, account_fetcher, &liqor).expect("always ok");
let amount = health_cache
.max_swap_source_for_health_ratio(source, target, min_health_ratio)
.context("getting max_swap_source")?;
2022-07-13 05:04:20 -07:00
Ok(amount)
};
2022-07-13 04:02:25 -07:00
// try liquidating
2022-07-13 05:04:20 -07:00
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)
2022-07-13 05:04:20 -07:00
.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() {
2022-07-13 04:02:25 -07:00
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 = get_max_liab_transfer(*liab_token_index, *asset_token_index)
.context("getting max_liab_transfer")?;
2022-07-13 04:02:25 -07:00
//
// TODO: log liqor's assets in UI form
// TODO: log liquee's liab_needed, need to refactor program code to be able to be accessed from client side
// TODO: swap inherited liabs to desired asset for liqor
//
let sig = mango_client
.liq_token_with_token(
(pubkey, &account),
*asset_token_index,
*liab_token_index,
max_liab_transfer,
2022-07-13 04:02:25 -07:00
)
.context("sending liq_token_with_token")?;
log::info!(
2022-07-13 05:04:20 -07:00
"Liquidated token with token for {}..., maint_health was {}, tx sig {:?}",
2022-07-13 04:02:25 -07:00
&pubkey.to_string()[..3],
maint_health,
sig
);
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub fn process_accounts<'a>(
mango_client: &MangoClient,
account_fetcher: &ChainDataAccountFetcher,
2022-07-13 04:02:25 -07:00
accounts: impl Iterator<Item = &'a Pubkey>,
) -> anyhow::Result<()> {
for pubkey in accounts {
match process_account(mango_client, account_fetcher, pubkey) {
2022-07-13 04:02:25 -07:00
Err(err) => log::error!("error liquidating account {}: {:?}", pubkey, err),
_ => {}
};
}
Ok(())
}