Adding Optimism Hook and dispatch with metadata (#2580)
Use mailbox callback to authenticate messages in hooks where necessary (#2609) Co-authored-by: Kunal Arora <55632507+aroralanuk@users.noreply.github.com>pull/2736/head
parent
46f5311a95
commit
0e10306d4b
@ -1,185 +0,0 @@ |
||||
// SPDX-License-Identifier: MIT OR Apache-2.0 |
||||
pragma solidity >=0.8.0; |
||||
|
||||
// ============ Internal Imports ============ |
||||
import {Versioned} from "./upgrade/Versioned.sol"; |
||||
import {Message} from "./libs/Message.sol"; |
||||
import {TypeCasts} from "./libs/TypeCasts.sol"; |
||||
import {IInterchainSecurityModule, ISpecifiesInterchainSecurityModule} from "./interfaces/IInterchainSecurityModule.sol"; |
||||
import {IPostDispatchHook} from "./interfaces/hooks/IPostDispatchHook.sol"; |
||||
import {IMessageRecipient} from "./interfaces/IMessageRecipientV3.sol"; |
||||
import {IMailboxV3} from "./interfaces/IMailboxV3.sol"; |
||||
|
||||
// ============ External Imports ============ |
||||
import {Address} from "@openzeppelin/contracts/utils/Address.sol"; |
||||
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; |
||||
|
||||
contract MailboxV3 is IMailboxV3, Versioned, Ownable { |
||||
// ============ Libraries ============ |
||||
|
||||
using Message for bytes; |
||||
using TypeCasts for bytes32; |
||||
using TypeCasts for address; |
||||
|
||||
// ============ Constants ============ |
||||
|
||||
// Domain of chain on which the contract is deployed |
||||
uint32 public immutable localDomain; |
||||
|
||||
// ============ Public Storage ============ |
||||
|
||||
// A monotonically increasing nonce for outbound unique message IDs. |
||||
uint32 public nonce; |
||||
|
||||
// The default ISM, used if the recipient fails to specify one. |
||||
IInterchainSecurityModule public defaultIsm; |
||||
|
||||
// The default post dispatch hook, used for post processing of dispatched messages. |
||||
IPostDispatchHook public defaultHook; |
||||
|
||||
// Mapping of message ID to whether or not that message has been delivered. |
||||
mapping(bytes32 => bool) public delivered; |
||||
|
||||
// ============ Events ============ |
||||
|
||||
/** |
||||
* @notice Emitted when the default ISM is updated |
||||
* @param module The new default ISM |
||||
*/ |
||||
event DefaultIsmSet(address indexed module); |
||||
|
||||
/** |
||||
* @notice Emitted when the default hook is updated |
||||
* @param hook The new default hook |
||||
*/ |
||||
event DefaultHookSet(address indexed hook); |
||||
|
||||
// ============ Constructor ============ |
||||
|
||||
constructor(uint32 _localDomain, address _owner) { |
||||
localDomain = _localDomain; |
||||
_transferOwnership(_owner); |
||||
} |
||||
|
||||
// ============ External Functions ============ |
||||
|
||||
/** |
||||
* @notice Sets the default ISM for the Mailbox. |
||||
* @param _module The new default ISM. Must be a contract. |
||||
*/ |
||||
function setDefaultIsm(address _module) external onlyOwner { |
||||
require(Address.isContract(_module), "!contract"); |
||||
defaultIsm = IInterchainSecurityModule(_module); |
||||
emit DefaultIsmSet(_module); |
||||
} |
||||
|
||||
function setDefaultHook(address _hook) external onlyOwner { |
||||
require(Address.isContract(_hook), "!contract"); |
||||
defaultHook = IPostDispatchHook(_hook); |
||||
emit DefaultHookSet(_hook); |
||||
} |
||||
|
||||
/** |
||||
* @notice Dispatches a message to the destination domain & recipient. |
||||
* @param _destinationDomain Domain of destination chain |
||||
* @param _recipientAddress Address of recipient on destination chain as bytes32 |
||||
* @param _messageBody Raw bytes content of message body |
||||
* @return The message ID inserted into the Mailbox's merkle tree |
||||
*/ |
||||
function dispatch( |
||||
uint32 _destinationDomain, |
||||
bytes32 _recipientAddress, |
||||
bytes calldata _messageBody |
||||
) external payable override returns (bytes32) { |
||||
// Format the message into packed bytes. |
||||
bytes memory _message = Message.formatMessage( |
||||
VERSION, |
||||
nonce, |
||||
localDomain, |
||||
msg.sender.addressToBytes32(), |
||||
_destinationDomain, |
||||
_recipientAddress, |
||||
_messageBody |
||||
); |
||||
|
||||
// effects |
||||
nonce += 1; |
||||
bytes32 _id = _message.id(); |
||||
emit DispatchId(_id); |
||||
emit Dispatch(_message); |
||||
|
||||
// interactions |
||||
defaultHook.postDispatch{value: msg.value}(_message); |
||||
return _id; |
||||
} |
||||
|
||||
/** |
||||
* @notice Attempts to deliver `_message` to its recipient. Verifies |
||||
* `_message` via the recipient's ISM using the provided `_metadata`. |
||||
* @param _metadata Metadata used by the ISM to verify `_message`. |
||||
* @param _message Formatted Hyperlane message (refer to Message.sol). |
||||
*/ |
||||
function process(bytes calldata _metadata, bytes calldata _message) |
||||
external |
||||
payable |
||||
override |
||||
{ |
||||
// Check that the message was intended for this mailbox. |
||||
require(_message.version() == VERSION, "!version"); |
||||
require(_message.destination() == localDomain, "!destination"); |
||||
|
||||
// Check that the message hasn't already been delivered. |
||||
bytes32 _id = _message.id(); |
||||
require(delivered[_id] == false, "delivered"); |
||||
|
||||
address recipient = _message.recipientAddress(); |
||||
|
||||
// Verify the message via the ISM. |
||||
IInterchainSecurityModule _ism = IInterchainSecurityModule( |
||||
recipientIsm(recipient) |
||||
); |
||||
require(_ism.verify(_metadata, _message), "!module"); |
||||
|
||||
// effects |
||||
delivered[_id] = true; |
||||
emit Process(_message); |
||||
emit ProcessId(_id); |
||||
|
||||
// Deliver the message to the recipient. (interactions) |
||||
IMessageRecipient(recipient).handle{value: msg.value}( |
||||
_message.origin(), |
||||
_message.sender(), |
||||
_message.body() |
||||
); |
||||
} |
||||
|
||||
// ============ Public Functions ============ |
||||
|
||||
/** |
||||
* @notice Returns the ISM to use for the recipient, defaulting to the |
||||
* default ISM if none is specified. |
||||
* @param _recipient The message recipient whose ISM should be returned. |
||||
* @return The ISM to use for `_recipient`. |
||||
*/ |
||||
function recipientIsm(address _recipient) |
||||
public |
||||
view |
||||
returns (IInterchainSecurityModule) |
||||
{ |
||||
// Use a default interchainSecurityModule if one is not specified by the |
||||
// recipient. |
||||
// This is useful for backwards compatibility and for convenience as |
||||
// recipients are not mandated to specify an ISM. |
||||
try |
||||
ISpecifiesInterchainSecurityModule(_recipient) |
||||
.interchainSecurityModule() |
||||
returns (IInterchainSecurityModule _val) { |
||||
// If the recipient specifies a zero address, use the default ISM. |
||||
if (address(_val) != address(0)) { |
||||
return _val; |
||||
} |
||||
// solhint-disable-next-line no-empty-blocks |
||||
} catch {} |
||||
return defaultIsm; |
||||
} |
||||
} |
@ -1,15 +0,0 @@ |
||||
// SPDX-License-Identifier: MIT |
||||
pragma solidity >=0.8.0; |
||||
|
||||
import {MailboxClient} from "../client/MailboxClient.sol"; |
||||
import {IPostDispatchHook} from "../interfaces/hooks/IPostDispatchHook.sol"; |
||||
|
||||
abstract contract AbstractHook is MailboxClient, IPostDispatchHook { |
||||
constructor(address mailbox) MailboxClient(mailbox) {} |
||||
|
||||
function postDispatch(bytes calldata message) external payable onlyMailbox { |
||||
_postDispatch(message); |
||||
} |
||||
|
||||
function _postDispatch(bytes calldata message) internal virtual; |
||||
} |
@ -0,0 +1,88 @@ |
||||
// SPDX-License-Identifier: MIT OR Apache-2.0 |
||||
pragma solidity >=0.8.0; |
||||
|
||||
/*@@@@@@@ @@@@@@@@@ |
||||
@@@@@@@@@ @@@@@@@@@ |
||||
@@@@@@@@@ @@@@@@@@@ |
||||
@@@@@@@@@ @@@@@@@@@ |
||||
@@@@@@@@@@@@@@@@@@@@@@@@@ |
||||
@@@@@ HYPERLANE @@@@@@@ |
||||
@@@@@@@@@@@@@@@@@@@@@@@@@ |
||||
@@@@@@@@@ @@@@@@@@@ |
||||
@@@@@@@@@ @@@@@@@@@ |
||||
@@@@@@@@@ @@@@@@@@@ |
||||
@@@@@@@@@ @@@@@@@@*/ |
||||
|
||||
// ============ Internal Imports ============ |
||||
import {AbstractMessageIdAuthorizedIsm} from "../isms/hook/AbstractMessageIdAuthorizedIsm.sol"; |
||||
import {TypeCasts} from "../libs/TypeCasts.sol"; |
||||
import {Message} from "../libs/Message.sol"; |
||||
import {OPStackHookMetadata} from "../libs/hooks/OPStackHookMetadata.sol"; |
||||
import {MailboxClient} from "../client/MailboxClient.sol"; |
||||
import {IPostDispatchHook} from "../interfaces/hooks/IPostDispatchHook.sol"; |
||||
|
||||
// ============ External Imports ============ |
||||
import {ICrossDomainMessenger} from "../interfaces/optimism/ICrossDomainMessenger.sol"; |
||||
import {Address} from "@openzeppelin/contracts/utils/Address.sol"; |
||||
|
||||
/** |
||||
* @title AbstractMessageIdAuthHook |
||||
* @notice Message hook to inform an Abstract Message ID ISM of messages published through |
||||
* the native OPStack bridge. |
||||
* @dev V3 WIP |
||||
*/ |
||||
abstract contract AbstractMessageIdAuthHook is |
||||
IPostDispatchHook, |
||||
MailboxClient |
||||
{ |
||||
using Message for bytes; |
||||
|
||||
// ============ Constants ============ |
||||
|
||||
// address for ISM to verify messages |
||||
address public immutable ism; |
||||
// Domain of chain on which the ISM is deployed |
||||
uint32 public immutable destinationDomain; |
||||
|
||||
// ============ Constructor ============ |
||||
|
||||
constructor( |
||||
address mailbox, |
||||
uint32 _destinationDomain, |
||||
address _ism |
||||
) MailboxClient(mailbox) { |
||||
require(_ism != address(0), "invalid ISM"); |
||||
require(_destinationDomain != 0, "invalid destination domain"); |
||||
ism = _ism; |
||||
destinationDomain = _destinationDomain; |
||||
} |
||||
|
||||
/** |
||||
* @notice Hook to inform the optimism ISM of messages published through. |
||||
* metadata The metadata for the hook caller |
||||
* @param message The message being dispatched |
||||
*/ |
||||
function postDispatch(bytes calldata metadata, bytes calldata message) |
||||
external |
||||
payable |
||||
override |
||||
{ |
||||
bytes32 id = message.id(); |
||||
require(isLatestDispatched(id), "message not latest dispatched"); |
||||
require( |
||||
message.destination() == destinationDomain, |
||||
"invalid destination domain" |
||||
); |
||||
// TODO: handle msg.value? |
||||
|
||||
bytes memory payload = abi.encodeCall( |
||||
AbstractMessageIdAuthorizedIsm.verifyMessageId, |
||||
id |
||||
); |
||||
_sendMessageId(metadata, payload); |
||||
} |
||||
|
||||
function _sendMessageId(bytes calldata metadata, bytes memory payload) |
||||
internal |
||||
virtual; |
||||
} |
@ -0,0 +1,55 @@ |
||||
// SPDX-License-Identifier: MIT OR Apache-2.0 |
||||
pragma solidity >=0.8.0; |
||||
|
||||
/*@@@@@@@ @@@@@@@@@ |
||||
@@@@@@@@@ @@@@@@@@@ |
||||
@@@@@@@@@ @@@@@@@@@ |
||||
@@@@@@@@@ @@@@@@@@@ |
||||
@@@@@@@@@@@@@@@@@@@@@@@@@ |
||||
@@@@@ HYPERLANE @@@@@@@ |
||||
@@@@@@@@@@@@@@@@@@@@@@@@@ |
||||
@@@@@@@@@ @@@@@@@@@ |
||||
@@@@@@@@@ @@@@@@@@@ |
||||
@@@@@@@@@ @@@@@@@@@ |
||||
@@@@@@@@@ @@@@@@@@*/ |
||||
|
||||
import {Message} from "../libs/Message.sol"; |
||||
import {IPostDispatchHook} from "../interfaces/hooks/IPostDispatchHook.sol"; |
||||
import {IMailbox} from "../interfaces/IMailbox.sol"; |
||||
|
||||
contract ConfigFallbackDomainRoutingHook is IPostDispatchHook { |
||||
using Message for bytes; |
||||
|
||||
IMailbox public immutable mailbox; |
||||
|
||||
/// @notice message sender => destination => recipient => hook |
||||
mapping(address => mapping(uint32 => mapping(bytes32 => IPostDispatchHook))) |
||||
public customHooks; |
||||
|
||||
constructor(address _mailbox) { |
||||
mailbox = IMailbox(_mailbox); |
||||
} |
||||
|
||||
function postDispatch(bytes calldata metadata, bytes calldata message) |
||||
public |
||||
payable |
||||
override |
||||
{ |
||||
IPostDispatchHook configuredHook = customHooks[message.senderAddress()][ |
||||
message.destination() |
||||
][message.recipient()]; |
||||
if (address(configuredHook) == address(0)) { |
||||
configuredHook = mailbox.defaultHook(); |
||||
} |
||||
|
||||
configuredHook.postDispatch{value: msg.value}(metadata, message); |
||||
} |
||||
|
||||
function setHook( |
||||
uint32 destinationDomain, |
||||
bytes32 recipient, |
||||
IPostDispatchHook hook |
||||
) external { |
||||
customHooks[msg.sender][destinationDomain][recipient] = hook; |
||||
} |
||||
} |
@ -1,27 +1,46 @@ |
||||
// SPDX-License-Identifier: MIT |
||||
pragma solidity >=0.8.0; |
||||
|
||||
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; |
||||
|
||||
import {AbstractHook} from "./AbstractHook.sol"; |
||||
// ============ Internal Imports ============ |
||||
import {Message} from "../libs/Message.sol"; |
||||
|
||||
import {IPostDispatchHook} from "../interfaces/hooks/IPostDispatchHook.sol"; |
||||
|
||||
contract DomainRoutingHook is AbstractHook, Ownable { |
||||
// ============ External Imports ============ |
||||
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; |
||||
|
||||
contract DomainRoutingHook is IPostDispatchHook, Ownable { |
||||
using Message for bytes; |
||||
|
||||
struct HookConfig { |
||||
uint32 destination; |
||||
address hook; |
||||
} |
||||
|
||||
mapping(uint32 => IPostDispatchHook) public hooks; |
||||
|
||||
constructor(address _mailbox, address _owner) AbstractHook(_mailbox) { |
||||
constructor(address _owner) { |
||||
_transferOwnership(_owner); |
||||
} |
||||
|
||||
function setHook(uint32 destination, address hook) external onlyOwner { |
||||
function setHook(uint32 destination, address hook) public onlyOwner { |
||||
hooks[destination] = IPostDispatchHook(hook); |
||||
} |
||||
|
||||
function _postDispatch(bytes calldata message) internal override { |
||||
hooks[message.destination()].postDispatch{value: msg.value}(message); |
||||
function setHooks(HookConfig[] calldata configs) external onlyOwner { |
||||
for (uint256 i = 0; i < configs.length; i++) { |
||||
setHook(configs[i].destination, configs[i].hook); |
||||
} |
||||
} |
||||
|
||||
function postDispatch(bytes calldata metadata, bytes calldata message) |
||||
external |
||||
payable |
||||
virtual |
||||
override |
||||
{ |
||||
hooks[message.destination()].postDispatch{value: msg.value}( |
||||
metadata, |
||||
message |
||||
); |
||||
} |
||||
} |
||||
|
@ -1,95 +0,0 @@ |
||||
// SPDX-License-Identifier: MIT OR Apache-2.0 |
||||
pragma solidity >=0.8.0; |
||||
|
||||
/*@@@@@@@ @@@@@@@@@ |
||||
@@@@@@@@@ @@@@@@@@@ |
||||
@@@@@@@@@ @@@@@@@@@ |
||||
@@@@@@@@@ @@@@@@@@@ |
||||
@@@@@@@@@@@@@@@@@@@@@@@@@ |
||||
@@@@@ HYPERLANE @@@@@@@ |
||||
@@@@@@@@@@@@@@@@@@@@@@@@@ |
||||
@@@@@@@@@ @@@@@@@@@ |
||||
@@@@@@@@@ @@@@@@@@@ |
||||
@@@@@@@@@ @@@@@@@@@ |
||||
@@@@@@@@@ @@@@@@@@*/ |
||||
|
||||
// ============ Internal Imports ============ |
||||
import {TypeCasts} from "../../libs/TypeCasts.sol"; |
||||
import {IMessageHook} from "../../interfaces/hooks/IMessageHook.sol"; |
||||
import {IMessageDispatcher} from "./interfaces/IMessageDispatcher.sol"; |
||||
import {ERC5164ISM} from "../../isms/hook/ERC5164ISM.sol"; |
||||
|
||||
// ============ External Imports ============ |
||||
|
||||
import {Address} from "@openzeppelin/contracts/utils/Address.sol"; |
||||
|
||||
/** |
||||
* @title 5164MessageHook |
||||
* @notice Message hook to inform the 5164 ISM of messages published through |
||||
* any of the 5164 adapters. |
||||
*/ |
||||
contract ERC5164MessageHook is IMessageHook { |
||||
using TypeCasts for address; |
||||
// ============ Constants ============ |
||||
|
||||
// Domain of chain on which the ERC5164ISM is deployed |
||||
uint32 public immutable destinationDomain; |
||||
// Dispatcher used to send messages |
||||
IMessageDispatcher public immutable dispatcher; |
||||
// address for ERC5164ISM to verify messages |
||||
address public immutable ism; |
||||
|
||||
// ============ Constructor ============ |
||||
|
||||
constructor( |
||||
uint32 _destinationDomain, |
||||
address _dispatcher, |
||||
address _ism |
||||
) { |
||||
require( |
||||
_destinationDomain != 0, |
||||
"ERC5164Hook: invalid destination domain" |
||||
); |
||||
require(_ism != address(0), "ERC5164Hook: invalid ISM"); |
||||
destinationDomain = _destinationDomain; |
||||
|
||||
require( |
||||
Address.isContract(_dispatcher), |
||||
"ERC5164Hook: invalid dispatcher" |
||||
); |
||||
dispatcher = IMessageDispatcher(_dispatcher); |
||||
ism = _ism; |
||||
} |
||||
|
||||
// ============ External Functions ============ |
||||
|
||||
/** |
||||
* @notice Hook to inform the ERC5164ISM of messages published through. |
||||
* @dev anyone can call this function, that's why we need to send msg.sender |
||||
* @param _destinationDomain The destination domain of the message. |
||||
* @param _messageId The message ID. |
||||
* @return gasOverhead The gas overhead for the function call on destination. |
||||
*/ |
||||
function postDispatch(uint32 _destinationDomain, bytes32 _messageId) |
||||
public |
||||
payable |
||||
override |
||||
returns (uint256) |
||||
{ |
||||
require(msg.value == 0, "ERC5164Hook: no value allowed"); |
||||
require( |
||||
_destinationDomain == destinationDomain, |
||||
"ERC5164Hook: invalid destination domain" |
||||
); |
||||
|
||||
bytes memory _payload = abi.encodeCall( |
||||
ERC5164ISM.verifyMessageId, |
||||
(msg.sender.addressToBytes32(), _messageId) |
||||
); |
||||
|
||||
dispatcher.dispatchMessage(_destinationDomain, ism, _payload); |
||||
|
||||
// EIP-5164 doesn't specify a gas overhead |
||||
return 0; |
||||
} |
||||
} |
@ -0,0 +1,52 @@ |
||||
// SPDX-License-Identifier: MIT OR Apache-2.0 |
||||
pragma solidity >=0.8.0; |
||||
|
||||
/*@@@@@@@ @@@@@@@@@ |
||||
@@@@@@@@@ @@@@@@@@@ |
||||
@@@@@@@@@ @@@@@@@@@ |
||||
@@@@@@@@@ @@@@@@@@@ |
||||
@@@@@@@@@@@@@@@@@@@@@@@@@ |
||||
@@@@@ HYPERLANE @@@@@@@ |
||||
@@@@@@@@@@@@@@@@@@@@@@@@@ |
||||
@@@@@@@@@ @@@@@@@@@ |
||||
@@@@@@@@@ @@@@@@@@@ |
||||
@@@@@@@@@ @@@@@@@@@ |
||||
@@@@@@@@@ @@@@@@@@*/ |
||||
|
||||
// ============ Internal Imports ============ |
||||
import {TypeCasts} from "../libs/TypeCasts.sol"; |
||||
import {IPostDispatchHook} from "../interfaces/hooks/IPostDispatchHook.sol"; |
||||
import {IMessageDispatcher} from "../interfaces/hooks/IMessageDispatcher.sol"; |
||||
import {AbstractMessageIdAuthHook} from "./AbstractMessageIdAuthHook.sol"; |
||||
|
||||
// ============ External Imports ============ |
||||
import {Address} from "@openzeppelin/contracts/utils/Address.sol"; |
||||
|
||||
/** |
||||
* @title 5164MessageHook |
||||
* @notice Message hook to inform the 5164 ISM of messages published through |
||||
* any of the 5164 adapters. |
||||
*/ |
||||
contract ERC5164Hook is AbstractMessageIdAuthHook { |
||||
IMessageDispatcher immutable dispatcher; |
||||
|
||||
constructor( |
||||
address _mailbox, |
||||
uint32 _destinationDomain, |
||||
address _ism, |
||||
address _dispatcher |
||||
) AbstractMessageIdAuthHook(_mailbox, _destinationDomain, _ism) { |
||||
require( |
||||
Address.isContract(_dispatcher), |
||||
"ERC5164Hook: invalid dispatcher" |
||||
); |
||||
dispatcher = IMessageDispatcher(_dispatcher); |
||||
} |
||||
|
||||
function _sendMessageId( |
||||
bytes calldata, /* metadata */ |
||||
bytes memory payload |
||||
) internal override { |
||||
dispatcher.dispatchMessage(destinationDomain, ism, payload); |
||||
} |
||||
} |
@ -0,0 +1,70 @@ |
||||
// SPDX-License-Identifier: MIT OR Apache-2.0 |
||||
pragma solidity >=0.8.0; |
||||
|
||||
/*@@@@@@@ @@@@@@@@@ |
||||
@@@@@@@@@ @@@@@@@@@ |
||||
@@@@@@@@@ @@@@@@@@@ |
||||
@@@@@@@@@ @@@@@@@@@ |
||||
@@@@@@@@@@@@@@@@@@@@@@@@@ |
||||
@@@@@ HYPERLANE @@@@@@@ |
||||
@@@@@@@@@@@@@@@@@@@@@@@@@ |
||||
@@@@@@@@@ @@@@@@@@@ |
||||
@@@@@@@@@ @@@@@@@@@ |
||||
@@@@@@@@@ @@@@@@@@@ |
||||
@@@@@@@@@ @@@@@@@@*/ |
||||
|
||||
// ============ Internal Imports ============ |
||||
import {AbstractMessageIdAuthHook} from "./AbstractMessageIdAuthHook.sol"; |
||||
import {TypeCasts} from "../libs/TypeCasts.sol"; |
||||
import {Message} from "../libs/Message.sol"; |
||||
import {OPStackHookMetadata} from "../libs/hooks/OPStackHookMetadata.sol"; |
||||
import {IPostDispatchHook} from "../interfaces/hooks/IPostDispatchHook.sol"; |
||||
|
||||
// ============ External Imports ============ |
||||
import {ICrossDomainMessenger} from "../interfaces/optimism/ICrossDomainMessenger.sol"; |
||||
import {Address} from "@openzeppelin/contracts/utils/Address.sol"; |
||||
|
||||
/** |
||||
* @title OPStackHook |
||||
* @notice Message hook to inform the Optimism ISM of messages published through |
||||
* the native OPStack bridge. |
||||
* @dev V3 WIP |
||||
*/ |
||||
contract OPStackHook is AbstractMessageIdAuthHook { |
||||
using OPStackHookMetadata for bytes; |
||||
|
||||
// ============ Constants ============ |
||||
|
||||
ICrossDomainMessenger public immutable l1Messenger; |
||||
|
||||
// Gas limit for sending messages to L2 |
||||
// First 1.92e6 gas is provided by Optimism, see more here: |
||||
// https://community.optimism.io/docs/developers/bridge/messaging/#for-l1-%E2%87%92-l2-transactions |
||||
uint32 internal constant GAS_LIMIT = 1_920_000; |
||||
|
||||
// ============ Constructor ============ |
||||
|
||||
constructor( |
||||
address _mailbox, |
||||
uint32 _destinationDomain, |
||||
address _ism, |
||||
address _messenger |
||||
) AbstractMessageIdAuthHook(_mailbox, _destinationDomain, _ism) { |
||||
require( |
||||
Address.isContract(_messenger), |
||||
"ERC5164Hook: invalid dispatcher" |
||||
); |
||||
l1Messenger = ICrossDomainMessenger(_messenger); |
||||
} |
||||
|
||||
function _sendMessageId(bytes calldata metadata, bytes memory payload) |
||||
internal |
||||
override |
||||
{ |
||||
l1Messenger.sendMessage{value: metadata.msgValue()}( |
||||
ism, |
||||
payload, |
||||
GAS_LIMIT |
||||
); |
||||
} |
||||
} |
@ -1,98 +0,0 @@ |
||||
// SPDX-License-Identifier: MIT OR Apache-2.0 |
||||
pragma solidity >=0.8.0; |
||||
|
||||
/*@@@@@@@ @@@@@@@@@ |
||||
@@@@@@@@@ @@@@@@@@@ |
||||
@@@@@@@@@ @@@@@@@@@ |
||||
@@@@@@@@@ @@@@@@@@@ |
||||
@@@@@@@@@@@@@@@@@@@@@@@@@ |
||||
@@@@@ HYPERLANE @@@@@@@ |
||||
@@@@@@@@@@@@@@@@@@@@@@@@@ |
||||
@@@@@@@@@ @@@@@@@@@ |
||||
@@@@@@@@@ @@@@@@@@@ |
||||
@@@@@@@@@ @@@@@@@@@ |
||||
@@@@@@@@@ @@@@@@@@*/ |
||||
|
||||
// ============ Internal Imports ============ |
||||
import {IMessageHook} from "../interfaces/hooks/IMessageHook.sol"; |
||||
import {OptimismISM} from "../isms/hook/OptimismISM.sol"; |
||||
import {TypeCasts} from "../libs/TypeCasts.sol"; |
||||
|
||||
// ============ External Imports ============ |
||||
import {ICrossDomainMessenger} from "@eth-optimism/contracts/libraries/bridge/ICrossDomainMessenger.sol"; |
||||
import {Address} from "@openzeppelin/contracts/utils/Address.sol"; |
||||
|
||||
/** |
||||
* @title OptimismMessageHook |
||||
* @notice Message hook to inform the Optimism ISM of messages published through |
||||
* the native Optimism bridge. |
||||
*/ |
||||
contract OptimismMessageHook is IMessageHook { |
||||
using TypeCasts for address; |
||||
// ============ Constants ============ |
||||
|
||||
// Domain of chain on which the optimism ISM is deployed |
||||
uint32 public immutable destinationDomain; |
||||
// Messenger used to send messages from L1 -> L2 |
||||
ICrossDomainMessenger public immutable l1Messenger; |
||||
// address for Optimism ISM to verify messages |
||||
address public immutable ism; |
||||
// Gas limit for sending messages to L2 |
||||
// First 1.92e6 gas is provided by Optimism, see more here: |
||||
// https://community.optimism.io/docs/developers/bridge/messaging/#for-l1-%E2%87%92-l2-transactions |
||||
uint32 internal constant GAS_LIMIT = 1_920_000; |
||||
|
||||
// ============ Constructor ============ |
||||
|
||||
constructor( |
||||
uint32 _destinationDomain, |
||||
address _messenger, |
||||
address _ism |
||||
) { |
||||
require( |
||||
_destinationDomain != 0, |
||||
"OptimismHook: invalid destination domain" |
||||
); |
||||
require(_ism != address(0), "OptimismHook: invalid ISM"); |
||||
destinationDomain = _destinationDomain; |
||||
|
||||
require( |
||||
Address.isContract(_messenger), |
||||
"OptimismHook: invalid messenger" |
||||
); |
||||
l1Messenger = ICrossDomainMessenger(_messenger); |
||||
ism = _ism; |
||||
} |
||||
|
||||
// ============ External Functions ============ |
||||
|
||||
/** |
||||
* @notice Hook to inform the optimism ISM of messages published through. |
||||
* @dev anyone can call this function, that's why we need to send msg.sender |
||||
* @param _destination The destination domain of the message. |
||||
* @param _messageId The message ID. |
||||
* @return gasOverhead The gas overhead for the function call on L2. |
||||
*/ |
||||
function postDispatch(uint32 _destination, bytes32 _messageId) |
||||
public |
||||
payable |
||||
override |
||||
returns (uint256) |
||||
{ |
||||
require(msg.value == 0, "OptimismHook: no value allowed"); |
||||
require( |
||||
_destination == destinationDomain, |
||||
"OptimismHook: invalid destination domain" |
||||
); |
||||
|
||||
bytes memory _payload = abi.encodeCall( |
||||
OptimismISM.verifyMessageId, |
||||
(msg.sender.addressToBytes32(), _messageId) |
||||
); |
||||
|
||||
l1Messenger.sendMessage(ism, _payload, GAS_LIMIT); |
||||
|
||||
// calling the verifyMessageId function is ~25k gas but we get 1.92m gas from Optimism |
||||
return 0; |
||||
} |
||||
} |
@ -1,55 +0,0 @@ |
||||
// SPDX-License-Identifier: MIT OR Apache-2.0 |
||||
pragma solidity >=0.8.0; |
||||
|
||||
import {IInterchainSecurityModule} from "./IInterchainSecurityModule.sol"; |
||||
import {IPostDispatchHook} from "./hooks/IPostDispatchHook.sol"; |
||||
|
||||
interface IMailboxV3 { |
||||
// ============ Events ============ |
||||
/** |
||||
* @notice Emitted when a new message is dispatched via Hyperlane |
||||
* @param message Raw bytes of message |
||||
*/ |
||||
event Dispatch(bytes message); |
||||
|
||||
/** |
||||
* @notice Emitted when a new message is dispatched via Hyperlane |
||||
* @param messageId The unique message identifier |
||||
*/ |
||||
event DispatchId(bytes32 indexed messageId); |
||||
|
||||
/** |
||||
* @notice Emitted when a Hyperlane message is delivered |
||||
* @param message Raw bytes of message |
||||
*/ |
||||
event Process(bytes message); |
||||
|
||||
/** |
||||
* @notice Emitted when a Hyperlane message is processed |
||||
* @param messageId The unique message identifier |
||||
*/ |
||||
event ProcessId(bytes32 indexed messageId); |
||||
|
||||
function localDomain() external view returns (uint32); |
||||
|
||||
function delivered(bytes32 messageId) external view returns (bool); |
||||
|
||||
function defaultIsm() external view returns (IInterchainSecurityModule); |
||||
|
||||
function defaultHook() external view returns (IPostDispatchHook); |
||||
|
||||
function dispatch( |
||||
uint32 _destinationDomain, |
||||
bytes32 _recipientAddress, |
||||
bytes calldata _messageBody |
||||
) external payable returns (bytes32); |
||||
|
||||
function process(bytes calldata _metadata, bytes calldata _message) |
||||
external |
||||
payable; |
||||
|
||||
function recipientIsm(address _recipient) |
||||
external |
||||
view |
||||
returns (IInterchainSecurityModule); |
||||
} |
@ -1,9 +0,0 @@ |
||||
// SPDX-License-Identifier: MIT OR Apache-2.0 |
||||
pragma solidity >=0.8.0; |
||||
|
||||
interface IMessageHook { |
||||
function postDispatch(uint32 _destination, bytes32 _messageId) |
||||
external |
||||
payable |
||||
returns (uint256); |
||||
} |
@ -0,0 +1,20 @@ |
||||
// SPDX-License-Identifier: MIT |
||||
pragma solidity >=0.8.0; |
||||
|
||||
/** |
||||
* @title ICrossDomainMessenger interface for bedrock update |
||||
* @dev eth-optimism's version uses strict 0.8.15 which we don't want to restrict to |
||||
*/ |
||||
interface ICrossDomainMessenger { |
||||
/** |
||||
* Sends a cross domain message to the target messenger. |
||||
* @param _target Target contract address. |
||||
* @param _message Message to send to the target. |
||||
* @param _gasLimit Gas limit for the provided message. |
||||
*/ |
||||
function sendMessage( |
||||
address _target, |
||||
bytes calldata _message, |
||||
uint32 _gasLimit |
||||
) external payable; |
||||
} |
@ -0,0 +1,17 @@ |
||||
// SPDX-License-Identifier: MIT OR Apache-2.0 |
||||
pragma solidity >=0.8.0; |
||||
|
||||
/** |
||||
* Format of metadata: |
||||
* |
||||
* [0:32] Msg value to be sent to L2 |
||||
*/ |
||||
library OPStackHookMetadata { |
||||
function msgValue(bytes calldata _metadata) |
||||
internal |
||||
pure |
||||
returns (uint256) |
||||
{ |
||||
return uint256(bytes32(_metadata[0:32])); |
||||
} |
||||
} |
@ -0,0 +1,16 @@ |
||||
// SPDX-License-Identifier: MIT OR Apache-2.0 |
||||
pragma solidity >=0.8.0; |
||||
|
||||
import {IPostDispatchHook} from "../interfaces/hooks/IPostDispatchHook.sol"; |
||||
|
||||
contract TestPostDispatchHook is IPostDispatchHook { |
||||
event PostDispatchHookCalled(); |
||||
|
||||
function postDispatch( |
||||
bytes calldata, /*metadata*/ |
||||
bytes calldata /*message*/ |
||||
) external payable override { |
||||
// test - emit event |
||||
emit PostDispatchHookCalled(); |
||||
} |
||||
} |
@ -0,0 +1,71 @@ |
||||
// SPDX-License-Identifier: Apache-2.0 |
||||
pragma solidity ^0.8.13; |
||||
|
||||
import "forge-std/Test.sol"; |
||||
|
||||
import {TypeCasts} from "../../contracts/libs/TypeCasts.sol"; |
||||
import {MessageUtils} from "../isms/IsmTestUtils.sol"; |
||||
import {Mailbox} from "../../contracts/Mailbox.sol"; |
||||
import {FallbackDomainRoutingHook} from "../../contracts/hooks/FallbackDomainRoutingHook.sol"; |
||||
import {TestPostDispatchHook} from "../../contracts/test/TestPostDispatchHook.sol"; |
||||
import {TestRecipient} from "../../contracts/test/TestRecipient.sol"; |
||||
|
||||
contract FallbackDomainRoutingHookTest is Test { |
||||
using TypeCasts for address; |
||||
FallbackDomainRoutingHook internal fallbackHook; |
||||
TestPostDispatchHook internal configuredTestHook; |
||||
TestPostDispatchHook internal mailboxDefaultHook; |
||||
TestRecipient internal testRecipient; |
||||
Mailbox internal mailbox; |
||||
|
||||
uint32 internal constant TEST_ORIGIN_DOMAIN = 1; |
||||
uint32 internal constant TEST_DESTINATION_DOMAIN = 2; |
||||
bytes internal testMessage; |
||||
|
||||
event PostDispatchHookCalled(); |
||||
|
||||
function setUp() public { |
||||
mailbox = new Mailbox(TEST_ORIGIN_DOMAIN, address(this)); |
||||
configuredTestHook = new TestPostDispatchHook(); |
||||
mailboxDefaultHook = new TestPostDispatchHook(); |
||||
testRecipient = new TestRecipient(); |
||||
fallbackHook = new FallbackDomainRoutingHook( |
||||
address(mailbox), |
||||
address(this) |
||||
); |
||||
testMessage = _encodeTestMessage(); |
||||
mailbox.setDefaultHook(address(mailboxDefaultHook)); |
||||
} |
||||
|
||||
function test_postDispatchHook_configured() public payable { |
||||
fallbackHook.setHook( |
||||
TEST_DESTINATION_DOMAIN, |
||||
address(configuredTestHook) |
||||
); |
||||
|
||||
vm.expectEmit(false, false, false, false, address(configuredTestHook)); |
||||
emit PostDispatchHookCalled(); |
||||
|
||||
fallbackHook.postDispatch{value: msg.value}("", testMessage); |
||||
} |
||||
|
||||
function test_postDispatch_default() public payable { |
||||
vm.expectEmit(false, false, false, false, address(mailboxDefaultHook)); |
||||
emit PostDispatchHookCalled(); |
||||
|
||||
fallbackHook.postDispatch{value: msg.value}("", testMessage); |
||||
} |
||||
|
||||
function _encodeTestMessage() internal view returns (bytes memory) { |
||||
return |
||||
MessageUtils.formatMessage( |
||||
uint8(0), // version |
||||
uint32(1), // nonce |
||||
TEST_ORIGIN_DOMAIN, |
||||
address(this).addressToBytes32(), |
||||
TEST_DESTINATION_DOMAIN, |
||||
address(testRecipient).addressToBytes32(), |
||||
abi.encodePacked("Hello from the other chain!") |
||||
); |
||||
} |
||||
} |
Loading…
Reference in new issue