Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/ccip-warp-route' into pb/default…
Browse files Browse the repository at this point in the history
…hook-sdk
  • Loading branch information
paulbalaji committed Feb 11, 2025
2 parents 5a7dba0 + 9a010df commit 0cc7e03
Show file tree
Hide file tree
Showing 18 changed files with 1,829 additions and 33 deletions.
5 changes: 5 additions & 0 deletions .changeset/breezy-meals-buy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@hyperlane-xyz/core': minor
---

Implement warp route amount routing ISM
5 changes: 5 additions & 0 deletions .changeset/mean-cherries-reflect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@hyperlane-xyz/core': minor
---

Implement CCIP hook and ISM
5 changes: 5 additions & 0 deletions .changeset/proud-clocks-kneel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@hyperlane-xyz/core': minor
---

Implement warp amount routing hook
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: test
on:
# Triggers the workflow on pushes to main branch, ignoring md files
push:
branches: [main]
branches: [main, ccip-warp-route]
# Triggers on pull requests, ignoring md files
pull_request:
branches:
Expand Down
2 changes: 1 addition & 1 deletion solidity/contracts/client/GasRouter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ abstract contract GasRouter is Router {
*/
function quoteGasPayment(
uint32 _destinationDomain
) external view returns (uint256) {
) external view virtual returns (uint256) {
return _GasRouter_quoteDispatch(_destinationDomain, "", address(hook));
}

Expand Down
83 changes: 83 additions & 0 deletions solidity/contracts/hooks/CCIPHook.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.0;

/*@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@ HYPERLANE @@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@*/

// ============ Internal Imports ============
import {AbstractMessageIdAuthHook} from "./libs/AbstractMessageIdAuthHook.sol";
import {Message} from "../libs/Message.sol";
import {TypeCasts} from "../libs/TypeCasts.sol";

// ============ External Imports ============
import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol";
import {Client} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol";
import {Address} from "@openzeppelin/contracts/utils/Address.sol";

/**
* @title CCIPHook
* @notice Message hook to inform the CCIP of messages published through CCIP.
*/
contract CCIPHook is AbstractMessageIdAuthHook {
using Message for bytes;
using TypeCasts for bytes32;

IRouterClient internal immutable ccipRouter;
uint64 public immutable ccipDestination;

// ============ Constructor ============

constructor(
address _ccipRouter,
uint64 _ccipDestination,
address _mailbox,
uint32 _destination,
bytes32 _ism
) AbstractMessageIdAuthHook(_mailbox, _destination, _ism) {
ccipDestination = _ccipDestination;
ccipRouter = IRouterClient(_ccipRouter);
}

// ============ Internal functions ============

function _buildCCIPMessage(
bytes calldata message
) internal view returns (Client.EVM2AnyMessage memory) {
// Create an EVM2AnyMessage struct in memory with necessary information for sending a cross-chain message
return
Client.EVM2AnyMessage({
receiver: abi.encode(ism),
data: abi.encode(message.id()),
tokenAmounts: new Client.EVMTokenAmount[](0),
extraArgs: "",
feeToken: address(0)
});
}

function _quoteDispatch(
bytes calldata /*metadata*/,
bytes calldata message
) internal view override returns (uint256) {
Client.EVM2AnyMessage memory ccipMessage = _buildCCIPMessage(message);

return ccipRouter.getFee(ccipDestination, ccipMessage);
}

function _sendMessageId(
bytes calldata /*metadata*/,
bytes calldata message
) internal override {
Client.EVM2AnyMessage memory ccipMessage = _buildCCIPMessage(message);

ccipRouter.ccipSend{value: msg.value}(ccipDestination, ccipMessage);
}
}
53 changes: 53 additions & 0 deletions solidity/contracts/hooks/routing/AmountRoutingHook.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.0;

// ============ External Imports ============
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {Address} from "@openzeppelin/contracts/utils/Address.sol";

// ============ Internal Imports ============
import {AbstractPostDispatchHook} from "../libs/AbstractPostDispatchHook.sol";
import {IPostDispatchHook} from "../../interfaces/hooks/IPostDispatchHook.sol";
import {AmountPartition} from "../../token/libs/AmountPartition.sol";
import {IInterchainSecurityModule} from "../../interfaces/IInterchainSecurityModule.sol";
import {Message} from "../../libs/Message.sol";
import {PackageVersioned} from "../../PackageVersioned.sol";
import {TokenMessage} from "../../token/libs/TokenMessage.sol";

/**
* @title AmountRoutingHook
*/
contract AmountRoutingHook is AmountPartition, AbstractPostDispatchHook {
constructor(
address _lowerHook,
address _upperHook,
uint256 _threshold
) AmountPartition(_lowerHook, _upperHook, _threshold) {}

function hookType() external pure override returns (uint8) {
return uint8(IPostDispatchHook.Types.AMOUNT_ROUTING);
}

function _postDispatch(
bytes calldata _metadata,
bytes calldata _message
) internal override {
uint256 quote = _quoteDispatch(_metadata, _message);
IPostDispatchHook(_partition(_message)).postDispatch{value: quote}(
_metadata,
_message
);
return _refund(_metadata, _message, msg.value - quote);
}

function _quoteDispatch(
bytes calldata _metadata,
bytes calldata _message
) internal view override returns (uint256) {
return
IPostDispatchHook(_partition(_message)).quoteDispatch(
_metadata,
_message
);
}
}
3 changes: 2 additions & 1 deletion solidity/contracts/interfaces/hooks/IPostDispatchHook.sol
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ interface IPostDispatchHook {
RATE_LIMITED,
ARB_L2_TO_L1,
OP_L2_TO_L1,
MAILBOX_DEFAULT_HOOK
MAILBOX_DEFAULT_HOOK,
AMOUNT_ROUTING
}

/**
Expand Down
70 changes: 70 additions & 0 deletions solidity/contracts/isms/hook/CCIPIsm.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.0;

/*@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@ HYPERLANE @@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@*/

// ============ Internal Imports ============

import {IInterchainSecurityModule} from "../../interfaces/IInterchainSecurityModule.sol";
import {Message} from "../../libs/Message.sol";
import {TypeCasts} from "../../libs/TypeCasts.sol";
import {AbstractMessageIdAuthorizedIsm} from "./AbstractMessageIdAuthorizedIsm.sol";

// ============ External Imports ============
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol";
import {Client} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol";
import {CCIPReceiver} from "@chainlink/contracts-ccip/src/v0.8/ccip/applications/CCIPReceiver.sol";

/**
* @title CCIPIsm
* @notice Uses CCIP hook to verify interchain messages.
*/
contract CCIPIsm is AbstractMessageIdAuthorizedIsm, CCIPReceiver {
using TypeCasts for bytes32;

// ============ Constants ============

uint8 public constant moduleType =
uint8(IInterchainSecurityModule.Types.NULL);

uint64 public immutable ccipOrigin;

// ============ Storage ============
constructor(
address _ccipRouter,
uint64 _ccipOrigin
) CCIPReceiver(_ccipRouter) {
ccipOrigin = _ccipOrigin;
}

// ============ Internal functions ============
function _ccipReceive(
Client.Any2EVMMessage memory any2EvmMessage
) internal override {
require(
ccipOrigin == any2EvmMessage.sourceChainSelector,
"Unauthorized origin"
);

bytes32 sender = abi.decode(any2EvmMessage.sender, (bytes32));
require(sender == authorizedHook, "Unauthorized hook");

bytes32 messageId = abi.decode(any2EvmMessage.data, (bytes32));
preVerifyMessage(messageId, msg.value);
}

function _isAuthorized() internal view override returns (bool) {
return msg.sender == getRouter();
}
}
38 changes: 38 additions & 0 deletions solidity/contracts/isms/warp-route/AmountRoutingIsm.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.0;

// ============ External Imports ============
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {Address} from "@openzeppelin/contracts/utils/Address.sol";

// ============ Internal Imports ============
import {AbstractRoutingIsm} from "../routing/AbstractRoutingIsm.sol";
import {AmountPartition} from "../../token/libs/AmountPartition.sol";
import {IInterchainSecurityModule} from "../../interfaces/IInterchainSecurityModule.sol";
import {Message} from "../../libs/Message.sol";
import {PackageVersioned} from "../../PackageVersioned.sol";
import {TokenMessage} from "../../token/libs/TokenMessage.sol";

/**
* @title AmountRoutingIsm
*/
contract AmountRoutingIsm is AmountPartition, AbstractRoutingIsm {
constructor(
address _lowerIsm,
address _upperIsm,
uint256 _threshold
) AmountPartition(_lowerIsm, _upperIsm, _threshold) {}

// ============ Public Functions ============
/**
* @notice Returns the ISM responsible for verifying _message
* @dev Routes to upperISM ISM if amount > threshold, otherwise lowerISM ISM.
* @param _message Formatted Hyperlane message (see Message.sol).
* @return module The ISM to use to verify _message
*/
function route(
bytes calldata _message
) public view override returns (IInterchainSecurityModule) {
return IInterchainSecurityModule(_partition(_message));
}
}
44 changes: 44 additions & 0 deletions solidity/contracts/token/libs/AmountPartition.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.0;

// ============ External Imports ============
import {Address} from "@openzeppelin/contracts/utils/Address.sol";

// ============ Internal Imports ============
import {Message} from "../../libs/Message.sol";
import {PackageVersioned} from "../../PackageVersioned.sol";
import {TokenMessage} from "../../token/libs/TokenMessage.sol";

/**
* @title AmountPartition
*/
abstract contract AmountPartition is PackageVersioned {
using Message for bytes;
using TokenMessage for bytes;
using Address for address;

address public immutable lower;
address public immutable upper;
uint256 public immutable threshold;

constructor(address _lower, address _upper, uint256 _threshold) {
require(
_lower.isContract() && _upper.isContract(),
"AmountPartition: lower and upper must be contracts"
);
lower = _lower;
upper = _upper;
threshold = _threshold;
}

function _partition(
bytes calldata _message
) internal view returns (address) {
uint256 amount = _message.body().amount();
if (amount >= threshold) {
return upper;
} else {
return lower;
}
}
}
16 changes: 16 additions & 0 deletions solidity/contracts/token/libs/TokenRouter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,22 @@ abstract contract TokenRouter is GasRouter {
*/
function balanceOf(address account) external virtual returns (uint256);

/**
* @notice Returns the gas payment required to dispatch a message to the given domain's router.
* @param _destinationDomain The domain of the router.
* @return _gasPayment Payment computed by the registered InterchainGasPaymaster.
*/
function quoteGasPayment(
uint32 _destinationDomain
) external view override returns (uint256) {
return
_GasRouter_quoteDispatch(
_destinationDomain,
TokenMessage.format(bytes32(0), type(uint256).max, bytes("")),
address(hook)
);
}

/**
* @dev Mints tokens to recipient when router receives transfer message.
* @dev Emits `ReceivedTransferRemote` event on the destination chain.
Expand Down
2 changes: 1 addition & 1 deletion solidity/hardhat.config.cts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import 'solidity-coverage';
*/
module.exports = {
solidity: {
version: '0.8.19',
version: '0.8.22',
settings: {
optimizer: {
enabled: true,
Expand Down
1 change: 1 addition & 0 deletions solidity/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"version": "5.11.3",
"dependencies": {
"@arbitrum/nitro-contracts": "^1.2.1",
"@chainlink/contracts-ccip": "^1.5.0",
"@eth-optimism/contracts": "^0.6.0",
"@hyperlane-xyz/utils": "8.6.1",
"@layerzerolabs/lz-evm-oapp-v2": "2.0.2",
Expand Down
1 change: 1 addition & 0 deletions solidity/remappings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
@eth-optimism=../node_modules/@eth-optimism
@layerzerolabs=../node_modules/@layerzerolabs
@openzeppelin=../node_modules/@openzeppelin
@chainlink=../node_modules/@chainlink
ds-test/=lib/forge-std/lib/ds-test/src/
forge-std/=lib/forge-std/src/
fx-portal/=lib/fx-portal/
Loading

0 comments on commit 0cc7e03

Please sign in to comment.