clmm-common 0.1.39

Blockchain, Clmm for Solana
Documentation
use std::ops::{Div, Sub};

use borsh::BorshDeserialize;
use solana_program::pubkey::Pubkey;
use tabled::Tabled;

use crate::contract::state::tick::Tick;
use crate::error::ErrorCode;
use crate::program::SWAP_PROGRAM_ID;

#[derive(BorshDeserialize, Tabled)]
pub struct TickArrayHeader {
    pub array_index: u16,
    pub tick_spacing: u16,
    pub clmmpool: Pubkey,
}

#[derive(Debug, Tabled)]
pub struct TickArrayToShow {
    pub address: Pubkey,
    pub array_index: u16,
    pub tick_spacing: u16,
    pub clmmpool: Pubkey,
    pub array_space: u32,
    pub start_tick_index: i32,
    pub end_tick_index: i32,
}

pub struct TickArray {
    /// The tick array index in tick array bit map.
    pub array_index: u16,
    /// The tick spacing
    pub tick_spacing: u16,
    /// The [Clmmpool] address.
    pub clmmpool: Pubkey,
    /// The ticks.
    pub ticks: [Tick; 64],
}

impl Default for TickArray {
    #[inline]
    fn default() -> Self {
        TickArray {
            array_index: 0,
            tick_spacing: 0,
            clmmpool: Pubkey::default(),
            ticks: [Tick::default(); 64],
        }
    }
}

#[derive(Tabled)]
pub struct ArrayInfo {
    array_index: u16,
    start_tick_index: i32,
    end_tick_index: i32,
}

impl TickArray {
    pub const CAP: usize = 64;
    pub const LEN: usize = 2 + 2 + 32 + Tick::LEN * TickArray::CAP;

    #[inline]
    pub fn array_spacing(&self) -> usize {
        self.tick_spacing as usize * TickArray::CAP
    }

    #[inline]
    pub fn start_tick_index(&self) -> i32 {
        Tick::min(self.tick_spacing) + (self.array_index as usize * self.array_spacing()) as i32
    }

    #[inline]
    pub fn end_tick_index(&self) -> i32 {
        self.start_tick_index() + self.array_spacing() as i32 - self.tick_spacing as i32
    }

    #[inline]
    pub fn is_in_array(&self, tick_index: i32) -> bool {
        tick_index >= self.start_tick_index() && tick_index <= self.end_tick_index()
    }

    #[inline]
    pub fn array_index(tick_index: i32, tick_spacing: u16) -> Result<u16, ErrorCode> {
        let min = Tick::min(tick_spacing);
        let max = Tick::max(tick_spacing);
        if tick_index < min || tick_index > max {
            return Err(ErrorCode::InvalidTickIndex);
        }
        let array_spacing = (TickArray::CAP * tick_spacing as usize) as i32;
        Ok(((tick_index - min) / array_spacing) as u16)
    }

    pub fn array_info(tick_index: i32, tick_spacing: u16) -> ArrayInfo {
        let array_index = TickArray::array_index(tick_index, tick_spacing).unwrap();
        let array_spacing = tick_spacing as usize * TickArray::CAP;
        let start_tick_index =
            Tick::min(tick_spacing) + (array_index as usize * array_spacing) as i32;
        let end_tick_index = start_tick_index + array_spacing as i32 - tick_spacing as i32;
        ArrayInfo {
            array_index,
            start_tick_index,
            end_tick_index,
        }
    }

    pub fn is_min_tick_array(&self) -> bool {
        self.start_tick_index() == Tick::min(self.tick_spacing)
    }

    pub fn is_max_tick_array(&self) -> bool {
        self.end_tick_index() >= Tick::max(self.tick_spacing)
    }

    #[inline]
    pub fn tick_offset(&self, tick_index: i32) -> usize {
        tick_index
            .sub(self.start_tick_index())
            .div(self.tick_spacing as i32) as usize
    }

    pub fn get_tick(&self, tick_index: i32) -> Option<&Tick> {
        let offset = self.tick_offset(tick_index);
        if offset < TickArray::CAP as usize {
            let t = self.ticks[offset as usize];
            if t.is_initialized {
                return Some(&self.ticks[offset as usize]);
            }
        }
        return None;
    }

    pub fn search_range(&self, tick_index: i32, a_to_b: bool) -> Option<(usize, usize)> {
        match a_to_b {
            true => {
                if tick_index < self.start_tick_index() {
                    return None;
                }
                let end = if tick_index >= self.end_tick_index() {
                    TickArray::CAP - 1
                } else {
                    self.tick_offset(tick_index)
                    //let mut offset = self.tick_offset(tick_index);
                    //if tick_index % self.tick_spacing as i32 != 0 {
                    //    offset = offset + 1
                    //}
                    //offset - 1
                };
                Some((0, end))
            }
            false => {
                if tick_index >= self.end_tick_index() {
                    return None;
                }
                let start = if tick_index < self.start_tick_index() {
                    0
                } else {
                    self.tick_offset(tick_index) + 1
                };
                Some((start, TickArray::CAP - 1))
            }
        }
    }

    pub fn get_next_initialized_tick(&self, tick_index: i32, a_to_b: bool) -> Option<&Tick> {
        let search_range = self.search_range(tick_index, a_to_b);
        if search_range.is_none() {
            return None;
        }
        let (start, end) = search_range.unwrap();
        match a_to_b {
            true => {
                for i in (start..=end).rev() {
                    let t = self.ticks[i];
                    if t.is_initialized {
                        return Some(&self.ticks[i]);
                    }
                }
                None
            }
            false => {
                for i in start..=end {
                    let t = self.ticks[i];
                    if t.is_initialized {
                        return Some(&self.ticks[i]);
                    }
                }
                None
            }
        }
    }

    pub fn is_tick_array_valid(&self) -> bool {
        for tick in &self.ticks {
            if tick.is_initialized {
                return true;
            }
        }
        false
    }

    pub fn find_address(clmmpool: &Pubkey, array_index: u16) -> Pubkey {
        let (address, _) = Pubkey::find_program_address(
            &[
                b"tick_array",
                clmmpool.as_ref(),
                array_index.to_le_bytes().as_ref(),
            ],
            &SWAP_PROGRAM_ID,
        );
        address
    }

    pub fn deserialize(buf: Vec<u8>) -> Option<TickArray> {
        let header = TickArrayHeader::deserialize(&mut &buf[8..44]).unwrap();
        let mut tick_array = TickArray::default();
        tick_array.clmmpool = header.clmmpool;
        tick_array.array_index = header.array_index;
        tick_array.tick_spacing = header.tick_spacing;
        let mut s = 44;
        let mut e = Tick::LEN + 44;
        for i in 0..64 {
            let tick = Tick::deserialize(&mut &buf[s..e]).unwrap();
            tick_array.ticks[i] = tick;
            s = s + Tick::LEN;
            e = e + Tick::LEN;
        }
        Some(tick_array)
    }

    pub fn calculate_tick_array_key(tick_array: &[u8], array_index: &[u8]) -> (Pubkey, u8) {
        Pubkey::find_program_address(&[b"tick_array", tick_array, array_index], &SWAP_PROGRAM_ID)
    }

    pub fn to_tick_array_show(&self) -> TickArrayToShow {
        TickArrayToShow {
            address: TickArray::find_address(&self.clmmpool, self.array_index),
            array_index: self.array_index,
            tick_spacing: self.tick_spacing,
            clmmpool: self.clmmpool,
            array_space: 0,
            start_tick_index: self.start_tick_index(),
            end_tick_index: self.end_tick_index(),
        }
    }
}