orca_whirlpools_client/state/
tick_array.rs1use 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
260impl 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}