use std::str::FromStr;
use super::instructions::{get_liquidate_ix, LiquidateParams};
use anchor_client::{
solana_client::{rpc_client::RpcClient, rpc_config::RpcSimulateTransactionConfig},
solana_sdk::{
address_lookup_table::state::AddressLookupTable,
commitment_config::CommitmentConfig,
compute_budget::ComputeBudgetInstruction,
message::{v0::Message, AddressLookupTableAccount, VersionedMessage},
signer::Signer,
transaction::VersionedTransaction,
},
Cluster,
};
use anchor_lang::prelude::Pubkey;
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
use regex::Regex;
#[derive(Debug, Clone)]
pub struct VaultLiquidationsParams {
pub vault_id: u16,
pub col_per_unit_debt: u128,
pub absorb: bool,
pub signer: Option<Pubkey>,
pub cluster: Cluster,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct VaultLiquidation {
pub amt_out: u64,
pub amt_in: u64,
pub top_tick: i32,
}
pub fn get_vault_liquidations(
params: VaultLiquidationsParams,
) -> anyhow::Result<Vec<VaultLiquidation>> {
let signer = params.signer.unwrap_or(Pubkey::from_str(
"HEyJLdMfZhhQ7FHCtjD5DWDFNFQhaeAVAsHeWqoY6dSD",
)?);
let liquidate = get_liquidate_ix(LiquidateParams {
vault_id: params.vault_id,
debt_amt: (2_u128.pow(64) - 1) as u64,
col_per_unit_debt: params.col_per_unit_debt,
absorb: params.absorb,
to: Pubkey::default(),
signer,
cluster: params.cluster.clone(),
})?;
let rpc = RpcClient::new_with_commitment(
params.cluster.url().to_string(),
CommitmentConfig::confirmed(),
);
let recent_blockhash = rpc.get_latest_blockhash()?;
let address_lookup_table_accounts =
fetch_table_accounts(&rpc, liquidate.address_lookup_table_addresses);
let compute_ix = ComputeBudgetInstruction::set_compute_unit_limit(1_000_000u32);
let mut ixs = vec![compute_ix];
ixs.extend(liquidate.ixs);
let message = Message::try_compile(
&signer,
&ixs,
&address_lookup_table_accounts,
recent_blockhash,
)?;
let transaction =
VersionedTransaction::try_new(VersionedMessage::V0(message), &[&Keypair::new(signer)])?;
let simulation = rpc.simulate_transaction_with_config(
&transaction,
RpcSimulateTransactionConfig {
replace_recent_blockhash: true,
sig_verify: false,
..Default::default()
},
)?;
Ok(parse_vault_liquidations(
simulation.value.logs.unwrap_or_default(),
))
}
fn fetch_table_accounts(
client: &RpcClient,
table_addresses: Vec<Pubkey>,
) -> Vec<AddressLookupTableAccount> {
table_addresses
.par_iter()
.filter_map(|address_lookup_table_key| {
match client.get_account(&address_lookup_table_key) {
Ok(raw_account) => {
let address_lookup_table =
match AddressLookupTable::deserialize(&raw_account.data) {
Ok(table) => table,
Err(_) => return None,
};
let address_lookup_table_account = AddressLookupTableAccount {
key: address_lookup_table_key.clone(),
addresses: address_lookup_table.addresses.to_vec(),
};
Some(address_lookup_table_account)
}
Err(_) => None,
}
})
.collect()
}
#[derive(Debug, Clone)]
struct Keypair {
pubkey: Pubkey,
}
impl Keypair {
fn new(pubkey: Pubkey) -> Self {
Keypair { pubkey }
}
}
impl Signer for Keypair {
fn pubkey(&self) -> Pubkey {
self.pubkey
}
fn try_pubkey(&self) -> Result<Pubkey, anchor_client::solana_sdk::signer::SignerError> {
Ok(self.pubkey())
}
fn try_sign_message(
&self,
_message: &[u8],
) -> Result<
anchor_client::solana_sdk::signature::Signature,
anchor_client::solana_sdk::signer::SignerError,
> {
Ok(anchor_client::solana_sdk::signature::Signature::default())
}
fn is_interactive(&self) -> bool {
true
}
}
fn parse_vault_liquidations(logs: Vec<String>) -> Vec<VaultLiquidation> {
let mut results = Vec::new();
let re = Regex::new(r"VaultLiquidationResult: \[([^\]]+)\]").unwrap();
for log in logs {
if let Some(caps) = re.captures(&log) {
if let Some(m) = caps.get(1) {
let values: Vec<&str> = m.as_str().split(", ").map(|v| v.trim()).collect();
if values.len() == 3 {
let amt_out = values[0].parse::<u64>().unwrap_or(0);
let amt_in = values[1].parse::<u64>().unwrap_or(0);
let top_tick = values[2].parse::<i32>().unwrap_or(0);
if amt_in != 0 && amt_out != 0 {
results.push(VaultLiquidation {
amt_out,
amt_in,
top_tick,
});
}
}
}
}
}
results
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_vault_liquidations() {
let logs = vec![
"Program ComputeBudget111111111111111111111111111111 invoke [1]".to_string(),
"Program ComputeBudget111111111111111111111111111111 success".to_string(),
"Program jupr81YtYssSyPt8jbnGuiWon5f6x9TcDEFxYe3Bdzi invoke [1]".to_string(),
"Program log: Instruction: Liquidate".to_string(),
"Program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL invoke [2]".to_string(),
"Program log: Create".to_string(),
"Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [3]".to_string(),
"Program log: Instruction: GetAccountDataSize".to_string(),
"VaultLiquidationResult: [100, 200, 300]".to_string(),
"VaultLiquidationResult: [300, 400, 500]".to_string(),
];
let expected = vec![
VaultLiquidation {
amt_out: 100,
amt_in: 200,
top_tick: 300,
},
VaultLiquidation {
amt_out: 300,
amt_in: 400,
top_tick: 500,
},
];
assert_eq!(parse_vault_liquidations(logs), expected);
}
}