feat(twap-oracle): implement RecordTick instruction

Add RecordTick — a permissionless instruction that reads the current tick
from a CurrentTickAccount and advances a PriceObservations ring buffer.

Authorization is implicit: both PDAs are verified against price_source_id,
so the tick can only have been written by whoever controls that price source.
A sampling guard silently no-ops if less than `window_duration /
OBSERVATIONS_CAPACITY`` ms have elapsed, allowing keepers to call blindly on
every block. Tick-delta truncation clamps the per-observation delta to
`MAX_TICK_DELTA (9 116)` before advancing tick_cumulative, with
last_recorded_tick tracking the untruncated position for the next delta.

Also switches ObservationEntry.tick_cumulative to use elapsed milliseconds
rather than seconds.

Closes #116
This commit is contained in:
r4bbit
2026-06-17 14:46:30 +02:00
parent 3285d5787e
commit e8fe634a2c
6 changed files with 1001 additions and 3 deletions
@@ -105,6 +105,32 @@ mod twap_oracle {
Ok(spel_framework::SpelOutput::execute(post_states, vec![]))
}
/// Records the current tick into a price observations ring buffer.
///
/// Expected accounts:
/// 1. `price_observations` — initialized PDA owned by this oracle program.
/// 2. `current_tick_account` — initialized PDA owned by this oracle program.
/// 3. `clock` — read-only LEZ clock account.
#[instruction]
pub fn record_tick(
ctx: ProgramContext,
price_observations: AccountWithMetadata,
current_tick_account: AccountWithMetadata,
clock: AccountWithMetadata,
price_source_id: AccountId,
window_duration: u64,
) -> SpelResult {
let post_states = twap_oracle_program::record_tick::record_tick(
price_observations,
current_tick_account,
clock,
price_source_id,
window_duration,
ctx.self_program_id,
);
Ok(spel_framework::SpelOutput::execute(post_states, vec![]))
}
/// Updates the tick stored in an existing current tick account.
///
/// Expected accounts: