jup-lend-sdk 0.1.3

SDK for Jupiter lending protocol
Documentation
use anchor_lang::Result;

use crate::math::{casting::Cast, safe_math::SafeMath};

use super::errors::ErrorCodes;

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()?,
    ))
}