Skip to content

🔨 contracts: support hyperlane on redeployer#860

Draft
itofarina wants to merge 6 commits intomainfrom
hyperlane
Draft

🔨 contracts: support hyperlane on redeployer#860
itofarina wants to merge 6 commits intomainfrom
hyperlane

Conversation

@itofarina
Copy link
Member

@itofarina itofarina commented Mar 3, 2026

Summary by CodeRabbit

  • New Features

    • Deployable EXA routers enabling cross-domain transfers across OP, BASE, and POLYGON.
  • Tests

    • Added multi-domain integration tests (round-trip transfers, access-control revert cases).
    • Updated gas snapshot with numerous test gas adjustments and tightened a deployment expectation to zero supply.
  • Chores

    • Added mailbox network addresses to deployment config.
    • Updated dev dependencies, import remappings, and build manifest.
  • Style/Docs

    • Minor dictionary and changelog additions.

@changeset-bot
Copy link

changeset-bot bot commented Mar 3, 2026

🦋 Changeset detected

Latest commit: a778f0d

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

This PR includes changesets to release 0 packages

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

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

coderabbitai bot commented Mar 3, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds Hyperlane integration and mailbox addresses, new Redeployer router/EXA deploy/upgrade functions, a multi-domain HypEXA test suite, dependency and remapping updates, a gas-snapshot refresh, and minor test expectation adjustment; no public API signatures removed.

Changes

Cohort / File(s) Summary
Dependencies & Config
contracts/package.json, contracts/remappings.txt, package.json, cspell.json
Pinned @exactly/protocol to a commit ref, added @hyperlane-xyz/core, added remappings for Hyperlane/Exactly, switched neverBuiltDependenciesonlyBuiltDependencies, and added spellcheck words.
Deployment Metadata
contracts/deploy.json
Added mailbox addresses under accounts.swapper for networks 10, 137, and 8453.
Redeployer / Scripts
contracts/script/Redeployer.s.sol
Added Hyperlane token/router imports and new public functions: deployEXAImpl(), upgradeEXA(address), deployRouter(address), setupRouter(address,uint32); adjusted broadcast/admin flow and added second initialize call.
Tests — New
contracts/test/HypEXA.t.sol
New HypEXATest implementing multi-domain EXA transfer flows and access-control revert tests across OP, BASE, and POLYGON using Redeployer helpers.
Tests — Updated
contracts/test/Redeployer.t.sol
Changed expectation: base token totalSupply == 0 after EXA deploy (removed prior minted-supply/assertions).
Gas Snapshots
contracts/.gas-snapshot
Bulk updates: refreshed gas measurements and added/renamed test entries reflecting updated gas costs and new tests.
Changelog / Metadata
.changeset/beige-sails-worry.md
Added new changeset file.

Sequence Diagram

sequenceDiagram
    participant OP as OP Chain
    participant OPRouter as OP Router
    participant Hyperlane as Hyperlane Mailbox
    participant BaseRouter as BASE Router
    participant BASE as BASE Chain
    participant PolyRouter as POLYGON Router

    OP->>OPRouter: transferRemote(amount, remoteDomain, gas)
    OPRouter->>Hyperlane: sendMessage(payload)
    Hyperlane->>BaseRouter: deliverMessage(payload)
    BaseRouter->>BASE: handle -> mint/burn
    BaseRouter->>Hyperlane: optionally send response
    Hyperlane->>OPRouter: deliverAck/response
    OPRouter->>OP: finalize (update balances/supply)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • cruzdanilo
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'support hyperlane on redeployer' directly reflects the main changes: adding Hyperlane integration to the Redeployer contract with new deployment/setup functions, router management, and comprehensive cross-chain transfer tests.

✏️ 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 hyperlane

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.

@gemini-code-assist
Copy link

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the project's interoperability by integrating Hyperlane, a cross-chain communication protocol. The changes enable the EXA token to be bridged and managed across different blockchain domains, expanding its utility and reach. This involved updating core dependencies, modifying deployment processes to include Hyperlane-specific components, and adding comprehensive tests to ensure the robustness of the new cross-chain capabilities.

Highlights

  • Hyperlane Integration: Integrated Hyperlane for cross-chain functionality, enabling the EXA token to be transferred and managed across multiple blockchain networks.
  • Deployment and Configuration Updates: Introduced new deployment scripts and configuration settings to facilitate the deployment and setup of Hyperlane routers and an Hyperlane-compatible EXA token.
  • New Cross-Chain Test Suite: Added a dedicated test suite to verify the end-to-end functionality of cross-chain EXA token transfers via Hyperlane, including round-trip scenarios and access control.
  • Dependency Management: Updated project dependencies to include the Hyperlane core library and adjusted existing dependency configurations for compatibility.
Changelog
  • contracts/.gas-snapshot
    • Updated gas consumption benchmarks for various ExaPluginTest and RedeployerTest functions.
  • contracts/deploy.json
    • Added Hyperlane mailbox addresses for Optimism (10), Polygon (137), and Base (8453) domains.
  • contracts/package.json
    • Updated the @exactly/protocol dependency to a specific commit hash.
    • Added the @hyperlane-xyz/core dependency for Hyperlane integration.
  • contracts/remappings.txt
    • Added a remapping for @exactly/protocol to @openzeppelin/contracts.
    • Configured a remapping for @hyperlane-xyz to its node_modules path.
  • contracts/script/Redeployer.s.sol
    • Imported HypERC20Collateral and HypXERC20 from the Hyperlane core library.
    • Modified deployEXA to include an initialize2 call for the EXA token.
    • Added deployEXAImpl to deploy the latest EXA implementation.
    • Implemented upgradeEXA to upgrade an existing EXA proxy.
    • Introduced deployRouter to deploy a HypXERC20 router for cross-chain transfers.
    • Added setupRouter to configure the Hyperlane router with remote domains and grant necessary roles.
  • contracts/test/HypEXA.t.sol
    • Added a new test file for HypEXA to validate cross-chain functionality.
    • Included tests for test_roundTrip_opToBaseToOp and test_roundTrip_opToPolygonToBaseToOp to verify token bridging.
    • Added tests to ensure transferRemote and handle revert without the BRIDGE_ROLE.
  • contracts/test/Redeployer.t.sol
    • Adjusted the assertion for totalSupply in test_deployEXA_deploysAtSameAddress_onBase to expect zero initial supply.
  • cspell.json
    • Added 'hyperlane' and 'XERC' to the spellcheck dictionary.
  • package.json
    • Changed neverBuiltDependencies to onlyBuiltDependencies for @exactly/protocol.
Activity
  • No specific activity (comments, reviews, or progress updates) has been recorded for this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces Hyperlane integration for cross-chain EXA token transfers, adding new deployment scripts and tests. The changes are significant and lay the groundwork for bridging the EXA token.

My review identified a critical issue in the router deployment script where the mailbox address is incorrectly initialized to the zero address, which would break all cross-chain functionality. I also found a high-severity issue in the router setup logic that assumes router addresses are the same across all chains, which may not hold true in production and could lead to configuration errors. Additionally, I've pointed out a minor inefficiency in the deployEXA script function. Addressing these points will improve the robustness and correctness of the Hyperlane integration.

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: 5


ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 41d2a4c and adceeef.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (9)
  • contracts/.gas-snapshot
  • contracts/deploy.json
  • contracts/package.json
  • contracts/remappings.txt
  • contracts/script/Redeployer.s.sol
  • contracts/test/HypEXA.t.sol
  • contracts/test/Redeployer.t.sol
  • cspell.json
  • package.json

@cruzdanilo cruzdanilo changed the title Hyperlane 🔨 contracts: support hyperlane on redeployer Mar 3, 2026
@sentry
Copy link

sentry bot commented Mar 3, 2026

✅ All tests passed.

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 (3)
contracts/script/Redeployer.s.sol (3)

96-149: ⚠️ Potential issue | 🟠 Major

Run forge fmt on this file to unblock CI.

The pipeline is currently failing nx run @exactly/plugin:test:fmt due to formatting differences in this file.

As per coding guidelines **/*.sol: Follow Solhint rules strictly and use Forge fmt for code formatting.


113-121: ⚠️ Potential issue | 🟠 Major

Fail fast when EXA implementation is missing before upgradeEXA.

Line 120 upgrades to address(exa) without checking code presence, which defers failure to a less actionable downstream revert.

Proposed fix
 function upgradeEXA(address proxy) external {
   address admin = acct("admin");
+  if (address(exa).code.length == 0) revert EXAImplementationNotDeployed();
   ProxyAdmin p = ProxyAdmin(address(uint160(uint256(
       vm.load(proxy, bytes32(uint256(keccak256("eip1967.proxy.admin")) - 1))
   ))));
   vm.broadcast(p.owner());
   p.upgradeAndCall(
     ITransparentUpgradeableProxy(proxy), address(exa), abi.encodeCall(EXA.initialize2, (admin))
   );
 }
@@
 error ProxyAdminNotDeployed();
 error TargetNonceTooLow();
+error EXAImplementationNotDeployed();

124-130: 🛠️ Refactor suggestion | 🟠 Major

Align CREATE3 salt derivation with token (or remove token from the API).

Line 129 hardcodes "HypEXA" even though the function accepts a token; that creates deterministic-slot collisions for multi-token use, and Line 145 resolves that same fixed slot.

Proposed refactor
 function deployRouter(address token) external returns (HypXERC20 router) {
@@
-        keccak256(abi.encode("HypEXA")),
+        keccak256(abi.encode("HypEXA", token)),
@@
 function setupRouter(address token, uint32 remoteDomain) external {
   address admin = acct("admin");
-  address router = CREATE3_FACTORY.getDeployed(admin, keccak256(abi.encode("HypEXA")));
+  address router = CREATE3_FACTORY.getDeployed(admin, keccak256(abi.encode("HypEXA", token)));

Also applies to: 145-145


ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between adceeef and 7727570.

📒 Files selected for processing (5)
  • .changeset/beige-sails-worry.md
  • contracts/.gas-snapshot
  • contracts/deploy.json
  • contracts/script/Redeployer.s.sol
  • contracts/test/HypEXA.t.sol

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 (3)
contracts/script/Redeployer.s.sol (3)

121-127: 🛠️ Refactor suggestion | 🟠 Major

deployRouter ignores token in the deterministic salt, making reuse collision-prone.

The function accepts token but always uses the fixed "HypEXA" slot. Reusing it for another token collides on the same CREATE3 address.

Proposed refactor
-        keccak256(abi.encode("HypEXA")),
+        keccak256(abi.encode("HypEXA", token)),
@@
-    address router = CREATE3_FACTORY.getDeployed(admin, keccak256(abi.encode("HypEXA")));
+    address router = CREATE3_FACTORY.getDeployed(admin, keccak256(abi.encode("HypEXA", token)));

Also applies to: 142-142


140-146: ⚠️ Potential issue | 🟠 Major

setupRouter should fail fast if the deterministic router address is not deployed.

getDeployed can resolve an address before code exists. Without a code-length guard, role/config steps can silently target an undeployed address path.

Proposed fix
 function setupRouter(address token, uint32 remoteDomain) external {
   address admin = acct("admin");
   address router = CREATE3_FACTORY.getDeployed(admin, keccak256(abi.encode("HypEXA")));
+  if (router.code.length == 0) revert RouterNotDeployed();
   vm.startBroadcast(admin);
   EXA(token).grantRole(keccak256("BRIDGE_ROLE"), router);
   HypXERC20(router).enrollRemoteRouter(remoteDomain, bytes32(uint256(uint160(router))));
   vm.stopBroadcast();
 }
@@
 error TargetNonceTooLow();
+error RouterNotDeployed();

113-119: ⚠️ Potential issue | 🟠 Major

upgradeEXA should guard against missing EXA implementation deployment.

The upgrade path uses address(exa) directly; adding an explicit code-length guard gives a clearer, earlier failure mode.

Proposed fix
 function upgradeEXA(address proxy) external {
   address admin = acct("admin");
+  if (address(exa).code.length == 0) revert EXAImplementationNotDeployed();
   ProxyAdmin p =
     ProxyAdmin(address(uint160(uint256(vm.load(proxy, bytes32(uint256(keccak256("eip1967.proxy.admin")) - 1))))));
   vm.broadcast(p.owner());
   p.upgradeAndCall(ITransparentUpgradeableProxy(proxy), address(exa), abi.encodeCall(EXA.initialize2, (admin)));
 }
@@
 error TargetNonceTooLow();
+error EXAImplementationNotDeployed();

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7727570 and c7187e5.

📒 Files selected for processing (5)
  • .changeset/beige-sails-worry.md
  • contracts/.gas-snapshot
  • contracts/deploy.json
  • contracts/script/Redeployer.s.sol
  • contracts/test/HypEXA.t.sol

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 (1)
contracts/script/Redeployer.s.sol (1)

140-146: ⚠️ Potential issue | 🟠 Major

Guard setupRouter against undeployed router code.

getDeployed returns the deterministic address even before deployment. Without a code-length check, role grant can succeed while enrollRemoteRouter targets an address with no code, leaving router config inconsistent.

Proposed fix
 function setupRouter(address token, uint32 remoteDomain) external {
   address admin = acct("admin");
   address router = CREATE3_FACTORY.getDeployed(admin, keccak256(abi.encode("HypEXA")));
+  if (router.code.length == 0) revert RouterNotDeployed();
   vm.startBroadcast(admin);
   EXA(token).grantRole(keccak256("BRIDGE_ROLE"), router);
   HypXERC20(router).enrollRemoteRouter(remoteDomain, bytes32(uint256(uint160(router))));
   vm.stopBroadcast();
 }
@@
 error ProxyAdminNotDeployed();
 error TargetNonceTooLow();
+error RouterNotDeployed();

Run this read-only check to confirm the missing guard in the current implementation:

#!/bin/bash
set -euo pipefail
file="$(fd -p 'Redeployer.s.sol$' | head -n1)"
nl -ba "$file" | sed -n '140,147p'

Expected result: CREATE3_FACTORY.getDeployed(...) is followed by vm.startBroadcast(admin) with no router.code.length validation.


ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c7187e5 and b757239.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (9)
  • .changeset/beige-sails-worry.md
  • contracts/.gas-snapshot
  • contracts/deploy.json
  • contracts/package.json
  • contracts/remappings.txt
  • contracts/script/Redeployer.s.sol
  • contracts/test/HypEXA.t.sol
  • contracts/test/Redeployer.t.sol
  • cspell.json

Comment on lines +219 to +226
RedeployerTest:test_deployEXA_deploysAtSameAddress_onBase() (gas: 59986446)
RedeployerTest:test_deployExaFactory_deploysAtSameAddress_onEthereum() (gas: 276604537)
RedeployerTest:test_deployExaFactory_deploysAtSameAddress_onPolygon() (gas: 371278480)
RedeployerTest:test_deployExaFactory_deploysViaCreate3AtSameAddress_onPolygon() (gas: 48248553)
RedeployerTest:test_prepare_reverts_whenAdminIsDeployer() (gas: 32034206)
RedeployerTest:test_recoversNativeETHOnPolygon() (gas: 48419750)
RedeployerTest:test_run_reverts_whenAttackerUpgradesProxy() (gas: 42868965)
RedeployerTest:test_run_reverts_whenTargetNonceTooLow() (gas: 32499108)
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Add gas-budget guardrails for Redeployer critical paths.

Given the very high redeployer test gas figures, consider adding CI budget thresholds (or trend alerts) for these specific tests to catch future unintended growth early.

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


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3588c324-e849-4194-bc1e-e94abe72e9b5

📥 Commits

Reviewing files that changed from the base of the PR and between b757239 and a778f0d.

📒 Files selected for processing (5)
  • .changeset/beige-sails-worry.md
  • contracts/.gas-snapshot
  • contracts/deploy.json
  • contracts/script/Redeployer.s.sol
  • contracts/test/HypEXA.t.sol

Comment on lines +42 to +45
"mailbox": {
"10": "0xd4C1905BB1D26BC93DAC913e13CaCC278CdCC80D",
"137": "0x5d934f4e2f797775e53561bB72aca21ba36B96BB",
"8453": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D"
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider adding a mailbox default (or explicit unsupported-chain guard).

The acct("mailbox") path falls back to .accounts.mailbox.default when a chain key is absent; without it, unsupported chains fail with a generic JSON key error instead of a clearer deployment-time failure.

Comment on lines +121 to +133
function deployRouter(address token) external returns (HypXERC20 router) {
address admin = acct("admin");
vm.startBroadcast(admin);
router = HypXERC20(
CREATE3_FACTORY.deploy(
keccak256(abi.encode("HypEXA")),
abi.encodePacked(
type(TransparentUpgradeableProxy).creationCode,
abi.encode(
address(new HypXERC20(token, 1, acct("mailbox"))),
address(proxyAdmin),
abi.encodeCall(HypERC20Collateral.initialize, (address(0), address(0), admin))
)
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Where proxyAdmin deployment is currently validated:"
rg -nP 'address\(proxyAdmin\)\.code\.length' contracts/script/Redeployer.s.sol

echo
echo "run() guard context:"
sed -n '71,75p' contracts/script/Redeployer.s.sol

echo
echo "deployRouter() context:"
sed -n '121,138p' contracts/script/Redeployer.s.sol

Repository: exactly/exa

Length of output: 1088


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Full deployRouter() function signature:"
sed -n '121,121p' contracts/script/Redeployer.s.sol

echo
echo "Check for other callers of deployRouter in the file:"
rg -n 'deployRouter' contracts/script/Redeployer.s.sol

echo
echo "Check if proxyAdmin is set before deployRouter calls:"
sed -n '70,85p' contracts/script/Redeployer.s.sol

Repository: exactly/exa

Length of output: 1056


Add proxyAdmin code-length guard to deployRouter() to prevent proxy deployment with undeployed admin.

The run() function validates address(proxyAdmin).code.length at line 73, but deployRouter() is a separate external entry point that uses address(proxyAdmin) at line 131 without the same check. Since deployRouter() can be called independently, the proxy admin validation must be present here as well.

🛠️ Proposed fix
 function deployRouter(address token) external returns (HypXERC20 router) {
   address admin = acct("admin");
+  if (address(proxyAdmin).code.length == 0) revert ProxyAdminNotDeployed();
   vm.startBroadcast(admin);
   router = HypXERC20(
     CREATE3_FACTORY.deploy(
       keccak256(abi.encode("HypEXA")),

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