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 {
pub array_index: u16,
pub tick_spacing: u16,
pub clmmpool: Pubkey,
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)
};
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(),
}
}
}