switchboard_solana/oracle_program/accounts/
history_buffer.rs

1use crate::prelude::*;
2use bytemuck::{try_cast_slice, try_from_bytes};
3use bytemuck::{Pod, Zeroable};
4use std::cell::Ref;
5use superslice::*;
6
7#[zero_copy(unsafe)]
8#[derive(Default, Debug)]
9#[repr(packed)]
10pub struct AggregatorHistoryRow {
11    /// The timestamp of the sample.
12    pub timestamp: i64,
13    /// The value of the sample.
14    pub value: SwitchboardDecimal,
15}
16unsafe impl Pod for AggregatorHistoryRow {}
17unsafe impl Zeroable for AggregatorHistoryRow {}
18
19pub struct AggregatorHistoryBuffer<'a> {
20    /// The current index of the round robin buffer.
21    pub insertion_idx: usize,
22    /// The array of samples collected from the aggregator.
23    pub rows: Ref<'a, [AggregatorHistoryRow]>,
24}
25
26impl<'a> AggregatorHistoryBuffer<'a> {
27    /// Returns the deserialized Switchboard history buffer account
28    ///
29    /// # Arguments
30    ///
31    /// * `history_buffer` - A Solana AccountInfo referencing an existing Switchboard history buffer account
32    pub fn new(
33        history_buffer: &'a AccountInfo,
34    ) -> anchor_lang::Result<AggregatorHistoryBuffer<'a>> {
35        let data = history_buffer.try_borrow_data()?;
36
37        let mut disc_bytes = [0u8; 8];
38        disc_bytes.copy_from_slice(&data[..8]);
39        if disc_bytes != AggregatorHistoryBuffer::discriminator() {
40            return Err(SwitchboardError::AccountDiscriminatorMismatch.into());
41        }
42
43        let insertion_idx: u32 = *try_from_bytes::<u32>(&data[8..12]).unwrap();
44        return Ok(Self {
45            insertion_idx: insertion_idx as usize,
46            rows: Ref::map(data, |data| try_cast_slice(&data[12..]).unwrap()),
47        });
48    }
49
50    /// Return the previous row in the history buffer for a given timestamp
51    ///
52    /// # Arguments
53    ///
54    /// * `timestamp` - A unix timestamp to search in the history buffer
55    pub fn lower_bound(&self, timestamp: i64) -> Option<AggregatorHistoryRow> {
56        if self.rows[self.insertion_idx].timestamp == 0 {
57            return None;
58        }
59        let lower = &self.rows[..self.insertion_idx + 1];
60        let lahr = lower.lower_bound_by(|x| {
61            let other: i64 = x.timestamp;
62            other.cmp(&timestamp)
63        });
64        if lahr < lower.len() && lower[lahr].timestamp == timestamp {
65            return Some(lower[lahr]);
66        }
67        if lahr != 0 {
68            return Some(lower[lahr - 1]);
69        }
70
71        if self.insertion_idx + 1 < self.rows.len()
72            && self.rows[self.insertion_idx + 1].timestamp != 0
73        {
74            let upper = &self.rows[self.insertion_idx + 1..];
75            let uahr = upper.lower_bound_by(|x| {
76                let other: i64 = x.timestamp;
77                other.cmp(&timestamp)
78            });
79            if uahr < upper.len() && upper[uahr].timestamp == timestamp {
80                return Some(upper[uahr]);
81            }
82            if uahr != 0 {
83                return Some(upper[uahr - 1]);
84            }
85        }
86        None
87    }
88}
89
90impl<'a> Discriminator for AggregatorHistoryBuffer<'a> {
91    const DISCRIMINATOR: [u8; 8] = [66, 85, 70, 70, 69, 82, 120, 120];
92}
93
94impl<'a> Owner for AggregatorHistoryBuffer<'a> {
95    fn owner() -> Pubkey {
96        *SWITCHBOARD_PROGRAM_ID
97    }
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103
104    // impl<'a> Default for AggregatorHistoryBuffer<'a> {
105    // fn default() -> Self {
106    // unsafe { std::mem::zeroed() }
107    // }
108    // }
109
110    // insertion_idx = 1
111    // 1646249940   - 100.6022611525
112    // 1646249949   - 100.5200735
113    // 1646249713   - 100.3012875
114    // 1646249752   - 100.495469495
115    // 1646249881   - 100.5763445
116    // 1646249893   - 100.4691257925
117    // 1646249902   - 100.517196115
118    // 1646249911   - 100.5026458225
119    // 1646249918   - 100.52034706
120    // 1646249929   - 100.6000855
121
122    const HISTORY_BUFFER_DATA: [u8; 292] = [
123        66, 85, 70, 70, 69, 82, 120, 120, 1, 0, 0, 0, 212, 199, 31, 98, 0, 0, 0, 0, 69, 210, 158,
124        59, 234, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 221, 199, 31, 98, 0, 0, 0, 0, 95,
125        37, 234, 59, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 241, 198, 31, 98, 0, 0, 0, 0,
126        11, 195, 200, 59, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 24, 199, 31, 98, 0, 0, 0,
127        0, 183, 43, 255, 101, 23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 153, 199, 31, 98, 0,
128        0, 0, 0, 117, 187, 242, 59, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 165, 199, 31,
129        98, 0, 0, 0, 0, 69, 250, 67, 236, 233, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 174,
130        199, 31, 98, 0, 0, 0, 0, 83, 177, 74, 103, 23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0,
131        183, 199, 31, 98, 0, 0, 0, 0, 113, 186, 62, 0, 234, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0,
132        0, 0, 190, 199, 31, 98, 0, 0, 0, 0, 146, 224, 37, 87, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
133        8, 0, 0, 0, 201, 199, 31, 98, 0, 0, 0, 0, 215, 90, 246, 59, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
134        0, 0, 7, 0, 0, 0,
135    ];
136
137    // DiFWjRtc9PQGposykEULC93y7uTXde3Eyr7HnQ7kvqkD
138    const HISTORY_BUFFER_PUBKEY: Pubkey = Pubkey::new_from_array([
139        188, 221, 119, 59, 130, 153, 226, 148, 95, 158, 33, 63, 106, 233, 240, 46, 242, 141, 150,
140        147, 148, 158, 88, 14, 59, 66, 18, 82, 181, 250, 102, 130,
141    ]);
142
143    #[test]
144    fn test_history_buffer() {
145        let mut history_data = HISTORY_BUFFER_DATA;
146        let mut lamports = 0;
147        let history_account_info = AccountInfo::new(
148            &HISTORY_BUFFER_PUBKEY,
149            false,
150            false,
151            &mut lamports,
152            &mut history_data,
153            &SWITCHBOARD_PROGRAM_ID,
154            false,
155            0,
156        );
157        let history_buffer = AggregatorHistoryBuffer::new(&history_account_info).unwrap();
158
159        // let mut counter = 0;
160        // for row in history_buffer.rows.iter() {
161        //     let val: f64 = row.value.try_into().unwrap();
162        //     println!(
163        //         "[{}] {} - {:?} = {}",
164        //         counter, row.timestamp, row.value, val
165        //     );
166        //     counter = counter + 1;
167        // }
168
169        // Get result at exact timestamp, lower bound
170        match history_buffer.lower_bound(1646249940) {
171            None => panic!("failed to retrieve a value for a valid timestamp"),
172            Some(row) => {
173                let correct_value = SwitchboardDecimal {
174                    mantissa: 1006022611525,
175                    scale: 10,
176                };
177                if row.value != correct_value {
178                    panic!(
179                        "failed to retrieve correct value at exact timestamp 1646249940. received: {:?}, expected: {:?}",
180                        row.value, correct_value
181                    )
182                }
183            }
184        };
185
186        // Get result at exact timestamp, lower bound
187        match history_buffer.lower_bound(1646249949) {
188            None => panic!("failed to retrieve a value for a valid timestamp"),
189            Some(row) => {
190                let correct_value = SwitchboardDecimal {
191                    mantissa: 1005200735,
192                    scale: 7,
193                };
194                if row.value != correct_value {
195                    panic!(
196                        "failed to retrieve correct value at exact timestamp 1646249940. received: {:?}, expected: {:?}",
197                        row.value, correct_value
198                    )
199                }
200            }
201        };
202
203        // Get result at exact timestamp, upper bound
204        match history_buffer.lower_bound(1646249911) {
205            None => panic!("failed to retrieve a value for a valid timestamp"),
206            Some(row) => {
207                let correct_value = SwitchboardDecimal {
208                    mantissa: 1005026458225,
209                    scale: 10,
210                };
211                if row.value != correct_value {
212                    panic!(
213                        "failed to retrieve correct value at exact timestamp 1646249911. received: {:?}, expected: {:?}",
214                        row.value, correct_value
215                    )
216                }
217            }
218        };
219
220        // Get result at exact timestamp, upper bound
221        match history_buffer.lower_bound(1646249929) {
222            None => panic!("failed to retrieve a value for a valid timestamp"),
223            Some(row) => {
224                let correct_value = SwitchboardDecimal {
225                    mantissa: 1006000855,
226                    scale: 7,
227                };
228                if row.value != correct_value {
229                    panic!(
230                        "failed to retrieve correct value at exact timestamp 1646249911. received: {:?}, expected: {:?}",
231                        row.value, correct_value
232                    )
233                }
234            }
235        };
236
237        // Get lower bound result
238        match history_buffer.lower_bound(1646249912) {
239            None => panic!("failed to retrieve a value for a valid timestamp"),
240            Some(row) => {
241                let correct_value = SwitchboardDecimal {
242                    mantissa: 1005026458225,
243                    scale: 10,
244                };
245                if row.value != correct_value {
246                    panic!("failed to retrieve correct value for timestamp 1646249912. received: {:?}, expected: {:?}",row.value, correct_value)
247                }
248            }
249        };
250
251        // Get previous result
252        match history_buffer.lower_bound(1646249910) {
253            None => panic!("failed to retrieve a value for a valid timestamp"),
254            Some(row) => {
255                let correct_value = SwitchboardDecimal {
256                    mantissa: 100517196115,
257                    scale: 9,
258                };
259                if row.value != correct_value {
260                    panic!(
261                        "failed to retrieve correct value for timestamp 1646249910. received: {:?}, expected: {:?}",
262                        row.value, correct_value
263                    )
264                }
265            }
266        };
267
268        // Get future result
269        match history_buffer.lower_bound(2646249911) {
270            None => panic!("failed to retrieve a value for a valid timestamp"),
271            Some(row) => {
272                let correct_value = SwitchboardDecimal {
273                    mantissa: 1005200735,
274                    scale: 7,
275                };
276                if row.value != correct_value {
277                    panic!("failed to retrieve correct value for timestamp 2646249911. received: {:?}, expected: {:?}",row.value, correct_value)
278                }
279            }
280        };
281
282        // Get past result
283        match history_buffer.lower_bound(646249911) {
284            None => (),
285            Some(row) => panic!("retrieved row when no value was expected {:?}", row.value),
286        };
287    }
288}