test(cpi): PdaCostProbe + measurement script for P3.1 — find_program_address vs pdas_batch_derive CU cost#149
Open
anil-rome wants to merge 2 commits into
Open
test(cpi): PdaCostProbe + measurement script for P3.1 — find_program_address vs pdas_batch_derive CU cost#149anil-rome wants to merge 2 commits into
anil-rome wants to merge 2 commits into
Conversation
added 2 commits
May 14, 2026 08:04
…address vs pdas_batch_derive CU cost Adds the probe contract + measurement infrastructure for the audit plan's P3.1 question: does `pdas_batch_derive(seed_groups, program_id)` save material Solana CU when batching N PDAs against the same program, vs N separate `SystemProgram.find_program_address` calls? ## Contract `contracts/cpi/test/PdaCostProbe.sol` — three view functions exposing the call shapes adapters actually use: - `probeOnePda(idx)` — baseline: 1 find_program_address dispatch (after the constant `rome_evm_program_id` call, which is identical across all three probes — so deltas isolate the find_program_address cost). - `probeSevenIndividual(baseIdx)` — Meteora DAMM-AMM cluster shape: 7 sequential find_program_address dispatches against the same program_id. - `probeSevenBatched(baseIdx)` — same 7 PDAs but via `CpiProgram.pdas_batch_derive(seed_groups, programId)` — one dispatch, one program_id. Seeds match Meteora's `b"pool" + ...` shape so the measurement is representative, not a microbench. Returns are bytes32 / bytes32[] / PdaWithBump[] to defeat dead-code elimination during emulation. ## Script `scripts/cpi/measure-pda-cost.ts` — runs against any Rome chain via `--network <chain>`. Deploys probe once (caches the address in `deployments/<network>.PdaCostProbe.json` for reruns), then calls each probe via `rome_emulateTx` × 3 samples per probe, parses `computeUnitsConsumed` from the response, and prints a per-probe table + a decision verdict against the P3.1 spec's 50K-CU savings threshold. If the response payload doesn't expose `computeUnitsConsumed` as a top-level field, the script logs the keys it does see so we can iterate without re-deploying. ## Verify-before-build (P3.1 gate) Red team's exact concern (2026-05-14 review): `find_program_address` is an `EthCall` variant — not a true CPI — so the per-hop CU cost is dominated by EVM-side ABI marshaling + atomic-tx wrapper overhead. Whether batching meaningfully reduces that overhead requires measurement, not extrapolation. The existing prior in rome-evm-private/CLAUDE.md (~115K CU per hop end-to-end + ~76K saved per hop in the `derive_user_ata` 2-hop collapse) suggests material savings, but the seven-PDA batch shape hasn't been measured directly. If the measurement confirms ≥50K CU savings on the 7-PDA batched shape vs the 7 individual calls, P3.2 ships the `BatchPdaDeriver` library wrapping `pdas_batch_derive`. Otherwise it's scrapped. ## What this PR does NOT do No on-chain transactions are submitted by merging this PR. The probe contract is not deployed anywhere. The script is read-only when run (it deploys a probe once, then ALL subsequent calls go through `rome_emulateTx` which only simulates). The actual measurement run is gated on operator approval — see "How to run" below. ## How to run (operator-gated) ```bash # 1. Compile npx hardhat compile # 2. Run measurement against any Rome chain that has the new # pdas_batch_derive selector dispatched on-chain. Marcus 121301 was # confirmed live on 2026-05-11 per rome-evm-private/CLAUDE.md. npx hardhat run scripts/cpi/measure-pda-cost.ts --network marcus # or npx hardhat run scripts/cpi/measure-pda-cost.ts --network aurelius ``` The script prints a CU table + a verdict line that's the input to the P3.2 go/no-go decision. ## Compile evidence `npx hardhat compile` produces 73 Solidity files with no new warnings (pre-existing Meteora factory size + orra.sol mutability warnings unchanged).
…-cost.ts When hardhat.config.ts uses `url: "..."` directly the field is a string, but when it uses `configVariable(...)` the field is an object with `.get()` method. The original code assumed string, so JsonRpcProvider crashed with `TypeError: url.clone is not a function`. Resolves the field at runtime so the script works for both shapes. Verified live against Marcus (chainId 121301).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Code-only PR — adds the probe contract + measurement infrastructure for the audit-plan P3.1 question: does
pdas_batch_derivesave material Solana CU when batching N PDAs against the same program, vs N separatefind_program_addresscalls? Red-team-gated: no library shipped until measurement confirms ≥50K CU savings.No on-chain side effects from merging this PR. Both files are test-only; the script doesn't run automatically. The actual measurement is gated on operator-approved run (Marcus or Aurelius) — see "How to run" in the commit message.
Contract
contracts/cpi/test/PdaCostProbe.sol:probeOnePda(idx)— baseline (1 find_program_address dispatch)probeSevenIndividual(baseIdx)— Meteora DAMM-AMM shape (7 sequential dispatches)probeSevenBatched(baseIdx)— same 7 PDAs viapdas_batch_derive(one dispatch)Each probe has the same
rome_evm_program_id()overhead, so deltas isolate the find_program_address / batch costs.Script
scripts/cpi/measure-pda-cost.ts:deployments/<chain>.PdaCostProbe.json).rome_emulateTx.computeUnitsConsumedfrom response, prints table + verdict.TDD discipline
Same pattern as Phase 1 + 2:
The "failing test" analog here: running the script without a deployed probe → it deploys, then measures. No false-positive risk in the measurement itself.
What I need from you
This PR can merge as code-only. Then to actually run the measurement, pick a chain:
npx hardhat run scripts/cpi/measure-pda-cost.ts --network marcus # or npx hardhat run scripts/cpi/measure-pda-cost.ts --network aureliusMarcus 121301 is the cleaner target (per CLAUDE.md
pdas_batch_derive"first invocation on Marcus 121301 was 2026-05-11" — so we know it's dispatched).Output is a per-probe CU table + a decision line. ~15 seconds of wall time per probe × 3 samples × 3 probes = ~2-3 min total.
Test plan
npx hardhat compileclean (73 files)computeUnitsConsumedfor all 3 probesOut of scope