Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 30 commits into from
Jul 3, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
5c544da
init
aroralanuk May 27, 2024
4fbcd1a
forge install: nitro-contracts
aroralanuk May 27, 2024
068ff5a
sendTxToL1
aroralanuk May 27, 2024
23fac90
forge install: foundry-arbitrum
aroralanuk May 27, 2024
7fc660f
rm foundry-arb
aroralanuk May 27, 2024
658aa64
add event
aroralanuk May 27, 2024
eceb2d8
temp deploy script
aroralanuk May 30, 2024
45b13d0
just contracts
aroralanuk May 30, 2024
1ce117a
Merge branch 'kunal/arb-l2-hook-contracts' into kunal/arb-l2-l1-hook
aroralanuk May 30, 2024
7c09ade
deploy hook script
aroralanuk Jun 1, 2024
0c12f2f
direct mailbox call
aroralanuk Jun 6, 2024
aa3f5e5
change to exeTx in verify
aroralanuk Jun 11, 2024
e23d2f7
lock on verifyMessageId
aroralanuk Jun 13, 2024
b5a3e96
add tests
aroralanuk Jun 14, 2024
070c182
revert temp changes
aroralanuk Jun 14, 2024
f7f8b20
merged with main
aroralanuk Jun 14, 2024
59a7da4
Merge branch 'main' into kunal/arb-l2-l1-hook
aroralanuk Jun 26, 2024
0bb418c
merge
aroralanuk Jun 26, 2024
56fb53c
changes from selrelay
aroralanuk Jun 26, 2024
89395a2
changes for async outbox call
aroralanuk Jun 27, 2024
b555b74
add gas estimate
aroralanuk Jun 27, 2024
e2da11f
revert
aroralanuk Jun 27, 2024
3f6a18f
Update CODE_OF_CONDUCT.md
aroralanuk Jun 27, 2024
734c330
forge-std
aroralanuk Jun 27, 2024
65d73ff
address yorke's comments
aroralanuk Jun 27, 2024
b5a78f3
switch to slicing
aroralanuk Jun 28, 2024
e4a2f94
add assembly back
aroralanuk Jun 28, 2024
5721d7e
add deploy script
aroralanuk Jul 1, 2024
9def606
final comment fixes
aroralanuk Jul 2, 2024
7bdcf5d
Merge branch 'main' into kunal/arb-l2-l1-hook
aroralanuk Jul 3, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 110 additions & 0 deletions solidity/contracts/hooks/ArbL2ToL1Hook.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// 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 AbstractPostDispatchHook, MailboxClient {
using StandardHookMetadata for bytes;
using Message for bytes;

// ============ Events ============

// emitted when the Merkle tree state in bridge state is updated
event ArbSysMerkleTreeUpdated(uint256 size, uint256 leaf);

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

// left-padded address for ISM to verify messages
bytes32 public immutable remoteMailbox;
// Domain of chain on which the ISM is deployed
uint32 public immutable destinationDomain;
// precompile contract on L2 for sending messages to L1
ArbSys public immutable arbSys;

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

constructor(
address _mailbox,
uint32 _destinationDomain,
bytes32 _remoteMailbox,
address _arbSys
) MailboxClient(_mailbox) {
require(
_destinationDomain != 0,
"AbstractMessageIdAuthHook: invalid destination domain"
);
remoteMailbox = _remoteMailbox;
destinationDomain = _destinationDomain;

require(Address.isContract(_arbSys), "ArbL2ToL1Hook: invalid ArbSys");
arbSys = ArbSys(_arbSys);
}

// ============ Internal functions ============
function _quoteDispatch(
bytes calldata,
bytes calldata
) internal pure override returns (uint256) {
return 0; // gas subsidized by the L2
}

/// @inheritdoc IPostDispatchHook
function hookType() external pure override returns (uint8) {
return uint8(IPostDispatchHook.Types.ARBITRUM_L2_TO_L1_HOOK);
}

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

/// @inheritdoc AbstractPostDispatchHook
function _postDispatch(
bytes calldata metadata,
bytes calldata message
) internal override {
bytes32 id = message.id();
require(
_isLatestDispatched(id),
"ArbL2ToL1Hook: message not latest dispatched"
);
require(
message.destination() == destinationDomain,
"ArbL2ToL1Hook: invalid destination domain"
);

bytes memory payload = abi.encodeCall(
Mailbox.process,
(metadata, message)
);

arbSys.sendTxToL1(TypeCasts.bytes32ToAddress(remoteMailbox), payload);
}
Fixed Show fixed Hide fixed
}
4 changes: 2 additions & 2 deletions solidity/contracts/hooks/libs/AbstractMessageIdAuthHook.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand All @@ -68,7 +68,7 @@ abstract contract AbstractMessageIdAuthHook is
function _postDispatch(
bytes calldata metadata,
bytes calldata message
) internal override {
) internal virtual override {
bytes32 id = message.id();
require(
_isLatestDispatched(id),
Expand Down
3 changes: 2 additions & 1 deletion solidity/contracts/interfaces/hooks/IPostDispatchHook.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ interface IPostDispatchHook {
PAUSABLE,
PROTOCOL_FEE,
LAYER_ZERO_V1,
Rate_Limited_Hook
Rate_Limited_Hook,
ARBITRUM_L2_TO_L1_HOOK
}

/**
Expand Down
76 changes: 76 additions & 0 deletions solidity/contracts/isms/hook/ArbL2ToL1Ism.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// 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 {AbstractMessageIdAuthorizedIsm} from "./AbstractMessageIdAuthorizedIsm.sol";

// ============ External Imports ============
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,
IInterchainSecurityModule
{
// ============ Constants ============

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

// ============ Public Storage ============
/// @notice address for the authorized hook
bytes32 public immutable authorizedHook;

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

constructor(
address _bridge,
bytes32 _hook
) CrossChainEnabledArbitrumL1(_bridge) {
require(
Address.isContract(_bridge),
"ArbL2ToL1Ism: invalid Arbitrum Bridge"
);
require(_hook != bytes32(0), "ArbL2ToL1Ism: invalid authorized hook");
authorizedHook = _hook;
}

// ============ Initializer ============

function verify(
bytes calldata,
/*_metadata*/
bytes calldata message
) external returns (bool) {
return _isAuthorized();
}

// ============ Internal function ============

/**
* @notice Check if sender is authorized to message `verifyMessageId`.
*/
function _isAuthorized() internal view returns (bool) {
return
_crossChainSender() == TypeCasts.bytes32ToAddress(authorizedHook);
}
}
1 change: 1 addition & 0 deletions solidity/foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ verbosity = 4
mainnet = "https://eth.merkle.io"
optimism = "https://mainnet.optimism.io "
polygon = "https://rpc.ankr.com/polygon"
arbitrum = "https://arbitrum.llamarpc.com"

[fuzz]
runs = 50
Expand Down
5 changes: 3 additions & 2 deletions solidity/package.json
aroralanuk marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
{
"name": "@hyperlane-xyz/core",
"description": "Core solidity contracts for Hyperlane",
"version": "3.13.0",
"version": "3.12.2",
"dependencies": {
"@arbitrum/nitro-contracts": "^1.2.1",
"@eth-optimism/contracts": "^0.6.0",
"@hyperlane-xyz/utils": "3.13.0",
"@hyperlane-xyz/utils": "3.12.2",
"@layerzerolabs/lz-evm-oapp-v2": "2.0.2",
"@openzeppelin/contracts": "^4.9.3",
"@openzeppelin/contracts-upgradeable": "^v4.9.3",
Expand Down
3 changes: 2 additions & 1 deletion solidity/remappings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
@eth-optimism=../node_modules/@eth-optimism
ds-test/=lib/forge-std/lib/ds-test/src/
forge-std/=lib/forge-std/src/
fx-portal/=lib/fx-portal/
fx-portal/=lib/fx-portal/
aroralanuk marked this conversation as resolved.
Show resolved Hide resolved
@arbitrum=../node_modules/@arbitrum
52 changes: 52 additions & 0 deletions solidity/script/DeployArbHook.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.0;

import "forge-std/Script.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";

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 ARBSYS = 0x0000000000000000000000000000000000000064;
address constant L2_MAILBOX = 0x598facE78a4302f11E3de0bee1894Da0b2Cb71F8;
address constant L2_HOOK = 0xB057Fb841027a8554521DcCdeC3c3474CaC99AB5;

function deployIsm() external {
deployerPrivateKey = vm.envUint("DEPLOYER_PRIVATE_KEY");

vm.startBroadcast(deployerPrivateKey);

ism = new ArbL2ToL1Ism(L1_BRIDGE, TypeCasts.addressToBytes32(L2_HOOK));

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_MAILBOX),
ARBSYS
);

vm.stopBroadcast();
}
}
82 changes: 82 additions & 0 deletions solidity/test/isms/ArbL2ToL1Ism.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// 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 {Message} from "../../contracts/libs/Message.sol";
import {AbstractMessageIdAuthorizedIsm} from "../../contracts/isms/hook/AbstractMessageIdAuthorizedIsm.sol";
import {ArbL2ToL1Hook} from "../../contracts/hooks/ArbL2ToL1Hook.sol";
import {TestRecipient} from "../../contracts/test/TestRecipient.sol";

contract ArbL2ToL1IsmTest is Test {
uint8 internal constant HYPERLANE_VERSION = 1;
uint32 internal constant MAINNET_DOMAIN = 1;
uint32 internal constant ARBITRUM_DOMAIN = 42161;

address internal constant L2_ARBSYS_ADDRESS =
0x0000000000000000000000000000000000000064;

TestMailbox public l2Mailbox;
ArbL2ToL1Hook public hook;
ArbL2ToL1Hook public ism; // TODO: fix

TestRecipient internal testRecipient;
bytes internal testMessage =
abi.encodePacked("Hello from the other chain!");
bytes internal encodedMessage;
bytes32 internal messageId;

function setUp() public {
testRecipient = new TestRecipient();
aroralanuk marked this conversation as resolved.
Show resolved Hide resolved

encodedMessage = _encodeTestMessage();
messageId = Message.id(encodedMessage);
}

function deployArbHook() public {
l2Mailbox = new TestMailbox(ARBITRUM_DOMAIN);
hook = new ArbL2ToL1Hook(
address(l2Mailbox),
MAINNET_DOMAIN,
TypeCasts.addressToBytes32(
0xc005dc82818d67AF737725bD4bf75435d065D239
aroralanuk marked this conversation as resolved.
Show resolved Hide resolved
),
L2_ARBSYS_ADDRESS
);
}

function deployAll() public {
deployArbHook();
}

function test_postDispatch() public {
deployAll();

bytes memory encodedHookData = abi.encodeCall(
AbstractMessageIdAuthorizedIsm.verifyMessageId,
(messageId)
);

l2Mailbox.updateLatestDispatchedId(messageId);

hook.postDispatch(encodedHookData, encodedMessage);
}

/* ============ helper functions ============ */

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
);
}
}
Loading
Loading