Skip to content

Conversation

@Amxx
Copy link
Collaborator

@Amxx Amxx commented Jan 9, 2026

Fixes #5903

PR Checklist

  • Tests
  • Documentation
  • Changeset entry (run npx changeset add)

@Amxx Amxx requested a review from a team as a code owner January 9, 2026 13:17
@changeset-bot
Copy link

changeset-bot bot commented Jan 9, 2026

🦋 Changeset detected

Latest commit: 415f5d8

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
openzeppelin-solidity Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 9, 2026

Walkthrough

This pull request introduces two new abstract smart contracts for cross-chain communication: CrosschainRemoteController manages cross-chain linkage by storing gateway and counterpart addresses per chain and dispatching cross-chain messages through registered gateways, while CrosschainRemoteExecutor extends ERC7786Recipient to receive and process incoming cross-chain messages by decoding mode-encoded payloads and executing single calls, batch calls, or delegatecalls. A mock contract and comprehensive test suite are also added to support development and validation.

Possibly related PRs

  • PR #5914: Implements ERC-7786 cross-chain link management with similar _setLink and link storage patterns alongside gateway messaging logic
  • PR #5904: Integrates ERC-7786 primitives (ERC7786Recipient, IERC7786GatewaySource) that are directly extended and used by the controller and executor contracts

Suggested labels

crosschain

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main changes by naming both new contracts being introduced.
Description check ✅ Passed The description references issue #5903 and indicates test completion, relating to the pull request changes.
Linked Issues check ✅ Passed The PR introduces CrosschainRemoteExecutor and CrosschainRemoteController contracts that implement ERC7786 RemoteExecutor functionality as required by issue #5903.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the crosschain remote executor and controller contracts with supporting tests and mocks.

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

✨ Finishing touches
  • 📝 Generate docstrings

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

Copy link
Contributor

@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

🤖 Fix all issues with AI agents
In @contracts/crosschain/CrosschainRemoteController.sol:
- Around line 26-33: Add an explicit validation in _crosschainExecute: call
getLink(chain) as before, then require that gateway != address(0) (and
optionally counterpart.length > 0) and revert with a descriptive error such as
LinkNotRegistered(chain) (from CrosschainLinked) before calling
IERC7786GatewaySource(gateway).sendMessage; this prevents calling the zero
address and surfaces a clear error when a chain link is unregistered.
🧹 Nitpick comments (1)
test/crosschain/CrosschainExecutor.test.js (1)

131-150: Consider specifying the expected revert reason for direct setter call.

The crosschain call path correctly expects FailedCall, but the direct call (line 133) uses a bare .to.be.reverted without specifying the expected error. This could mask unexpected revert reasons.

♻️ Suggested improvement
-      await expect(this.executor.$_setup(this.eoa.address, this.chain.toErc7930(this.newController))).to.be.reverted;
+      await expect(this.executor.$_setup(this.eoa.address, this.chain.toErc7930(this.newController)))
+        .to.be.revertedWithoutReason(); // EOA has no code, so supportsAttribute call fails

Or if there's a specific custom error expected, use revertedWithCustomError.

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ad83688 and d3dbb62.

📒 Files selected for processing (4)
  • contracts/crosschain/CrosschainRemoteController.sol
  • contracts/crosschain/CrosschainRemoteExecutor.sol
  • contracts/mocks/crosschain/CrosschainRemoteControllerMock.sol
  • test/crosschain/CrosschainExecutor.test.js
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: Amxx
Repo: OpenZeppelin/openzeppelin-contracts PR: 5914
File: contracts/crosschain/bridges/BridgeERC20.sol:57-58
Timestamp: 2025-10-03T13:14:57.679Z
Learning: In cross-chain bridge contracts like BridgeERC20, when processing incoming messages in _processMessage, avoid validation checks that revert on malformed addresses. Reverting would create cross-chain inconsistency where tokens are locked/burned on the source chain but never minted on the destination. Instead, use best-effort address extraction (e.g., address(bytes20(toBinary))) to maintain atomicity across chains. If tokens are minted to an incorrect address due to user error, recovery may be possible through admin controls rather than leaving funds permanently locked.
📚 Learning: 2025-10-03T13:14:57.679Z
Learnt from: Amxx
Repo: OpenZeppelin/openzeppelin-contracts PR: 5914
File: contracts/crosschain/bridges/BridgeERC20.sol:57-58
Timestamp: 2025-10-03T13:14:57.679Z
Learning: In cross-chain bridge contracts like BridgeERC20, when processing incoming messages in _processMessage, avoid validation checks that revert on malformed addresses. Reverting would create cross-chain inconsistency where tokens are locked/burned on the source chain but never minted on the destination. Instead, use best-effort address extraction (e.g., address(bytes20(toBinary))) to maintain atomicity across chains. If tokens are minted to an incorrect address due to user error, recovery may be possible through admin controls rather than leaving funds permanently locked.

Applied to files:

  • contracts/crosschain/CrosschainRemoteController.sol
  • contracts/crosschain/CrosschainRemoteExecutor.sol
📚 Learning: 2025-08-29T13:16:08.640Z
Learnt from: Amxx
Repo: OpenZeppelin/openzeppelin-contracts PR: 5904
File: contracts/mocks/crosschain/ERC7786RecipientMock.sol:12-14
Timestamp: 2025-08-29T13:16:08.640Z
Learning: In OpenZeppelin contracts, mock contracts (like ERC7786RecipientMock) don't require input validation such as zero-address checks in constructors, as they are only used for testing purposes in controlled environments.

Applied to files:

  • contracts/mocks/crosschain/CrosschainRemoteControllerMock.sol
  • contracts/crosschain/CrosschainRemoteExecutor.sol
📚 Learning: 2025-08-28T16:58:18.879Z
Learnt from: Amxx
Repo: OpenZeppelin/openzeppelin-contracts PR: 5904
File: contracts/mocks/crosschain/ERC7786GatewayMock.sol:27-31
Timestamp: 2025-08-28T16:58:18.879Z
Learning: In the OpenZeppelin ERC7786 implementation, the `UnsupportedAttribute(bytes4 selector)` error is defined in the `IERC7786GatewaySource` interface, making it directly accessible to inheriting contracts without needing interface qualification.

Applied to files:

  • contracts/crosschain/CrosschainRemoteExecutor.sol
🧬 Code graph analysis (1)
test/crosschain/CrosschainExecutor.test.js (2)
test/crosschain/ERC7786Recipient.test.js (1)
  • getLocalChain (15-15)
test/helpers/erc7579.js (6)
  • encodeMode (15-24)
  • encodeSingle (26-27)
  • CALL_TYPE_BATCH (12-12)
  • encodeBatch (29-39)
  • CALL_TYPE_DELEGATE (13-13)
  • encodeDelegate (41-42)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: Redirect rules - solidity-contracts
  • GitHub Check: Header rules - solidity-contracts
  • GitHub Check: Pages changed - solidity-contracts
  • GitHub Check: tests
  • GitHub Check: slither
  • GitHub Check: tests-foundry
  • GitHub Check: halmos
  • GitHub Check: tests-upgradeable
  • GitHub Check: coverage
🔇 Additional comments (13)
contracts/mocks/crosschain/CrosschainRemoteControllerMock.sol (1)

8-10: LGTM!

Simple mock contract that correctly extends CrosschainRemoteController and forwards the constructor parameters. Appropriate for testing purposes. Based on learnings, mock contracts don't require additional input validation.

test/crosschain/CrosschainExecutor.test.js (4)

16-35: LGTM!

The fixture correctly establishes the bidirectional link: the executor knows about the controller (via constructor), and the controller knows about the executor (via $_setLink). Event verification during setup is good practice.


42-52: LGTM!

Good verification of the bidirectional relationship between controller and executor. The use of eventually.deep.equal for the array comparison is appropriate.


54-95: LGTM!

Comprehensive coverage of all three call types (single, batch, delegatecall) plus the invalid mode error path. The delegate mode test correctly expects this.gateway as the caller since delegatecall executes in the executor's context.


152-156: LGTM!

Good verification that reconfigure is restricted to self-calls only.

contracts/crosschain/CrosschainRemoteExecutor.sol (5)

10-18: LGTM!

State variables are appropriately private with public getters. The CrosschainControllerSet event captures both gateway and controller for proper observability.


20-22: LGTM!

Constructor delegates to _setup, ensuring consistent initialization and validation.


32-46: LGTM!

The access control pattern (msg.sender == address(this)) ensures reconfiguration can only happen through the executor itself, typically via a cross-chain call from the authorized controller. The gateway validation via supportsAttribute is an elegant way to verify ERC-7786 compliance while also rejecting EOAs.


48-54: LGTM!

Proper authorization check requiring both gateway instance match and controller sender match. The equal comparison from Bytes library handles the dynamic bytes comparison correctly.


57-74: Verify behavior with short payloads.

If payload is shorter than 32 bytes, the slice payload[0x00:0x20] will revert with an out-of-bounds error. This may be intentional (malformed messages should fail), but consider whether the revert message is clear enough for debugging cross-chain issues.

contracts/crosschain/CrosschainRemoteController.sol (3)

14-18: LGTM!

The mapping key is bytes to support flexible chain identifiers. The using declarations for utility libraries are appropriate.


20-24: LGTM!

Constructor correctly iterates through provided links with allowOverride=false, ensuring no duplicate chain entries are silently overwritten during initialization.


51-64: LGTM!

The _setLink implementation correctly:

  1. Validates the gateway is ERC-7786 compliant
  2. Derives the chain identifier from the counterpart address
  3. Enforces single-registration semantics unless explicitly overriding
  4. Emits the appropriate event

The pattern of deriving the chain key from the counterpart simplifies the API and prevents mismatches.

Comment on lines 26 to 33
function _crosschainExecute(bytes memory chain, Mode mode, bytes memory executionCalldata) internal virtual {
(address gateway, bytes memory counterpart) = getLink(chain);
IERC7786GatewaySource(gateway).sendMessage(
counterpart,
abi.encodePacked(mode, executionCalldata),
new bytes[](0)
);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing validation for unregistered chain link.

If getLink(chain) returns (address(0), "") for an unregistered chain, the call to IERC7786GatewaySource(address(0)).sendMessage(...) will revert, but with a generic error. Consider adding an explicit check with a descriptive error.

🔧 Suggested improvement
 function _crosschainExecute(bytes memory chain, Mode mode, bytes memory executionCalldata) internal virtual {
     (address gateway, bytes memory counterpart) = getLink(chain);
+    require(gateway != address(0), CrosschainLinked.LinkNotRegistered(chain));
     IERC7786GatewaySource(gateway).sendMessage(
         counterpart,
         abi.encodePacked(mode, executionCalldata),
         new bytes[](0)
     );
 }

Note: This assumes a LinkNotRegistered error exists or can be added to CrosschainLinked.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In @contracts/crosschain/CrosschainRemoteController.sol around lines 26 - 33,
Add an explicit validation in _crosschainExecute: call getLink(chain) as before,
then require that gateway != address(0) (and optionally counterpart.length > 0)
and revert with a descriptive error such as LinkNotRegistered(chain) (from
CrosschainLinked) before calling IERC7786GatewaySource(gateway).sendMessage;
this prevents calling the zero address and surfaces a clear error when a chain
link is unregistered.

Copy link
Collaborator Author

@Amxx Amxx left a comment

Choose a reason for hiding this comment

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

Questions:

  • Do we want CrosschainRemoteExecutor to access calls from one vs multiple controllers ?
  • Is the CrosschainRemoteController really usefull, or should be rather have a Library with a function
library CrosschainRemoteExecutorUtils {
  function execute(
    address gateway, 
    bytes memory executor, 
    Mode mode, 
    bytes memory executionCalldata
  ) internal returns (bytes32) {
    return IERC7786GatewaySource(gateway).sendMessage(executor, abi.encodePacked(mode, executionCalldata), new bytes[](0));
  }
}

@Amxx Amxx added this to the 5.7 milestone Jan 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add ERC7786 RemoteExecutor

1 participant