11 lines
262 B
Rust
Raw Normal View History

//! The TWAP Oracle Program implementation.
pub use twap_oracle_core as core;
pub mod create_current_tick_account;
feat(twap-oracle): implement CreateOraclePriceAccount instruction Adds the CreateOraclePriceAccount instruction to the TWAP oracle program. The instruction initialises a canonical OraclePriceAccount PDA for a given price source and time window, seeding it with a non-zero initial price and the current block timestamp so the account is immediately valid to consumers. - PDA mirrors PriceObservations: derived from (oracle_program_id, price_source_id, window_duration) with a distinct seed constant, so each (source, window) pair maps to a distinct oracle price account that cannot collide with its corresponding observations account. - source_id is not a parameter: it is always set to price_source.account_id. Accepting it as a free parameter would allow callers to register a price account that claims to represent a source it does not control. Deriving it from the authorized price source account closes that vector entirely. - Authorization follows the same model as CreatePriceObservations: is_authorized = true on the price source proves the caller controls it; the PDA check ensures the supplied oracle price account address is the one derived from that specific source and window. - The initial timestamp is read from the canonical 1-block LEZ clock (CLOCK_01_PROGRAM_ACCOUNT_ID), never from a caller-supplied value. The clock account_id is asserted, so a caller cannot substitute an account they control to forge the seeding timestamp. - A zero price or zero timestamp is rejected at creation. Both are the "no valid price" sentinel consumers treat as unset, so an account must never be created in that state; the instruction asserts a non-zero initial_price and a non-zero clock timestamp. - initial_price is a Q64.64 fixed-point value (real price = initial_price / 2^64), matching the oracle price representation. The non-zero check rejects the sentinel but cannot validate scale — supplying a correctly-scaled value is the caller's responsibility. Closes #129
2026-05-28 20:42:59 +02:00
pub mod create_oracle_price_account;
pub mod create_price_observations;
feat(twap-oracle): implement PublishPrice with tick-to-price conversion and tail extrapolation Add PublishPrice — a permissionless instruction that computes the TWAP over a PriceObservations buffer, extrapolated to the current time, and writes it to the consumer-facing OraclePriceAccount. The stored body averages [t1, t2] (t1 = oldest valid entry, t2 = most recent), needing no boundary search since each buffer is calibrated to one window_duration. The final segment from t2 to `now` is extrapolated from the live tick in the CurrentTickAccount (added as a fourth account), mirroring Uniswap's OracleLibrary.consult. This keeps the published timestamp = now truthful: an unchanged price yields a fresh stamp and the correct value, and a republish picks up a since-reported move instead of freezing the pre-move average. The live tick is only credited since it was written, so the tail is split at the current tick's last_updated: boundary = clamp(current_tick.last_updated, t2.ts, now) clamped_tick = last_recorded_tick + clamp(current_tick - last_recorded_tick, ±MAX_TICK_DELTA) cum_now = t2.tick_cumulative + last_recorded_tick * (boundary - t2.ts) // before the live tick took effect + clamped_tick * (now - boundary) // live tick, only since last_updated twap_tick = (cum_now - t1.tick_cumulative) / (now - t1.ts) // floor (div_euclid) Splitting at last_updated stops a tick written moments before publish from being smeared across a stale gap and inflating a supposedly fresh TWAP. The live-tick segment is clamped against last_recorded_tick by MAX_TICK_DELTA — the same bound RecordTick applies — capping how far a current-tick move can shift the result. A zero-length tail (now == t2.ts) leaves the pure stored-window average. If fewer than two observations exist the call is a silent no-op, leaving the price account at timestamp = 0 (the uninitialized signal consumers reject). While young, the TWAP covers the available span, which may be shorter than the window. The TWAP tick is converted to a price ratio via the Uniswap v3 sqrtPriceX96 representation (pure integer, zkVM-safe), stored as a Q64.64 in OraclePriceAccount.price — source-agnostic, no tick framing leaks into the standard. Out-of-range ticks clamp; ratios above 2^64 saturate at u128::MAX. Adds PRICE_FRACTIONAL_BITS = 64; removes the placeholder TWAP_PRICE_BIAS encoding. Closes #117
2026-06-02 18:07:45 +02:00
pub mod publish_price;
pub mod record_tick;
pub mod update_current_tick;