Skip to content

fix(tn-reth): reject value-bearing calls to the BLS precompile (#1286) - #1297

Open
MavenRain wants to merge 9 commits into
mainfrom
fix/1286-bls-precompile-payability-guard
Open

fix(tn-reth): reject value-bearing calls to the BLS precompile (#1286)#1297
MavenRain wants to merge 9 commits into
mainfrom
fix/1286-bls-precompile-payability-guard

Conversation

@MavenRain

@MavenRain MavenRain commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Closes #1286. (Cantina #21)

Problem

Registering an address as a precompile short-circuits the interpreter, so the CALLVALUE guard a compiled non-payable dispatcher would run never executes (#1190). On the pinned revm-handler 15.0.0 the caller-to-target balance transfer is journaled before the precompile runs and commits when it succeeds. The BLS precompile entrypoint at 0xb151 forwarded only input.data and input.gas, so a successful blsVerify call with attached value both returned the verification result and permanently credited 0xb151.

Nothing reads or pays out that balance: no selector draws from it, and the genesis account carries only the INVALID-opcode marker bytecode. Unlike 0x7e1 there is not even a governance burn to destroy stray wei, so value that lands there is inert forever. A bare value send already reverts on the short-calldata check; a value-bearing blsVerify call was the inconsistent survivor. PR #1201 closed the same gap for the TEL dispatcher at 0x7e1; this closes the remaining BLS half of the Cantina finding.

Fix

  • crates/tn-reth/src/evm/bls_precompile/mod.rs: reject nonzero PrecompileInput::value at the top of bls_precompile, before dispatch. blsVerify is pure, so the payable list is empty. The gate keeps fix(tn-reth): enforce payability in the TEL precompile dispatcher #1201's fail-safe classification: a selector added later rejects value unless it is explicitly made payable. Indirect frames cannot land wei here either way (DELEGATECALL value is apparent only; CALLCODE transfers its explicit operand from the executing contract to itself), and rejecting them mirrors a compiled non-payable dispatcher, whose CALLVALUE check fires in those frames too. The rejection surfaces as a precompile-error halt, the same mechanism as every other error from the dispatcher and identical to the TEL gate's.
  • Doc comment on add_bls_precompile pinning DynPrecompile::new_stateful as load-bearing for this gate: reth's engine-tree precompile cache keys entries on calldata alone and caches only pure precompiles, so new_stateful (is_pure() == false) is what guarantees a cached zero-value Ok can never be replayed for a value-bearing call with identical calldata.
  • Doc comments: a module-level payability note and a # Payability section on the entrypoint.

Scope note: the TEL dispatcher's other two gates do not transfer here. blsVerify touches no state, so is_static needs no check, and the direct-call guard (#1210) protects caller-based authorization this precompile does not perform. Payability was the one missing gate.

Testing

  • New regression test test_value_bearing_verify_rejected (tests/it/bls_precompile_props.rs): a value-bearing blsVerify call through the real EVM path must halt with HaltReason::PrecompileError specifically (so a harness-level nonce/balance/gas failure cannot satisfy the test by accident), 0xb151's balance stays zero, and the caller keeps the wei; a zero-value control with identical calldata verifies. Confirmed by mutation: with the guard reverted the test fails (the call succeeds), with the guard restored it passes.
  • cargo test -p tn-reth --test it bls_precompile_props green under the pinned toolchain. No existing test sends nonzero value to 0xb151, so no behavior change for the existing suite.
  • Nightly cargo fmt --all -- --check green; scoped cargo clippy --no-deps over the buildplan-emitted crate list. The one pre-existing failure on main (the main fails cargo check --workspace --all-targets: eth_syncing test calls get_rpc_server without the #1235 BaseFeeContainer argument #1283 E0061 in the env/rpc.rs unit tests, fixed by open PR fix(tn-reth): pass the base-fee argument in the eth_syncing rpc test (#1283) #1285) is untouched by this diff; the tn-reth lib and it test targets were linted as separate invocations so that failure could not mask them.
  • Multi-agent review over the staged diff plus a deep semantic pass against the pinned revm/alloy-evm sources (journal/commit path, STATICCALL/DELEGATECALL/CALLCODE interplay, cache coupling, gas semantics); its findings are folded into the doc comments and the hardened test assertion above.

Registering 0xb151 short-circuits the interpreter, so no compiled
CALLVALUE guard runs and revm commits the journaled value transfer when
the call succeeds.  The dispatcher forwarded only calldata and gas, so a
successful blsVerify call with attached value stranded the wei at an
address with no outflow path.

Gate the entrypoint on zero call value, mirroring the payability gate
PR #1201 added to the TEL dispatcher (blsVerify is pure, so the payable
list is empty), and add a value-bearing regression test with a
zero-value control.

Closes #1286.

Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
@MavenRain
MavenRain requested a review from Huwonk August 27, 2026 16:34
@MavenRain MavenRain self-assigned this Aug 27, 2026
@MavenRain
MavenRain deployed to merge-into-main August 27, 2026 16:34 — with GitHub Actions Active
…le-payability-guard

Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
…le-payability-guard

Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
…le-payability-guard

Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
…le-payability-guard

Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
…le-payability-guard

Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
…le-payability-guard

Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>

@grantkee grantkee left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

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.

bls precompile: no payability guard, so value attached to blsVerify is stranded at 0xb151 with no outflow path

2 participants