Skip to content

[Provers M01] Proof Stale Time in Optimism#87

Merged
luiz-lvj merged 11 commits intomainfrom
fix/provers-M01
Feb 24, 2026
Merged

[Provers M01] Proof Stale Time in Optimism#87
luiz-lvj merged 11 commits intomainfrom
fix/provers-M01

Conversation

@luiz-lvj
Copy link
Collaborator

@luiz-lvj luiz-lvj commented Feb 20, 2026

This PR changes the Optimism ChildToParentProver to use a pusher/buffer system instead of the native predeploy on OP chains.

Summary by CodeRabbit

  • New Features

    • Added an Optimism L1→L2 block-hash buffer and pusher with stricter cross-domain sender validation; provers now read hashes from the buffer and constructors accept a buffer address.
  • Tests

    • Added tests and mocks for buffer/pusher behavior, access control, cross-domain relay, and integration with provers and benchmarks; updated existing tests to feed hashes via the buffer.
  • Chores

    • Updated test snapshot numeric values.

@coderabbitai
Copy link

coderabbitai bot commented Feb 20, 2026

📝 Walkthrough

Walkthrough

Adds an Optimism L1→L2 block-hash buffering flow: new OptimismBuffer and OptimismPusher contracts, provers refactored to read parent-chain hashes from an IBuffer, and tests/mocks/benchmarks updated to use buffer.receiveHashes instead of L1 predeploy interactions.

Changes

Cohort / File(s) Summary
Optimism Buffer & Pusher
src/contracts/block-hash-pusher/optimism/OptimismBuffer.sol, src/contracts/block-hash-pusher/optimism/OptimismPusher.sol
Add OptimismBuffer (access-controlled receiveHashes, l2CrossDomainMessenger & pusher immutables, custom errors) and OptimismPusher (builds block-hash arrays, encodes L2 calldata, sends cross-domain messages via L1 messenger proxy).
IBuffer + mocks
src/contracts/block-hash-pusher/interfaces/IBuffer.sol, test/block-hash-pusher/mocks/MockOpCrosschainDomainMessenger.sol, test/common/mocks/BufferMock*
Introduce IBuffer interface and test mocks including a mock L2 cross-domain messenger that exposes transient xDomainMessageSender and relayMessage behavior.
Prover refactor (Optimism)
src/contracts/provers/optimism/ChildToParentProver.sol
Prover now accepts blockHashBuffer in constructor, adds blockHashBuffer state and BLOCK_HASH_MAPPING_SLOT, and reads parent-chain block hashes via IBuffer/slot derivation instead of L1 predeploy.
Provers (minor docs)
src/contracts/provers/linea/ChildToParentProver.sol, src/contracts/provers/scroll/ChildToParentProver.sol, src/contracts/provers/zksync/ChildToParentProver.sol
Non-functional comment updates pointing slot-derivation reference to a different BaseBuffer source.
Tests updated / added
test/block-hash-pusher/optimism/OptimismBuffer.t.sol, test/block-hash-pusher/optimism/OptimismPusher.t.sol, test/provers/optimism/ChildToParentProver.t.sol, test/provers/*, test/Receiver.t.sol, test/VerifyBroadcastMessageBenchmark.t.sol
New tests for OptimismBuffer and OptimismPusher; many tests refactored to instantiate provers with a blockHashBuffer, feed hashes via buffer.receiveHashes, update inputs to abi.encode(blockNumber), and remove L1 predeploy/outbox mocks where applicable.
Snapshot
snapshots/verifyBroadcastMessage.json
Updated numeric snapshot values (two entries changed).

Sequence Diagram(s)

sequenceDiagram
    participant L1 as L1 Caller
    participant Pusher as OptimismPusher
    participant L1Messenger as L1CrossDomainMessenger
    participant L2Messenger as L2CrossDomainMessenger
    participant Buffer as OptimismBuffer
    participant Prover as ChildToParentProver

    L1->>Pusher: pushHashes(buffer, firstBlockNumber, batchSize, l2TxData)
    Pusher->>Pusher: build block-hash array & encode IBuffer.receiveHashes
    Pusher->>L1Messenger: sendMessage(buffer, calldata, gasLimit)
    L1Messenger->>L2Messenger: relayMessage(...)
    L2Messenger->>Buffer: receiveHashes(firstBlockNumber, blockHashes)
    Buffer->>Buffer: check caller == l2CrossDomainMessenger
    Buffer->>Buffer: check xDomainMessageSender == pusher
    Buffer->>Buffer: store hashes (emit BlockHashesPushed if written)
    Prover->>Buffer: parentChainBlockHash(targetBlockNumber)
    Buffer-->>Prover: return block hash
    Prover->>Prover: verify target state commitment using returned hash
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested Reviewers

  • pepebndc
  • frangio
  • nahimterrazas

Poem

🐇 I hop and gather hashes, neat and quick,
I bundle blocks and whisper them through the wick,
A cross-chain bridge of nibble, hop, and cheer,
Buffers filled — provers nod — the path is clear! 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title '[Provers M01] Proof Stale Time in Optimism' is vague and does not clearly convey the main technical change, which involves migrating from native predeploy to a pusher/buffer system. Consider a more specific title that reflects the primary change, such as '[Provers M01] Replace Optimism predeploy with block hash buffer' or similar, to clearly communicate the architectural shift.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/provers-M01

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
test/VerifyBroadcastMessageBenchmark.t.sol (1)

400-404: ⚠️ Potential issue | 🟡 Minor

Stale comment: still references "L1Block predeploy".

Line 403 says "Hop 1: OP-C2P gets Ethereum block hash from L1Block predeploy" but the prover now reads from the block hash buffer.

📝 Proposed fix
-    ///      Hop 1: OP-C2P gets Ethereum block hash from L1Block predeploy
+    ///      Hop 1: OP-C2P gets Ethereum block hash from block hash buffer
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/VerifyBroadcastMessageBenchmark.t.sol` around lines 400 - 404, Update
the stale comment in the benchmark description in
VerifyBroadcastMessageBenchmark.t.sol so Hop 1 no longer says "OP-C2P gets
Ethereum block hash from L1Block predeploy" but instead references reading from
the block hash buffer (e.g., change the Hop 1 line to mention "reads Ethereum
block hash from block hash buffer"); adjust any related phrasing in the same
comment block (the route/Hop descriptions) to reflect "block hash buffer" rather
than "L1Block predeploy" so the documentation matches the current prover
behavior.
test/provers/optimism/ChildToParentProver.t.sol (1)

13-17: ⚠️ Potential issue | 🟡 Minor

Stale NatSpec: still references "L1Block predeploy".

The comment on Line 16 says "This prover reads L1 block hashes from the L1Block predeploy on Optimism" but the prover now reads from a block hash buffer.

📝 Proposed fix
-/**
- * `@title` Optimism ChildToParentProver Test
- * `@notice` Tests for the Optimism ChildToParentProver contract
- * `@dev` This prover reads L1 block hashes from the L1Block predeploy on Optimism
- */
+/**
+ * `@title` Optimism ChildToParentProver Test
+ * `@notice` Tests for the Optimism ChildToParentProver contract
+ * `@dev` This prover reads L1 block hashes from a block hash buffer on Optimism
+ */
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/provers/optimism/ChildToParentProver.t.sol` around lines 13 - 17, The
NatSpec header for the Optimism ChildToParentProver test is stale: update the
comment that currently says "reads L1 block hashes from the L1Block predeploy on
Optimism" to reflect the current implementation (it now reads from the block
hash buffer). Edit the top comment for the ChildToParentProver test to say
something like "This prover reads L1 block hashes from the block hash buffer on
Optimism" so the description matches the actual code paths in the
ChildToParentProver tests.
🧹 Nitpick comments (5)
test/provers/optimism/ChildToParentProver.t.sol (1)

42-83: Test test_verifyStorageSlot_broadcaster doesn't exercise verifyTargetStateCommitment or getTargetStateCommitment.

The buffer hashes are pushed (Line 73) but neither verifyTargetStateCommitment nor getTargetStateCommitment is called — only verifyStorageSlot is tested, which is a pure function that doesn't interact with the buffer at all. Consider adding test coverage for the new buffer-based target state commitment paths.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/provers/optimism/ChildToParentProver.t.sol` around lines 42 - 83, The
test currently pushes the block hash into the buffer but never calls the
buffer-backed target state commitment paths; after
buffer.receiveHashes(blockNumber, blockHashes) call into
childToParentProver.verifyTargetStateCommitment(...) and
childToParentProver.getTargetStateCommitment(...) (using the same blockHash and
the appropriate proof data from the JSON/rlpBlockHeader or the encoded input)
and add assertions that their returned commitment/verification result matches
the expected commitment computed from the test vectors; insert these calls just
before calling childToParentProver.verifyStorageSlot so the buffer path is
exercised (referencing buffer.receiveHashes,
childToParentProver.verifyTargetStateCommitment, and
childToParentProver.getTargetStateCommitment).
test/Receiver.t.sol (1)

286-290: Optional: inline the IBuffer cast or hoist buffer to function scope.

The pattern IBuffer buffer = IBuffer(blockHashBuffer) is repeated in five OP test functions, each immediately after the blockHashBuffer variable is already in scope. Since BufferMock is declared as plain address, consider either casting inline (IBuffer(blockHashBuffer).receiveHashes(...)) or declaring buffer once at the start of each function alongside blockHashBuffer.

♻️ Example inline simplification (applies analogously to all five occurrences)
-        IBuffer buffer = IBuffer(blockHashBuffer);
-        bytes32[] memory blockHashes = new bytes32[](1);
-        blockHashes[0] = blockHash;
-
-        buffer.receiveHashes(blockNumber, blockHashes);
+        bytes32[] memory blockHashes = new bytes32[](1);
+        blockHashes[0] = blockHash;
+        IBuffer(blockHashBuffer).receiveHashes(blockNumber, blockHashes);

Also applies to: 357-361, 430-434, 495-499, 558-562

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/Receiver.t.sol` around lines 286 - 290, The IBuffer cast is redundantly
assigned to a local variable in multiple test functions (e.g., IBuffer buffer =
IBuffer(blockHashBuffer) before calling receiveHashes); either inline the cast
at the call site (IBuffer(blockHashBuffer).receiveHashes(...)) or hoist a single
IBuffer buffer = IBuffer(blockHashBuffer) declaration to the top of each test
function where blockHashBuffer is declared so the repeated pattern is removed;
update all five occurrences referencing IBuffer, blockHashBuffer, and
receiveHashes accordingly.
test/block-hash-pusher/optimism/OptimismBuffer.t.sol (3)

94-104: Parameterized test_ function should follow testFuzz_ naming convention; weak vm.expectRevert() on Line 101.

Two issues:

  1. Naming: test_receiveHashes_reverts_if_xDomainMessageSender_does_not_match_pusher(address notPusher) is a fuzz test (Foundry treats any test* function with parameters as fuzz). The testFuzz_ prefix signals this intent clearly and matches the convention used elsewhere in this file.

  2. Assertion strength: vm.expectRevert() on Line 101 accepts any revert—including unexpected panics or wrong custom errors. The actual expected error from OptimismBuffer when the xDomain sender doesn't match is DomainMessageSenderMismatch; the test should assert that specific selector.

♻️ Proposed changes
-    function test_receiveHashes_reverts_if_xDomainMessageSender_does_not_match_pusher(address notPusher) public {
+    function testFuzz_receiveHashes_reverts_if_xDomainMessageSender_does_not_match_pusher(address notPusher) public {
         vm.assume(notPusher != pusher);
         OptimismBuffer buffer = new OptimismBuffer(address(mockOpCrosschainDomainMessenger), pusher);

         bytes memory l2Calldata = abi.encodeCall(buffer.receiveHashes, (1, new bytes32[](1)));

-        vm.expectRevert();
+        vm.expectRevert(abi.encodeWithSelector(OptimismBuffer.DomainMessageSenderMismatch.selector));
         vm.prank(relayer);
         mockOpCrosschainDomainMessenger.relayMessage(address(buffer), notPusher, l2Calldata, 0);
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/block-hash-pusher/optimism/OptimismBuffer.t.sol` around lines 94 - 104,
Rename the fuzz test function
test_receiveHashes_reverts_if_xDomainMessageSender_does_not_match_pusher to
start with testFuzz_ (e.g.,
testFuzz_receiveHashes_reverts_if_xDomainMessageSender_does_not_match_pusher) so
it follows the file's fuzz naming convention, and replace the weak
vm.expectRevert() call with a targeted revert assertion using the OptimismBuffer
error selector
(expectRevert(abi.encodeWithSelector(OptimismBuffer.DomainMessageSenderMismatch.selector)))
before calling mockOpCrosschainDomainMessenger.relayMessage so the test asserts
the specific DomainMessageSenderMismatch error instead of any revert.

22-23: uint8 firstBlockNumber artificially caps the fuzz domain to [0, 255].

Real Ethereum block numbers are in the tens of millions. Any block-number-dependent logic above 255 in receiveHashes or BaseBuffer would never be exercised by this fuzz run. The only boundary this intentionally covers is firstBlockNumber == 0. Use uint256 and tighten the assumption if the zero-check is all that matters, or widen to a representative range.

♻️ Proposed change
-    function testFuzz_receiveHashes(uint16 batchSize, uint8 firstBlockNumber) public {
+    function testFuzz_receiveHashes(uint16 batchSize, uint256 firstBlockNumber) public {
         vm.assume(batchSize > 0 && batchSize <= 8191);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/block-hash-pusher/optimism/OptimismBuffer.t.sol` around lines 22 - 23,
The fuzz parameter firstBlockNumber is declared as uint8 which limits testing to
0..255; change the parameter in testFuzz_receiveHashes from uint8 to uint256 so
block-number-dependent logic in receiveHashes and BaseBuffer can be exercised,
and update the vm.assume to either only exclude negatives (e.g., keep
vm.assume(batchSize > 0 && batchSize <= 8191) and add vm.assume(firstBlockNumber
>= 0)) or constrain firstBlockNumber to a representative range (e.g., <= 1e8)
depending on whether you only need to preserve the zero-check or want realistic
block-number coverage.

42-42: Misleading test name — rename to reflect the actual duplicate-push scenario.

test_receiveHashes_does_not_emit_event_when_no_hashes_written sounds like it covers a zero-hash batch, but the test actually verifies that replaying an identical push (all blocks already stored) suppresses the BlockHashesPushed event. A name like test_receiveHashes_does_not_emit_event_on_duplicate_push is more precise.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/block-hash-pusher/optimism/OptimismBuffer.t.sol` at line 42, Rename the
misleading test function
test_receiveHashes_does_not_emit_event_when_no_hashes_written to reflect the
duplicate-push behavior being tested (e.g.,
test_receiveHashes_does_not_emit_event_on_duplicate_push); update the function
declaration and any references or docstrings/comments inside the
OptimismBuffer.t.sol test file so the name accurately describes that the test
replays an identical push (all blocks already stored) and asserts that
BlockHashesPushed is suppressed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/contracts/block-hash-pusher/optimism/OptimismPusher.sol`:
- Line 9: Update the NatSpec title comment from "OPtimismPusher" to the correct
"OptimismPusher" in the OptimismPusher contract header comment; locate the
top-of-file NatSpec/title line (for the OptimismPusher contract) and correct the
capitalization/typo so it matches the contract name OptimismPusher.

In `@test/provers/optimism/ChildToParentProver.t.sol`:
- Line 8: The test imports the wrong chain-specific prover: change the import of
ChildToParentProver that currently points to provers/zksync/ to the
provers/optimism/ implementation so the test uses the optimism-specific contract
(update the import statement referencing ChildToParentProver to the optimism
path); this preserves the chain-specific pattern and avoids future divergence
issues.

---

Outside diff comments:
In `@test/provers/optimism/ChildToParentProver.t.sol`:
- Around line 13-17: The NatSpec header for the Optimism ChildToParentProver
test is stale: update the comment that currently says "reads L1 block hashes
from the L1Block predeploy on Optimism" to reflect the current implementation
(it now reads from the block hash buffer). Edit the top comment for the
ChildToParentProver test to say something like "This prover reads L1 block
hashes from the block hash buffer on Optimism" so the description matches the
actual code paths in the ChildToParentProver tests.

In `@test/VerifyBroadcastMessageBenchmark.t.sol`:
- Around line 400-404: Update the stale comment in the benchmark description in
VerifyBroadcastMessageBenchmark.t.sol so Hop 1 no longer says "OP-C2P gets
Ethereum block hash from L1Block predeploy" but instead references reading from
the block hash buffer (e.g., change the Hop 1 line to mention "reads Ethereum
block hash from block hash buffer"); adjust any related phrasing in the same
comment block (the route/Hop descriptions) to reflect "block hash buffer" rather
than "L1Block predeploy" so the documentation matches the current prover
behavior.

---

Nitpick comments:
In `@test/block-hash-pusher/optimism/OptimismBuffer.t.sol`:
- Around line 94-104: Rename the fuzz test function
test_receiveHashes_reverts_if_xDomainMessageSender_does_not_match_pusher to
start with testFuzz_ (e.g.,
testFuzz_receiveHashes_reverts_if_xDomainMessageSender_does_not_match_pusher) so
it follows the file's fuzz naming convention, and replace the weak
vm.expectRevert() call with a targeted revert assertion using the OptimismBuffer
error selector
(expectRevert(abi.encodeWithSelector(OptimismBuffer.DomainMessageSenderMismatch.selector)))
before calling mockOpCrosschainDomainMessenger.relayMessage so the test asserts
the specific DomainMessageSenderMismatch error instead of any revert.
- Around line 22-23: The fuzz parameter firstBlockNumber is declared as uint8
which limits testing to 0..255; change the parameter in testFuzz_receiveHashes
from uint8 to uint256 so block-number-dependent logic in receiveHashes and
BaseBuffer can be exercised, and update the vm.assume to either only exclude
negatives (e.g., keep vm.assume(batchSize > 0 && batchSize <= 8191) and add
vm.assume(firstBlockNumber >= 0)) or constrain firstBlockNumber to a
representative range (e.g., <= 1e8) depending on whether you only need to
preserve the zero-check or want realistic block-number coverage.
- Line 42: Rename the misleading test function
test_receiveHashes_does_not_emit_event_when_no_hashes_written to reflect the
duplicate-push behavior being tested (e.g.,
test_receiveHashes_does_not_emit_event_on_duplicate_push); update the function
declaration and any references or docstrings/comments inside the
OptimismBuffer.t.sol test file so the name accurately describes that the test
replays an identical push (all blocks already stored) and asserts that
BlockHashesPushed is suppressed.

In `@test/provers/optimism/ChildToParentProver.t.sol`:
- Around line 42-83: The test currently pushes the block hash into the buffer
but never calls the buffer-backed target state commitment paths; after
buffer.receiveHashes(blockNumber, blockHashes) call into
childToParentProver.verifyTargetStateCommitment(...) and
childToParentProver.getTargetStateCommitment(...) (using the same blockHash and
the appropriate proof data from the JSON/rlpBlockHeader or the encoded input)
and add assertions that their returned commitment/verification result matches
the expected commitment computed from the test vectors; insert these calls just
before calling childToParentProver.verifyStorageSlot so the buffer path is
exercised (referencing buffer.receiveHashes,
childToParentProver.verifyTargetStateCommitment, and
childToParentProver.getTargetStateCommitment).

In `@test/Receiver.t.sol`:
- Around line 286-290: The IBuffer cast is redundantly assigned to a local
variable in multiple test functions (e.g., IBuffer buffer =
IBuffer(blockHashBuffer) before calling receiveHashes); either inline the cast
at the call site (IBuffer(blockHashBuffer).receiveHashes(...)) or hoist a single
IBuffer buffer = IBuffer(blockHashBuffer) declaration to the top of each test
function where blockHashBuffer is declared so the repeated pattern is removed;
update all five occurrences referencing IBuffer, blockHashBuffer, and
receiveHashes accordingly.

import {IPusher} from "../interfaces/IPusher.sol";
import {ICrossDomainMessenger} from "@eth-optimism/contracts/libraries/bridge/ICrossDomainMessenger.sol";

/// @title OPtimismPusher
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Typo in NatSpec title: "OPtimismPusher" → "OptimismPusher".

📝 Proposed fix
-/// `@title` OPtimismPusher
+/// `@title` OptimismPusher
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// @title OPtimismPusher
/// `@title` OptimismPusher
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/contracts/block-hash-pusher/optimism/OptimismPusher.sol` at line 9,
Update the NatSpec title comment from "OPtimismPusher" to the correct
"OptimismPusher" in the OptimismPusher contract header comment; locate the
top-of-file NatSpec/title line (for the OptimismPusher contract) and correct the
capitalization/typo so it matches the contract name OptimismPusher.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/contracts/provers/optimism/ChildToParentProver.sol (1)

15-19: Avoid hardcoding the buffer mapping slot.
Hardcoding blockHashMappingSlot = 1 tightly couples this prover to BaseBuffer’s storage layout; any layout change will silently break proofs. Consider exposing the slot from the buffer contract (e.g., via an interface getter/constant) and referencing that instead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/contracts/provers/optimism/ChildToParentProver.sol` around lines 15 - 19,
The contract hardcodes blockHashMappingSlot = 1 which couples
ChildToParentProver to BaseBuffer's storage layout; change this by removing the
hardcoded constant and instead query the buffer contract for its slot at
deployment or construction: add or use an interface (e.g., IBlockHashBuffer)
with a public getter/constant like blockHashMappingSlot() and store the returned
value in a non-constant state variable (replace the current blockHashMappingSlot
constant), initialize it from blockHashBuffer in the constructor, and update any
references to use the new state variable so the prover reads the actual buffer
mapping slot at runtime.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/contracts/provers/optimism/ChildToParentProver.sol`:
- Around line 28-30: Add a guard in the constructor of ChildToParentProver to
ensure the _blockHashBuffer address is not zero: validate that _blockHashBuffer
!= address(0) and revert with a clear message (e.g., "blockHashBuffer: zero
address") before assigning blockHashBuffer = _blockHashBuffer; this prevents
runtime reverts when reading the buffer later from functions in
ChildToParentProver.
- Line 57: The require calls using custom errors (e.g.
require(targetStateCommitment != bytes32(0), InvalidTargetStateCommitment()))
rely on the via-IR compiler pipeline; either enable --via-ir in the build config
or replace these requires with explicit checks and reverts to ensure
compatibility — for example, change the single-line require to: if
(targetStateCommitment == bytes32(0)) revert InvalidTargetStateCommitment(); and
do the same for the other similar require (the one around line 71) so both use
explicit if + revert with the custom error.

---

Nitpick comments:
In `@src/contracts/provers/optimism/ChildToParentProver.sol`:
- Around line 15-19: The contract hardcodes blockHashMappingSlot = 1 which
couples ChildToParentProver to BaseBuffer's storage layout; change this by
removing the hardcoded constant and instead query the buffer contract for its
slot at deployment or construction: add or use an interface (e.g.,
IBlockHashBuffer) with a public getter/constant like blockHashMappingSlot() and
store the returned value in a non-constant state variable (replace the current
blockHashMappingSlot constant), initialize it from blockHashBuffer in the
constructor, and update any references to use the new state variable so the
prover reads the actual buffer mapping slot at runtime.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9aa1c09 and f322565.

📒 Files selected for processing (3)
  • snapshots/verifyBroadcastMessage.json
  • src/contracts/provers/optimism/ChildToParentProver.sol
  • test/Receiver.t.sol
🚧 Files skipped from review as they are similar to previous changes (1)
  • snapshots/verifyBroadcastMessage.json

Comment on lines +28 to 30
constructor(address _blockHashBuffer, uint256 _homeChainId) {
blockHashBuffer = _blockHashBuffer;
homeChainId = _homeChainId;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Guard against a zero buffer address.
A zero blockHashBuffer will cause runtime reverts when reading the buffer, so it’s safer to validate in the constructor.

🔧 Proposed fix
+    error InvalidBlockHashBuffer();
+
     constructor(address _blockHashBuffer, uint256 _homeChainId) {
+        if (_blockHashBuffer == address(0)) revert InvalidBlockHashBuffer();
         blockHashBuffer = _blockHashBuffer;
         homeChainId = _homeChainId;
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
constructor(address _blockHashBuffer, uint256 _homeChainId) {
blockHashBuffer = _blockHashBuffer;
homeChainId = _homeChainId;
error InvalidBlockHashBuffer();
constructor(address _blockHashBuffer, uint256 _homeChainId) {
if (_blockHashBuffer == address(0)) revert InvalidBlockHashBuffer();
blockHashBuffer = _blockHashBuffer;
homeChainId = _homeChainId;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/contracts/provers/optimism/ChildToParentProver.sol` around lines 28 - 30,
Add a guard in the constructor of ChildToParentProver to ensure the
_blockHashBuffer address is not zero: validate that _blockHashBuffer !=
address(0) and revert with a clear message (e.g., "blockHashBuffer: zero
address") before assigning blockHashBuffer = _blockHashBuffer; this prevents
runtime reverts when reading the buffer later from functions in
ChildToParentProver.

homeBlockHash, rlpBlockHeader, blockHashBuffer, slot, accountProof, storageProof
);

require(targetStateCommitment != bytes32(0), InvalidTargetStateCommitment());
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

Solidity 0.8.30 custom errors require statement second argument

💡 Result:

In Solidity 0.8.30, require has a 3rd overload that accepts a custom error as the second argument:

error Unauthorized(address caller);

function f(address owner) external view {
    require(msg.sender == owner, Unauthorized(msg.sender));
}

The Solidity docs list the overload as require(bool, error). [1]

However, custom errors in require are only supported in the via-IR pipeline (introduced in Solidity 0.8.26). So if you compile without --via-ir / settings.viaIR: true, this may fail to compile; in that case use:

if (msg.sender != owner) revert Unauthorized(msg.sender);

[2]

Sources:
[1] Solidity 0.8.30 docs – Expressions and Control Structures (require overloads) (docs.soliditylang.org)
[2] Solidity 0.8.26 release announcement – Custom errors support in require (via-IR only) (soliditylang.org)

Citations:


🏁 Script executed:

# Check the actual code at the specified lines
cat -n src/contracts/provers/optimism/ChildToParentProver.sol | sed -n '50,80p'

Repository: openintentsframework/broadcaster

Length of output: 1867


🏁 Script executed:

# Check the Solidity compiler version specified in the contract
head -30 src/contracts/provers/optimism/ChildToParentProver.sol | grep -E "pragma solidity|import"

Repository: openintentsframework/broadcaster

Length of output: 377


Clarify via-IR requirement for custom errors in require. Solidity 0.8.30 supports custom errors as the second argument to require(), but only when compiling with the via-IR pipeline. If this project doesn't use --via-ir, the code won't compile. Verify the build configuration or use explicit revert for guaranteed compatibility:

-        require(targetStateCommitment != bytes32(0), InvalidTargetStateCommitment());
+        if (targetStateCommitment == bytes32(0)) revert InvalidTargetStateCommitment();

Also applies to: 71-71

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/contracts/provers/optimism/ChildToParentProver.sol` at line 57, The
require calls using custom errors (e.g. require(targetStateCommitment !=
bytes32(0), InvalidTargetStateCommitment())) rely on the via-IR compiler
pipeline; either enable --via-ir in the build config or replace these requires
with explicit checks and reverts to ensure compatibility — for example, change
the single-line require to: if (targetStateCommitment == bytes32(0)) revert
InvalidTargetStateCommitment(); and do the same for the other similar require
(the one around line 71) so both use explicit if + revert with the custom error.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
src/contracts/provers/optimism/ChildToParentProver.sol (2)

28-31: Guard against a zero _blockHashBuffer address.

A zero address will cause runtime reverts when calling IBuffer(blockHashBuffer).parentChainBlockHash(...) or when proof verification targets address(0). Adding a constructor check makes the failure mode explicit at deploy time.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/contracts/provers/optimism/ChildToParentProver.sol` around lines 28 - 31,
The constructor currently accepts _blockHashBuffer without validation; add a
guard in the constructor of ChildToParentProver to require(_blockHashBuffer !=
address(0), "blockHashBuffer zero") so blockHashBuffer is never set to
address(0) (keep assigning blockHashBuffer = _blockHashBuffer and homeChainId =
_homeChainId) to prevent runtime reverts when later calling
IBuffer(blockHashBuffer).parentChainBlockHash(...) or verifying proofs against
address(0).

56-56: Custom errors in require need the via-IR pipeline.

Already flagged — require(condition, CustomError()) syntax requires --via-ir compilation. Same applies to line 70.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/contracts/provers/optimism/ChildToParentProver.sol` at line 56, The
contract ChildToParentProver uses require(..., InvalidTargetStateCommitment())
which relies on the via-IR compilation pipeline; fix this by either (A)
replacing those require(...) calls with explicit revert
InvalidTargetStateCommitment() style revert statements (e.g., check
targetStateCommitment and revert InvalidTargetStateCommitment() on failure) or
(B) enable the --via-ir compiler flag in your build so require(condition,
CustomError()) is supported; update both the require referencing
InvalidTargetStateCommitment and the other require that uses a custom error in
the same contract accordingly (refer to the ChildToParentProver contract and the
InvalidTargetStateCommitment custom error symbol to locate the spots).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/contracts/provers/optimism/ChildToParentProver.sol`:
- Around line 48-50: The comment above the slot calculation incorrectly links to
OffchainLabs' block-hash-pusher; update the inline doc comment to reference this
repo's BaseBuffer implementation instead. Locate the comment that precedes
"uint256 slot =
uint256(SlotDerivation.deriveMapping(bytes32(BLOCK_HASH_MAPPING_SLOT),
targetBlockNumber));" and replace the external URL with the project's BaseBuffer
link (the openintentsframework/broadcaster BaseBuffer) so the reference is
consistent with SlotDerivation.deriveMapping, BLOCK_HASH_MAPPING_SLOT and
targetBlockNumber usage.

---

Duplicate comments:
In `@src/contracts/provers/optimism/ChildToParentProver.sol`:
- Around line 28-31: The constructor currently accepts _blockHashBuffer without
validation; add a guard in the constructor of ChildToParentProver to
require(_blockHashBuffer != address(0), "blockHashBuffer zero") so
blockHashBuffer is never set to address(0) (keep assigning blockHashBuffer =
_blockHashBuffer and homeChainId = _homeChainId) to prevent runtime reverts when
later calling IBuffer(blockHashBuffer).parentChainBlockHash(...) or verifying
proofs against address(0).
- Line 56: The contract ChildToParentProver uses require(...,
InvalidTargetStateCommitment()) which relies on the via-IR compilation pipeline;
fix this by either (A) replacing those require(...) calls with explicit revert
InvalidTargetStateCommitment() style revert statements (e.g., check
targetStateCommitment and revert InvalidTargetStateCommitment() on failure) or
(B) enable the --via-ir compiler flag in your build so require(condition,
CustomError()) is supported; update both the require referencing
InvalidTargetStateCommitment and the other require that uses a custom error in
the same contract accordingly (refer to the ChildToParentProver contract and the
InvalidTargetStateCommitment custom error symbol to locate the spots).

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f322565 and be6bd35.

📒 Files selected for processing (2)
  • snapshots/verifyBroadcastMessage.json
  • src/contracts/provers/optimism/ChildToParentProver.sol
🚧 Files skipped from review as they are similar to previous changes (1)
  • snapshots/verifyBroadcastMessage.json

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (2)
src/contracts/provers/optimism/ChildToParentProver.sol (2)

28-30: ⚠️ Potential issue | 🟡 Minor

Guard against a zero blockHashBuffer address.
Fail fast in the constructor to avoid opaque runtime reverts later.

🔧 Proposed fix
+    error InvalidBlockHashBuffer();
+
     constructor(address _blockHashBuffer, uint256 _homeChainId) {
+        if (_blockHashBuffer == address(0)) revert InvalidBlockHashBuffer();
         blockHashBuffer = _blockHashBuffer;
         homeChainId = _homeChainId;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/contracts/provers/optimism/ChildToParentProver.sol` around lines 28 - 30,
The constructor in ChildToParentProver.sol should guard against a zero
blockHashBuffer address to fail fast; update the constructor (the constructor
that assigns blockHashBuffer and homeChainId) to require(_blockHashBuffer !=
address(0)) and revert with a clear message before assigning blockHashBuffer, so
the contract cannot be deployed with a zero address and later trigger opaque
runtime errors.

56-70: Confirm via-IR is enabled for custom-error require, or switch to explicit revert.
Without via-IR, Solidity 0.8.30 won’t accept require(bool, error) and the build will fail.

✅ Compatible alternative
-        require(targetStateCommitment != bytes32(0), InvalidTargetStateCommitment());
+        if (targetStateCommitment == bytes32(0)) revert InvalidTargetStateCommitment();
...
-        require(targetStateCommitment != bytes32(0), InvalidTargetStateCommitment());
+        if (targetStateCommitment == bytes32(0)) revert InvalidTargetStateCommitment();
#!/bin/bash
set -euo pipefail
rg -n "viaIR|via-ir|via_ir" -g "foundry.toml" -g "hardhat.config.*" -g "*.json"
rg -n "solc" -g "foundry.toml" -g "hardhat.config.*" -g "*.json"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/contracts/provers/optimism/ChildToParentProver.sol` around lines 56 - 70,
The custom-error form of require(targetStateCommitment != bytes32(0),
InvalidTargetStateCommitment()) is not compatible without via-IR; either enable
via-IR in the compiler config or replace those require(...) calls with explicit
checks and reverts: e.g., in the constructor and in
getTargetStateCommitment(uint256 input) (look for require(targetStateCommitment
!= bytes32(0), InvalidTargetStateCommitment()) and the other require/use in
getTargetStateCommitment), change to if (targetStateCommitment == bytes32(0))
revert InvalidTargetStateCommitment(); so the code compiles on Solidity 0.8.30
without via-IR.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/contracts/provers/optimism/ChildToParentProver.sol`:
- Around line 28-30: The constructor in ChildToParentProver.sol should guard
against a zero blockHashBuffer address to fail fast; update the constructor (the
constructor that assigns blockHashBuffer and homeChainId) to
require(_blockHashBuffer != address(0)) and revert with a clear message before
assigning blockHashBuffer, so the contract cannot be deployed with a zero
address and later trigger opaque runtime errors.
- Around line 56-70: The custom-error form of require(targetStateCommitment !=
bytes32(0), InvalidTargetStateCommitment()) is not compatible without via-IR;
either enable via-IR in the compiler config or replace those require(...) calls
with explicit checks and reverts: e.g., in the constructor and in
getTargetStateCommitment(uint256 input) (look for require(targetStateCommitment
!= bytes32(0), InvalidTargetStateCommitment()) and the other require/use in
getTargetStateCommitment), change to if (targetStateCommitment == bytes32(0))
revert InvalidTargetStateCommitment(); so the code compiles on Solidity 0.8.30
without via-IR.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between be6bd35 and 6e254fe.

📒 Files selected for processing (5)
  • src/contracts/provers/linea/ChildToParentProver.sol
  • src/contracts/provers/optimism/ChildToParentProver.sol
  • src/contracts/provers/scroll/ChildToParentProver.sol
  • src/contracts/provers/zksync/ChildToParentProver.sol
  • test/provers/optimism/ChildToParentProver.t.sol
✅ Files skipped from review due to trivial changes (2)
  • src/contracts/provers/zksync/ChildToParentProver.sol
  • src/contracts/provers/scroll/ChildToParentProver.sol

@luiz-lvj luiz-lvj merged commit 0daee31 into main Feb 24, 2026
3 checks passed
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.

2 participants