use anchor_client::{
solana_sdk::{commitment_config::CommitmentConfig, instruction::Instruction},
Client, Cluster,
};
use anchor_lang::{
prelude::{AccountMeta, Pubkey},
system_program,
};
use spl_associated_token_account::get_associated_token_address_with_program_id;
use std::sync::Arc;
use crate::{
borrow::{
get_vault_program,
read::{get_oracle, get_oracle_price_liquidate, VaultOraclePriceLiquidate},
VaultProgram,
},
liquidity::LIQUIDITY_PROGRAM_ID,
math::{casting::Cast, safe_math::SafeMath, tick::TickMath, u256::safe_multiply_divide},
programs::vaults::{
accounts::{Branch, Tick, VaultConfig, VaultMetadata, VaultState},
client::{
accounts::{self},
args,
},
types, ID,
},
ReadKeypair,
};
use spl_associated_token_account::ID as ASSOCIATED_TOKEN_PROGRAM_ID;
use super::{
helpers::{
load_relevant_branches_for_liquidate, load_relevant_ticks_for_liquidate,
load_relevant_ticks_has_debt_for_liquidate,
},
pda,
};
use crate::liquidity;
pub struct LiquidateContext {
pub accounts: accounts::Liquidate,
pub other_ixs: Vec<Instruction>,
pub lookup_table: Pubkey,
pub remaining_accounts_indices: Vec<u8>,
pub remaining_accounts: Vec<AccountMeta>,
}
pub struct OtherInstructionsLiquidateParams<'a> {
pub vault_id: u16,
pub vault_state: VaultState,
pub signer: Pubkey,
pub program: &'a VaultProgram,
}
pub struct OtherInstructionsLiquidate {
pub other_ixs: Vec<Instruction>,
pub new_branch_pda: Pubkey,
}
pub(crate) async fn get_other_instructions_liquidate<'a>(
params: OtherInstructionsLiquidateParams<'a>,
) -> anyhow::Result<OtherInstructionsLiquidate> {
let program = params.program;
let mut other_ixs: Vec<Instruction> = vec![];
let new_branch_id = if params.vault_state.branch_liquidated == 1 {
params.vault_state.total_branch_id + 1
} else {
params.vault_state.current_branch_id
};
let new_branch_pda = pda::get_branch(params.vault_id, new_branch_id);
let tick_pda = pda::get_tick(params.vault_id, params.vault_state.topmost_tick);
let (new_branch_data, tick_data) = tokio::join!(
async { program.account::<Branch>(new_branch_pda).await.ok() },
async { program.account::<Tick>(tick_pda).await.ok() }
);
if new_branch_data.is_none() {
let ix = program
.request()
.accounts(accounts::InitBranch {
signer: params.signer,
vault_config: pda::get_vault_config(params.vault_id),
branch: new_branch_pda,
system_program: system_program::ID,
})
.args(args::InitBranch {
vault_id: params.vault_id,
branch_id: new_branch_id,
})
.instructions()?
.remove(0);
other_ixs.push(ix);
}
if tick_data.is_none() {
let ix = program
.request()
.accounts(accounts::InitTick {
signer: params.signer,
vault_config: pda::get_vault_config(params.vault_id),
tick_data: tick_pda,
system_program: system_program::ID,
})
.args(args::InitTick {
vault_id: params.vault_id,
tick: params.vault_state.topmost_tick,
})
.instructions()?
.remove(0);
other_ixs.push(ix);
}
return Ok(OtherInstructionsLiquidate {
other_ixs,
new_branch_pda,
});
}
pub struct RemainingAccountsLiquidateParams<'a> {
pub vault_id: u16,
pub vault_state: VaultState,
pub vault_config: VaultConfig,
pub other_ixs: Vec<Instruction>,
pub oracle_price: Option<u64>,
pub program: &'a VaultProgram,
pub signer: Pubkey,
}
pub struct RemainingAccountsLiquidate {
pub other_ixs: Vec<Instruction>,
pub remaining_accounts: Vec<AccountMeta>,
pub remaining_accounts_indices: Vec<u8>,
}
pub(crate) async fn get_remaining_accounts_liquidate<'a>(
params: RemainingAccountsLiquidateParams<'a>,
) -> anyhow::Result<RemainingAccountsLiquidate> {
let program = params.program;
let mut remaining_accounts = vec![];
let mut other_ixs: Vec<Instruction> = vec![];
let (oracle, branches) = tokio::join!(
async {
if let Some(price) = params.oracle_price {
let oracle_data = get_oracle(params.vault_config.oracle, &program).await?;
Ok(VaultOraclePriceLiquidate {
price: price.cast()?,
sources: oracle_data.sources,
})
} else {
get_oracle_price_liquidate(params.vault_config.oracle, &program).await
}
},
load_relevant_branches_for_liquidate(params.vault_id, params.vault_state, &program)
);
let oracle = oracle?;
let branches = branches?;
for source in &oracle.sources {
remaining_accounts.push(AccountMeta::new_readonly(source.source, false));
}
let liquidation_ratio: u128 = safe_multiply_divide(
oracle.price,
TickMath::ZERO_TICK_SCALED_RATIO,
10u128.pow(15),
)?;
let threshold_ratio: u128 = liquidation_ratio
.safe_mul(
params
.vault_config
.liquidation_threshold
.cast()
.map_err(|_| anyhow::anyhow!("Failed to cast liquidation threshold"))?,
)
.map_err(|err| anyhow::anyhow!("Failed to calculate threshold ratio: {}", err))?
.safe_div(1000)
.map_err(|err| anyhow::anyhow!("Failed to calculate threshold ratio: {}", err))?;
for branch in &branches {
remaining_accounts.push(AccountMeta::new(
pda::get_branch(params.vault_id, branch.branch_id),
false,
))
}
let (liquidation_tick, _) = TickMath::get_tick_at_ratio(threshold_ratio)?;
let (tick_accounts, next_tick) = load_relevant_ticks_for_liquidate(
params.vault_id,
params.vault_state,
liquidation_tick,
&program,
)
.await?;
let tick_has_debt = load_relevant_ticks_has_debt_for_liquidate(
params.vault_id,
params.vault_state.topmost_tick,
next_tick,
&program,
)
.await?;
let tick_futures: Vec<_> = tick_accounts
.iter()
.map(async |tick_account| {
let tick_pda = pda::get_tick(tick_account.vault_id, tick_account.tick);
if let Err(_) = program.account::<Tick>(tick_pda).await {
return Some(
program
.request()
.args(args::InitTick {
vault_id: tick_account.vault_id,
tick: tick_account.tick,
})
.accounts(accounts::InitTick {
signer: params.signer,
vault_config: pda::get_vault_config(tick_account.vault_id),
tick_data: tick_pda,
system_program: system_program::ID,
})
.instructions()
.unwrap()
.remove(0),
);
}
None
})
.collect();
let init_instructions: Vec<Instruction> = futures::future::join_all(tick_futures)
.await
.into_iter()
.flatten()
.collect();
for init_instruction in init_instructions {
other_ixs.push(init_instruction);
}
for tick_account in &tick_accounts {
remaining_accounts.push(AccountMeta::new(
pda::get_tick(tick_account.vault_id, tick_account.tick),
false,
));
}
for tick_has_debt_array in &tick_has_debt {
remaining_accounts.push(AccountMeta::new(
pda::get_tick_has_debt(params.vault_id, tick_has_debt_array.index),
false,
));
}
return Ok(RemainingAccountsLiquidate {
other_ixs,
remaining_accounts,
remaining_accounts_indices: vec![
oracle.sources.len() as u8,
branches.len() as u8,
tick_accounts.len() as u8,
tick_has_debt.len() as u8,
],
});
}
async fn get_liquidate_ctx(
vault_id: u16,
to: Pubkey,
signer: Pubkey,
cluster: Cluster,
) -> anyhow::Result<LiquidateContext> {
let program = get_vault_program(
cluster,
Arc::new(ReadKeypair::new()),
CommitmentConfig::confirmed(),
)?;
let (vault_state, vault_config, vault_metadata) = tokio::join!(
program.account::<VaultState>(pda::get_vault_state(vault_id)),
program.account::<VaultConfig>(pda::get_vault_config(vault_id)),
program.account::<VaultMetadata>(pda::get_vault_metadata(vault_id))
);
let vault_state = vault_state?;
let vault_config = vault_config?;
let vault_metadata = vault_metadata?;
let rpc = program.rpc();
let (supply_token_program_acc, borrow_token_program_acc) = tokio::join!(
rpc.get_account(&vault_config.supply_token),
rpc.get_account(&vault_config.borrow_token)
);
let (supply_token_program, borrow_token_program) = (
supply_token_program_acc?.owner,
borrow_token_program_acc?.owner,
);
let OtherInstructionsLiquidate {
other_ixs,
new_branch_pda,
} = get_other_instructions_liquidate(OtherInstructionsLiquidateParams {
vault_id,
vault_state,
program: &program,
signer,
})
.await?;
let RemainingAccountsLiquidate {
other_ixs,
remaining_accounts,
remaining_accounts_indices,
} = get_remaining_accounts_liquidate(RemainingAccountsLiquidateParams {
other_ixs,
vault_id,
vault_state,
vault_config,
oracle_price: None,
program: &program,
signer,
})
.await?;
return Ok(LiquidateContext {
accounts: accounts::Liquidate {
signer,
signer_token_account: get_associated_token_address_with_program_id(
&signer,
&vault_config.borrow_token,
&borrow_token_program,
),
to,
to_token_account: get_associated_token_address_with_program_id(
&to,
&vault_config.supply_token,
&supply_token_program,
),
vault_config: pda::get_vault_config(vault_id),
vault_state: pda::get_vault_state(vault_id),
supply_token: vault_config.supply_token,
borrow_token: vault_config.borrow_token,
oracle: vault_config.oracle,
new_branch: new_branch_pda,
supply_token_reserves_liquidity: liquidity::pda::get_token_reserve(
vault_config.supply_token,
),
borrow_token_reserves_liquidity: liquidity::pda::get_token_reserve(
vault_config.borrow_token,
),
vault_supply_position_on_liquidity: liquidity::pda::get_user_supply_position(
vault_config.supply_token,
pda::get_vault_config(vault_id),
),
vault_borrow_position_on_liquidity: liquidity::pda::get_user_borrow_position(
vault_config.borrow_token,
pda::get_vault_config(vault_id),
),
supply_rate_model: liquidity::pda::get_rate_model(vault_config.supply_token),
borrow_rate_model: liquidity::pda::get_rate_model(vault_config.borrow_token),
supply_token_claim_account: Some(liquidity::pda::get_user_claim_account(
vault_config.supply_token,
pda::get_vault_config(vault_id),
)),
liquidity: liquidity::pda::get_liquidity(),
liquidity_program: LIQUIDITY_PROGRAM_ID,
vault_supply_token_account: get_associated_token_address_with_program_id(
&liquidity::pda::get_liquidity(),
&vault_config.supply_token,
&supply_token_program,
),
vault_borrow_token_account: get_associated_token_address_with_program_id(
&liquidity::pda::get_liquidity(),
&vault_config.borrow_token,
&borrow_token_program,
),
supply_token_program,
borrow_token_program,
system_program: system_program::ID,
associated_token_program: ASSOCIATED_TOKEN_PROGRAM_ID,
oracle_program: vault_config.oracle_program,
},
other_ixs,
lookup_table: vault_metadata.lookup_table,
remaining_accounts_indices,
remaining_accounts,
});
}
#[derive(Debug, Clone)]
pub struct Liquidate {
pub ixs: Vec<Instruction>,
pub address_lookup_table_addresses: Vec<Pubkey>,
}
pub async fn get_liquidate_ix(
vault_id: u16,
debt_amt: u64,
col_per_unit_debt: u128,
absorb: bool,
to: Pubkey,
signer: Pubkey,
cluster: Cluster,
) -> anyhow::Result<Liquidate> {
let provider = Client::new_with_options(
cluster.clone(),
Arc::new(ReadKeypair::new()),
CommitmentConfig::confirmed(),
);
let program = provider.program(ID)?;
let ctx = get_liquidate_ctx(vault_id, to, signer, cluster).await?;
let mut ixs = ctx.other_ixs.clone();
let ix = program
.request()
.accounts(ctx.accounts)
.accounts(ctx.remaining_accounts)
.args(args::Liquidate {
debt_amt: debt_amt,
col_per_unit_debt: col_per_unit_debt,
absorb: absorb,
transfer_type: Some(types::TransferType::DIRECT),
remaining_accounts_indices: ctx.remaining_accounts_indices,
})
.instructions()?
.remove(0);
ixs.push(ix);
Ok(Liquidate {
ixs,
address_lookup_table_addresses: vec![ctx.lookup_table],
})
}