-
Notifications
You must be signed in to change notification settings - Fork 369
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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 #2846 ### Backward compatibility Yes ### Testing Unit and e2e with arbitrumsepolia->sepolia
- Loading branch information
1 parent
9cff8c2
commit f733379
Showing
13 changed files
with
904 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.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 | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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/ | ||
fx-portal/=lib/fx-portal/ |
Oops, something went wrong.