Skip to content

Add price accumulator support for TWAP oracle #111

@0x-r4bbit

Description

@0x-r4bbit

The TWAP oracle program (RFP-019) depends on the AMM pool exposing a price accumulator — a running record of the pool's price over time.
Currently PoolDefinition only stores the current reserves. This issue tracks the changes needed in the AMM program so RFP-019 has the data
it needs.

Background

A TWAP (Time-Weighted Average Price) is computed from two accumulator snapshots:

twap_tick = (tickCumulative[t2] - tickCumulative[t1]) / (t2 - t1)

tickCumulative is a running sum of current_tick × elapsed_seconds, updated at block boundaries before any trades execute. The current_tick is an integer encoding of the current spot price derived from the reserve ratio:

tick = floor( log(reserve_b / reserve_a) / log(1.0001) )

Computed via integer bit manipulation (MSB + iterative squaring) — no floating point.

What needs to be done

  1. Add accumulator fields to PoolDefinition in amm_core
pub struct PoolDefinition {
    // existing fields ...

    /// Current price tick, derived from reserve_b / reserve_a.
    /// Updated on every state-changing operation.
    pub current_tick: i32,

    /// Timestamp (ms) at which current_tick was last recorded.
    /// Used by the oracle program to advance tickCumulative.
    pub last_observation_timestamp: u64,
}
  1. Compute and write current_tick on every state change

Every instruction that modifies reserves must update current_tick and last_observation_timestamp before writing the pool post-state.
The tick must be computed from the post-swap reserves (the price after the trade, not before), because that is what the next block's
accumulator advance will be based on.

  1. Implement tick_from_reserves(reserve_a, reserve_b) -> i32

Pure integer implementation in amm_core. No floating point. Approach:

  1. Represent price = reserve_b / reserve_a as a fixed-point integer
  2. Find floor(log2(price)) via 127 - leading_zeros() on the numerator
  3. Refine to the tick index using iterative squaring against the log(1.0001) constant (Uniswap v3 TickMath approach)

Acceptance criteria

  • PoolDefinition contains current_tick: i32 and last_observation_timestamp: u64
  • tick_from_reserves() is implemented in amm_core with unit tests covering known reserve ratios → expected ticks
  • All state-changing instructions update both fields in their post-state output
  • Existing tests remain green
  • New tests cover: tick computed correctly after NewDefinition, tick updated after a swap that changes the reserve ratio

Metadata

Metadata

Assignees

No one assigned
    No fields configured for Feature.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions