1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
use crate::prelude::*;
use bytemuck::{try_cast_slice, try_from_bytes};
use bytemuck::{Pod, Zeroable};
use std::cell::Ref;
use superslice::*;

#[zero_copy(unsafe)]
#[derive(Default, Debug)]
#[repr(packed)]
pub struct AggregatorHistoryRow {
    /// The timestamp of the sample.
    pub timestamp: i64,
    /// The value of the sample.
    pub value: SwitchboardDecimal,
}
unsafe impl Pod for AggregatorHistoryRow {}
unsafe impl Zeroable for AggregatorHistoryRow {}

pub struct AggregatorHistoryBuffer<'a> {
    /// The current index of the round robin buffer.
    pub insertion_idx: usize,
    /// The array of samples collected from the aggregator.
    pub rows: Ref<'a, [AggregatorHistoryRow]>,
}

impl<'a> AggregatorHistoryBuffer<'a> {
    /// Returns the deserialized Switchboard history buffer account
    ///
    /// # Arguments
    ///
    /// * `history_buffer` - A Solana AccountInfo referencing an existing Switchboard history buffer account
    pub fn new(
        history_buffer: &'a AccountInfo,
    ) -> anchor_lang::Result<AggregatorHistoryBuffer<'a>> {
        let data = history_buffer.try_borrow_data()?;

        let mut disc_bytes = [0u8; 8];
        disc_bytes.copy_from_slice(&data[..8]);
        if disc_bytes != AggregatorHistoryBuffer::discriminator() {
            return Err(SwitchboardError::AccountDiscriminatorMismatch.into());
        }

        let insertion_idx: u32 = *try_from_bytes::<u32>(&data[8..12]).unwrap();
        return Ok(Self {
            insertion_idx: insertion_idx as usize,
            rows: Ref::map(data, |data| try_cast_slice(&data[12..]).unwrap()),
        });
    }

    /// Return the previous row in the history buffer for a given timestamp
    ///
    /// # Arguments
    ///
    /// * `timestamp` - A unix timestamp to search in the history buffer
    pub fn lower_bound(&self, timestamp: i64) -> Option<AggregatorHistoryRow> {
        if self.rows[self.insertion_idx].timestamp == 0 {
            return None;
        }
        let lower = &self.rows[..self.insertion_idx + 1];
        let lahr = lower.lower_bound_by(|x| {
            let other: i64 = x.timestamp;
            other.cmp(&timestamp)
        });
        if lahr < lower.len() && lower[lahr].timestamp == timestamp {
            return Some(lower[lahr]);
        }
        if lahr != 0 {
            return Some(lower[lahr - 1]);
        }

        if self.insertion_idx + 1 < self.rows.len()
            && self.rows[self.insertion_idx + 1].timestamp != 0
        {
            let upper = &self.rows[self.insertion_idx + 1..];
            let uahr = upper.lower_bound_by(|x| {
                let other: i64 = x.timestamp;
                other.cmp(&timestamp)
            });
            if uahr < upper.len() && upper[uahr].timestamp == timestamp {
                return Some(upper[uahr]);
            }
            if uahr != 0 {
                return Some(upper[uahr - 1]);
            }
        }
        None
    }
}

impl<'a> Discriminator for AggregatorHistoryBuffer<'a> {
    const DISCRIMINATOR: [u8; 8] = [66, 85, 70, 70, 69, 82, 120, 120];
}

impl<'a> Owner for AggregatorHistoryBuffer<'a> {
    fn owner() -> Pubkey {
        *SWITCHBOARD_PROGRAM_ID
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    // impl<'a> Default for AggregatorHistoryBuffer<'a> {
    // fn default() -> Self {
    // unsafe { std::mem::zeroed() }
    // }
    // }

    // insertion_idx = 1
    // 1646249940   - 100.6022611525
    // 1646249949   - 100.5200735
    // 1646249713   - 100.3012875
    // 1646249752   - 100.495469495
    // 1646249881   - 100.5763445
    // 1646249893   - 100.4691257925
    // 1646249902   - 100.517196115
    // 1646249911   - 100.5026458225
    // 1646249918   - 100.52034706
    // 1646249929   - 100.6000855

    const HISTORY_BUFFER_DATA: [u8; 292] = [
        66, 85, 70, 70, 69, 82, 120, 120, 1, 0, 0, 0, 212, 199, 31, 98, 0, 0, 0, 0, 69, 210, 158,
        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,
        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,
        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,
        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,
        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,
        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,
        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,
        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,
        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,
        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,
        0, 0, 7, 0, 0, 0,
    ];

    // DiFWjRtc9PQGposykEULC93y7uTXde3Eyr7HnQ7kvqkD
    const HISTORY_BUFFER_PUBKEY: Pubkey = Pubkey::new_from_array([
        188, 221, 119, 59, 130, 153, 226, 148, 95, 158, 33, 63, 106, 233, 240, 46, 242, 141, 150,
        147, 148, 158, 88, 14, 59, 66, 18, 82, 181, 250, 102, 130,
    ]);

    #[test]
    fn test_history_buffer() {
        let mut history_data = HISTORY_BUFFER_DATA;
        let mut lamports = 0;
        let history_account_info = AccountInfo::new(
            &HISTORY_BUFFER_PUBKEY,
            false,
            false,
            &mut lamports,
            &mut history_data,
            &SWITCHBOARD_PROGRAM_ID,
            false,
            0,
        );
        let history_buffer = AggregatorHistoryBuffer::new(&history_account_info).unwrap();

        // let mut counter = 0;
        // for row in history_buffer.rows.iter() {
        //     let val: f64 = row.value.try_into().unwrap();
        //     println!(
        //         "[{}] {} - {:?} = {}",
        //         counter, row.timestamp, row.value, val
        //     );
        //     counter = counter + 1;
        // }

        // Get result at exact timestamp, lower bound
        match history_buffer.lower_bound(1646249940) {
            None => panic!("failed to retrieve a value for a valid timestamp"),
            Some(row) => {
                let correct_value = SwitchboardDecimal {
                    mantissa: 1006022611525,
                    scale: 10,
                };
                if row.value != correct_value {
                    panic!(
                        "failed to retrieve correct value at exact timestamp 1646249940. received: {:?}, expected: {:?}",
                        row.value, correct_value
                    )
                }
            }
        };

        // Get result at exact timestamp, lower bound
        match history_buffer.lower_bound(1646249949) {
            None => panic!("failed to retrieve a value for a valid timestamp"),
            Some(row) => {
                let correct_value = SwitchboardDecimal {
                    mantissa: 1005200735,
                    scale: 7,
                };
                if row.value != correct_value {
                    panic!(
                        "failed to retrieve correct value at exact timestamp 1646249940. received: {:?}, expected: {:?}",
                        row.value, correct_value
                    )
                }
            }
        };

        // Get result at exact timestamp, upper bound
        match history_buffer.lower_bound(1646249911) {
            None => panic!("failed to retrieve a value for a valid timestamp"),
            Some(row) => {
                let correct_value = SwitchboardDecimal {
                    mantissa: 1005026458225,
                    scale: 10,
                };
                if row.value != correct_value {
                    panic!(
                        "failed to retrieve correct value at exact timestamp 1646249911. received: {:?}, expected: {:?}",
                        row.value, correct_value
                    )
                }
            }
        };

        // Get result at exact timestamp, upper bound
        match history_buffer.lower_bound(1646249929) {
            None => panic!("failed to retrieve a value for a valid timestamp"),
            Some(row) => {
                let correct_value = SwitchboardDecimal {
                    mantissa: 1006000855,
                    scale: 7,
                };
                if row.value != correct_value {
                    panic!(
                        "failed to retrieve correct value at exact timestamp 1646249911. received: {:?}, expected: {:?}",
                        row.value, correct_value
                    )
                }
            }
        };

        // Get lower bound result
        match history_buffer.lower_bound(1646249912) {
            None => panic!("failed to retrieve a value for a valid timestamp"),
            Some(row) => {
                let correct_value = SwitchboardDecimal {
                    mantissa: 1005026458225,
                    scale: 10,
                };
                if row.value != correct_value {
                    panic!("failed to retrieve correct value for timestamp 1646249912. received: {:?}, expected: {:?}",row.value, correct_value)
                }
            }
        };

        // Get previous result
        match history_buffer.lower_bound(1646249910) {
            None => panic!("failed to retrieve a value for a valid timestamp"),
            Some(row) => {
                let correct_value = SwitchboardDecimal {
                    mantissa: 100517196115,
                    scale: 9,
                };
                if row.value != correct_value {
                    panic!(
                        "failed to retrieve correct value for timestamp 1646249910. received: {:?}, expected: {:?}",
                        row.value, correct_value
                    )
                }
            }
        };

        // Get future result
        match history_buffer.lower_bound(2646249911) {
            None => panic!("failed to retrieve a value for a valid timestamp"),
            Some(row) => {
                let correct_value = SwitchboardDecimal {
                    mantissa: 1005200735,
                    scale: 7,
                };
                if row.value != correct_value {
                    panic!("failed to retrieve correct value for timestamp 2646249911. received: {:?}, expected: {:?}",row.value, correct_value)
                }
            }
        };

        // Get past result
        match history_buffer.lower_bound(646249911) {
            None => (),
            Some(row) => panic!("retrieved row when no value was expected {:?}", row.value),
        };
    }
}