Skip to content

Comments

Add Optimism Pusher and Buffer#85

Open
luiz-lvj wants to merge 3 commits intomainfrom
pushers
Open

Add Optimism Pusher and Buffer#85
luiz-lvj wants to merge 3 commits intomainfrom
pushers

Conversation

@luiz-lvj
Copy link
Collaborator

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

Summary by CodeRabbit

  • New Features

    • Added cross-chain block hash synchronization between Ethereum and Optimism, enabling Layer 1 block hashes to be transmitted and stored on Layer 2 through secure cross-domain messaging.
    • Implemented block hash reception with sender validation and access control on Optimism Layer 2.
  • Tests

    • Added comprehensive test suites for block hash synchronization, covering hash transmission, event validation, constructor validation, and cross-chain messaging scenarios.

@coderabbitai
Copy link

coderabbitai bot commented Feb 19, 2026

📝 Walkthrough

Walkthrough

Adds Optimism-specific block hash synchronization contracts: OptimismBuffer (L2 receiver) and OptimismPusher (L1 sender) with cross-domain messenger integration, plus mock and test infrastructure for validating cross-chain messaging behavior.

Changes

Cohort / File(s) Summary
Optimism Block Hash Contracts
src/contracts/block-hash-pusher/optimism/OptimismBuffer.sol, src/contracts/block-hash-pusher/optimism/OptimismPusher.sol
New OptimismBuffer extends BaseBuffer to receive L1 block hashes on L2 via ICrossDomainMessenger with sender validation. OptimismPusher implements IPusher to push hashes from L1 through L1CrossDomainMessengerProxy with L2 gas limit configuration.
Testing Infrastructure
test/block-hash-pusher/mocks/MockOpCrosschainDomainMessenger.sol, test/block-hash-pusher/optimism/OptimismBuffer.t.sol, test/block-hash-pusher/optimism/OptimismPusher.t.sol
Mock messenger simulating IL2CrossDomainMessenger with transient sender state for testing. Comprehensive test suites validating hash reception, event emission, constructor validation, access control, and cross-chain relaying behavior with fuzzing.

Sequence Diagram

sequenceDiagram
    participant L1Pusher as L1: OptimismPusher
    participant L1Messenger as L1: CrossDomainMessenger
    participant L2Messenger as L2: CrossDomainMessenger
    participant L2Buffer as L2: OptimismBuffer
    
    L1Pusher->>L1Pusher: buildBlockHashArray()
    L1Pusher->>L1Pusher: encodeReceiveHashesCall()
    L1Pusher->>L1Messenger: sendMessage(L2Buffer, calldata, gasLimit)
    Note over L1Messenger: Stores message for relaying
    L1Messenger->>L2Messenger: relayMessage(L2Buffer, L1Pusher, calldata)
    L2Messenger->>L2Messenger: setTransientSender(L1Pusher)
    L2Messenger->>L2Buffer: receiveHashes(firstBlockNumber, hashes)
    L2Buffer->>L2Buffer: validateSender == L2Messenger
    L2Buffer->>L2Buffer: validateXDomainSender == pusher
    L2Buffer->>L2Buffer: _receiveHashes() - persist hashes
    L2Buffer->>L2Buffer: emit BlockHashesPushed
    L2Messenger->>L2Messenger: clearTransientSender()
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

  • PR #56 — Introduces BaseBuffer and IPusher interfaces that OptimismBuffer and OptimismPusher directly implement and extend.
  • PR #60 — Modifies pusher/buffer design to use immutable constructor-set pusher fields and explicit buffer parameters, which this PR implements for Optimism.
  • PR #80 — Affects the receiveHashes/\_receiveHashes flow and BlockHashesPushed emission that OptimismBuffer delegates through.

Suggested reviewers

  • pepebndc
  • frangio
  • nahimterrazas

Poem

🐰 Cross the chains with hashes bright,
From L1's dawn to L2's night,
Optimism's messenger flies,
Block-by-block, the buffer flies,
Via domain calls we synchronize! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add Optimism Pusher and Buffer' directly and clearly summarizes the main changes: introducing two new contracts (OptimismPusher and OptimismBuffer) for the Optimism integration.
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 pushers

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

🧹 Nitpick comments (5)
src/contracts/block-hash-pusher/optimism/OptimismBuffer.sol (1)

42-49: receiveHashes access-control pattern is correct.

Checking msg.sender == l2CrossDomainMessengerCached before calling xDomainMessageSender() is the standard Optimism two-step guard—the first prevents anyone from spoofing the cross-domain sender, and the second ensures the relayed message actually originated from _pusher.

One nit: Line 43 routes through the public l2CrossDomainMessenger() getter to build l2CrossDomainMessengerCached. Using the immutable _l2CrossDomainMessenger directly is cheaper and avoids the extra dispatch.

♻️ Proposed refactor
-ICrossDomainMessenger l2CrossDomainMessengerCached = ICrossDomainMessenger(l2CrossDomainMessenger());
+ICrossDomainMessenger l2CrossDomainMessengerCached = ICrossDomainMessenger(_l2CrossDomainMessenger);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/contracts/block-hash-pusher/optimism/OptimismBuffer.sol` around lines 42
- 49, The receiveHashes function currently calls the public getter
l2CrossDomainMessenger() to obtain the messenger, adding an unnecessary external
call; replace that with the immutable storage value _l2CrossDomainMessenger
directly (use ICrossDomainMessenger(_l2CrossDomainMessenger)) when constructing
l2CrossDomainMessengerCached in receiveHashes to save gas and avoid extra
dispatch while retaining the same access-control checks (msg.sender comparison
and xDomainMessageSender() verification).
test/block-hash-pusher/optimism/OptimismBuffer.t.sol (2)

22-23: uint8 firstBlockNumber severely limits fuzz coverage to 0–255.

Real-world block numbers are in the billions. If BaseBuffer._receiveHashes contains any range-dependent validation (e.g., overflow guards, mapping key constraints), this narrow range will never surface them. Consider a wider type with a non-zero lower bound:

♻️ Proposed widened range
-function testFuzz_receiveHashes(uint16 batchSize, uint8 firstBlockNumber) public {
-    vm.assume(batchSize > 0 && batchSize <= 8191);
+function testFuzz_receiveHashes(uint16 batchSize, uint256 firstBlockNumber) public {
+    vm.assume(batchSize > 0 && batchSize <= 8191);
+    vm.assume(firstBlockNumber <= type(uint256).max - batchSize); // prevent overflow in _receiveHashes
🤖 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 too narrow (uint8) and should be widened
to cover realistic block ranges; change the testFuzz_receiveHashes signature to
use a larger integer type (e.g., uint32 or uint64) and add a vm.assume to
constrain it to a non-zero lower bound and a reasonable upper bound (for example
vm.assume(firstBlockNumber > 0 && firstBlockNumber <= SOME_MAX)) so
BaseBuffer._receiveHashes range-dependent behavior is exercised; update any uses
of firstBlockNumber in testFuzz_receiveHashes accordingly.

94-104: Fuzz test missing testFuzz_ prefix; vm.expectRevert() is overly broad.

The function accepts an address notPusher parameter, making Foundry treat it as a fuzz test, but the name starts with test_ rather than the conventional testFuzz_. Additionally, vm.expectRevert() with no arguments catches any revert—at minimum assert on the "Message sending failed" string (the revert the mock produces) to guard against accidentally passing due to an unrelated revert.

♻️ Proposed fixes
-/// forge-config: default.allow_internal_expect_revert = true
-function test_receiveHashes_reverts_if_xDomainMessageSender_does_not_match_pusher(address notPusher) public {
+/// forge-config: default.allow_internal_expect_revert = true
+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("Message sending failed");
     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 test function to start with the fuzz prefix (e.g., change
test_receiveHashes_reverts_if_xDomainMessageSender_does_not_match_pusher to
testFuzz_receiveHashes_reverts_if_xDomainMessageSender_does_not_match_pusher) so
Foundry treats it as a fuzz test, and tighten the revert assertion by replacing
the broad vm.expectRevert() with an assertion that matches the mock revert
payload (e.g., vm.expectRevert(bytes("Message sending failed"))) before calling
mockOpCrosschainDomainMessenger.relayMessage on the OptimismBuffer instance to
ensure the failure is the expected "Message sending failed" from the mock.
src/contracts/block-hash-pusher/optimism/OptimismPusher.sol (1)

42-47: Decode l2TransactionData before _buildBlockHashArray to fail fast.

Line 45 decodes l2TransactionData after the expensive _buildBlockHashArray call (which reads multiple block hashes via the BLOCKHASH opcode). If l2TransactionData is malformed or empty, the call reverts after all that work. Swapping the order makes an invalid input revert cheaply upfront.

♻️ Proposed refactor
+    OptimismL2Transaction memory l2Transaction = abi.decode(l2TransactionData, (OptimismL2Transaction));
+
     bytes32[] memory blockHashes = _buildBlockHashArray(firstBlockNumber, batchSize);
     bytes memory l2Calldata = abi.encodeCall(IBuffer.receiveHashes, (firstBlockNumber, blockHashes));

-    OptimismL2Transaction memory l2Transaction = abi.decode(l2TransactionData, (OptimismL2Transaction));
-
     ICrossDomainMessenger(l1CrossDomainMessengerProxy()).sendMessage(buffer, l2Calldata, l2Transaction.gasLimit);
🤖 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` around lines 42
- 47, Decode l2TransactionData into OptimismL2Transaction before calling
_buildBlockHashArray so malformed or empty input fails fast; specifically move
the abi.decode(l2TransactionData, (OptimismL2Transaction)) call ahead of the
call to _buildBlockHashArray(firstBlockNumber, batchSize), then construct
blockHashes, l2Calldata and proceed to
ICrossDomainMessenger(l1CrossDomainMessengerProxy()).sendMessage(buffer,
l2Calldata, l2Transaction.gasLimit) as before.
test/block-hash-pusher/mocks/MockOpCrosschainDomainMessenger.sol (1)

26-29: Consider bubbling up the original revert data for better test diagnostics.

When the inner _target.call fails, returnData is captured but immediately discarded in favor of a hardcoded string revert. This masks the actual revert reason (e.g., DomainMessageSenderMismatch, InvalidSender), making failed test assertions harder to diagnose.

♻️ Proposed refactor to propagate the original revert
 (bool callSuccess, bytes memory returnData) = _target.call(_message);
 if (!callSuccess) {
-    revert("Message sending failed");
+    assembly {
+        revert(add(returnData, 32), mload(returnData))
+    }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/block-hash-pusher/mocks/MockOpCrosschainDomainMessenger.sol` around
lines 26 - 29, The current mock swallows the original revert reason after
calling _target.call by throwing a fixed string; update the failure branch in
MockOpCrosschainDomainMessenger so that when callSuccess is false it reverts
with the original returnData (e.g., using assembly revert(add(returnData, 32),
mload(returnData))) to propagate the exact revert payload from _target.call, and
optionally fall back to a generic message only if returnData is empty.
🤖 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: Fix the NatSpec typo in the contract title: update the `@title` comment
from "OPtimismPusher" to "OptimismPusher" in the OptimismPusher contract header
(the top-of-file NatSpec for the contract named OptimismPusher) so the
documented title matches the contract name; simply edit the `@title` line to the
correct capitalization.

In `@test/block-hash-pusher/optimism/OptimismPusher.t.sol`:
- Around line 33-35: The test calls optimismPusher.pushHashes with offsets
block.number - 1000 and block.number - 8000 which under a fork will pass
bytes32(0) for each hash because BLOCKHASH only returns non-zero for the last
256 blocks; update the test to either (A) use offsets within 256 blocks of the
fork tip (e.g., replace -1000 and -8000 with values <=256) when you need real
block hashes, or (B) if you only want to validate messaging behavior, add a
clear comment above the optimismPusher.pushHashes calls stating that older
hashes will be zero on a fork and the test intentionally validates messaging
only; locate and edit the optimismPusher.pushHashes calls and the containing
test to apply one of these fixes.

---

Nitpick comments:
In `@src/contracts/block-hash-pusher/optimism/OptimismBuffer.sol`:
- Around line 42-49: The receiveHashes function currently calls the public
getter l2CrossDomainMessenger() to obtain the messenger, adding an unnecessary
external call; replace that with the immutable storage value
_l2CrossDomainMessenger directly (use
ICrossDomainMessenger(_l2CrossDomainMessenger)) when constructing
l2CrossDomainMessengerCached in receiveHashes to save gas and avoid extra
dispatch while retaining the same access-control checks (msg.sender comparison
and xDomainMessageSender() verification).

In `@src/contracts/block-hash-pusher/optimism/OptimismPusher.sol`:
- Around line 42-47: Decode l2TransactionData into OptimismL2Transaction before
calling _buildBlockHashArray so malformed or empty input fails fast;
specifically move the abi.decode(l2TransactionData, (OptimismL2Transaction))
call ahead of the call to _buildBlockHashArray(firstBlockNumber, batchSize),
then construct blockHashes, l2Calldata and proceed to
ICrossDomainMessenger(l1CrossDomainMessengerProxy()).sendMessage(buffer,
l2Calldata, l2Transaction.gasLimit) as before.

In `@test/block-hash-pusher/mocks/MockOpCrosschainDomainMessenger.sol`:
- Around line 26-29: The current mock swallows the original revert reason after
calling _target.call by throwing a fixed string; update the failure branch in
MockOpCrosschainDomainMessenger so that when callSuccess is false it reverts
with the original returnData (e.g., using assembly revert(add(returnData, 32),
mload(returnData))) to propagate the exact revert payload from _target.call, and
optionally fall back to a generic message only if returnData is empty.

In `@test/block-hash-pusher/optimism/OptimismBuffer.t.sol`:
- Around line 22-23: The fuzz parameter firstBlockNumber is too narrow (uint8)
and should be widened to cover realistic block ranges; change the
testFuzz_receiveHashes signature to use a larger integer type (e.g., uint32 or
uint64) and add a vm.assume to constrain it to a non-zero lower bound and a
reasonable upper bound (for example vm.assume(firstBlockNumber > 0 &&
firstBlockNumber <= SOME_MAX)) so BaseBuffer._receiveHashes range-dependent
behavior is exercised; update any uses of firstBlockNumber in
testFuzz_receiveHashes accordingly.
- Around line 94-104: Rename the test function to start with the fuzz prefix
(e.g., change
test_receiveHashes_reverts_if_xDomainMessageSender_does_not_match_pusher to
testFuzz_receiveHashes_reverts_if_xDomainMessageSender_does_not_match_pusher) so
Foundry treats it as a fuzz test, and tighten the revert assertion by replacing
the broad vm.expectRevert() with an assertion that matches the mock revert
payload (e.g., vm.expectRevert(bytes("Message sending failed"))) before calling
mockOpCrosschainDomainMessenger.relayMessage on the OptimismBuffer instance to
ensure the failure is the expected "Message sending failed" from the mock.

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 @title NatSpec: OPtimismPusherOptimismPusher.

📝 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, Fix
the NatSpec typo in the contract title: update the `@title` comment from
"OPtimismPusher" to "OptimismPusher" in the OptimismPusher contract header (the
top-of-file NatSpec for the contract named OptimismPusher) so the documented
title matches the contract name; simply edit the `@title` line to the correct
capitalization.

Comment on lines +33 to +35
optimismPusher.pushHashes(buffer, block.number - 1000, 10, l2TransactionData);

optimismPusher.pushHashes(buffer, block.number - 8000, 20, l2TransactionData);
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

block.number - 1000 and block.number - 8000 will always push zero hashes on a fork.

The EVM BLOCKHASH opcode only returns a non-zero value for the 256 most recent blocks. Any block older than that yields bytes32(0). The fork test therefore pushes arrays of zero hashes for those two calls, which doesn't test real block hash integrity. If the intent is to validate the messaging flow only, this is fine — but a comment would clarify the limitation. If real hashes are needed, stay within 256 blocks of the fork tip.

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

In `@test/block-hash-pusher/optimism/OptimismPusher.t.sol` around lines 33 - 35,
The test calls optimismPusher.pushHashes with offsets block.number - 1000 and
block.number - 8000 which under a fork will pass bytes32(0) for each hash
because BLOCKHASH only returns non-zero for the last 256 blocks; update the test
to either (A) use offsets within 256 blocks of the fork tip (e.g., replace -1000
and -8000 with values <=256) when you need real block hashes, or (B) if you only
want to validate messaging behavior, add a clear comment above the
optimismPusher.pushHashes calls stating that older hashes will be zero on a fork
and the test intentionally validates messaging only; locate and edit the
optimismPusher.pushHashes calls and the containing test to apply one of these
fixes.

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