use std::{str::FromStr, sync::Arc};
use super::{
constants::*,
helpers::{end_liquidate, get_branches_from_remaining_accounts},
tick_has_debt::{
fetch_next_tick_absorb, fetch_next_tick_liquidate,
get_tick_has_debt_from_remaining_accounts_liquidate,
},
};
use anchor_client::{
solana_client::rpc_config::RpcSimulateTransactionConfig,
solana_sdk::{commitment_config::CommitmentConfig, transaction::Transaction},
Cluster,
};
use anchor_client::{solana_sdk::message::Message, Client};
use anchor_lang::prelude::{AccountMeta, Pubkey};
use anyhow::Ok;
use crate::{
borrow::{
errors::ErrorCodes,
instructions::{
get_other_instructions_liquidate, get_remaining_accounts_liquidate,
OtherInstructionsLiquidate, OtherInstructionsLiquidateParams,
RemainingAccountsLiquidate, RemainingAccountsLiquidateParams,
},
state::{BranchMemoryVars, CurrentLiquidity, TickMemoryVars},
ticks::{get_current_partials_ratio, get_next_ref_tick, get_ticks_from_remaining_accounts},
VaultProgram,
},
liquidity,
math::{
bn::div_big_number, casting::Cast, safe_math::SafeMath, tick::TickMath,
u256::safe_multiply_divide,
},
oracle::ORACLE_PROGRAM_ID,
programs::{
oracle,
vaults::{
self,
accounts::{
Branch, Tick, TickHasDebtArray, TokenReserve, VaultConfig, VaultMetadata,
VaultState,
},
types::Sources,
},
},
ReadKeypair,
};
use super::{get_vault_program, pda, simulations::VaultLiquidation};
pub async fn get_all_vault_config(cluster: Cluster) -> anyhow::Result<Vec<VaultConfig>> {
let program = get_vault_program(
cluster,
Arc::new(ReadKeypair::new()),
CommitmentConfig::confirmed(),
)?;
Ok(program
.accounts::<VaultConfig>(vec![])
.await?
.into_iter()
.map(|(_, config)| config)
.collect::<Vec<_>>())
}
pub async fn get_vault_config(vault_id: u16, cluster: Cluster) -> anyhow::Result<VaultConfig> {
let program = get_vault_program(
cluster,
Arc::new(ReadKeypair::new()),
CommitmentConfig::confirmed(),
)?;
Ok(program.account(pda::get_vault_config(vault_id)).await?)
}
pub async fn get_vault_state(vault_id: u16, cluster: Cluster) -> anyhow::Result<VaultState> {
let program = get_vault_program(
cluster,
Arc::new(ReadKeypair::new()),
CommitmentConfig::confirmed(),
)?;
Ok(program.account(pda::get_vault_state(vault_id)).await?)
}
pub async fn get_vault_metadata(vault_id: u16, cluster: Cluster) -> anyhow::Result<VaultState> {
let program = get_vault_program(
cluster,
Arc::new(ReadKeypair::new()),
CommitmentConfig::confirmed(),
)?;
Ok(program.account(pda::get_vault_state(vault_id)).await?)
}
pub async fn get_vault_liquidations(
vault_id: u16,
col_per_unit_debt: u128,
absorb: bool,
signer: Option<Pubkey>,
cluster: Cluster,
) -> anyhow::Result<Vec<VaultLiquidation>> {
get_vault_liquidations_with_options(
vault_id,
VaultLiquidationsOptions {
signer,
oracle_price: None,
col_per_unit_debt,
absorb,
cluster,
},
)
.await
}
pub struct VaultLiquidationsOptions {
pub signer: Option<Pubkey>,
pub oracle_price: Option<u64>,
pub col_per_unit_debt: u128,
pub absorb: bool,
pub cluster: Cluster,
}
impl Default for VaultLiquidationsOptions {
fn default() -> Self {
Self {
signer: None,
oracle_price: None,
col_per_unit_debt: 0,
absorb: false,
cluster: Cluster::Mainnet,
}
}
}
pub async fn get_vault_liquidations_with_options(
vault_id: u16,
options: VaultLiquidationsOptions,
) -> anyhow::Result<Vec<VaultLiquidation>> {
let signer = options.signer.unwrap_or(Pubkey::from_str(
"HEyJLdMfZhhQ7FHCtjD5DWDFNFQhaeAVAsHeWqoY6dSD",
)?);
let debt_amt = (2_u128.pow(64) - 1) as u64;
let program = get_vault_program(
options.cluster.clone(),
Arc::new(ReadKeypair::from_pubkey(signer)),
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 OtherInstructionsLiquidate {
other_ixs,
new_branch_pda,
..
} = get_other_instructions_liquidate(OtherInstructionsLiquidateParams {
vault_id,
vault_state,
program: &program,
signer,
})
.await?;
let RemainingAccountsLiquidate {
remaining_accounts_indices,
remaining_accounts,
..
} = get_remaining_accounts_liquidate(RemainingAccountsLiquidateParams {
other_ixs,
vault_id,
vault_state,
vault_config,
oracle_price: options.oracle_price,
program: &program,
signer,
})
.await?;
let (_, _, actual_debt_amt, actual_col_amt) = {
let (vault_state, vault_config, new_branch) = tokio::join!(
program.account::<VaultState>(pda::get_vault_state(vault_id)),
program.account::<VaultConfig>(pda::get_vault_config(vault_id)),
program.account::<Branch>(new_branch_pda)
);
let mut vault_state = vault_state?;
let vault_config = vault_config?;
let mut new_branch = new_branch?;
if vault_state.topmost_tick == TickMath::COLD_TICK {
return Ok(vec![]);
}
let debt_amt = scale_amounts(debt_amt.cast()?, vault_metadata.borrow_mint_decimals)?;
let (supply_token_reserves, borrow_token_reserves) = tokio::join!(
program.account::<TokenReserve>(liquidity::pda::get_token_reserve(
vault_config.supply_token
)),
program.account::<TokenReserve>(liquidity::pda::get_token_reserve(
vault_config.borrow_token
))
);
let supply_token_reserves = supply_token_reserves?;
let borrow_token_reserves = borrow_token_reserves?;
let (
_, _, supply_ex_price, borrow_ex_price, ) = vault_state.load_exchange_prices(
&vault_config,
&supply_token_reserves,
&borrow_token_reserves,
)?;
let mut current_data: Box<CurrentLiquidity> = Box::new(CurrentLiquidity::default());
let exchange_rate: u128 = if let Some(price) = options.oracle_price {
price.cast()?
} else {
get_oracle_price_liquidate_from_remaining_accounts(
vault_config.oracle,
&remaining_accounts,
&remaining_accounts_indices,
&program,
)
.await?
.price
};
let (col_per_debt, liquidation_tick, max_tick) = get_ticks_from_oracle_price(
&vault_config,
supply_ex_price,
borrow_ex_price,
&remaining_accounts,
&remaining_accounts_indices,
exchange_rate,
)
.await?;
let (tick_has_debts, branches, ticks) = tokio::join!(
get_tick_has_debt_from_remaining_accounts_liquidate(
&remaining_accounts,
&remaining_accounts_indices,
&program,
),
get_branches_from_remaining_accounts(
&remaining_accounts,
&remaining_accounts_indices,
&program,
),
get_ticks_from_remaining_accounts(
&remaining_accounts,
&remaining_accounts_indices,
&program,
)
);
let mut tick_has_debts = tick_has_debts?;
let mut branches = branches?;
let mut ticks = ticks?;
if vault_state.topmost_tick > max_tick {
self::absorb(
&mut vault_state,
&mut ticks,
&mut tick_has_debts,
&mut branches,
&mut new_branch,
max_tick,
)?;
if debt_amt == 0 {
return Ok(vec![]);
}
}
current_data.tick = vault_state.topmost_tick;
if debt_amt
< scale_amounts(
MIN_DEBT.cast::<i128>()?,
vault_metadata.borrow_mint_decimals,
)?
{
return Err((ErrorCodes::VaultInvalidLiquidationAmt).into());
}
current_data.tick_status = vault_state.get_tick_status();
let mut tick_info: Box<TickMemoryVars> = Box::new(TickMemoryVars::default());
tick_info.tick = current_data.tick;
current_data.debt_remaining = debt_amt
.cast::<u128>()?
.safe_mul(EXCHANGE_PRICES_PRECISION)?
.safe_div(borrow_ex_price)?;
let total_debt: u128 = vault_state.get_total_borrow()?;
if total_debt.safe_div(BILLION)? > current_data.debt_remaining {
return Err((ErrorCodes::VaultInvalidLiquidationAmt).into());
}
if options.absorb {
vault_state.absorb_dust_amount_for_liquidate(&mut current_data)?;
}
if current_data.tick > liquidation_tick && current_data.debt_remaining > 0 {
let mut branch: Box<BranchMemoryVars> = Box::new(BranchMemoryVars::default());
{
let branch_0 = branches
.iter()
.find(|b| b.branch_id == vault_state.current_branch_id)
.ok_or(ErrorCodes::VaultBranchNotFound)?;
branch.set_branch_data_in_memory(&branch_0)?;
}
let mut next_tick = TickMath::COLD_TICK;
if current_data.is_perfect_tick() {
current_data.ratio = TickMath::get_ratio_at_tick(tick_info.tick)?;
next_tick = current_data.tick;
} else {
(current_data.ratio, tick_info.partials) = get_current_partials_ratio(
branch.data.minima_tick_partials,
TickMath::get_ratio_at_tick(tick_info.tick)?,
)?;
if liquidation_tick + 1 == tick_info.tick && tick_info.partials == 1 {
return Ok(vec![]);
}
}
let mut is_first_iteration = true;
loop {
let additional_debt: u128 = if current_data.is_perfect_tick() {
let tick_data = ticks
.iter_mut()
.find(|b| b.tick == current_data.tick)
.ok_or(ErrorCodes::VaultTickNotFound)?;
let debt = tick_data.get_raw_debt()?;
tick_data.set_liquidated(branch.id, branch.debt_factor);
debt
} else {
branch.data.debt_liquidity.cast()?
};
current_data.debt = current_data.debt.safe_add(additional_debt)?;
current_data.col = current_data.col.safe_add(
additional_debt
.safe_mul(TickMath::ZERO_TICK_SCALED_RATIO)?
.safe_div(current_data.ratio)?,
)?;
if (next_tick == current_data.tick && current_data.is_perfect_tick())
|| is_first_iteration
{
is_first_iteration = false;
next_tick = fetch_next_tick_liquidate(
&mut tick_has_debts,
current_data.tick,
liquidation_tick,
current_data.is_perfect_tick(), )?;
}
(current_data.ref_tick, current_data.ref_tick_status) =
get_next_ref_tick(branch.minima_tick, next_tick, liquidation_tick)?;
if current_data.is_ref_tick_liquidated() {
let base_branch_id = branch.data.connected_branch_id;
let base_branch = branches
.iter()
.find(|b| b.branch_id == base_branch_id)
.ok_or(ErrorCodes::VaultBranchNotFound)?;
branch.set_base_branch_data(&base_branch)?;
(current_data.ref_ratio, tick_info.partials) = get_current_partials_ratio(
base_branch.minima_tick_partials,
TickMath::get_ratio_at_tick(current_data.ref_tick)?,
)?;
} else {
current_data.ref_ratio = TickMath::get_ratio_at_tick(current_data.ref_tick)?;
tick_info.partials = X30;
}
let mut debt_liquidated: u128 = current_data.get_debt_liquidated(col_per_debt)?;
let mut col_liquidated: u128 = debt_liquidated
.safe_mul(col_per_debt)?
.safe_div(10u128.pow(RATE_OUTPUT_DECIMALS))?;
if current_data.debt == debt_liquidated {
debt_liquidated = debt_liquidated.safe_sub(1)?;
}
if debt_liquidated >= current_data.debt_remaining
|| current_data.is_ref_tick_liquidation_threshold()
{
end_liquidate(
&mut current_data,
&mut tick_info,
&mut branch,
&mut debt_liquidated,
&mut col_liquidated,
col_per_debt,
scale_amounts(
MINIMUM_BRANCH_DEBT.cast::<i128>()?,
vault_metadata.borrow_mint_decimals,
)?
.cast::<u128>()?,
)?;
let branch_to_update = branches
.iter_mut()
.find(|b| b.branch_id == branch.id)
.ok_or(ErrorCodes::VaultBranchNotFound)?;
branch_to_update.update_state_at_liq_end(
tick_info.tick,
tick_info.partials,
current_data.debt,
branch.debt_factor.cast()?,
)?;
vault_state.update_state_at_liq_end(tick_info.tick, branch.id)?;
break;
}
let debt_factor = current_data.get_debt_factor(debt_liquidated)?;
current_data.reduce_debt_remaining(debt_liquidated)?;
current_data.update_totals(debt_liquidated, col_liquidated)?;
branch.update_branch_debt_factor(debt_factor)?;
if current_data.is_ref_tick_liquidated() {
let new_branch_debt_factor = branch.base_branch_data.debt_factor;
let current_branch = branches
.iter_mut()
.find(|b| b.branch_id == branch.id)
.ok_or(ErrorCodes::VaultBranchNotFound)?;
current_branch.merge_with_base_branch(div_big_number(
new_branch_debt_factor,
branch.debt_factor,
)?)?;
branch.debt_factor = new_branch_debt_factor;
branch.update_branch_to_base_branch();
}
current_data.update_next_iterations_with_ref();
}
}
let (actual_debt_amt, actual_col_amt) =
current_data.get_actual_amounts(borrow_ex_price, supply_ex_price, debt_amt.cast()?)?;
if actual_col_amt
.safe_mul(10u128.pow(RATE_OUTPUT_DECIMALS))?
.safe_div(actual_debt_amt)?
< options.col_per_unit_debt
{
return Err((ErrorCodes::VaultExcessSlippageLiquidation).into());
}
vault_state.reduce_total_supply(current_data.total_col_liq)?;
vault_state.reduce_total_borrow(current_data.total_debt_liq)?;
(
vault_config.vault_id,
vault_config.bump,
unscale_amounts(actual_debt_amt.cast()?, vault_metadata.borrow_mint_decimals)?
.cast::<u128>()?, unscale_amounts(actual_col_amt.cast()?, vault_metadata.supply_mint_decimals)?
.cast::<u128>()?, )
};
let topmost_tick = vault_state.topmost_tick;
Ok(vec![VaultLiquidation {
amt_in: actual_debt_amt.cast()?,
amt_out: actual_col_amt.cast()?,
top_tick: topmost_tick,
}])
}
pub fn scale_amounts(amount: i128, decimals: u8) -> anyhow::Result<i128> {
let scale: u128 = if decimals <= 9 {
10u128.pow((9 - decimals).cast()?)
} else {
return Err((ErrorCodes::VaultInvalidDecimals).into());
};
amount.safe_mul(scale.cast()?)
}
pub fn unscale_amounts(amount: i128, decimals: u8) -> anyhow::Result<i128> {
let scale: u128 = if decimals <= 9 {
10u128.pow((9 - decimals).cast()?)
} else {
return Err((ErrorCodes::VaultInvalidDecimals).into());
};
amount.safe_div(scale.cast()?)
}
fn absorb(
vault_state: &mut VaultState,
ticks: &mut Vec<Tick>,
tick_has_debts: &mut Vec<TickHasDebtArray>,
branches: &mut Vec<Branch>,
new_branch: &mut Branch,
max_tick: i32,
) -> anyhow::Result<(i128, i128)> {
let (next_tick, mut col_absorbed, mut debt_absorbed) = fetch_next_tick_absorb(
tick_has_debts,
ticks,
vault_state.topmost_tick.safe_add(1)?,
max_tick,
)?;
let mut branch_data = BranchMemoryVars::default();
let mut new_branch_id = 0;
branch_data.id = vault_state.current_branch_id;
let branch = branches
.iter()
.find(|branch| branch.branch_id == branch_data.id)
.ok_or(ErrorCodes::VaultBranchNotFound)?;
branch_data.set_branch_data(&branch)?;
if !vault_state.is_branch_liquidated() {
new_branch_id = branch_data.id;
if branch_data.data.connected_minima_tick != TickMath::COLD_TICK {
branch_data.id = branch_data.data.connected_branch_id;
branch_data.minima_tick = branch_data.data.connected_minima_tick;
let branch = branches
.iter()
.find(|branch| branch.branch_id == branch_data.id)
.ok_or(ErrorCodes::VaultBranchNotFound)?;
branch_data.set_branch_data(&branch)?;
} else {
branch_data.id = 0;
branch_data.reset_branch_data();
branch_data.minima_tick = TickMath::COLD_TICK;
}
} else {
branch_data.minima_tick = branch_data.data.minima_tick;
}
while branch_data.minima_tick > max_tick {
let current_ratio = branch_data.get_current_ratio_from_minima_tick()?;
let branch_debt: u128 = branch_data.data.debt_liquidity.cast()?;
debt_absorbed = debt_absorbed.safe_add(branch_debt)?;
col_absorbed = col_absorbed.safe_add(
branch_debt
.safe_mul(TickMath::ZERO_TICK_SCALED_RATIO)?
.safe_div(current_ratio)?,
)?;
let branch_to_update = branches
.iter_mut()
.find(|b| b.branch_id == branch_data.id)
.ok_or(ErrorCodes::VaultBranchNotFound)?;
branch_to_update.set_state_after_absorb(&branch_data.data);
if branch_data.data.connected_minima_tick != TickMath::COLD_TICK {
branch_data.id = branch_data.data.connected_branch_id;
branch_data.minima_tick = branch_data.data.connected_minima_tick;
let branch = branches
.iter()
.find(|branch| branch.branch_id == branch_data.id)
.ok_or(ErrorCodes::VaultBranchNotFound)?;
branch_data.set_branch_data(&branch)?;
} else {
branch_data.id = 0;
branch_data.reset_branch_data();
branch_data.minima_tick = TickMath::COLD_TICK;
}
}
if next_tick >= branch_data.minima_tick {
if next_tick > TickMath::COLD_TICK {
vault_state.topmost_tick = next_tick;
} else {
vault_state.reset_top_tick();
}
let init_new_branch = new_branch_id == 0;
if init_new_branch {
vault_state.update_branch_info_by_one();
new_branch_id = vault_state.total_branch_id;
} else {
vault_state.reset_branch_liquidated();
}
if branch_data.minima_tick > TickMath::COLD_TICK {
if new_branch_id != new_branch.branch_id {
return Err(ErrorCodes::VaultNewBranchInvalid.into());
}
new_branch.reset_branch_data();
new_branch.set_connections(branch_data.id, branch_data.minima_tick)?;
} else {
let branch_to_clear = if init_new_branch {
if new_branch_id != new_branch.branch_id {
return Err(ErrorCodes::VaultNewBranchInvalid.into());
}
new_branch
} else {
branches
.iter_mut()
.find(|branch| branch.branch_id == new_branch_id)
.ok_or(ErrorCodes::VaultBranchNotFound)?
};
branch_to_clear.reset_branch_data();
}
} else {
vault_state.update_state_at_liq_end(branch_data.minima_tick, branch_data.id)?;
if new_branch_id != 0 {
vault_state.total_branch_id = new_branch_id.safe_sub(1)?;
let branch_to_clear = branches
.iter_mut()
.find(|branch| branch.branch_id == new_branch_id)
.ok_or(ErrorCodes::VaultBranchNotFound)?;
if new_branch_id != branch_to_clear.branch_id {
return Err(ErrorCodes::VaultNewBranchInvalid.into());
}
branch_to_clear.reset_branch_data();
}
}
vault_state.add_absorbed_col_amount(col_absorbed)?;
vault_state.add_absorbed_debt_amount(debt_absorbed)?;
Ok((col_absorbed.cast()?, debt_absorbed.cast()?))
}
pub async fn get_ticks_from_oracle_price(
vault_config: &VaultConfig,
supply_ex_price: u128,
borrow_ex_price: u128,
remaining_accounts: &Vec<AccountMeta>,
remaining_accounts_indices: &Vec<u8>,
exchange_rate: u128,
) -> anyhow::Result<(u128, i32, i32)> {
let start_index: usize = 0;
let end_index: usize = start_index + remaining_accounts_indices[0].cast::<usize>()?;
if remaining_accounts.len() < end_index {
return Err(ErrorCodes::VaultLiquidateRemainingAccountsTooShort.into());
}
if exchange_rate > 10u128.pow(24) || exchange_rate == 0 {
return Err((ErrorCodes::VaultInvalidOraclePrice).into());
}
let mut debt_per_col: u128 =
safe_multiply_divide(exchange_rate, supply_ex_price, borrow_ex_price)?;
if debt_per_col == 0 {
return Err((ErrorCodes::VaultInvalidOraclePrice).into());
}
if debt_per_col > 10u128.pow(26) {
debt_per_col = 10u128.pow(26);
}
let raw_col_per_debt: u128 = 10u128
.pow(RATE_OUTPUT_DECIMALS * 2)
.safe_div(debt_per_col)?;
let col_per_debt: u128 = raw_col_per_debt
.safe_mul(FOUR_DECIMALS.safe_add(vault_config.liquidation_penalty.cast()?)?)?
.safe_div(FOUR_DECIMALS)?;
let liquidation_ratio: u128 = safe_multiply_divide(
debt_per_col,
TickMath::ZERO_TICK_SCALED_RATIO,
10u128.pow(RATE_OUTPUT_DECIMALS),
)?;
let threshold_ratio: u128 = liquidation_ratio
.safe_mul(vault_config.liquidation_threshold.cast()?)?
.safe_div(THREE_DECIMALS)?;
let (liquidation_tick, _) = TickMath::get_tick_at_ratio(threshold_ratio)?;
let max_ratio: u128 = liquidation_ratio
.safe_mul(vault_config.liquidation_max_limit.cast()?)?
.safe_div(THREE_DECIMALS)?;
let (max_tick, _) = TickMath::get_tick_at_ratio(max_ratio)?;
Ok((col_per_debt, liquidation_tick, max_tick))
}
pub async fn get_oracle(
oracle: Pubkey,
program: &VaultProgram,
) -> anyhow::Result<vaults::accounts::Oracle> {
let oracle_data: vaults::accounts::Oracle = program.account(oracle).await?;
Ok(oracle_data)
}
#[derive(Debug, Clone)]
pub struct VaultOraclePriceLiquidate {
pub price: u128,
pub sources: Vec<Sources>,
}
pub async fn get_oracle_price_liquidate_from_remaining_accounts(
oracle: Pubkey,
remaining_accounts: &Vec<AccountMeta>,
remaining_accounts_indices: &Vec<u8>,
program: &VaultProgram,
) -> anyhow::Result<VaultOraclePriceLiquidate> {
let oracle_data = get_oracle(oracle, program).await?;
let rpc = program.rpc();
let cluster = Cluster::Custom(rpc.url(), rpc.url());
let provider = Client::new_with_options(
cluster,
Arc::new(ReadKeypair::from_pubkey(program.payer())),
rpc.commitment(),
);
let oracle_program = provider.program(ORACLE_PROGRAM_ID)?;
let mut price = 0;
let start_index: usize = 0;
let end_index: usize = start_index + remaining_accounts_indices[0].cast::<usize>()?;
if remaining_accounts.len() < end_index {
return Err(ErrorCodes::VaultLiquidateRemainingAccountsTooShort.into());
}
let remaining_accounts = remaining_accounts
.iter()
.take(end_index)
.skip(start_index)
.map(|x| x.clone())
.collect::<Vec<_>>();
let instructions = oracle_program
.request()
.accounts(oracle::client::accounts::GetExchangeRateLiquidate { oracle })
.accounts(remaining_accounts)
.args(oracle::client::args::GetExchangeRateLiquidate {
_nonce: oracle_data.nonce,
})
.instructions()?;
let message = Message::new(&instructions, Some(&oracle_program.payer()));
let transaction = Transaction::new_unsigned(message);
let simulation = oracle_program
.rpc()
.simulate_transaction_with_config(
&transaction,
RpcSimulateTransactionConfig {
replace_recent_blockhash: true,
sig_verify: false,
..Default::default()
},
)
.await?;
if let Some(data) = &simulation.value.return_data {
#[allow(deprecated)]
let raw_bytes = base64::decode(&data.data.0)?;
price = u128::from_le_bytes(raw_bytes.try_into().unwrap());
}
Ok(VaultOraclePriceLiquidate {
price,
sources: oracle_data.sources,
})
}
pub async fn get_oracle_price_liquidate(
oracle: Pubkey,
program: &VaultProgram,
) -> anyhow::Result<VaultOraclePriceLiquidate> {
let oracle_data = get_oracle(oracle, program).await?;
let rpc = program.rpc();
let cluster = Cluster::Custom(rpc.url(), rpc.url());
let provider = Client::new_with_options(
cluster,
Arc::new(ReadKeypair::from_pubkey(program.payer())),
rpc.commitment(),
);
let oracle_program = provider.program(ORACLE_PROGRAM_ID)?;
let mut remaining_accounts = vec![];
for source in &oracle_data.sources {
remaining_accounts.push(AccountMeta::new_readonly(source.source, false));
}
let remaining_accounts_indices = vec![remaining_accounts.len() as u8];
let mut price = 0;
let start_index: usize = 0;
let end_index: usize = start_index + remaining_accounts_indices[0].cast::<usize>()?;
if remaining_accounts.len() < end_index {
return Err(ErrorCodes::VaultLiquidateRemainingAccountsTooShort.into());
}
let remaining_accounts = remaining_accounts
.iter()
.take(end_index)
.skip(start_index)
.map(|x| x.clone())
.collect::<Vec<_>>();
let instructions = oracle_program
.request()
.accounts(oracle::client::accounts::GetExchangeRateLiquidate { oracle })
.accounts(remaining_accounts)
.args(oracle::client::args::GetExchangeRateLiquidate {
_nonce: oracle_data.nonce,
})
.instructions()?;
let message = Message::new(&instructions, Some(&oracle_program.payer()));
let transaction = Transaction::new_unsigned(message);
let simulation = oracle_program
.rpc()
.simulate_transaction_with_config(
&transaction,
RpcSimulateTransactionConfig {
replace_recent_blockhash: true,
sig_verify: false,
..Default::default()
},
)
.await?;
if let Some(data) = &simulation.value.return_data {
#[allow(deprecated)]
let raw_bytes = base64::decode(&data.data.0)?;
price = u128::from_le_bytes(raw_bytes.try_into().unwrap());
}
Ok(VaultOraclePriceLiquidate {
price,
sources: oracle_data.sources,
})
}