(ctx: Context<Calc<'info>>)
| 63 | } |
| 64 | |
| 65 | pub fn handler<'info>(ctx: Context<Calc<'info>>) -> Result<ThreadResponse> { |
| 66 | // get accounts |
| 67 | let avg_buffer_acc = ctx.accounts.avg_buffer.as_ref(); |
| 68 | let price_buffer_acc = ctx.accounts.price_buffer.as_ref(); |
| 69 | let price_feed = &ctx.accounts.price_feed; |
| 70 | let stat = &mut ctx.accounts.stat; |
| 71 | let thread = &ctx.accounts.thread; |
| 72 | let time_series_acc = ctx.accounts.time_series.as_ref(); |
| 73 | |
| 74 | // load mut entries |
| 75 | let mut avg_buffer = load_entries_mut::<AvgBuffer, i64>(avg_buffer_acc.try_borrow_mut_data()?).unwrap(); |
| 76 | let mut price_buffer = load_entries_mut::<PriceBuffer, i64>(price_buffer_acc.try_borrow_mut_data()?).unwrap(); |
| 77 | let mut time_series = load_entries_mut::<TimeSeries, i64>(time_series_acc.try_borrow_mut_data()?).unwrap(); |
| 78 | |
| 79 | let mut next_instruction: Option<InstructionData> = None; |
| 80 | |
| 81 | match load_price_feed_from_account_info(&price_feed.to_account_info()) { |
| 82 | Ok(price_feed) => { |
| 83 | // Load Pyth price fee. |
| 84 | let price = price_feed.get_price_unchecked(); |
| 85 | |
| 86 | // Starting at the tail, start nullifying data points older than the lookback window. |
| 87 | // TODO This is a worst-case linear operation over a large time_series. |
| 88 | // Watch out for exceeding compute unit limits. Since this is a threaded instruction, |
| 89 | // we can run it as an infinite loop until we've cleared out all the old data. |
| 90 | match stat.head { |
| 91 | None => {}, // Noop |
| 92 | Some(head) => { |
| 93 | let mut tail = (head - stat.sample_count as i64 + 1).rem_euclid(stat.buffer_size as i64); |
| 94 | while stat.sample_count > 0 && time_series[tail as usize] < price.publish_time - stat.lookback_window { |
| 95 | stat.sample_sum -= price_buffer[tail as usize]; |
| 96 | stat.sample_count -= 1; |
| 97 | price_buffer[tail as usize] = i64::default(); |
| 98 | avg_buffer[tail as usize] = i64::default(); |
| 99 | time_series[tail as usize] = i64::default(); |
| 100 | tail = (tail + 1).rem_euclid(stat.buffer_size as i64); |
| 101 | } |
| 102 | } |
| 103 | } |
| 104 | |
| 105 | // Insert the new data point, and update head. |
| 106 | match stat.head { |
| 107 | // no data present |
| 108 | None => { |
| 109 | stat.head = Some(0); |
| 110 | time_series[0] = price.publish_time; |
| 111 | price_buffer[0] = price.price; |
| 112 | stat.sample_count += 1; |
| 113 | }, |
| 114 | // data present |
| 115 | Some(head) => { |
| 116 | // update head idx for next insertion |
| 117 | stat.head = Some((head + 1).rem_euclid(stat.buffer_size as i64)); |
| 118 | |
| 119 | // If the buffer is not yet full, increment the sample count. |
| 120 | // Otherwise, subtract the data value that's about to be overwritten from the sum. |
| 121 | if stat.sample_count < stat.buffer_size { |
| 122 | stat.sample_count += 1 |
no outgoing calls
no test coverage detected