test(twap): cover RecordTick end-to-end and add zkVM cycle benchmark

Add the first end-to-end coverage of the oracle's RecordTick path, which
previously existed only as native unit tests:

- amm_twap_observations_accumulate_across_swaps_and_yield_time_weighted_average:
  drives swaps + RecordTick across simulated time, then checks the cumulative
  accumulator and the consulted time-weighted average.
- amm_twap_record_tick_sampling_guard_skips_calls_below_min_interval: exercises
  the min-interval sampling guard through the real instruction path.

Running RecordTick through the zkVM surfaced that committing the oracle-owned
~100 KiB observations account costs ~50.9M cycles — over the 2^25 (~33.5M)
public-execution limit — so the instruction aborted on chain. Reduce
OBSERVATIONS_CAPACITY 6396 -> 2048 (~16.8M cycles, ~half the limit); window
coverage is unchanged, only sampling resolution.

Add programs/benchmark, a standalone crate (excluded from the workspace so CI
and the Makefile skip it) that runs the guest ELF through the RISC Zero
executor and reports the per-instruction cycle split, reproducing the on-chain
pass/fail at the limit. Its cost-vs-capacity sweep still spans to 6396, guarding
against bumping capacity back into the over-budget range.
This commit is contained in:
r4bbit
2026-06-23 16:13:13 +02:00
parent c528d85a2b
commit dcd0a1e31c
8 changed files with 6382 additions and 10 deletions
+7 -9
View File
@@ -168,16 +168,14 @@ pub const MAX_TICK_DELTA: i32 = 9_116;
/// Number of entries in each price feed.
///
/// 6 396 is the maximum that fits within the `DATA_MAX_LENGTH = 100 KiB` runtime ceiling.
/// Each [`ObservationEntry`] is 16 bytes (`timestamp` 8 + `tick_cumulative` 8); fixed overhead
/// is 52 bytes (`price_source_id` 32 + `write_index` 4 + `total_entries` 8 +
/// `last_recorded_tick` 4 + Borsh `Vec` length prefix 4), leaving 102 348 bytes for entries:
/// `floor(102 348 / 16) = 6 396`.
/// Bounded by the zkVM cycle budget, not storage: `RecordTick` commits this owned account on both
/// read and write, so cost scales with size and a full 100 KiB buffer exceeds the public-execution
/// limit. See `programs/benchmark/README.md` and `programs/benchmark/tests/twap_cycle_bench.rs`.
///
/// The effective history window depends on the `window_duration` used to derive the feed PDA
/// and the sampling guard: `min_interval = window_duration / OBSERVATIONS_CAPACITY`. A 24 h feed
/// samples every ~13 s; a 7 d feed every ~94 s; a 30 d feed every ~7 min.
pub const OBSERVATIONS_CAPACITY: u32 = 6396;
/// Capacity primarily affects resolution: `min_interval = window_duration / OBSERVATIONS_CAPACITY`
/// (integer division), so full-buffer coverage is approximately `window_duration` (up to
/// `< OBSERVATIONS_CAPACITY` ms shorter) — only resolution changes meaningfully.
pub const OBSERVATIONS_CAPACITY: u32 = 2048;
/// A single price entry written to a [`PriceObservations`].
#[derive(