Skip to content

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
masterfrom
pda-cost-probe
Open

test(cpi): PdaCostProbe + measurement script for P3.1 — find_program_address vs pdas_batch_derive CU cost#149
anil-rome wants to merge 2 commits into
masterfrom
pda-cost-probe

Conversation

@anil-rome
Copy link
Copy Markdown
Contributor

Summary

Code-only PR — adds the probe contract + measurement infrastructure for the audit-plan P3.1 question: does pdas_batch_derive save material Solana CU when batching N PDAs against the same program, vs N separate find_program_address calls? 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 via pdas_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:

  • Deploys probe once per chain (caches deploy receipt to deployments/<chain>.PdaCostProbe.json).
  • Calls each probe × 3 samples via rome_emulateTx.
  • Parses computeUnitsConsumed from response, prints table + verdict.
  • Threshold: ≥50K CU savings on 7-PDA batch → proceed to P3.2 (BatchPdaDeriver library).

TDD discipline

Same pattern as Phase 1 + 2:

  1. Restate the contract: "Measure find_program_address CU cost; compare individual vs batched; >50K CU savings = ship library."
  2. Write the measurement protocol first (this PR).
  3. Run on a live chain — captures the numbers (operator-gated next step).
  4. Decide: ship P3.2 library or scrap.

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 aurelius

Marcus 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 compile clean (73 files)
  • Operator approves run on Marcus or Aurelius
  • Measurement captures computeUnitsConsumed for all 3 probes
  • Numeric verdict produced → P3.2 go/no-go decision committed

Out of scope

  • The actual numerical decision on P3.2 (BatchPdaDeriver library). This PR is purely measurement infra; the library itself is conditional on what the numbers say.

Anil Kumar 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).
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