Skip to content

Comments

[Provers N03] Implicit constants are hardcoded#82

Open
luiz-lvj wants to merge 3 commits intomainfrom
fix/provers-N03
Open

[Provers N03] Implicit constants are hardcoded#82
luiz-lvj wants to merge 3 commits intomainfrom
fix/provers-N03

Conversation

@luiz-lvj
Copy link
Collaborator

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

In the ParentToChild contract of the ZKsync Era rollup, several fields of the constructed L2Log rely on values that are effectively constant at the protocol level but are either hardcoded directly or implicitly derived from user input.

Specifically:

The sender field of the L2Log is hardcoded to the address of the L1Messenger contract.
The key field is derived from user input (_message.sender), even though, due to the construction logic in the L1Messenger contract, this value is expected in practice to be constant and effectively equal to the address of the ZKSyncBroadcaster contract.
Encoding protocol-level constants in this way reduces code clarity and obscures the intended semantics of the L2Log structure. In particular, sourcing an effectively constant value from user input may be misleading and increases the risk of confusion or incorrect usage, even if the current execution path constrains the input.

This PR adds the L1_MESSENGER as a constant but keeps the broadcaster as an user input. This is done like this because although the prover will mostly be used to verify messages sent by the broadcaster, it is intended to be more open and the broadcaster logic is used at the receiver level, not the prover level.

Summary by CodeRabbit

  • New Features

    • Added broadcaster address support to the ZkSync prover contract.
  • Changes

    • Message payload structure updated (sender removed; broadcaster used for key derivation).
    • Prover constructor signature extended to accept a broadcaster parameter.
    • Updated verification snapshot value to reflect new expected output.
  • Tests

    • All relevant tests updated to pass and validate the new broadcaster parameter and message format.

@coderabbitai
Copy link

coderabbitai bot commented Feb 18, 2026

📝 Walkthrough

Walkthrough

The changes update a ZkSync message verification snapshot value and modify the ParentToChildProver contract to use a configurable L1 messenger address instead of a hardcoded one, converting the internal function from pure to view to access the new constant.

Changes

Cohort / File(s) Summary
Snapshot Update
snapshots/verifyBroadcastMessage.json
Updated "ZkSyncL2ToEthereum" value from "125479" to "125528" in verification snapshot.
ParentToChildProver Contract
src/contracts/provers/zksync/ParentToChildProver.sol
Added public constant L1_MESSENGER for configurable address. Changed _l2MessageToLog from internal pure to internal view to access the constant. Updated log sender from fixed ZK address to L1_MESSENGER.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

Suggested reviewers

  • frangio

Poem

🐰 A messenger constant takes the stage,
Where pure became view on this page,
No longer hardcoded, now configurable and bright,
The snapshot updates to keep tests in sight! ✨

🚥 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 '[Provers N03] Implicit constants are hardcoded' is directly related to the main change in the PR, which adds the L1_MESSENGER constant to the ParentToChildProver contract and refactors how protocol-level constants are represented.
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-N03

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.

Caution

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

⚠️ Outside diff range comments (4)
src/contracts/provers/zksync/ParentToChildProver.sol (4)

266-282: ⚠️ Potential issue | 🟡 Minor

Stale @dev on _l2MessageToLog — key is now broadcaster, not the message sender.

The comment "The message sender is encoded as the key" is no longer accurate; the key is now fixed to broadcaster.

📝 Proposed fix
 /// `@dev` Transforms an L2Message into the L2Log format used for Merkle tree hashing.
 ///      Uses fixed values for shard ID (0), service flag (true), and sender address
-///      (the ZkSync system contract address). The message sender is encoded as the key,
-///      and the message data hash is used as the value.
+///      (the L1Messenger system contract). The broadcaster address is encoded as the key,
+///      and the message data hash is used as the value.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/contracts/provers/zksync/ParentToChildProver.sol` around lines 266 - 282,
The NatSpec `@dev` for _l2MessageToLog is stale because the key is no longer the
message sender but the fixed broadcaster; update the comment to reflect that the
key field is bytes32(uint256(uint160(broadcaster))) and not derived from
_message.sender, clarifying that sender remains L1_MESSENGER and value is
keccak256(_message.data); edit the `@dev` block in the _l2MessageToLog function to
describe these exact mappings (fixed shard ID 0, isService true, sender
L1_MESSENGER, key = broadcaster, value = hash of _message.data).

170-175: ⚠️ Potential issue | 🟡 Minor

Stale @param input description still mentions sender as an L2Message field.

Line 174 says the message "contains txNumberInBatch, sender, and data" but sender is no longer a field of L2Message.

📝 Proposed fix
-///              - message: The L2 message to be verified (contains txNumberInBatch, sender, and data).
+///              - message: The L2 message to be verified (contains txNumberInBatch and data).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/contracts/provers/zksync/ParentToChildProver.sol` around lines 170 - 175,
The NatSpec for the `input` parameter is stale: it says the `L2Message` contains
"txNumberInBatch, sender, and data" but `L2Message` no longer has `sender`.
Update the `@param input` description in ParentToChildProver.sol to reflect the
actual fields of the `L2Message` struct (refer to the `L2Message` definition) —
remove the `sender` mention and list the current fields (e.g., `txNumberInBatch`
and `data` or whatever `L2Message` currently contains) so the comment matches
the code.

102-116: ⚠️ Potential issue | 🟠 Major

Missing zero-address guard for _broadcaster.

If address(0) is passed, broadcaster is zero, key becomes bytes32(0), and every call to verifyStorageSlot will revert with BatchSettlementRootMismatch — no descriptive error is surfaced. A deployed prover would be permanently broken with no on-chain indication of the root cause.

🛡️ Proposed fix
     constructor(
         address _gatewayZkChain,
         uint256 _l2LogsRootHashSlot,
         uint256 _childChainId,
         uint256 _gatewayChainId,
         uint256 _homeChainId,
         address _broadcaster
     ) {
+        require(_broadcaster != address(0), "broadcaster cannot be zero");
         gatewayZkChain = IZkChain(_gatewayZkChain);
         l2LogsRootHashSlot = _l2LogsRootHashSlot;
         childChainId = _childChainId;
         gatewayChainId = _gatewayChainId;
         homeChainId = _homeChainId;
         broadcaster = _broadcaster;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/contracts/provers/zksync/ParentToChildProver.sol` around lines 102 - 116,
The constructor leaves broadcaster writable to the zero address which causes key
to be bytes32(0) and makes verifyStorageSlot revert with
BatchSettlementRootMismatch; add a zero-address guard in the constructor by
requiring _broadcaster != address(0) and revert with a clear message (e.g.,
"broadcaster cannot be zero") before assigning broadcaster so deployments cannot
be initialized in a broken state (update the ParentToChildProver constructor
that sets broadcaster and related state).

33-41: ⚠️ Potential issue | 🟡 Minor

Stale @param sender in L2Message NatSpec.

The sender field was removed from the struct but the @param sender doc comment remains.

📝 Proposed fix
 /// `@notice` An arbitrary length message passed from L2 to L1.
 /// `@dev` Under the hood it is an `L2Log` sent from the special system L2 contract.
 /// `@param` txNumberInBatch The L2 transaction number in a batch, in which the message was sent.
-/// `@param` sender The address of the L2 account from which the message was passed.
 /// `@param` data An arbitrary length message data.
 struct L2Message {
     uint16 txNumberInBatch;
     bytes data;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/contracts/provers/zksync/ParentToChildProver.sol` around lines 33 - 41,
The NatSpec for the L2Message struct is stale: it still documents a removed
`sender` field. Update the comment block above the `struct L2Message` to remove
the `@param sender` line and, if needed, adjust the remaining `@param` tags to
match the current struct fields (`txNumberInBatch` and `data`) so the
documentation accurately reflects the `L2Message` definition.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/contracts/provers/zksync/ParentToChildProver.sol`:
- Around line 266-282: The NatSpec `@dev` for _l2MessageToLog is stale because the
key is no longer the message sender but the fixed broadcaster; update the
comment to reflect that the key field is bytes32(uint256(uint160(broadcaster)))
and not derived from _message.sender, clarifying that sender remains
L1_MESSENGER and value is keccak256(_message.data); edit the `@dev` block in the
_l2MessageToLog function to describe these exact mappings (fixed shard ID 0,
isService true, sender L1_MESSENGER, key = broadcaster, value = hash of
_message.data).
- Around line 170-175: The NatSpec for the `input` parameter is stale: it says
the `L2Message` contains "txNumberInBatch, sender, and data" but `L2Message` no
longer has `sender`. Update the `@param input` description in
ParentToChildProver.sol to reflect the actual fields of the `L2Message` struct
(refer to the `L2Message` definition) — remove the `sender` mention and list the
current fields (e.g., `txNumberInBatch` and `data` or whatever `L2Message`
currently contains) so the comment matches the code.
- Around line 102-116: The constructor leaves broadcaster writable to the zero
address which causes key to be bytes32(0) and makes verifyStorageSlot revert
with BatchSettlementRootMismatch; add a zero-address guard in the constructor by
requiring _broadcaster != address(0) and revert with a clear message (e.g.,
"broadcaster cannot be zero") before assigning broadcaster so deployments cannot
be initialized in a broken state (update the ParentToChildProver constructor
that sets broadcaster and related state).
- Around line 33-41: The NatSpec for the L2Message struct is stale: it still
documents a removed `sender` field. Update the comment block above the `struct
L2Message` to remove the `@param sender` line and, if needed, adjust the
remaining `@param` tags to match the current struct fields (`txNumberInBatch`
and `data`) so the documentation accurately reflects the `L2Message` definition.

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

🤖 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/zksync/ParentToChildProver.sol`:
- Around line 106-114: The contract assigns broadcaster = _broadcaster in
ParentToChildProver without guarding against address(0), which lets
_l2MessageToLog produce bytes32(0) keys and break authenticity; add a
zero-address check in the constructor (or initializer) that sets broadcaster —
e.g., require(_broadcaster != address(0)) or revert with your project's custom
error (e.g., ZeroAddressBroadcaster()) — so broadcaster, _broadcaster and the
ParentToChildProver constructor/initializer reject address(0) inputs before
assignment.

Comment on lines 106 to 114
uint256 _homeChainId,
address _broadcaster
) {
gatewayZkChain = IZkChain(_gatewayZkChain);
l2LogsRootHashSlot = _l2LogsRootHashSlot;
childChainId = _childChainId;
gatewayChainId = _gatewayChainId;
homeChainId = _homeChainId;
broadcaster = _broadcaster;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Missing zero-address guard for _broadcaster.

If _broadcaster is address(0) — whether by mistake or misconfiguration — _l2MessageToLog will produce key = bytes32(0) for every log. Any message whose L2 log happens to carry a zero key would pass the Merkle inclusion check without a valid broadcaster, silently breaking message authenticity guarantees.

🛡️ Proposed fix
 constructor(
     address _gatewayZkChain,
     uint256 _l2LogsRootHashSlot,
     uint256 _childChainId,
     uint256 _gatewayChainId,
     uint256 _homeChainId,
     address _broadcaster
 ) {
+    require(_broadcaster != address(0), "broadcaster cannot be zero address");
     gatewayZkChain = IZkChain(_gatewayZkChain);
     l2LogsRootHashSlot = _l2LogsRootHashSlot;
     childChainId = _childChainId;
     gatewayChainId = _gatewayChainId;
     homeChainId = _homeChainId;
     broadcaster = _broadcaster;
 }

Or, if the project already uses a custom error pattern:

+    /// `@notice` Error thrown when a required address argument is the zero address.
+    error ZeroAddress();

     constructor(...) {
+        if (_broadcaster == address(0)) revert ZeroAddress();
         ...
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/contracts/provers/zksync/ParentToChildProver.sol` around lines 106 - 114,
The contract assigns broadcaster = _broadcaster in ParentToChildProver without
guarding against address(0), which lets _l2MessageToLog produce bytes32(0) keys
and break authenticity; add a zero-address check in the constructor (or
initializer) that sets broadcaster — e.g., require(_broadcaster != address(0))
or revert with your project's custom error (e.g., ZeroAddressBroadcaster()) — so
broadcaster, _broadcaster and the ParentToChildProver constructor/initializer
reject address(0) inputs before assignment.

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.

🧹 Nitpick comments (1)
src/contracts/provers/zksync/ParentToChildProver.sol (1)

268-268: Change internal view to internal pure.

_l2MessageToLog only reads the constant state variable L1_MESSENGER (line 65), which is inlined at compile-time. In Solidity 0.8, reading constant variables does not violate the pure mutability constraint, since constants are not stored in contract state. The function should remain pure to accurately reflect that it performs no state reads.

♻️ Proposed fix
-    function _l2MessageToLog(L2Message memory _message) internal view returns (L2Log memory) {
+    function _l2MessageToLog(L2Message memory _message) internal pure returns (L2Log memory) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/contracts/provers/zksync/ParentToChildProver.sol` at line 268, The
function _l2MessageToLog currently declared as "internal view" should be changed
to "internal pure" because it only uses the constant L1_MESSENGER (a
compile-time constant) and performs no state reads; update the function
signature from internal view to internal pure (ensure _l2MessageToLog and any
helpers it calls do not access storage or non-constant state) so the mutability
accurately reflects it is a pure function.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/contracts/provers/zksync/ParentToChildProver.sol`:
- Line 268: The function _l2MessageToLog currently declared as "internal view"
should be changed to "internal pure" because it only uses the constant
L1_MESSENGER (a compile-time constant) and performs no state reads; update the
function signature from internal view to internal pure (ensure _l2MessageToLog
and any helpers it calls do not access storage or non-constant state) so the
mutability accurately reflects it is a pure function.

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