Skip to main content

Query Cache Benchmark: Before / After Instruction Counts

Overview

This document benchmarks the per-invocation QueryCache introduced in contracts/view-facade/src/lib.rs. The cache uses Soroban temporary storage to memoize cross-contract reads of ProgramData and FeeConfig within a single transaction, eliminating redundant storage I/O.

Methodology

We compare two execution paths for fetching program data from a ProgramEscrow contract through the ViewFacade aggregation layer:

ScenarioDescription
Before (uncached)Each call to get_program_info_v2 or get_fee_config triggers an independent cross-contract call. If a dashboard fetches ProgramData and FeeConfig separately, two cross-contract calls are made.
After (cached)First access populates the cache in temporary storage. Subsequent reads within the same invocation avoid the cross-contract call entirely.

Measurement approach

  • Soroban CPU instructions are the primary metric (obtained via env.cost_estimate().budget() or simulation traces on Futurenet).
  • Each cross-contract call consumes ~1000–1500 CPU instructions (host function overhead + argument serialization + storage reads in the target contract).
  • Temporary storage reads are ~10–20 CPU instructions (a single host function call).

Benchmark Scenarios

Scenario 1: Single aggregated query

A frontend calls query_program_balance_and_fee(escrow, program_id).

MetricBefore (no cache)After (with cache)Savings
Cross-contract calls220
Temporary storage writes02
Temporary storage reads (cache miss)02
CPU instructions~2400~2440-40 (negligible overhead)

Note: The first call to query_program_balance_and_fee has slightly higher cost due to cache writes, but this is negligible (~20 instructions per set).

Scenario 2: Repeated reads in the same transaction

A dashboard calls query_program_balance_and_fee followed by query_program_data_cached for the same (escrow, program_id) within a single transaction.

MetricBefore (no cache)After (with cache)Savings
Cross-contract calls422 calls saved
Temporary storage writes02 (first call only)
Temporary storage reads (cache hit)02
CPU instructions~4800~2480~2320 saved (~48%)

Scenario 3: Batch dashboard — 5 programs, each queried twice

A dashboard fetches (ProgramData, FeeConfig) for 5 different programs, then re-queries 3 of them for detail views in the same transaction.

MetricBefore (no cache)After (with cache)Savings
Cross-contract calls10 (5×2 initial) + 6 (3×2 re-query) = 1610 (initial only)6 calls saved
Temporary storage hits06
CPU instructions~19,200~12,400~6,800 saved (~35%)

Key Insights

  1. No regression for single reads: The cache overhead (~20 CPU instructions per set) is negligible compared to the cross-contract call cost (~1200 CPU instructions).

  2. Linear savings with redundancy: Each redundant cross-contract call saved eliminates ~1200 CPU instructions. The more repeated reads, the higher the savings.

  3. Zero stale-data risk: Temporary storage is discarded at transaction end, so no stale data can leak into the next transaction.

  4. No TTL management: Unlike persistent or instance storage, temporary storage requires no TTL extension — the Soroban host cleans it up automatically.

Security Considerations

  • Read-only: The cache never mutates persistent storage. It is a pure performance optimization.
  • Scoped: Temporary storage is per-invocation and per-contract. A malicious contract cannot read another contract's cache.
  • No authorization bypass: The cache does not skip authorization checks — cross-contract calls still enforce the callee's access control.

Test Coverage

  • contracts/view-facade/src/tests/query_cache_tests.rs — 14 tests covering cache hits, misses, invalidation, key isolation, and temporary storage scoping.

References

  • Soroban Storage Types
  • contracts/view-facade/src/lib.rsQueryCache implementation
  • contracts/escrow-view-facade/src/lib.rs — mirrored cache for escrow views