Skip to content

perf(cold-start): verify-only fast path for Curve + Balancer (+ optional Curve code seed) - #31

Merged
KaiCode2 merged 3 commits into
hardening/tier-2-polishfrom
perf/curve-cold-start
Jul 7, 2026
Merged

perf(cold-start): verify-only fast path for Curve + Balancer (+ optional Curve code seed)#31
KaiCode2 merged 3 commits into
hardening/tier-2-polishfrom
perf/curve-cold-start

Conversation

@KaiCode2

@KaiCode2 KaiCode2 commented Jul 7, 2026

Copy link
Copy Markdown
Owner

Stacked on hardening/tier-2-polish (clean 2-commit diff).

Problem

Curve and Balancer cold-start were far slower than Uniswap V2/V3 on a cold boot. Both use a discover→verify planner, but it always re-ran discovery even when the pool's read-set was already known — fetching the pool/vault code and faulting each get_dy / getPoolTokens SLOAD one-at-a-time over RPC, then a second verify round. V2/V3 hydrate their whole hot state in one bundled eth_call and are fully offline immediately.

Changes

Verify-only fast path (the core win). When a pool's read-set is already known — CurveMetadata.discovered_slots / BalancerV2Metadata.balance_slots, populated by the first discovery, a trace, or a registry — the planner skips discovery entirely and warms exactly those slots in a single verify round, the same one-shot path V2/V3 use. Byte-for-byte unchanged when the read-set is empty (zero regression on first boot).

  • CurveColdStartPlanner: verify-only when discovered_slots is known.
  • BalancerV2ColdStartPlanner: verify-only when balance_slots is known (preserves the config-supplied tokens, since there is no getPoolTokens decode to repopulate them).
  • Both automatically become cold_start_many-eligible (supports_one_shot_hydration) once their read-set is known — folding into the same bundled bootstrap as V2/V3.

Optional Curve bytecode seed. CurveMetadata::with_code_seed(runtime): a caller-supplied Vyper runtime, verified once against on-chain EXTCODEHASH (mismatch → purged → falls back to lazy fetch, never a correctness risk), removing the one lazy code fetch a Curve pool otherwise pays on its first quote. Opt-in; makes sense for single-contract quotes (Curve get_dy).

Tooling & docs. examples/curve_cold_start_phases.rs times discovery vs verify-only vs cold_start_many; README / docs/curve-adapter.md / docs/benchmarks.md updated.

Measured

Tricrypto2 (the showcase pool), fresh run: cold-start ~712 ms → ~110 ms (~6× faster). Corroborated by the controlled numbers in docs/benchmarks.md — Curve 3pool 361→75 ms (4.8×), Balancer BAL/WETH 333→76 ms (4.4×). Warm quote latency (~80 µs) is unchanged; this is a cold-start optimization only.

API surface

Additive only: a CurveMetadata.code_seed: Option<Bytes> field + with_code_seed builder (#[non_exhaustive] struct → non-breaking). The verify-only speedup needs no new API — it rides on the existing cold_start / cold_start_many methods reading metadata the crate populates itself, so default users get it automatically on every warm after the first.

Verification

217 tests pass; clippy (all-targets/all-features), cargo doc (all-features, #![warn(missing_docs)]), and the all-features build are clean. New tests: verify-only skip-discovery + unfetchable-slot repair (Curve + Balancer), the code_seeds hook, and positive one-shot classification.

Follow-up

First-ever boot of an unknown pool still pays one-time discovery. A generalized eth_createAccessList-based discovery (fetch the access list from the node in one call, then one-shot batch-load those slots — cold warming in two shots) would make first boot fast by default with no new user API. Planned as a separate PR.

🤖 Generated with Claude Code

KaiCode2 and others added 2 commits July 7, 2026 14:09
…onal code seed

CurveColdStartPlanner now skips discovery when CurveMetadata.discovered_slots is already known (a prior discovery, a trace, or a registry): no pool-account/bytecode fetch and no cold-cache get_dy slot-faulting, just a single verify round over the known slots. This makes a known-read-set single-pool cold_start as cheap as the bundled cold_start_many storage-program path Uniswap V2/V3 use. The discover->verify path is unchanged when discovered_slots is empty.

Adds optional CurveMetadata.code_seed (+ with_code_seed builder + CurveAdapter::code_seeds): a caller-supplied Vyper runtime, verified once against on-chain EXTCODEHASH (mismatch -> purged -> lazy fetch), removing the one lazy code fetch a Curve pool otherwise pays on its first simulate_swap. Additive, non-breaking.

Tests: verify-only skips-discovery + unfetchable-slot repair (cold_start_adoption), code_seeds unit test (curve), positive Curve one-shot classification (bootstrap_many). Adds examples/curve_cold_start_phases.rs and updates docs (curve-adapter, README, benchmarks). 216 tests pass; clippy/doc/all-features clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ports the Curve verify-only fast path to BalancerV2ColdStartPlanner: when BalancerV2Metadata.balance_slots is already known (a prior discovery or a trace), skip the getPoolTokens discovery -- no vault-account fetch, no cold-cache faulting -- and warm exactly those slots in a single verify round, matching the bundled cold_start_many storage-program path. Config-supplied tokens are preserved (no getPoolTokens decode repopulates them). The discover->verify path is unchanged when balance_slots is empty.

Adds balancer_cold_start_verify_only_skips_discovery. 217 tests pass; clippy/all-features/doc clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@KaiCode2
KaiCode2 merged commit c46d405 into hardening/tier-2-polish Jul 7, 2026
9 checks passed
@KaiCode2
KaiCode2 deleted the perf/curve-cold-start branch July 8, 2026 09:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant