The home for Hyperlane core contracts, sdk packages, and other infrastructure
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
hyperlane-monorepo/solidity/contracts/isms/hook/ArbL2ToL1Ism.sol

152 lines
4.8 KiB

// 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 {IBridge} from "@arbitrum/nitro-contracts/src/bridge/IBridge.sol";
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;
uint256 private constant DATA_LENGTH = 68;
uint256 private constant MESSAGE_ID_END = 36;
// ============ Constructor ============
constructor(address _bridge) CrossChainEnabledArbitrumL1(_bridge) {
require(
Address.isContract(_bridge),
"ArbL2ToL1Ism: invalid Arbitrum Bridge"
);
arbOutbox = IOutbox(IBridge(_bridge).activeOutbox());
}
// ============ External Functions ============
/// @inheritdoc IInterchainSecurityModule
function verify(
bytes calldata metadata,
bytes calldata message
) external override returns (bool) {
if (!isVerified(message)) {
_verifyWithOutboxCall(metadata, message);
require(isVerified(message), "ArbL2ToL1Ism: message not verified");
}
releaseValueToRecipient(message);
return true;
}
// ============ 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 {
(
bytes32[] memory proof,
uint256 index,
address l2Sender,
address to,
uint256 l2Block,
uint256 l1Block,
uint256 l2Timestamp,
uint256 value,
bytes memory data
) = abi.decode(
metadata,
(
bytes32[],
uint256,
address,
address,
uint256,
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 preVerifyMessage(bytes32 messageId)
require(
data.length == DATA_LENGTH,
"ArbL2ToL1Ism: invalid data length"
);
bytes32 messageId = message.id();
bytes32 convertedBytes;
assembly {
// data = 0x[4 bytes function signature][32 bytes messageId]
convertedBytes := mload(add(data, MESSAGE_ID_END))
}
// check if the parsed message id matches the message id of the message
require(
convertedBytes == messageId,
"ArbL2ToL1Ism: invalid message id"
);
arbOutbox.executeTransaction(
proof,
index,
l2Sender,
to,
l2Block,
l1Block,
l2Timestamp,
value,
data
);
}
/// @inheritdoc AbstractMessageIdAuthorizedIsm
function _isAuthorized() internal view override returns (bool) {
return
_crossChainSender() == TypeCasts.bytes32ToAddress(authorizedHook);
}
}