Skip to main content

orca_whirlpools_client/state/
tick_array.rs

1use borsh::{BorshDeserialize, BorshSerialize};
2use solana_account_info::AccountInfo;
3
4use crate::{
5    DynamicTick, DynamicTickArray, DynamicTickData, FixedTickArray, Tick,
6    DYNAMIC_TICK_ARRAY_DISCRIMINATOR, FIXED_TICK_ARRAY_DISCRIMINATOR,
7};
8
9#[derive(Clone, Debug, Eq, PartialEq)]
10#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
11#[cfg_attr(feature = "serde", serde(tag = "type"))]
12pub enum TickArray {
13    FixedTickArray(FixedTickArray),
14    DynamicTickArray(DynamicTickArray),
15}
16
17impl TickArray {
18    pub fn from_bytes(bytes: &[u8]) -> Result<Self, std::io::Error> {
19        if bytes.len() < 8 {
20            return Err(std::io::Error::new(
21                std::io::ErrorKind::InvalidInput,
22                "Invalid account data length",
23            ));
24        }
25        let discriminator = &bytes[0..8];
26        if discriminator == FIXED_TICK_ARRAY_DISCRIMINATOR {
27            let tick_array = FixedTickArray::from_bytes(bytes)?;
28            Ok(Self::FixedTickArray(tick_array))
29        } else if discriminator == DYNAMIC_TICK_ARRAY_DISCRIMINATOR {
30            let dynamic_tick_array = DynamicTickArray::from_bytes(bytes)?;
31            Ok(Self::DynamicTickArray(dynamic_tick_array))
32        } else {
33            Err(std::io::Error::new(
34                std::io::ErrorKind::InvalidInput,
35                "Invalid account discriminator",
36            ))
37        }
38    }
39}
40
41impl<'a> TryFrom<&AccountInfo<'a>> for TickArray {
42    type Error = std::io::Error;
43
44    fn try_from(account_info: &AccountInfo<'a>) -> Result<Self, Self::Error> {
45        let data: &[u8] = &(*account_info.data).borrow();
46        Self::from_bytes(data)
47    }
48}
49
50impl From<TickArray> for FixedTickArray {
51    fn from(val: TickArray) -> Self {
52        match val {
53            TickArray::FixedTickArray(tick_array) => tick_array,
54            TickArray::DynamicTickArray(dynamic_tick_array) => dynamic_tick_array.into(),
55        }
56    }
57}
58
59impl From<TickArray> for DynamicTickArray {
60    fn from(val: TickArray) -> Self {
61        match val {
62            TickArray::DynamicTickArray(dynamic_tick_array) => dynamic_tick_array,
63            TickArray::FixedTickArray(tick_array) => tick_array.into(),
64        }
65    }
66}
67
68impl From<FixedTickArray> for DynamicTickArray {
69    fn from(val: FixedTickArray) -> Self {
70        DynamicTickArray {
71            discriminator: DYNAMIC_TICK_ARRAY_DISCRIMINATOR.try_into().unwrap(),
72            start_tick_index: val.start_tick_index,
73            whirlpool: val.whirlpool,
74            tick_bitmap: val
75                .ticks
76                .iter()
77                .enumerate()
78                .fold(0u128, |acc, (offset, tick)| {
79                    if tick.initialized {
80                        acc | (1u128 << offset)
81                    } else {
82                        acc
83                    }
84                }),
85            ticks: val.ticks.map(|tick| tick.into()),
86        }
87    }
88}
89
90impl From<DynamicTickArray> for FixedTickArray {
91    fn from(val: DynamicTickArray) -> Self {
92        FixedTickArray {
93            discriminator: FIXED_TICK_ARRAY_DISCRIMINATOR.try_into().unwrap(),
94            start_tick_index: val.start_tick_index,
95            whirlpool: val.whirlpool,
96            ticks: val.ticks.map(|tick| tick.into()),
97        }
98    }
99}
100
101impl From<Tick> for DynamicTick {
102    fn from(val: Tick) -> Self {
103        match val.initialized {
104            true => DynamicTick::Initialized(DynamicTickData {
105                liquidity_net: val.liquidity_net,
106                liquidity_gross: val.liquidity_gross,
107                fee_growth_outside_a: val.fee_growth_outside_a,
108                fee_growth_outside_b: val.fee_growth_outside_b,
109                reward_growths_outside: val.reward_growths_outside,
110            }),
111            false => DynamicTick::Uninitialized,
112        }
113    }
114}
115
116impl From<DynamicTick> for Tick {
117    fn from(val: DynamicTick) -> Self {
118        match val {
119            DynamicTick::Initialized(tick) => Tick {
120                initialized: true,
121                liquidity_net: tick.liquidity_net,
122                liquidity_gross: tick.liquidity_gross,
123                fee_growth_outside_a: tick.fee_growth_outside_a,
124                fee_growth_outside_b: tick.fee_growth_outside_b,
125                reward_growths_outside: tick.reward_growths_outside,
126            },
127            DynamicTick::Uninitialized => Tick {
128                initialized: false,
129                liquidity_net: 0,
130                liquidity_gross: 0,
131                fee_growth_outside_a: 0,
132                fee_growth_outside_b: 0,
133                reward_growths_outside: [0, 0, 0],
134            },
135        }
136    }
137}
138
139#[cfg(feature = "fetch")]
140pub fn fetch_tick_array(
141    rpc: &solana_client::rpc_client::RpcClient,
142    address: &solana_program::pubkey::Pubkey,
143) -> Result<crate::DecodedAccount<TickArray>, std::io::Error> {
144    let accounts = fetch_all_tick_array(rpc, &[*address])?;
145    Ok(accounts[0].clone())
146}
147
148#[cfg(feature = "fetch")]
149pub fn fetch_all_tick_array(
150    rpc: &solana_client::rpc_client::RpcClient,
151    addresses: &[solana_program::pubkey::Pubkey],
152) -> Result<Vec<crate::shared::DecodedAccount<TickArray>>, std::io::Error> {
153    let accounts = rpc
154        .get_multiple_accounts(addresses)
155        .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?;
156    let mut decoded_accounts: Vec<crate::shared::DecodedAccount<TickArray>> = Vec::new();
157    for i in 0..addresses.len() {
158        let address = addresses[i];
159        let account = accounts[i].as_ref().ok_or(std::io::Error::new(
160            std::io::ErrorKind::Other,
161            format!("Account not found: {}", address),
162        ))?;
163        let data = TickArray::from_bytes(&account.data)?;
164        decoded_accounts.push(crate::shared::DecodedAccount {
165            address,
166            account: account.clone(),
167            data,
168        });
169    }
170    Ok(decoded_accounts)
171}
172
173#[cfg(feature = "fetch")]
174pub fn fetch_maybe_tick_array(
175    rpc: &solana_client::rpc_client::RpcClient,
176    address: &solana_program::pubkey::Pubkey,
177) -> Result<crate::shared::MaybeAccount<TickArray>, std::io::Error> {
178    let accounts = fetch_all_maybe_tick_array(rpc, &[*address])?;
179    Ok(accounts[0].clone())
180}
181
182#[cfg(feature = "fetch")]
183pub fn fetch_all_maybe_tick_array(
184    rpc: &solana_client::rpc_client::RpcClient,
185    addresses: &[solana_program::pubkey::Pubkey],
186) -> Result<Vec<crate::shared::MaybeAccount<TickArray>>, std::io::Error> {
187    let accounts = rpc
188        .get_multiple_accounts(addresses)
189        .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?;
190    let mut decoded_accounts: Vec<crate::shared::MaybeAccount<TickArray>> = Vec::new();
191    for i in 0..addresses.len() {
192        let address = addresses[i];
193        if let Some(account) = accounts[i].as_ref() {
194            let data = TickArray::from_bytes(&account.data)?;
195            decoded_accounts.push(crate::shared::MaybeAccount::Exists(
196                crate::shared::DecodedAccount {
197                    address,
198                    account: account.clone(),
199                    data,
200                },
201            ));
202        } else {
203            decoded_accounts.push(crate::shared::MaybeAccount::NotFound(address));
204        }
205    }
206    Ok(decoded_accounts)
207}
208
209impl BorshSerialize for TickArray {
210    fn serialize<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
211        match self {
212            TickArray::FixedTickArray(tick_array) => tick_array.serialize(writer),
213            TickArray::DynamicTickArray(dynamic_tick_array) => dynamic_tick_array.serialize(writer),
214        }
215    }
216}
217
218impl BorshDeserialize for TickArray {
219    fn deserialize_reader<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> {
220        let mut buf = Vec::new();
221        reader.read_to_end(&mut buf)?;
222        Self::from_bytes(&buf)
223    }
224}
225
226#[cfg(feature = "anchor")]
227impl anchor_lang::AccountDeserialize for TickArray {
228    fn try_deserialize_unchecked(buf: &mut &[u8]) -> anchor_lang::Result<Self> {
229        Ok(Self::from_bytes(buf)?)
230    }
231}
232
233#[cfg(feature = "anchor")]
234impl anchor_lang::AccountSerialize for TickArray {
235    fn try_serialize<W: std::io::Write>(&self, writer: &mut W) -> anchor_lang::Result<()> {
236        match self {
237            TickArray::FixedTickArray(tick_array) => tick_array.try_serialize(writer),
238            TickArray::DynamicTickArray(dynamic_tick_array) => {
239                dynamic_tick_array.try_serialize(writer)
240            }
241        }
242    }
243}
244
245#[cfg(feature = "anchor")]
246impl anchor_lang::Owner for TickArray {
247    fn owner() -> anchor_lang::prelude::Pubkey {
248        anchor_lang::prelude::Pubkey::from(crate::WHIRLPOOL_ID.to_bytes())
249    }
250}
251
252#[cfg(feature = "anchor-idl-build")]
253impl anchor_lang::IdlBuild for TickArray {}
254
255#[cfg(feature = "anchor-idl-build")]
256impl anchor_lang::Discriminator for TickArray {
257    const DISCRIMINATOR: [u8; 8] = [0; 8];
258}
259
260// For backwards compatibility with
261
262impl TickArray {
263    #[deprecated = "Use FixedTickArray::LEN instead or DynamicTickArray::MIN_LEN|MAX_LEN instead"]
264    pub const LEN: usize = FixedTickArray::LEN;
265}
266
267#[cfg(test)]
268mod from_fixed_tick_array_test {
269    use super::*;
270
271    #[test]
272    fn test_from_fixed_tick_array() {
273        let mut ticks: [Tick; 88] = std::array::from_fn(|_| Tick {
274            initialized: false,
275            liquidity_net: 0,
276            liquidity_gross: 0,
277            fee_growth_outside_a: 0,
278            fee_growth_outside_b: 0,
279            reward_growths_outside: [0, 0, 0],
280        });
281
282        ticks[1] = Tick {
283            initialized: true,
284            liquidity_net: 100,
285            liquidity_gross: 100,
286            fee_growth_outside_a: 300,
287            fee_growth_outside_b: 400,
288            reward_growths_outside: [500, 600, 700],
289        };
290
291        ticks[86] = Tick {
292            initialized: true,
293            liquidity_net: 200,
294            liquidity_gross: 200,
295            fee_growth_outside_a: 800,
296            fee_growth_outside_b: 900,
297            reward_growths_outside: [1000, 1100, 1200],
298        };
299
300        let fixed_tick_array = FixedTickArray {
301            discriminator: FIXED_TICK_ARRAY_DISCRIMINATOR.try_into().unwrap(),
302            start_tick_index: 88,
303            whirlpool: solana_program::pubkey::Pubkey::new_unique(),
304            ticks,
305        };
306        let dynamic_tick_array: DynamicTickArray = fixed_tick_array.clone().into();
307
308        assert_eq!(dynamic_tick_array.start_tick_index, 88);
309        assert_eq!(dynamic_tick_array.whirlpool, fixed_tick_array.whirlpool);
310        assert_eq!(dynamic_tick_array.tick_bitmap, (1 << 1) | (1 << 86));
311        for (i, tick) in dynamic_tick_array.ticks.iter().enumerate() {
312            if i == 1 {
313                assert_eq!(
314                    tick,
315                    &DynamicTick::Initialized(DynamicTickData {
316                        liquidity_net: 100,
317                        liquidity_gross: 100,
318                        fee_growth_outside_a: 300,
319                        fee_growth_outside_b: 400,
320                        reward_growths_outside: [500, 600, 700],
321                    })
322                );
323            } else if i == 86 {
324                assert_eq!(
325                    tick,
326                    &DynamicTick::Initialized(DynamicTickData {
327                        liquidity_net: 200,
328                        liquidity_gross: 200,
329                        fee_growth_outside_a: 800,
330                        fee_growth_outside_b: 900,
331                        reward_growths_outside: [1000, 1100, 1200],
332                    })
333                );
334            } else {
335                assert_eq!(tick, &DynamicTick::Uninitialized);
336            }
337        }
338    }
339}