jup-lend-sdk 0.2.13

SDK for Jupiter lending protocol
Documentation
use crate::{
    math::{casting::Cast, safe_math::SafeMath, tick::TickMath},
    programs::vaults::accounts::Tick,
};

use super::{
    constants::{FOUR_DECIMALS, X30},
    errors::ErrorCodes,
    VaultProgram,
};
use anchor_lang::prelude::AccountMeta;
use anyhow::Result;

pub const MIN_TICK: i32 = -16383;
pub const MAX_TICK: i32 = 16383;
pub const TICK_HAS_DEBT_ARRAY_SIZE: usize = 8;
pub const TICK_HAS_DEBT_CHILDREN_SIZE: usize = 32; // 32 bytes = 256 bits
pub const BIT_PER_BYTE: usize = 8;
pub const TICK_HAS_DEBT_CHILDREN_SIZE_IN_BITS: usize = TICK_HAS_DEBT_CHILDREN_SIZE * BIT_PER_BYTE;
pub const TICKS_PER_TICK_HAS_DEBT: usize =
    TICK_HAS_DEBT_ARRAY_SIZE * TICK_HAS_DEBT_CHILDREN_SIZE_IN_BITS; // 8 * 256 = 2048

// Total range: -16383 to 16383 = 32767 ticks
// Each index covers 2048 ticks, so we need 16 indices to cover all ticks
pub const TOTAL_INDICES_NEEDED: usize = 16;

/// Get the index (0-15) for a given tick
pub fn get_array_index_for_tick(tick: i32) -> Result<u8> {
    if tick < MIN_TICK || tick > MAX_TICK {
        return Err(ErrorCodes::VaultTickHasDebtOutOfRange.into());
    }

    // Convert tick to 0-based index
    let tick_offset = tick.safe_sub(MIN_TICK)?; // 0 to 32766

    // Each index covers 2048 ticks
    let index = (tick_offset / TICKS_PER_TICK_HAS_DEBT as i32) as u8;

    Ok(index)
}

/// Given a tick and the array index, returns (map_index, byte_index, bit_index) where:
/// - map_index: the index within tick_has_debt array (0-7)
/// - byte_index: the index within the children_bits array (0-31)
/// - bit_index: the bit index within the byte (0-7)
pub fn get_tick_indices_for_array(tick: i32, array_index: u8) -> Result<(u8, u8, u8)> {
    // Validate tick range
    if tick < MIN_TICK || tick > MAX_TICK {
        return Err(ErrorCodes::VaultTickHasDebtOutOfRange.into());
    }

    // Get the expected index for this tick
    let expected_index = get_array_index_for_tick(tick)?;
    if expected_index != array_index {
        return Err(ErrorCodes::VaultTickHasDebtIndexMismatch.into());
    }

    // Get the first tick for this array index
    let first_tick_for_index = get_first_tick_for_array_index(array_index)?;

    // Calculate position within this array (0 to 2047)
    let tick_within_array = tick.safe_sub(first_tick_for_index)?;

    if tick_within_array >= TICKS_PER_TICK_HAS_DEBT as i32 {
        return Err(ErrorCodes::VaultTickHasDebtOutOfRange.into());
    }

    // Each map_index covers 256 ticks
    let map_index =
        (tick_within_array / TICK_HAS_DEBT_CHILDREN_SIZE_IN_BITS as i32).cast::<u8>()?;

    // Within each map, calculate byte and bit position
    let tick_within_map = tick_within_array % TICK_HAS_DEBT_CHILDREN_SIZE_IN_BITS as i32;
    let byte_index = (tick_within_map / BIT_PER_BYTE as i32).cast::<u8>()?;
    let bit_index = (tick_within_map % BIT_PER_BYTE as i32).cast::<u8>()?;

    Ok((map_index, byte_index, bit_index))
}

/// Get the first tick for a given index (0-15)
pub fn get_first_tick_for_array_index(index: u8) -> Result<i32> {
    if index >= TOTAL_INDICES_NEEDED as u8 {
        return Err(ErrorCodes::VaultTickHasDebtOutOfRange.into());
    }

    // Index layout:
    // index 0: ticks -16383 to -14336 (2048 ticks)
    // index 1: ticks -14335 to -12288 (2048 ticks)
    // index 2: ticks -12287 to -10240 (2048 ticks)
    // index 3: ticks -10239 to -8192 (2048 ticks)
    // index 4: ticks -8191 to -6144 (2048 ticks)
    // index 5: ticks -6143 to -4096 (2048 ticks)
    // index 6: ticks -4095 to -2048 (2048 ticks)
    // index 7: ticks -2047 to 0 (2048 ticks)
    // index 8: ticks 1 to 2048 (2048 ticks)
    // index 9: ticks 2049 to 4096 (2048 ticks)
    // ...
    // index 15: ticks 14336 to 16383 (2047 ticks)

    Ok(MIN_TICK.safe_add((index as i32).safe_mul(TICKS_PER_TICK_HAS_DEBT as i32)?)?)
}

pub fn get_tick_indices(tick: i32) -> Result<(u8, usize, usize, usize)> {
    let array_index = get_array_index_for_tick(tick)?;
    let (map_index, byte_index, bit_index) = get_tick_indices_for_array(tick, array_index)?;

    Ok((
        array_index,
        map_index.cast()?,
        byte_index.cast()?,
        bit_index.cast()?,
    ))
}

pub fn get_next_ref_tick(
    minima_tick: i32,
    next_tick: i32,
    liquidation_tick: i32,
) -> Result<(i32, u8)> {
    // Fetching refTick. refTick is the biggest tick of these 3:
    // 1. Next tick with liquidity (from tickHasDebt)
    // 2. Minima tick of current branch
    // 3. Liquidation threshold tick

    if minima_tick > next_tick && minima_tick > liquidation_tick {
        Ok((minima_tick, 2))
    } else if next_tick > liquidation_tick {
        // next tick will be next tick from perfect tick
        Ok((next_tick, 1))
    } else {
        // next tick is threshold tick
        Ok((liquidation_tick, 3))
    }
}

pub fn get_current_partials_ratio(minima_tick_partials: u32, ratio: u128) -> Result<(u128, u128)> {
    // Liquidated tick - has partials
    let ratio_one_less = ratio
        .safe_mul(FOUR_DECIMALS)?
        .safe_div(TickMath::TICK_SPACING)?;

    let length = ratio.safe_sub(ratio_one_less)?;

    // Get partials from branch data
    let partials = minima_tick_partials.cast()?;

    // Calculate current ratio with partials
    let current_ratio = ratio_one_less.safe_add((length.safe_mul(partials)?).safe_div(X30)?)?;

    Ok((current_ratio, partials))
}

pub async fn get_ticks_from_remaining_accounts(
    remaining_accounts: &Vec<AccountMeta>,
    remaining_accounts_indices: &Vec<u8>,
    program: &VaultProgram,
) -> Result<Vec<Tick>> {
    let total_ticks_length: usize = remaining_accounts_indices[2].cast::<usize>()?;

    // remaining_accounts_indices[0] is oracle sources length
    // remaining_accounts_indices[1] is branches length
    // remaining_accounts_indices[2] is ticks length
    // remaining_accounts_indices[3] is tick has debt length

    let start_index: usize = remaining_accounts_indices[0].cast::<usize>()?
        + remaining_accounts_indices[1].cast::<usize>()?;

    let end_index: usize = start_index + total_ticks_length;

    Ok(futures::future::try_join_all(
        remaining_accounts
            .iter()
            .take(end_index)
            .skip(start_index)
            .map(|account| program.account::<Tick>(account.pubkey)),
    )
    .await?)
}

pub fn get_tick_partials(ratio_one_less: u128, final_ratio: u128) -> Result<u128> {
    let ratio = ratio_one_less
        .safe_mul(TickMath::TICK_SPACING)?
        .safe_div(FOUR_DECIMALS)?;

    let length = ratio.safe_sub(ratio_one_less)?;
    let partials = (final_ratio.safe_sub(ratio_one_less)?)
        .safe_mul(X30)?
        .safe_div(length)?;

    Ok(partials)
}