feat: support Arbitrum L2->L1 post dispatch hook (#3853)

### Description

- Contract support for the enabling postDispatch hook with the Arbitrum
nitro bridge from L2 to L1
- asynchronously via executeTransaction call to verifyMessageId first
and then the relayer calling the verify message with no metadata (note:
this supports msg.value)
- synchronously via a single verify call which in turn calls
executeTransaction on outbox to gets the message verified in the
verifyMessageId (note: this doesn't support msg.value as ism.verify
isn't payable)
- Added a script for deploying the hook and ISM since the sdk doesn't
support it yet.

### Drive-by changes

- changing the type from "rate_limited_hook" to "RATE_LIMITED" to
maintain consistency

### Related issues

- fixes https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/2846

### Backward compatibility

Yes

### Testing

Unit and e2e with arbitrumsepolia->sepolia
pull/4093/head
Kunal Arora 4 months ago committed by GitHub
parent 9cff8c2d3c
commit f733379488
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 82
      solidity/contracts/hooks/ArbL2ToL1Hook.sol
  2. 2
      solidity/contracts/hooks/libs/AbstractMessageIdAuthHook.sol
  3. 3
      solidity/contracts/hooks/warp-route/RateLimitedHook.sol
  4. 3
      solidity/contracts/interfaces/IInterchainSecurityModule.sol
  5. 3
      solidity/contracts/interfaces/hooks/IPostDispatchHook.sol
  6. 40
      solidity/contracts/isms/hook/AbstractMessageIdAuthorizedIsm.sol
  7. 147
      solidity/contracts/isms/hook/ArbL2ToL1Ism.sol
  8. 2
      solidity/contracts/test/TestRecipient.sol
  9. 1
      solidity/package.json
  10. 5
      solidity/remappings.txt
  11. 85
      solidity/script/DeployArbHook.s.sol
  12. 364
      solidity/test/isms/ArbL2ToL1Ism.t.sol
  13. 181
      yarn.lock

@ -0,0 +1,82 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.0;
/*@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@ HYPERLANE @@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@*/
// ============ Internal Imports ============
import {AbstractPostDispatchHook} from "./libs/AbstractMessageIdAuthHook.sol";
import {AbstractMessageIdAuthHook} from "./libs/AbstractMessageIdAuthHook.sol";
import {Mailbox} from "../Mailbox.sol";
import {StandardHookMetadata} from "./libs/StandardHookMetadata.sol";
import {Message} from "../libs/Message.sol";
import {TypeCasts} from "../libs/TypeCasts.sol";
import {IPostDispatchHook} from "../interfaces/hooks/IPostDispatchHook.sol";
import {MailboxClient} from "../client/MailboxClient.sol";
// ============ External Imports ============
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
import {ArbSys} from "@arbitrum/nitro-contracts/src/precompiles/ArbSys.sol";
/**
* @title ArbL2ToL1Hook
* @notice Message hook to inform the ArbL2ToL1iSM of messages published through
* the native Arbitrum bridge.
* @notice This works only for L2 -> L1 messages and has the 7 day delay as specified by the ArbSys contract.
*/
contract ArbL2ToL1Hook is AbstractMessageIdAuthHook {
using StandardHookMetadata for bytes;
// ============ Constants ============
// precompile contract on L2 for sending messages to L1
ArbSys public immutable arbSys;
// Immutable quote amount
uint256 public immutable GAS_QUOTE;
// ============ Constructor ============
constructor(
address _mailbox,
uint32 _destinationDomain,
bytes32 _ism,
address _arbSys,
uint256 _gasQuote
) AbstractMessageIdAuthHook(_mailbox, _destinationDomain, _ism) {
arbSys = ArbSys(_arbSys);
GAS_QUOTE = _gasQuote;
}
function hookType() external pure override returns (uint8) {
return uint8(IPostDispatchHook.Types.ARB_L2_TO_L1);
}
function _quoteDispatch(
bytes calldata,
bytes calldata
) internal view override returns (uint256) {
return GAS_QUOTE;
}
// ============ Internal functions ============
/// @inheritdoc AbstractMessageIdAuthHook
function _sendMessageId(
bytes calldata metadata,
bytes memory payload
) internal override {
arbSys.sendTxToL1{value: metadata.msgValue(0)}(
TypeCasts.bytes32ToAddress(ism),
payload
);
}
}

@ -58,7 +58,7 @@ abstract contract AbstractMessageIdAuthHook is
}
/// @inheritdoc IPostDispatchHook
function hookType() external pure returns (uint8) {
function hookType() external pure virtual returns (uint8) {
return uint8(IPostDispatchHook.Types.ID_AUTH_ISM);
}

@ -1,5 +1,6 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
import {MailboxClient} from "contracts/client/MailboxClient.sol";
import {IPostDispatchHook} from "contracts/interfaces/hooks/IPostDispatchHook.sol";
import {Message} from "contracts/libs/Message.sol";
@ -26,7 +27,7 @@ contract RateLimitedHook is IPostDispatchHook, MailboxClient, RateLimited {
/// @inheritdoc IPostDispatchHook
function hookType() external pure returns (uint8) {
return uint8(IPostDispatchHook.Types.Rate_Limited_Hook);
return uint8(IPostDispatchHook.Types.RATE_LIMITED);
}
/// @inheritdoc IPostDispatchHook

@ -10,7 +10,8 @@ interface IInterchainSecurityModule {
MERKLE_ROOT_MULTISIG,
MESSAGE_ID_MULTISIG,
NULL, // used with relayer carrying no metadata
CCIP_READ
CCIP_READ,
ARB_L2_TO_L1
}
/**

@ -25,7 +25,8 @@ interface IPostDispatchHook {
PAUSABLE,
PROTOCOL_FEE,
LAYER_ZERO_V1,
Rate_Limited_Hook
RATE_LIMITED,
ARB_L2_TO_L1
}
/**

@ -72,17 +72,24 @@ abstract contract AbstractMessageIdAuthorizedIsm is
*/
function verify(
bytes calldata,
/*_metadata*/
/*metadata*/
bytes calldata message
) external returns (bool) {
bytes32 messageId = message.id();
// check for the first bit (used for verification)
bool verified = verifiedMessages[messageId].isBitSet(
VERIFIED_MASK_INDEX
);
// rest 255 bits contains the msg.value passed from the hook
) external virtual returns (bool) {
bool verified = isVerified(message);
if (verified) {
releaseValueToRecipient(message);
}
return verified;
}
// ============ Public Functions ============
/**
* @notice Release the value to the recipient if the message is verified.
* @param message Message to release value for.
*/
function releaseValueToRecipient(bytes calldata message) public {
bytes32 messageId = message.id();
uint256 _msgValue = verifiedMessages[messageId].clearBit(
VERIFIED_MASK_INDEX
);
@ -91,7 +98,15 @@ abstract contract AbstractMessageIdAuthorizedIsm is
payable(message.recipientAddress()).sendValue(_msgValue);
}
}
return verified;
/**
* @notice Check if a message is verified through verifyMessageId first.
* @param message Message to check.
*/
function isVerified(bytes calldata message) public view returns (bool) {
bytes32 messageId = message.id();
// check for the first bit (used for verification)
return verifiedMessages[messageId].isBitSet(VERIFIED_MASK_INDEX);
}
/**
@ -113,5 +128,10 @@ abstract contract AbstractMessageIdAuthorizedIsm is
emit ReceivedMessage(messageId);
}
// ============ Internal Functions ============
/**
* @notice Check if sender is authorized to message `verifyMessageId`.
*/
function _isAuthorized() internal view virtual returns (bool);
}

@ -0,0 +1,147 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.0;
/*@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@ HYPERLANE @@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@*/
// ============ Internal Imports ============
import {IInterchainSecurityModule} from "../../interfaces/IInterchainSecurityModule.sol";
import {TypeCasts} from "../../libs/TypeCasts.sol";
import {Message} from "../../libs/Message.sol";
import {AbstractMessageIdAuthorizedIsm} from "./AbstractMessageIdAuthorizedIsm.sol";
// ============ External Imports ============
import {IOutbox} from "@arbitrum/nitro-contracts/src/bridge/IOutbox.sol";
import {CrossChainEnabledArbitrumL1} from "@openzeppelin/contracts/crosschain/arbitrum/CrossChainEnabledArbitrumL1.sol";
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
/**
* @title ArbL2ToL1Ism
* @notice Uses the native Arbitrum bridge to verify interchain messages from L2 to L1.
*/
contract ArbL2ToL1Ism is
CrossChainEnabledArbitrumL1,
AbstractMessageIdAuthorizedIsm
{
using Message for bytes;
// ============ Constants ============
// module type for the ISM
uint8 public constant moduleType =
uint8(IInterchainSecurityModule.Types.ARB_L2_TO_L1);
// arbitrum nitro contract on L1 to forward verification
IOutbox public arbOutbox;
// ============ Constructor ============
constructor(
address _bridge,
address _outbox
) CrossChainEnabledArbitrumL1(_bridge) {
require(
Address.isContract(_bridge),
"ArbL2ToL1Ism: invalid Arbitrum Bridge"
);
arbOutbox = IOutbox(_outbox);
}
// ============ External Functions ============
/// @inheritdoc IInterchainSecurityModule
function verify(
bytes calldata metadata,
bytes calldata message
) external override returns (bool) {
bool verified = isVerified(message);
if (verified) {
releaseValueToRecipient(message);
}
return verified || _verifyWithOutboxCall(metadata, message);
}
// ============ Internal function ============
/**
* @notice Verify message directly using the arbOutbox.executeTransaction function.
* @dev This is a fallback in case the message is not verified by the stateful verify function first.
* @dev This function doesn't support msg.value as the ism.verify call doesn't support it either.
*/
function _verifyWithOutboxCall(
bytes calldata metadata,
bytes calldata message
) internal returns (bool) {
(
bytes32[] memory proof,
uint256 index,
address l2Sender,
address to,
uint256 l2Block,
uint256 l1Block,
uint256 l2Timestamp,
bytes memory data
) = abi.decode(
metadata,
(
bytes32[],
uint256,
address,
address,
uint256,
uint256,
uint256,
bytes
)
);
// check if the sender of the l2 message is the authorized hook
require(
l2Sender == TypeCasts.bytes32ToAddress(authorizedHook),
"ArbL2ToL1Ism: l2Sender != authorizedHook"
);
// this data is an abi encoded call of verifyMessageId(bytes32 messageId)
require(data.length == 36, "ArbL2ToL1Ism: invalid data length");
bytes32 messageId = message.id();
bytes32 convertedBytes;
assembly {
// data = 0x[4 bytes function signature][32 bytes messageId]
convertedBytes := mload(add(data, 36))
}
// check if the parsed message id matches the message id of the message
require(
convertedBytes == messageId,
"ArbL2ToL1Ism: invalid message id"
);
// value send to 0
arbOutbox.executeTransaction(
proof,
index,
l2Sender,
to,
l2Block,
l1Block,
l2Timestamp,
0,
data
);
// the above bridge call will revert if the verifyMessageId call fails
return true;
}
/// @inheritdoc AbstractMessageIdAuthorizedIsm
function _isAuthorized() internal view override returns (bool) {
return
_crossChainSender() == TypeCasts.bytes32ToAddress(authorizedHook);
}
}

@ -46,4 +46,6 @@ contract TestRecipient is
function setInterchainSecurityModule(address _ism) external onlyOwner {
interchainSecurityModule = IInterchainSecurityModule(_ism);
}
receive() external payable {}
}

@ -3,6 +3,7 @@
"description": "Core solidity contracts for Hyperlane",
"version": "4.0.0",
"dependencies": {
"@arbitrum/nitro-contracts": "^1.2.1",
"@eth-optimism/contracts": "^0.6.0",
"@hyperlane-xyz/utils": "4.0.0",
"@layerzerolabs/lz-evm-oapp-v2": "2.0.2",

@ -1,6 +1,7 @@
@openzeppelin=../node_modules/@openzeppelin
@layerzerolabs=../node_modules/@layerzerolabs
@arbitrum=../node_modules/@arbitrum
@eth-optimism=../node_modules/@eth-optimism
@layerzerolabs=../node_modules/@layerzerolabs
@openzeppelin=../node_modules/@openzeppelin
ds-test/=lib/forge-std/lib/ds-test/src/
forge-std/=lib/forge-std/src/
fx-portal/=lib/fx-portal/

@ -0,0 +1,85 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.0;
import "forge-std/Script.sol";
import {Mailbox} from "../../contracts/Mailbox.sol";
import {TypeCasts} from "../../contracts/libs/TypeCasts.sol";
import {ArbL2ToL1Hook} from "../../contracts/hooks/ArbL2ToL1Hook.sol";
import {ArbL2ToL1Ism} from "../../contracts/isms/hook/ArbL2ToL1Ism.sol";
import {TestRecipient} from "../../contracts/test/TestRecipient.sol";
import {TestIsm} from "../../contracts/test/TestIsm.sol";
contract DeployArbHook is Script {
uint256 deployerPrivateKey;
ArbL2ToL1Hook hook;
ArbL2ToL1Ism ism;
uint32 constant L1_DOMAIN = 11155111;
address constant L1_MAILBOX = 0xfFAEF09B3cd11D9b20d1a19bECca54EEC2884766;
address constant L1_BRIDGE = 0x38f918D0E9F1b721EDaA41302E399fa1B79333a9;
address constant L1_OUTBOX = 0x65f07C7D521164a4d5DaC6eB8Fac8DA067A3B78F;
address constant L1_ISM = 0x096A1c034c7Ad113B6dB786b7BA852cB67025458; // placeholder
bytes32 TEST_RECIPIENT =
0x000000000000000000000000155b1cd2f7cbc58d403b9be341fab6cd77425175; // placeholder
address constant ARBSYS = 0x0000000000000000000000000000000000000064;
address constant L2_MAILBOX = 0x598facE78a4302f11E3de0bee1894Da0b2Cb71F8;
address constant L2_HOOK = 0xd9d99AC1C645563576b8Df22cBebFC23FB60Ec73; // placeholder
function deployIsm() external {
deployerPrivateKey = vm.envUint("DEPLOYER_PRIVATE_KEY");
vm.startBroadcast(deployerPrivateKey);
ism = new ArbL2ToL1Ism(L1_BRIDGE, L1_OUTBOX);
TestRecipient testRecipient = new TestRecipient();
testRecipient.setInterchainSecurityModule(address(ism));
vm.stopBroadcast();
}
function deployHook() external {
deployerPrivateKey = vm.envUint("DEPLOYER_PRIVATE_KEY");
vm.startBroadcast(deployerPrivateKey);
hook = new ArbL2ToL1Hook(
L2_MAILBOX,
L1_DOMAIN,
TypeCasts.addressToBytes32(L1_ISM),
ARBSYS,
200_000 // estimated gas amount used for verify
);
vm.stopBroadcast();
}
function deployTestRecipient() external {
deployerPrivateKey = vm.envUint("DEPLOYER_PRIVATE_KEY");
vm.startBroadcast(deployerPrivateKey);
TestIsm noopIsm = new TestIsm();
noopIsm.setVerify(true);
TestRecipient testRecipient = new TestRecipient();
testRecipient.setInterchainSecurityModule(address(noopIsm));
console.log("TestRecipient address: %s", address(testRecipient));
vm.stopBroadcast();
}
function setAuthorizedHook() external {
deployerPrivateKey = vm.envUint("DEPLOYER_PRIVATE_KEY");
vm.startBroadcast(deployerPrivateKey);
ism = ArbL2ToL1Ism(L1_ISM);
ism.setAuthorizedHook(TypeCasts.addressToBytes32(L2_HOOK));
vm.stopBroadcast();
}
}

@ -0,0 +1,364 @@
// SPDX-License-Identifier: MIT or Apache-2.0
pragma solidity ^0.8.13;
import {Test} from "forge-std/Test.sol";
import {TypeCasts} from "../../contracts/libs/TypeCasts.sol";
import {MessageUtils} from "./IsmTestUtils.sol";
import {TestMailbox} from "../../contracts/test/TestMailbox.sol";
import {StandardHookMetadata} from "../../contracts/hooks/libs/StandardHookMetadata.sol";
import {Message} from "../../contracts/libs/Message.sol";
import {AbstractMessageIdAuthorizedIsm} from "../../contracts/isms/hook/AbstractMessageIdAuthorizedIsm.sol";
import {ArbL2ToL1Hook} from "../../contracts/hooks/ArbL2ToL1Hook.sol";
import {ArbL2ToL1Ism} from "../../contracts/isms/hook/ArbL2ToL1Ism.sol";
import {TestRecipient} from "../../contracts/test/TestRecipient.sol";
contract MockArbBridge {
error BridgeCallFailed();
address public activeOutbox;
address public l2ToL1Sender;
constructor() {
activeOutbox = address(this);
}
function setL2ToL1Sender(address _sender) external {
l2ToL1Sender = _sender;
}
function executeTransaction(
bytes32[] calldata /*proof*/,
uint256 /*index*/,
address /*l2Sender*/,
address to,
uint256 /*l2Block*/,
uint256 /*l1Block*/,
uint256 /*timestamp*/,
uint256 value,
bytes calldata data
) external payable {
(bool success, bytes memory returndata) = to.call{value: value}(data);
if (!success) {
if (returndata.length > 0) {
// solhint-disable-next-line no-inline-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert BridgeCallFailed();
}
}
}
}
contract MockArbSys {
function sendTxToL1(
address destination,
bytes calldata data
) external payable returns (uint256) {}
}
contract ArbL2ToL1IsmTest is Test {
uint8 internal constant HYPERLANE_VERSION = 1;
uint32 internal constant MAINNET_DOMAIN = 1;
uint32 internal constant ARBITRUM_DOMAIN = 42161;
uint256 internal constant GAS_QUOTE = 120_000;
uint256 internal constant MOCK_LEAF_INDEX = 40160;
uint256 internal constant MOCK_L2_BLOCK = 54220000;
uint256 internal constant MOCK_L1_BLOCK = 6098300;
address internal constant L2_ARBSYS_ADDRESS =
0x0000000000000000000000000000000000000064;
MockArbBridge internal arbBridge;
TestMailbox public l2Mailbox;
ArbL2ToL1Hook public hook;
ArbL2ToL1Ism public ism;
TestRecipient internal testRecipient;
bytes internal testMessage =
abi.encodePacked("Hello from the other chain!");
bytes internal encodedMessage;
bytes internal testMetadata =
StandardHookMetadata.overrideRefundAddress(address(this));
bytes32 internal messageId;
function setUp() public {
// Arbitrum bridge mock setup
vm.etch(L2_ARBSYS_ADDRESS, address(new MockArbSys()).code);
testRecipient = new TestRecipient();
encodedMessage = _encodeTestMessage();
messageId = Message.id(encodedMessage);
}
///////////////////////////////////////////////////////////////////
/// SETUP ///
///////////////////////////////////////////////////////////////////
function deployHook() public {
l2Mailbox = new TestMailbox(ARBITRUM_DOMAIN);
hook = new ArbL2ToL1Hook(
address(l2Mailbox),
MAINNET_DOMAIN,
TypeCasts.addressToBytes32(address(ism)),
L2_ARBSYS_ADDRESS,
GAS_QUOTE
);
}
function deployIsm() public {
arbBridge = new MockArbBridge();
ism = new ArbL2ToL1Ism(address(arbBridge), address(arbBridge));
}
function deployAll() public {
deployIsm();
deployHook();
ism.setAuthorizedHook(TypeCasts.addressToBytes32(address(hook)));
}
function test_postDispatch() public {
deployAll();
bytes memory encodedHookData = abi.encodeCall(
AbstractMessageIdAuthorizedIsm.verifyMessageId,
(messageId)
);
l2Mailbox.updateLatestDispatchedId(messageId);
vm.expectCall(
L2_ARBSYS_ADDRESS,
abi.encodeCall(
MockArbSys.sendTxToL1,
(address(ism), encodedHookData)
)
);
hook.postDispatch(testMetadata, encodedMessage);
}
function testFork_postDispatch_revertWhen_chainIDNotSupported() public {
deployAll();
bytes memory message = MessageUtils.formatMessage(
0,
uint32(0),
ARBITRUM_DOMAIN,
TypeCasts.addressToBytes32(address(this)),
2, // wrong domain
TypeCasts.addressToBytes32(address(testRecipient)),
testMessage
);
l2Mailbox.updateLatestDispatchedId(Message.id(message));
vm.expectRevert(
"AbstractMessageIdAuthHook: invalid destination domain"
);
hook.postDispatch(testMetadata, message);
}
function test_postDispatch_revertWhen_notLastDispatchedMessage() public {
deployAll();
// vm.expectRevert(
// "AbstractMessageIdAuthHook: message not latest dispatched"
// );
// hook.postDispatch(testMetadata, encodedMessage);
}
function test_verify_outboxCall() public {
deployAll();
bytes memory encodedOutboxTxMetadata = _encodeOutboxTx(
address(hook),
address(ism),
messageId
);
arbBridge.setL2ToL1Sender(address(hook));
assertTrue(ism.verify(encodedOutboxTxMetadata, encodedMessage));
}
function test_verify_statefulVerify() public {
deployAll();
bytes memory encodedHookData = abi.encodeCall(
AbstractMessageIdAuthorizedIsm.verifyMessageId,
(messageId)
);
arbBridge.setL2ToL1Sender(address(hook));
arbBridge.executeTransaction{value: 1 ether}(
new bytes32[](0),
MOCK_LEAF_INDEX,
address(hook),
address(ism),
MOCK_L2_BLOCK,
MOCK_L1_BLOCK,
block.timestamp,
1 ether,
encodedHookData
);
vm.etch(address(arbBridge), new bytes(0)); // this is a way to test that the arbBridge isn't called again
assertTrue(ism.verify(new bytes(0), encodedMessage));
assertEq(address(testRecipient).balance, 1 ether);
}
function test_verify_statefulAndOutbox() public {
deployAll();
bytes memory encodedHookData = abi.encodeCall(
AbstractMessageIdAuthorizedIsm.verifyMessageId,
(messageId)
);
arbBridge.setL2ToL1Sender(address(hook));
arbBridge.executeTransaction{value: 1 ether}(
new bytes32[](0),
MOCK_LEAF_INDEX,
address(hook),
address(ism),
MOCK_L2_BLOCK,
MOCK_L1_BLOCK,
block.timestamp,
1 ether,
encodedHookData
);
bytes memory encodedOutboxTxMetadata = _encodeOutboxTx(
address(hook),
address(ism),
messageId
);
vm.etch(address(arbBridge), new bytes(0)); // this is a way to test that the arbBridge isn't called again
assertTrue(ism.verify(encodedOutboxTxMetadata, encodedMessage));
assertEq(address(testRecipient).balance, 1 ether);
}
function test_verify_revertsWhen_noStatefulOrOutbox() public {
deployAll();
vm.expectRevert();
ism.verify(new bytes(0), encodedMessage);
}
function test_verify_revertsWhen_notAuthorizedHook() public {
deployAll();
bytes memory encodedOutboxTxMetadata = _encodeOutboxTx(
address(this),
address(ism),
messageId
);
arbBridge.setL2ToL1Sender(address(hook));
vm.expectRevert("ArbL2ToL1Ism: l2Sender != authorizedHook");
ism.verify(encodedOutboxTxMetadata, encodedMessage);
}
function test_verify_revertsWhen_invalidIsm() public {
deployAll();
bytes memory encodedOutboxTxMetadata = _encodeOutboxTx(
address(hook),
address(this),
messageId
);
arbBridge.setL2ToL1Sender(address(hook));
vm.expectRevert(); // BridgeCallFailed()
ism.verify(encodedOutboxTxMetadata, encodedMessage);
}
function test_verify_revertsWhen_incorrectMessageId() public {
deployAll();
bytes32 incorrectMessageId = keccak256("incorrect message id");
bytes memory encodedOutboxTxMetadata = _encodeOutboxTx(
address(hook),
address(ism),
incorrectMessageId
);
bytes memory encodedHookData = abi.encodeCall(
AbstractMessageIdAuthorizedIsm.verifyMessageId,
(incorrectMessageId)
);
arbBridge.setL2ToL1Sender(address(hook));
// through outbox call
vm.expectRevert("ArbL2ToL1Ism: invalid message id");
ism.verify(encodedOutboxTxMetadata, encodedMessage);
// through statefulVerify
arbBridge.executeTransaction(
new bytes32[](0),
MOCK_LEAF_INDEX,
address(hook),
address(ism),
MOCK_L2_BLOCK,
MOCK_L1_BLOCK,
block.timestamp,
0,
encodedHookData
);
vm.etch(address(arbBridge), new bytes(0)); // to stop the outbox route
vm.expectRevert();
assertFalse(ism.verify(new bytes(0), encodedMessage));
}
// function test_verify_withMsgValue
/* ============ helper functions ============ */
function _encodeOutboxTx(
address _hook,
address _ism,
bytes32 _messageId
) internal view returns (bytes memory) {
bytes memory encodedHookData = abi.encodeCall(
AbstractMessageIdAuthorizedIsm.verifyMessageId,
(_messageId)
);
bytes32[] memory proof = new bytes32[](16);
return
abi.encode(
proof,
MOCK_LEAF_INDEX,
_hook,
_ism,
MOCK_L2_BLOCK,
MOCK_L1_BLOCK,
block.timestamp,
encodedHookData
);
}
function _encodeTestMessage() internal view returns (bytes memory) {
return
MessageUtils.formatMessage(
HYPERLANE_VERSION,
uint32(0),
ARBITRUM_DOMAIN,
TypeCasts.addressToBytes32(address(this)),
MAINNET_DOMAIN,
TypeCasts.addressToBytes32(address(testRecipient)),
testMessage
);
}
}

@ -29,6 +29,18 @@ __metadata:
languageName: node
linkType: hard
"@arbitrum/nitro-contracts@npm:^1.2.1":
version: 1.2.1
resolution: "@arbitrum/nitro-contracts@npm:1.2.1"
dependencies:
"@offchainlabs/upgrade-executor": "npm:1.1.0-beta.0"
"@openzeppelin/contracts": "npm:4.5.0"
"@openzeppelin/contracts-upgradeable": "npm:4.5.2"
patch-package: "npm:^6.4.7"
checksum: b8e682e85a6cb45757427d8d24a59752e4e69167d8347ddf36bb299a64a892d9d847bd11ee8d4c6b61b62688e83657b3a1691a1d1dfb924006b39caa64ec2df1
languageName: node
linkType: hard
"@arbitrum/sdk@npm:^3.0.0":
version: 3.0.0
resolution: "@arbitrum/sdk@npm:3.0.0"
@ -5734,6 +5746,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "@hyperlane-xyz/core@workspace:solidity"
dependencies:
"@arbitrum/nitro-contracts": "npm:^1.2.1"
"@eth-optimism/contracts": "npm:^0.6.0"
"@hyperlane-xyz/utils": "npm:4.0.0"
"@layerzerolabs/lz-evm-oapp-v2": "npm:2.0.2"
@ -7554,6 +7567,16 @@ __metadata:
languageName: node
linkType: hard
"@offchainlabs/upgrade-executor@npm:1.1.0-beta.0":
version: 1.1.0-beta.0
resolution: "@offchainlabs/upgrade-executor@npm:1.1.0-beta.0"
dependencies:
"@openzeppelin/contracts": "npm:4.7.3"
"@openzeppelin/contracts-upgradeable": "npm:4.7.3"
checksum: a8cd0cc24103cc42021c452220005efde535ba3596ec2ba5eb6dc299d1f3291c38a3d859621d7983bd7c43c80606d6e7d906e1081a1e499455ddea7ba64ab355
languageName: node
linkType: hard
"@openzeppelin-3/contracts@npm:@openzeppelin/contracts@^3.4.2-solc-0.7":
version: 3.4.2
resolution: "@openzeppelin/contracts@npm:3.4.2"
@ -7568,6 +7591,20 @@ __metadata:
languageName: node
linkType: hard
"@openzeppelin/contracts-upgradeable@npm:4.5.2":
version: 4.5.2
resolution: "@openzeppelin/contracts-upgradeable@npm:4.5.2"
checksum: 5e246da7a44bb982a312ebf79978735712140692d46273566e490159b98b9041ca72cc08c3d05172137a389be4caad5afc001480bc5557f3d47162f4626e3723
languageName: node
linkType: hard
"@openzeppelin/contracts-upgradeable@npm:4.7.3":
version: 4.7.3
resolution: "@openzeppelin/contracts-upgradeable@npm:4.7.3"
checksum: 7c72ffeca867478b5aa8e8c7adb3d1ce114cfdc797ed4f3cd074788cf4da25d620ffffd624ac7e9d1223eecffeea9f7b79200ff70dc464cc828c470ccd12ddf1
languageName: node
linkType: hard
"@openzeppelin/contracts-upgradeable@npm:^4.6.0":
version: 4.9.5
resolution: "@openzeppelin/contracts-upgradeable@npm:4.9.5"
@ -7589,6 +7626,20 @@ __metadata:
languageName: node
linkType: hard
"@openzeppelin/contracts@npm:4.5.0":
version: 4.5.0
resolution: "@openzeppelin/contracts@npm:4.5.0"
checksum: 8bfa1733732420331728cedd7f1f5f4e4ae0700b32c9e5def19b2d42dbb0b246709e8e22abd457e8269d743012ff2aed4e3f100a942f45d9507cb78d5dbd435b
languageName: node
linkType: hard
"@openzeppelin/contracts@npm:4.7.3":
version: 4.7.3
resolution: "@openzeppelin/contracts@npm:4.7.3"
checksum: 3d16ed8943938373ecc331c2ab83c3e8d0d89aed0c2a109aaa61ca6524b4c31cb5a81185c6f93ce9ee2dda685a4328fd85bd217929ae598f4be813d5d4cd1b78
languageName: node
linkType: hard
"@openzeppelin/contracts@npm:^4.2.0":
version: 4.9.6
resolution: "@openzeppelin/contracts@npm:4.9.6"
@ -10752,6 +10803,13 @@ __metadata:
languageName: node
linkType: hard
"@yarnpkg/lockfile@npm:^1.1.0":
version: 1.1.0
resolution: "@yarnpkg/lockfile@npm:1.1.0"
checksum: cd19e1114aaf10a05126aeea8833ef4ca8af8a46e88e12884f8359d19333fd19711036dbc2698dbe937f81f037070cf9a8da45c2e8c6ca19cafd7d15659094ed
languageName: node
linkType: hard
"JSONStream@npm:^1.3.5":
version: 1.3.5
resolution: "JSONStream@npm:1.3.5"
@ -13154,6 +13212,19 @@ __metadata:
languageName: node
linkType: hard
"cross-spawn@npm:^6.0.5":
version: 6.0.5
resolution: "cross-spawn@npm:6.0.5"
dependencies:
nice-try: "npm:^1.0.4"
path-key: "npm:^2.0.1"
semver: "npm:^5.5.0"
shebang-command: "npm:^1.2.0"
which: "npm:^1.2.9"
checksum: f07e643b4875f26adffcd7f13bc68d9dff20cf395f8ed6f43a23f3ee24fc3a80a870a32b246fd074e514c8fd7da5f978ac6a7668346eec57aa87bac89c1ed3a1
languageName: node
linkType: hard
"cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3":
version: 7.0.3
resolution: "cross-spawn@npm:7.0.3"
@ -15211,6 +15282,15 @@ __metadata:
languageName: node
linkType: hard
"find-yarn-workspace-root@npm:^2.0.0":
version: 2.0.0
resolution: "find-yarn-workspace-root@npm:2.0.0"
dependencies:
micromatch: "npm:^4.0.2"
checksum: 7fa7942849eef4d5385ee96a0a9a5a9afe885836fd72ed6a4280312a38690afea275e7d09b343fe97daf0412d833f8ac4b78c17fc756386d9ebebf0759d707a7
languageName: node
linkType: hard
"flat-cache@npm:^3.0.4":
version: 3.0.4
resolution: "flat-cache@npm:3.0.4"
@ -15446,7 +15526,7 @@ __metadata:
languageName: node
linkType: hard
"fs-extra@npm:^9.1.0":
"fs-extra@npm:^9.0.0, fs-extra@npm:^9.1.0":
version: 9.1.0
resolution: "fs-extra@npm:9.1.0"
dependencies:
@ -16181,7 +16261,7 @@ __metadata:
languageName: node
linkType: hard
"graceful-fs@npm:^4.1.5, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.9":
"graceful-fs@npm:^4.1.11, graceful-fs@npm:^4.1.5, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.9":
version: 4.2.11
resolution: "graceful-fs@npm:4.2.11"
checksum: bf152d0ed1dc159239db1ba1f74fdbc40cb02f626770dcd5815c427ce0688c2635a06ed69af364396da4636d0408fcf7d4afdf7881724c3307e46aff30ca49e2
@ -17182,6 +17262,17 @@ __metadata:
languageName: node
linkType: hard
"is-ci@npm:^2.0.0":
version: 2.0.0
resolution: "is-ci@npm:2.0.0"
dependencies:
ci-info: "npm:^2.0.0"
bin:
is-ci: bin.js
checksum: 77b869057510f3efa439bbb36e9be429d53b3f51abd4776eeea79ab3b221337fe1753d1e50058a9e2c650d38246108beffb15ccfd443929d77748d8c0cc90144
languageName: node
linkType: hard
"is-ci@npm:^3.0.1":
version: 3.0.1
resolution: "is-ci@npm:3.0.1"
@ -17220,6 +17311,15 @@ __metadata:
languageName: node
linkType: hard
"is-docker@npm:^2.0.0":
version: 2.2.1
resolution: "is-docker@npm:2.2.1"
bin:
is-docker: cli.js
checksum: 3fef7ddbf0be25958e8991ad941901bf5922ab2753c46980b60b05c1bf9c9c2402d35e6dc32e4380b980ef5e1970a5d9d5e5aa2e02d77727c3b6b5e918474c56
languageName: node
linkType: hard
"is-docker@npm:^3.0.0":
version: 3.0.0
resolution: "is-docker@npm:3.0.0"
@ -17507,6 +17607,15 @@ __metadata:
languageName: node
linkType: hard
"is-wsl@npm:^2.1.1":
version: 2.2.0
resolution: "is-wsl@npm:2.2.0"
dependencies:
is-docker: "npm:^2.0.0"
checksum: 20849846ae414997d290b75e16868e5261e86ff5047f104027026fd61d8b5a9b0b3ade16239f35e1a067b3c7cc02f70183cb661010ed16f4b6c7c93dad1b19d8
languageName: node
linkType: hard
"is-wsl@npm:^3.1.0":
version: 3.1.0
resolution: "is-wsl@npm:3.1.0"
@ -18494,6 +18603,15 @@ __metadata:
languageName: node
linkType: hard
"klaw-sync@npm:^6.0.0":
version: 6.0.0
resolution: "klaw-sync@npm:6.0.0"
dependencies:
graceful-fs: "npm:^4.1.11"
checksum: 0da397f8961313c3ef8f79fb63af9002cde5a8fb2aeb1a37351feff0dd6006129c790400c3f5c3b4e757bedcabb13d21ec0a5eaef5a593d59515d4f2c291e475
languageName: node
linkType: hard
"klaw@npm:^1.0.0":
version: 1.3.1
resolution: "klaw@npm:1.3.1"
@ -20113,6 +20231,13 @@ __metadata:
languageName: node
linkType: hard
"nice-try@npm:^1.0.4":
version: 1.0.5
resolution: "nice-try@npm:1.0.5"
checksum: 0b4af3b5bb5d86c289f7a026303d192a7eb4417231fe47245c460baeabae7277bcd8fd9c728fb6bd62c30b3e15cd6620373e2cf33353b095d8b403d3e8a15aff
languageName: node
linkType: hard
"nise@npm:^5.1.1":
version: 5.1.1
resolution: "nise@npm:5.1.1"
@ -20680,6 +20805,16 @@ __metadata:
languageName: node
linkType: hard
"open@npm:^7.4.2":
version: 7.4.2
resolution: "open@npm:7.4.2"
dependencies:
is-docker: "npm:^2.0.0"
is-wsl: "npm:^2.1.1"
checksum: 4fc02ed3368dcd5d7247ad3566433ea2695b0713b041ebc0eeb2f0f9e5d4e29fc2068f5cdd500976b3464e77fe8b61662b1b059c73233ccc601fe8b16d6c1cd6
languageName: node
linkType: hard
"optionator@npm:^0.8.1":
version: 0.8.3
resolution: "optionator@npm:0.8.3"
@ -20934,6 +21069,30 @@ __metadata:
languageName: node
linkType: hard
"patch-package@npm:^6.4.7":
version: 6.5.1
resolution: "patch-package@npm:6.5.1"
dependencies:
"@yarnpkg/lockfile": "npm:^1.1.0"
chalk: "npm:^4.1.2"
cross-spawn: "npm:^6.0.5"
find-yarn-workspace-root: "npm:^2.0.0"
fs-extra: "npm:^9.0.0"
is-ci: "npm:^2.0.0"
klaw-sync: "npm:^6.0.0"
minimist: "npm:^1.2.6"
open: "npm:^7.4.2"
rimraf: "npm:^2.6.3"
semver: "npm:^5.6.0"
slash: "npm:^2.0.0"
tmp: "npm:^0.0.33"
yaml: "npm:^1.10.2"
bin:
patch-package: index.js
checksum: e15b3848f008da2cc659abd6d84dfeab6ed25a999ba25692071c13409f198dad28b6e451ecfebc2139a0847ad8e608575d6724bcc887c56169df8a733b849e79
languageName: node
linkType: hard
"path-browserify@npm:^1.0.0":
version: 1.0.1
resolution: "path-browserify@npm:1.0.1"
@ -20962,6 +21121,13 @@ __metadata:
languageName: node
linkType: hard
"path-key@npm:^2.0.1":
version: 2.0.1
resolution: "path-key@npm:2.0.1"
checksum: 6e654864e34386a2a8e6bf72cf664dcabb76574dd54013add770b374384d438aca95f4357bb26935b514a4e4c2c9b19e191f2200b282422a76ee038b9258c5e7
languageName: node
linkType: hard
"path-key@npm:^3.0.0, path-key@npm:^3.1.0":
version: 3.1.1
resolution: "path-key@npm:3.1.1"
@ -22446,7 +22612,7 @@ __metadata:
languageName: node
linkType: hard
"rimraf@npm:^2.2.8":
"rimraf@npm:^2.2.8, rimraf@npm:^2.6.3":
version: 2.7.1
resolution: "rimraf@npm:2.7.1"
dependencies:
@ -22693,7 +22859,7 @@ __metadata:
languageName: node
linkType: hard
"semver@npm:2 || 3 || 4 || 5":
"semver@npm:2 || 3 || 4 || 5, semver@npm:^5.6.0":
version: 5.7.2
resolution: "semver@npm:5.7.2"
bin:
@ -23026,6 +23192,13 @@ __metadata:
languageName: node
linkType: hard
"slash@npm:^2.0.0":
version: 2.0.0
resolution: "slash@npm:2.0.0"
checksum: 512d4350735375bd11647233cb0e2f93beca6f53441015eea241fe784d8068281c3987fbaa93e7ef1c38df68d9c60013045c92837423c69115297d6169aa85e6
languageName: node
linkType: hard
"slash@npm:^3.0.0":
version: 3.0.0
resolution: "slash@npm:3.0.0"

Loading…
Cancel
Save