use anchor_client::{
solana_client::nonblocking::rpc_client::RpcClient,
solana_sdk::{
address_lookup_table::state::AddressLookupTable, message::AddressLookupTableAccount,
},
};
use anchor_lang::prelude::{AccountMeta, Pubkey};
use crate::{
math::{casting::Cast, safe_math::SafeMath, tick::TickMath, u256::safe_multiply_divide},
programs::vaults::accounts::{Branch, Tick, TickHasDebtArray, VaultState},
};
use super::{
constants::*,
errors::ErrorCodes,
pda,
state::{BranchMemoryVars, CurrentLiquidity, TickMemoryVars},
ticks::{get_tick_indices, get_tick_partials, MIN_TICK, TICK_HAS_DEBT_ARRAY_SIZE},
VaultProgram,
};
pub async fn load_relevant_branches_for_liquidate(
vault_id: u16,
vault_state: VaultState,
program: &VaultProgram,
) -> anyhow::Result<Vec<Branch>> {
let mut branches = vec![];
let current_branch_id = vault_state.current_branch_id;
if current_branch_id > 0 {
if let Ok(current_branch) = program
.account::<Branch>(pda::get_branch(vault_id, current_branch_id))
.await
{
branches.push(current_branch);
}
}
if !branches.is_empty() {
let mut connected_branch_id = branches[0].connected_branch_id;
while !branches.iter().any(|b| b.branch_id == connected_branch_id) {
if let Ok(connected_branch) = program
.account::<Branch>(pda::get_branch(vault_id, connected_branch_id))
.await
{
connected_branch_id = connected_branch.connected_branch_id;
branches.push(connected_branch);
} else {
break;
}
}
}
if !branches.iter().any(|b| b.branch_id == 0) {
branches.push(Branch {
vault_id,
branch_id: 0,
..Default::default()
});
}
Ok(branches)
}
async fn find_next_tick_with_debt(
vault_id: u16,
start_tick: i32,
program: &VaultProgram,
) -> anyhow::Result<i32> {
let (array_index, map_index, byte_index, bit_index) = get_tick_indices(start_tick)?;
let mut current_array_index = array_index;
let mut current_map_index = map_index;
let mut tick_has_debt_data = match program
.account::<TickHasDebtArray>(pda::get_tick_has_debt(vault_id, current_array_index))
.await
{
Ok(data) => data,
Err(_) => return Ok(MIN_TICK),
};
tick_has_debt_data.clear_bits(map_index, byte_index, bit_index)?;
loop {
let (next_tick, has_next_tick) =
tick_has_debt_data.fetch_next_top_tick(current_map_index)?;
if has_next_tick && next_tick != MIN_TICK {
return Ok(next_tick);
}
if current_array_index == 0 {
return Ok(MIN_TICK);
}
current_array_index -= 1;
current_map_index = TICK_HAS_DEBT_ARRAY_SIZE - 1;
tick_has_debt_data = match program
.account::<TickHasDebtArray>(pda::get_tick_has_debt(vault_id, current_array_index))
.await
{
Ok(data) => data,
Err(_) => return Ok(MIN_TICK),
};
}
}
pub async fn load_relevant_ticks_for_liquidate(
vault_id: u16,
vault_state: VaultState,
liquidation_tick: i32,
program: &VaultProgram,
) -> anyhow::Result<(Vec<Tick>, i32)> {
let mut ticks = vec![];
let top_tick = vault_state.topmost_tick;
if top_tick > liquidation_tick {
if let Ok(current_tick) = program
.account::<Tick>(pda::get_tick(vault_id, top_tick))
.await
{
ticks.push(current_tick);
}
}
let mut next_tick = find_next_tick_with_debt(vault_id, top_tick, &program).await?;
if !ticks.is_empty() {
while next_tick > liquidation_tick && !ticks.iter().any(|t| t.tick == next_tick) {
if let Ok(next_tick_data) = program
.account::<Tick>(pda::get_tick(vault_id, next_tick))
.await
{
ticks.push(next_tick_data);
if let Ok(tick) = find_next_tick_with_debt(vault_id, next_tick, &program).await {
next_tick = tick
} else {
break;
}
} else {
break;
}
}
}
Ok((ticks, next_tick))
}
pub async fn load_relevant_ticks_has_debt_for_liquidate(
vault_id: u16,
top_tick: i32,
next_tick: i32,
program: &VaultProgram,
) -> anyhow::Result<Vec<TickHasDebtArray>> {
let (top_tick_index, _, _, _) = get_tick_indices(top_tick)?;
let (next_tick_index, _, _, _) = get_tick_indices(next_tick)?;
let indices: Vec<u8> = (next_tick_index..=top_tick_index).rev().collect();
let tick_has_debt_array: Vec<TickHasDebtArray> = {
let futures: Vec<_> = indices
.iter()
.map(|&arr_idx| {
program.account::<TickHasDebtArray>(pda::get_tick_has_debt(vault_id, arr_idx))
})
.collect();
let results = futures::future::join_all(futures).await;
results
.into_iter()
.filter_map(|result| result.ok())
.collect()
};
Ok(tick_has_debt_array)
}
pub async fn fetch_table_accounts(
client: &RpcClient,
table_addresses: Vec<Pubkey>,
) -> Vec<AddressLookupTableAccount> {
let tasks: Vec<_> = table_addresses
.iter()
.map(async |address_lookup_table_key| {
match client.get_account(&address_lookup_table_key).await {
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();
let results = futures::future::join_all(tasks).await;
results.into_iter().filter_map(|result| result).collect()
}
pub async fn get_branches_from_remaining_accounts(
remaining_accounts: &Vec<AccountMeta>,
remaining_accounts_indices: &Vec<u8>,
program: &VaultProgram,
) -> anyhow::Result<Vec<Branch>> {
let length: usize = remaining_accounts_indices[1].cast::<usize>()?;
let start_index: usize = remaining_accounts_indices[0].cast()?;
let end_index: usize = start_index + length;
Ok(futures::future::try_join_all(
remaining_accounts
.iter()
.take(end_index)
.skip(start_index)
.map(|account| program.account::<Branch>(account.pubkey)),
)
.await?)
}
#[allow(clippy::too_many_arguments)]
pub fn end_liquidate<'info>(
current_data: &mut CurrentLiquidity,
tick_info: &mut TickMemoryVars,
branch_in_memory: &mut BranchMemoryVars,
debt_liquidated: &mut u128,
col_liquidated: &mut u128,
col_per_debt: u128,
minimum_branch_debt: u128,
) -> anyhow::Result<()> {
if *debt_liquidated >= current_data.debt_remaining {
*debt_liquidated = current_data.debt_remaining;
*col_liquidated = safe_multiply_divide(
*debt_liquidated,
col_per_debt,
10u128.pow(RATE_OUTPUT_DECIMALS),
)?;
let final_ratio: u128 = current_data.get_final_ratio(*col_liquidated, *debt_liquidated)?;
let (mut final_tick, ratio_one_less) = TickMath::get_tick_at_ratio(final_ratio)?;
if (final_tick < current_data.ref_tick) && (tick_info.partials == X30) {
final_tick = final_tick.safe_add(2)?;
tick_info.set_partials(1)?;
} else {
final_tick = final_tick.safe_add(1)?;
let existing_partials =
if current_data.is_ref_tick_liquidated() && final_tick == current_data.ref_tick {
tick_info.partials
} else {
0
};
tick_info.set_partials(get_tick_partials(ratio_one_less, final_ratio)?)?;
current_data
.check_is_ref_partials_safe_for_tick(existing_partials, tick_info.partials)?;
}
tick_info.set_tick(final_tick);
} else {
tick_info.set_tick(current_data.ref_tick.safe_add(1)?);
tick_info.set_partials(1)?;
}
let debt_factor = current_data.get_debt_factor(*debt_liquidated)?;
current_data.update_totals(*debt_liquidated, *col_liquidated)?;
branch_in_memory.update_branch_debt_factor(debt_factor)?;
if current_data.debt < minimum_branch_debt {
return Err(ErrorCodes::VaultBranchDebtTooLow.into());
}
Ok(())
}