Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"contracts/middleware/InterchainAccountRouter.sol": {
"content": "// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.13;
/*@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@ HYPERLANE @@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@*/
// ============ Internal Imports ============
import {OwnableMulticall} from "./libs/OwnableMulticall.sol";
import {InterchainAccountMessage, InterchainAccountMessageReveal} from "./libs/InterchainAccountMessage.sol";
import {CallLib} from "./libs/Call.sol";
import {MinimalProxy} from "../libs/MinimalProxy.sol";
import {TypeCasts} from "../libs/TypeCasts.sol";
import {StandardHookMetadata} from "../hooks/libs/StandardHookMetadata.sol";
import {Router} from "../client/Router.sol";
import {IPostDispatchHook} from "../interfaces/hooks/IPostDispatchHook.sol";
import {IInterchainSecurityModule} from "../interfaces/IInterchainSecurityModule.sol";
import {CommitmentReadIsm} from "../isms/ccip-read/CommitmentReadIsm.sol";
import {Mailbox} from "../Mailbox.sol";
import {Message} from "../libs/Message.sol";
import {AbstractRoutingIsm} from "../isms/routing/AbstractRoutingIsm.sol";
import {StandardHookMetadata} from "../hooks/libs/StandardHookMetadata.sol";
// ============ External Imports ============
import {Create2} from "@openzeppelin/contracts/utils/Create2.sol";
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
/*
* @title A contract that allows accounts on chain A to call contracts via a
* proxy contract on chain B.
*/
contract InterchainAccountRouter is Router, AbstractRoutingIsm {
// ============ Libraries ============
using TypeCasts for address;
using TypeCasts for bytes32;
using InterchainAccountMessage for bytes;
using Message for bytes;
using StandardHookMetadata for bytes;
// ============ Constants ============
address public immutable implementation;
bytes32 public immutable bytecodeHash;
CommitmentReadIsm public immutable CCIP_READ_ISM;
uint public immutable COMMIT_TX_GAS_USAGE;
// ============ Public Storage ============
mapping(uint32 destinationDomain => bytes32 ism) public isms;
// ============ Upgrade Gap ============
uint256[47] private __GAP;
// ============ Events ============
/**
* @notice Emitted when a default ISM is set for a remote domain
* @param domain The remote domain
* @param ism The address of the remote ISM
*/
event RemoteIsmEnrolled(uint32 indexed domain, bytes32 ism);
/**
* @notice Emitted when an interchain call is dispatched to a remote domain
* @param destination The destination domain on which to make the call
* @param owner The local owner of the remote ICA
* @param router The address of the remote router
* @param ism The address of the remote ISM
* @param salt The salt used to derive the interchain account
*/
event RemoteCallDispatched(
uint32 indexed destination,
address indexed owner,
bytes32 router,
bytes32 ism,
bytes32 salt
);
/**
* @notice Emitted when a commit-reveal interchain call is dispatched to a remote domain
* @param commitment The commitment that was dispatched
*/
event CommitRevealDispatched(bytes32 indexed commitment);
/**
* @notice Emitted when an interchain account contract is deployed
* @param account The address of the proxy account that was created
* @param origin The domain of the chain where the message was sent from
* @param router The router on the origin domain
* @param owner The address of the account that sent the message
* @param ism The address of the local ISM
* @param salt The salt used to derive the interchain account
*/
event InterchainAccountCreated(
address indexed account,
uint32 origin,
bytes32 router,
bytes32 owner,
address ism,
bytes32 salt
);
// ============ Constructor ============
constructor(
address _mailbox,
address _hook,
address _owner,
uint _commit_tx_gas_usage,
string[] memory _commitment_urls
) Router(_mailbox) {
setHook(_hook);
_transferOwnership(_owner);
bytes memory bytecode = _implementationBytecode(address(this));
implementation = Create2.deploy(0, bytes32(0), bytecode);
bytecodeHash = _proxyBytecodeHash(implementation);
CCIP_READ_ISM = new CommitmentReadIsm(_owner, _commitment_urls);
COMMIT_TX_GAS_USAGE = _commit_tx_gas_usage;
}
function interchainSecurityModule()
external
view
override
returns (IInterchainSecurityModule)
{
return IInterchainSecurityModule(address(this));
}
/**
* @notice Registers the address of remote InterchainAccountRouter
* and ISM contracts to use as a default when making interchain calls
* @param _destination The remote domain
* @param _router The address of the remote InterchainAccountRouter
* @param _ism The address of the remote ISM
*/
function enrollRemoteRouterAndIsm(
uint32 _destination,
bytes32 _router,
bytes32 _ism
) external onlyOwner {
_enrollRemoteRouterAndIsm(_destination, _router, _ism);
}
/**
* @notice Registers the address of remote InterchainAccountRouters
* and ISM contracts to use as defaults when making interchain calls
* @param _destinations The remote domains
* @param _routers The address of the remote InterchainAccountRouters
* @param _isms The address of the remote ISMs
*/
function enrollRemoteRouterAndIsms(
uint32[] calldata _destinations,
bytes32[] calldata _routers,
bytes32[] calldata _isms
) external onlyOwner {
require(
_destinations.length == _routers.length &&
_destinations.length == _isms.length,
"length mismatch"
);
for (uint256 i = 0; i < _destinations.length; i++) {
_enrollRemoteRouterAndIsm(_destinations[i], _routers[i], _isms[i]);
}
}
// ============ External Functions ============
/**
* @notice Dispatches a sequence of remote calls to be made by an owner's
* interchain account on the destination domain
* @dev Uses the default router and ISM addresses for the destination
* domain, reverting if none have been configured
* @dev Recommend using CallLib.build to format the interchain calls.
* @param _destination The remote domain of the chain to make calls on
* @param _calls The sequence of calls to make
* @return The Hyperlane message ID
*/
function callRemote(
uint32 _destination,
CallLib.Call[] calldata _calls
) public payable returns (bytes32) {
return callRemote(_destination, _calls, bytes(""));
}
/**
* @notice Dispatches a sequence of remote calls to be made by an owner's
* interchain account on the destination domain
* @dev Uses the default router and ISM addresses for the destination
* domain, reverting if none have been configured
* @dev Recommend using CallLib.build to format the interchain calls.
* @param _destination The remote domain of the chain to make calls on
* @param _calls The sequence of calls to make
* @param _hookMetadata The hook metadata to override with for the hook set by the owner
* @return The Hyperlane message ID
*/
function callRemote(
uint32 _destination,
CallLib.Call[] calldata _calls,
bytes memory _hookMetadata
) public payable returns (bytes32) {
bytes32 _router = routers(_destination);
bytes32 _ism = isms[_destination];
return
callRemoteWithOverrides(
_destination,
_router,
_ism,
_calls,
_hookMetadata
);
}
/**
* @notice Handles dispatched messages by relaying calls to the interchain account
* @param _origin The origin domain of the interchain account
* @param _sender The sender of the interchain message
* @param _message The InterchainAccountMessage containing the account
* owner, ISM, and sequence of calls to be relayed
* @dev Does not need to be onlyRemoteRouter, as this application is designed
* to receive messages from untrusted remote contracts.
*/
function handle(
uint32 _origin,
bytes32 _sender,
bytes calldata _message
) external payable override onlyMailbox {
InterchainAccountMessage.MessageType _messageType = _message
.messageType();
if (_messageType == InterchainAccountMessage.MessageType.REVEAL) {
// If the message is a reveal,
// the commitment should have been executed in the `verify` method of the ISM
// that verified this message. The commitment is deleted in `revealAndExecute`.
// Simply return.
return;
}
bytes32 _owner = _message.owner();
bytes32 _salt = _message.salt();
bytes32 _ism = _message.ism();
OwnableMulticall ica = getDeployedInterchainAccount(
_origin,
_owner,
_sender,
_ism.bytes32ToAddress(),
_salt
);
if (_messageType == InterchainAccountMessage.MessageType.CALLS) {
CallLib.Call[] memory calls = _message.calls();
ica.multicall{value: msg.value}(calls);
} else {
// This is definitely a message of type COMMITMENT
ica.setCommitment(_message.commitment());
}
}
function route(
bytes calldata _message
) public view override returns (IInterchainSecurityModule) {
bytes calldata _body = _message.body();
InterchainAccountMessage.MessageType _messageType = _body.messageType();
// If the ISM is not set, we need to check if the message is a reveal
// If it is, we need to set the ISM to the CCIP read ISM
// Otherwise, we need to set the ISM to the default ISM
address _ism;
if (_messageType == InterchainAccountMessage.MessageType.REVEAL) {
_ism = InterchainAccountMessageReveal
.revealIsm(_body)
.bytes32ToAddress();
_ism = _ism == address(0) ? address(CCIP_READ_ISM) : _ism;
} else {
_ism = InterchainAccountMessage.ism(_body).bytes32ToAddress();
_ism = _ism == address(0) ? address(mailbox.defaultIsm()) : _ism;
}
return IInterchainSecurityModule(_ism);
}
/**
* @notice Returns the local address of an interchain account
* @dev This interchain account is not guaranteed to have been deployed
* @param _origin The remote origin domain of the interchain account
* @param _router The remote origin InterchainAccountRouter
* @param _owner The remote owner of the interchain account
* @param _ism The local address of the ISM
* @return The local address of the interchain account
*/
function getLocalInterchainAccount(
uint32 _origin,
address _owner,
address _router,
address _ism
) external view returns (OwnableMulticall) {
return
getLocalInterchainAccount(
_origin,
_owner.addressToBytes32(),
_router.addressToBytes32(),
_ism
);
}
/**
* @notice Returns the remote address of a locally owned interchain account
* @dev This interchain account is not guaranteed to have been deployed
* @dev This function will only work if the destination domain is
* EVM compatible
* @param _destination The remote destination domain of the interchain account
* @param _owner The local owner of the interchain account
* @return The remote address of the interchain account
*/
function getRemoteInterchainAccount(
uint32 _destination,
address _owner
) external view returns (address) {
return
getRemoteInterchainAccount(
_destination,
_owner,
InterchainAccountMessage.EMPTY_SALT
);
}
/**
* @notice Returns the remote address of a locally owned interchain account
* @dev This interchain account is not guaranteed to have been deployed
* @dev This function will only work if the destination domain is
* EVM compatible
* @param _destination The remote destination domain of the interchain account
* @param _owner The local owner of the interchain account
* @param _userSalt A user provided salt. Allows control over account derivation.
* @return The remote address of the interchain account
*/
function getRemoteInterchainAccount(
uint32 _destination,
address _owner,
bytes32 _userSalt
) public view returns (address) {
address _router = routers(_destination).bytes32ToAddress();
address _ism = isms[_destination].bytes32ToAddress();
return getRemoteInterchainAccount(_owner, _router, _ism, _userSalt);
}
// ============ Public Functions ============
/**
* @notice Returns and deploys (if not already) an interchain account
* @param _origin The remote origin domain of the interchain account
* @param _owner The remote owner of the interchain account
* @param _router The remote origin InterchainAccountRouter
* @param _ism The local address of the ISM
* @return The address of the interchain account
*/
function getDeployedInterchainAccount(
uint32 _origin,
address _owner,
address _router,
address _ism
) public returns (OwnableMulticall) {
return
getDeployedInterchainAccount(
_origin,
_owner.addressToBytes32(),
_router.addressToBytes32(),
_ism,
InterchainAccountMessage.EMPTY_SALT
);
}
/*
* @notice Returns and deploys (if not already) an interchain account
* @param _origin The remote origin domain of the interchain account
* @param _owner The remote owner of the interchain account
* @param _router The remote origin InterchainAccountRouter
* @param _ism The local address of the ISM
* @return The address of the interchain account
*/
function getDeployedInterchainAccount(
uint32 _origin,
bytes32 _owner,
bytes32 _router,
address _ism
) public returns (OwnableMulticall) {
return
getDeployedInterchainAccount(
_origin,
_owner,
_router,
_ism,
InterchainAccountMessage.EMPTY_SALT
);
}
/**
* @notice Returns and deploys (if not already) an interchain account
* @param _origin The remote origin domain of the interchain account
* @param _owner The remote owner of the interchain account
* @param _router The remote origin InterchainAccountRouter
* @param _ism The local address of the ISM
* @return The address of the interchain account
*/
function getDeployedInterchainAccount(
uint32 _origin,
bytes32 _owner,
bytes32 _router,
address _ism,
bytes32 _userSalt
) public returns (OwnableMulticall) {
bytes32 _deploySalt = _getSalt(
_origin,
_owner,
_router,
_ism.addressToBytes32(),
_userSalt
);
address payable _account = _getLocalInterchainAccount(_deploySalt);
if (!Address.isContract(_account)) {
bytes memory _bytecode = MinimalProxy.bytecode(implementation);
_account = payable(Create2.deploy(0, _deploySalt, _bytecode));
emit InterchainAccountCreated(
_account,
_origin,
_router,
_owner,
_ism,
_userSalt
);
}
return OwnableMulticall(_account);
}
/**
* @notice Returns the local address of a remotely owned interchain account
* @dev This interchain account is not guaranteed to have been deployed
* @param _origin The remote origin domain of the interchain account
* @param _owner The remote owner of the interchain account
* @param _router The remote InterchainAccountRouter
* @param _ism The local address of the ISM
* @return The local address of the interchain account
*/
function getLocalInterchainAccount(
uint32 _origin,
bytes32 _owner,
bytes32 _router,
address _ism
) public view returns (OwnableMulticall) {
return
getLocalInterchainAccount(
_origin,
_owner,
_router,
_ism,
InterchainAccountMessage.EMPTY_SALT
);
}
/**
* @notice Returns the local address of a remotely owned interchain account
* @dev This interchain account is not guaranteed to have been deployed
* @param _origin The remote origin domain of the interchain account
* @param _owner The remote owner of the interchain account
* @param _router The remote InterchainAccountRouter
* @param _ism The local address of the ISM
* @param _userSalt A user provided salt. Allows control over account derivation.
* @return The local address of the interchain account
*/
function getLocalInterchainAccount(
uint32 _origin,
bytes32 _owner,
bytes32 _router,
address _ism,
bytes32 _userSalt
) public view returns (OwnableMulticall) {
return
OwnableMulticall(
_getLocalInterchainAccount(
_getSalt(
_origin,
_owner,
_router,
_ism.addressToBytes32(),
_userSalt
)
)
);
}
/**
* @notice Returns the remote address of a locally owned interchain account
* @dev This interchain account is not guaranteed to have been deployed
* @dev This function will only work if the destination domain is
* EVM compatible
* @param _owner The local owner of the interchain account
* @param _router The remote InterchainAccountRouter
* @param _ism The remote address of the ISM
* @return The remote address of the interchain account
*/
function getRemoteInterchainAccount(
address _owner,
address _router,
address _ism
) public view returns (address) {
return
getRemoteInterchainAccount(
_owner,
_router,
_ism,
InterchainAccountMessage.EMPTY_SALT
);
}
/**
* @notice Returns the remote address of a locally owned interchain account
* @dev This interchain account is not guaranteed to have been deployed
* @dev This function will only work if the destination domain is
* EVM compatible
* @param _owner The local owner of the interchain account
* @param _router The remote InterchainAccountRouter
* @param _ism The remote address of the ISM
* @param _userSalt Salt provided by the user, allows control over account derivation.
* @return The remote address of the interchain account
*/
function getRemoteInterchainAccount(
address _owner,
address _router,
address _ism,
bytes32 _userSalt
) public view returns (address) {
require(_router != address(0), "no router specified for destination");
// replicate router constructor Create2 derivation
address _implementation = Create2.computeAddress(
bytes32(0),
keccak256(_implementationBytecode(_router)),
_router
);
bytes32 _bytecodeHash = _proxyBytecodeHash(_implementation);
bytes32 _salt = _getSalt(
localDomain,
_owner.addressToBytes32(),
address(this).addressToBytes32(),
_ism.addressToBytes32(),
_userSalt
);
return Create2.computeAddress(_salt, _bytecodeHash, _router);
}
/**
* @notice Dispatches a sequence of remote calls to be made by an owner's
* interchain account on the destination domain
* @dev Recommend using CallLib.build to format the interchain calls
* @param _destination The remote domain of the chain to make calls on
* @param _router The remote router address
* @param _ism The remote ISM address
* @param _calls The sequence of calls to make
* @return The Hyperlane message ID
*/
function callRemoteWithOverrides(
uint32 _destination,
bytes32 _router,
bytes32 _ism,
CallLib.Call[] calldata _calls
) public payable returns (bytes32) {
return
callRemoteWithOverrides(
_destination,
_router,
_ism,
_calls,
bytes(""),
InterchainAccountMessage.EMPTY_SALT
);
}
/**
* @notice Dispatches a sequence of remote calls to be made by an owner's
* interchain account on the destination domain
* @dev Recommend using CallLib.build to format the interchain calls
* @param _destination The remote domain of the chain to make calls on
* @param _router The remote router address
* @param _ism The remote ISM address
* @param _calls The sequence of calls to make
* @return The Hyperlane message ID
*/
function callRemoteWithOverrides(
uint32 _destination,
bytes32 _router,
bytes32 _ism,
CallLib.Call[] calldata _calls,
bytes32 _userSalt
) public payable returns (bytes32) {
return
callRemoteWithOverrides(
_destination,
_router,
_ism,
_calls,
bytes(""),
_userSalt
);
}
/**
* @notice Dispatches a sequence of remote calls to be made by an owner's
* interchain account on the destination domain
* @dev Recommend using CallLib.build to format the interchain calls
* @param _destination The remote domain of the chain to make calls on
* @param _router The remote router address
* @param _ism The remote ISM address
* @param _calls The sequence of calls to make
* @param _hookMetadata The hook metadata to override with for the hook set by the owner
* @return The Hyperlane message ID
*/
function callRemoteWithOverrides(
uint32 _destination,
bytes32 _router,
bytes32 _ism,
CallLib.Call[] calldata _calls,
bytes memory _hookMetadata
) public payable returns (bytes32) {
return
callRemoteWithOverrides(
_destination,
_router,
_ism,
_calls,
_hookMetadata,
InterchainAccountMessage.EMPTY_SALT
);
}
/**
* @notice Dispatches a sequence of remote calls to be made by an owner's
* interchain account on the destination domain
* @dev Recommend using CallLib.build to format the interchain calls
* @param _destination The remote domain of the chain to make calls on
* @param _router The remote router address
* @param _ism The remote ISM address
* @param _calls The sequence of calls to make
* @param _hookMetadata The hook metadata to override with for the hook set by the owner
* @param _userSalt Salt provided by the user, allows control over account derivation.
* @return The Hyperlane message ID
*/
function callRemoteWithOverrides(
uint32 _destination,
bytes32 _router,
bytes32 _ism,
CallLib.Call[] calldata _calls,
bytes memory _hookMetadata,
bytes32 _userSalt
) public payable returns (bytes32) {
return
callRemoteWithOverrides(
_destination,
_router,
_ism,
_calls,
_hookMetadata,
_userSalt,
hook
);
}
/**
* @notice Dispatches a sequence of remote calls to be made by an owner's
* interchain account on the destination domain
* @dev Recommend using CallLib.build to format the interchain calls
* @param _destination The remote domain of the chain to make calls on
* @param _router The remote router address
* @param _ism The remote ISM address
* @param _calls The sequence of calls to make
* @param _hookMetadata The hook metadata to override with for the hook set by the owner
* @param _salt Salt which allows control over account derivation.
* @param _hook The hook to use after sending our message to the mailbox
* @return The Hyperlane message ID
*/
function callRemoteWithOverrides(
uint32 _destination,
bytes32 _router,
bytes32 _ism,
CallLib.Call[] calldata _calls,
bytes memory _hookMetadata,
bytes32 _salt,
IPostDispatchHook _hook
) public payable returns (bytes32) {
emit RemoteCallDispatched(
_destination,
msg.sender,
_router,
_ism,
_salt
);
bytes memory _body = InterchainAccountMessage.encode(
msg.sender,
_ism,
_calls,
_salt
);
return
_dispatchMessageWithHook(
_destination,
_router,
_body,
_hookMetadata,
_hook
);
}
// refunds from the commit dispatch call used to fund reveal dispatch call
receive() external payable {}
/**
* @notice Dispatches a commitment and reveal message to the destination domain.
* Useful for when we want to keep calldata secret (e.g. when executing a swap
* @dev The commitment message is dispatched first, followed by the reveal message.
* To find the calladata, the user must fetch the calldata from the url provided by the OffChainLookupIsm
* specified in the _ccipReadIsm parameter.
* The revealed calladata is executed by the `revealAndExecute` function, which will be called the OffChainLookupIsm in its `verify` function.
* @param _destination The remote domain of the chain to make calls on
* @param _router The remote router address
* @param _ism The remote ISM address
* @param _hookMetadata The hook metadata to override with for the hook set by the owner
* @param _salt Salt which allows control over account derivation.
* @param _hook The hook to use after sending our message to the mailbox
* @param _commitment The commitment to dispatch
* @return _commitmentMsgId The Hyperlane message ID of the commitment message
* @return _revealMsgId The Hyperlane message ID of the reveal message
*/
function callRemoteCommitReveal(
uint32 _destination,
bytes32 _router,
bytes32 _ism,
bytes32 _ccipReadIsm,
bytes memory _hookMetadata,
IPostDispatchHook _hook,
bytes32 _salt,
bytes32 _commitment
) public payable returns (bytes32 _commitmentMsgId, bytes32 _revealMsgId) {
bytes memory _commitmentMsg = InterchainAccountMessage
.encodeCommitment({
_owner: msg.sender.addressToBytes32(),
_ism: _ism,
_commitment: _commitment,
_userSalt: _salt
});
emit RemoteCallDispatched(
_destination,
msg.sender,
_router,
_ism,
_salt
);
emit CommitRevealDispatched(_commitment);
_commitmentMsgId = _dispatchMessageWithValue(
_destination,
_router,
_commitmentMsg,
StandardHookMetadata.formatMetadata(
0,
COMMIT_TX_GAS_USAGE,
address(this),
bytes("")
),
_hook,
msg.value
);
bytes memory _revealMsg = InterchainAccountMessage.encodeReveal({
_ism: _ccipReadIsm,
_commitment: _commitment
});
_revealMsgId = _dispatchMessageWithValue(
_destination,
_router,
_revealMsg,
_hookMetadata,
_hook,
address(this).balance
);
}
function callRemoteCommitReveal(
uint32 _destination,
bytes32 _router,
bytes32 _ism,
bytes memory _hookMetadata,
IPostDispatchHook _hook,
bytes32 _salt,
bytes32 _commitment
) public payable returns (bytes32 _commitmentMsgId, bytes32 _revealMsgId) {
return
callRemoteCommitReveal(
_destination,
_router,
_ism,
bytes32(0),
_hookMetadata,
_hook,
_salt,
_commitment
);
}
function callRemoteCommitReveal(
uint32 _destination,
bytes32 _commitment,
uint _gasLimit
) public payable returns (bytes32 _commitmentMsgId, bytes32 _revealMsgId) {
bytes32 _router = routers(_destination);
bytes32 _ism = isms[_destination];
bytes memory hookMetadata = StandardHookMetadata.formatMetadata(
0,
_gasLimit,
msg.sender,
bytes("")
);
return
callRemoteCommitReveal(
_destination,
_router,
_ism,
bytes32(0),
hookMetadata,
hook,
InterchainAccountMessage.EMPTY_SALT,
_commitment
);
}
// ============ Internal Functions ============
function _implementationBytecode(
address router
) internal pure returns (bytes memory) {
return
abi.encodePacked(
type(OwnableMulticall).creationCode,
abi.encode(router)
);
}
function _proxyBytecodeHash(
address _implementation
) internal pure returns (bytes32) {
return keccak256(MinimalProxy.bytecode(_implementation));
}
/**
* @dev Required for use of Router, compiler will not include this function in the bytecode
*/
function _handle(uint32, bytes32, bytes calldata) internal pure override {
assert(false);
}
/**
* @notice Overrides Router._enrollRemoteRouter to also enroll a default ISM
* @param _destination The remote domain
* @param _address The address of the remote InterchainAccountRouter
* @dev Sets the default ISM to the zero address
*/
function _enrollRemoteRouter(
uint32 _destination,
bytes32 _address
) internal override {
_enrollRemoteRouterAndIsm(
_destination,
_address,
InterchainAccountMessage.EMPTY_SALT
);
}
// ============ Private Functions ============
/**
* @notice Registers the address of a remote ISM contract to use as default
* @param _destination The remote domain
* @param _ism The address of the remote ISM
*/
function _enrollRemoteIsm(uint32 _destination, bytes32 _ism) private {
isms[_destination] = _ism;
emit RemoteIsmEnrolled(_destination, _ism);
}
/**
* @notice Registers the address of remote InterchainAccountRouter
* and ISM contracts to use as a default when making interchain calls
* @param _destination The remote domain
* @param _router The address of the remote InterchainAccountRouter
* @param _ism The address of the remote ISM
*/
function _enrollRemoteRouterAndIsm(
uint32 _destination,
bytes32 _router,
bytes32 _ism
) private {
require(
routers(_destination) == InterchainAccountMessage.EMPTY_SALT &&
isms[_destination] == InterchainAccountMessage.EMPTY_SALT,
"router and ISM defaults are immutable once set"
);
Router._enrollRemoteRouter(_destination, _router);
_enrollRemoteIsm(_destination, _ism);
}
/**
* @notice Dispatches an InterchainAccountMessage to the remote router
* @param _destination The remote domain
* @param _router The address of the remote InterchainAccountRouter
* @param _body The InterchainAccountMessage body
*/
function _dispatchMessage(
uint32 _destination,
bytes32 _router,
bytes memory _body
) private returns (bytes32) {
return
_dispatchMessageWithMetadata(
_destination,
_router,
_body,
bytes("")
);
}
/**
* @notice Dispatches an InterchainAccountMessage to the remote router with hook metadata
* @param _destination The remote domain
* @param _router The address of the remote InterchainAccountRouter
* @param _body The InterchainAccountMessage body
* @param _hookMetadata The hook metadata to override with for the hook set by the owner
*/
function _dispatchMessageWithMetadata(
uint32 _destination,
bytes32 _router,
bytes memory _body,
bytes memory _hookMetadata
) private returns (bytes32) {
return
_dispatchMessageWithHook(
_destination,
_router,
_body,
_hookMetadata,
hook
);
}
/**
* @notice Dispatches an InterchainAccountMessage to the remote router with hook metadata
* @param _destination The remote domain
* @param _router The address of the remote InterchainAccountRouter
* @param _body The InterchainAccountMessage body
* @param _hookMetadata The hook metadata to override with for the hook set by the owner
* @param _hook The hook to use after sending our message to the mailbox
*/
function _dispatchMessageWithHook(
uint32 _destination,
bytes32 _router,
bytes memory _body,
bytes memory _hookMetadata,
IPostDispatchHook _hook
) private returns (bytes32) {
return
_dispatchMessageWithValue(
_destination,
_router,
_body,
_hookMetadata,
_hook,
msg.value
);
}
/**
* @notice Dispatches an InterchainAccountMessage to the remote router using a `value` parameter for msg.value
* @param _value The amount to pass as `msg.value` to the mailbox.dispatch()
*/
function _dispatchMessageWithValue(
uint32 _destination,
bytes32 _router,
bytes memory _body,
bytes memory _hookMetadata,
IPostDispatchHook _hook,
uint _value
) private returns (bytes32) {
require(_router != bytes32(0), "no router specified for destination");
return
mailbox.dispatch{value: _value}(
_destination,
_router,
_body,
_hookMetadata,
_hook
);
}
/**
* @notice Returns the salt used to deploy an interchain account
* @param _origin The remote origin domain of the interchain account
* @param _owner The remote owner of the interchain account
* @param _router The remote origin InterchainAccountRouter
* @param _ism The local address of the ISM
* @param _userSalt Salt provided by the user, allows control over account derivation.
* @return The CREATE2 salt used for deploying the interchain account
*/
function _getSalt(
uint32 _origin,
bytes32 _owner,
bytes32 _router,
bytes32 _ism,
bytes32 _userSalt
) private pure returns (bytes32) {
return
keccak256(
abi.encodePacked(_origin, _owner, _router, _ism, _userSalt)
);
}
/**
* @notice Returns the address of the interchain account on the local chain
* @param _salt The CREATE2 salt used for deploying the interchain account
* @return The address of the interchain account
*/
function _getLocalInterchainAccount(
bytes32 _salt
) private view returns (address payable) {
return payable(Create2.computeAddress(_salt, bytecodeHash));
}
/**
* @notice Returns the gas payment required to dispatch a message to the given domain's router.
* @param _destination The domain of the destination router.
* @param _gasLimit The gas limit that the calls will use.
* @return _gasPayment Payment computed by the registered hooks via MailboxClient.
*/
function quoteGasPayment(
uint32 _destination,
uint256 _gasLimit
) public view returns (uint256 _gasPayment) {
return
_Router_quoteDispatch(
_destination,
new bytes(0),
StandardHookMetadata.overrideGasLimit(_gasLimit),
address(hook)
);
}
/**
* @notice Returns the gas payment required to dispatch a message to the given domain's router.
* @param _destination The domain of the destination router.
* @return _gasPayment Payment computed by the registered hooks via MailboxClient.
*/
function quoteGasPayment(
uint32 _destination
) public view returns (uint256 _gasPayment) {
return
_Router_quoteDispatch(
_destination,
bytes(""),
bytes(""),
address(hook)
);
}
/**
* @notice Returns the payment required to commit reveal to the destination router.
* @param _destination The domain of the destination router.
* @param gasLimit The gas limit that the reveal calls will use.
* @return _gasPayment Payment computed by the registered hooks via MailboxClient.
*/
function quoteGasForCommitReveal(
uint32 _destination,
uint256 gasLimit
) external view returns (uint256 _gasPayment) {
return
_Router_quoteDispatch(
_destination,
new bytes(0),
StandardHookMetadata.overrideGasLimit(COMMIT_TX_GAS_USAGE),
address(hook)
) + quoteGasPayment(_destination, gasLimit);
}
}
"
},
"contracts/middleware/libs/OwnableMulticall.sol": {
"content": "// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.13;
// ============ Internal Imports ============
import {CallLib} from "./Call.sol";
/*
* @title OwnableMulticall
* @dev Permits immutable owner address to execute calls with value to other contracts.
*/
contract OwnableMulticall {
/// @dev The owner will be the ICA Router that deployed this contract (via CREATE2).
address public immutable owner;
constructor(address _owner) {
require(_owner != address(0), "OwnableMulticall: invalid owner");
owner = _owner;
}
modifier onlyOwner() {
require(msg.sender == owner, "!owner");
_;
}
function multicall(
CallLib.Call[] calldata calls
) external payable onlyOwner {
return CallLib.multicall(calls);
}
/// @notice A mapping of commitment hashes to status
mapping(bytes32 commitmentHash => bool isPendingExecution)
public commitments;
event CommitmentSet(bytes32 indexed commitmentHash);
event CommitmentExecuted(bytes32 indexed commitmentHash);
/// @notice Sets the commitment value that will be executed next
/// @param _commitment The new commitment value to be set
function setCommitment(bytes32 _commitment) external onlyOwner {
require(
!commitments[_commitment],
"ICA: Previous commitment pending execution"
);
commitments[_commitment] = true;
emit CommitmentSet(_commitment);
}
/// @dev The calls represented by the commitment can only be executed once per commitment,
/// though you can submit the same commitment again after the calls have been executed.
function revealAndExecute(
CallLib.Call[] calldata calls,
bytes32 salt
) external payable returns (bytes32 executedCommitment) {
// Check if metadata matches stored commitment (checks)
bytes32 revealedHash = keccak256(
abi.encodePacked(salt, abi.encode(calls))
);
require(commitments[revealedHash], "ICA: Invalid Reveal");
// Delete the commitment (effects)
executedCommitment = revealedHash;
delete commitments[revealedHash];
emit CommitmentExecuted(executedCommitment);
// Execute the calls (interactions)
CallLib.multicall(calls);
return executedCommitment;
}
// solhint-disable-next-line no-empty-blocks
receive() external payable {}
}
"
},
"contracts/middleware/libs/InterchainAccountMessage.sol": {
"content": "// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.0;
import {CallLib} from "./Call.sol";
import {TypeCasts} from "../../libs/TypeCasts.sol";
/**
* Format of CALLS message:
* [ 0: 1] MessageType.CALLS (uint8)
* [ 1: 33] ICA owner (bytes32)
* [ 33: 65] ICA ISM (bytes32)
* [ 65: 97] User Salt (bytes32)
* [ 97:????] Calls (CallLib.Call[]), abi encoded
*
* Format of COMMITMENT message:
* [ 0: 1] MessageType.COMMITMENT (uint8)
* [ 1: 33] ICA owner (bytes32)
* [ 33: 65] ICA ISM (bytes32)
* [ 65: 97] User Salt (bytes32)
* [ 97: 129] Commitment (bytes32)
*/
library InterchainAccountMessage {
using TypeCasts for bytes32;
using TypeCasts for address;
enum MessageType {
CALLS,
COMMITMENT,
REVEAL
}
bytes32 internal constant EMPTY_SALT = bytes32(0);
/**
* @notice Returns formatted (packed) InterchainAccountMessage
* @dev This function should only be used in memory message construction.
* @param _owner The owner of the interchain account
* @param _ism The address of the remote ISM
* @param _calls The sequence of calls to make
* @return Formatted message body
*/
function encode(
address _owner,
bytes32 _ism,
CallLib.Call[] calldata _calls
) internal pure returns (bytes memory) {
return encode(TypeCasts.addressToBytes32(_owner), _ism, _calls);
}
/**
* @notice Returns formatted (packed) InterchainAccountMessage
* @dev This function should only be used in memory message construction.
* @param _owner The owner of the interchain account
* @param _ism The address of the remote ISM
* @param _calls The sequence of calls to make
* @return Formatted message body
*/
function encode(
bytes32 _owner,
bytes32 _ism,
CallLib.Call[] calldata _calls
) internal pure returns (bytes memory) {
return encode(_owner, _ism, _calls, EMPTY_SALT);
}
/**
* @notice Returns formatted (packed) InterchainAccountMessage
* @dev This function should only be used in memory message construction.
* @param _owner The owner of the interchain account
* @param _ism The address of the remote ISM
* @param _calls The sequence of calls to make
* @return Formatted message body
*/
function encode(
address _owner,
bytes32 _ism,
CallLib.Call[] calldata _calls,
bytes32 _userSalt
) internal pure returns (bytes memory) {
return
encode(TypeCasts.addressToBytes32(_owner), _ism, _calls, _userSalt);
}
/**
* @notice Returns formatted (packed) InterchainAccountMessage
* @dev This function should only be used in memory message construction.
* @param _owner The owner of the interchain account
* @param _ism The address of the remote ISM
* @param _calls The sequence of calls to make
* @return Formatted message body
*/
function encode(
bytes32 _owner,
bytes32 _ism,
CallLib.Call[] calldata _calls,
bytes32 _userSalt
) internal pure returns (bytes memory) {
bytes memory prefix = abi.encodePacked(
MessageType.CALLS,
_owner,
_ism,
_userSalt
);
bytes memory suffix = abi.encode(_calls);
return bytes.concat(prefix, suffix);
}
/**
* @notice Returns formatted (packed) InterchainAccountMessage
* @dev This function should only be used in memory message construction.
* @param _owner The owner of the interchain account
* @param _ism The address of the remote ISM
* @return Formatted message body
*/
function encodeCommitment(
bytes32 _owner,
bytes32 _ism,
bytes32 _commitment,
bytes32 _userSalt
) internal pure returns (bytes memory) {
return
abi.encodePacked(
MessageType.COMMITMENT,
_owner,
_ism,
_userSalt,
_commitment
);
}
function encodeReveal(
bytes32 _ism,
bytes32 _commitment
) internal pure returns (bytes memory) {
return abi.encodePacked(MessageType.REVEAL, _ism, _commitment);
}
function messageType(
bytes calldata _message
) internal pure returns (MessageType) {
return MessageType(uint8(_message[0]));
}
function owner(bytes calldata _message) internal pure returns (bytes32) {
return bytes32(_message[1:33]);
}
/**
* @notice Parses and returns the ISM address from the provided message
* @param _message The interchain account message
* @return The ISM encoded in the message
*/
function ism(bytes calldata _message) internal pure returns (bytes32) {
return bytes32(_message[33:65]);
}
function salt(bytes calldata _message) internal pure returns (bytes32) {
return bytes32(_message[65:97]);
}
function calls(
bytes calldata _message
) internal pure returns (CallLib.Call[] memory) {
return abi.decode(_message[97:], (CallLib.Call[]));
}
function commitment(
bytes calldata _message
) internal pure returns (bytes32) {
return bytes32(_message[97:]);
}
}
/**
* Format of REVEAL message:
* [ 0: 1] MessageType.REVEAL (uint8)
* [ 1: 33] ICA ISM (bytes32)
* [ 33: 65] Commitment (bytes32)
*/
library InterchainAccountMessageReveal {
function revealIsm(
bytes calldata _message
) internal pure returns (bytes32) {
return bytes32(_message[1:33]);
}
function revealCommitment(
bytes calldata _message
) internal pure returns (bytes32) {
return bytes32(_message[33:65]);
}
}
"
},
"contracts/middleware/libs/Call.sol": {
"content": "// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.13;
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
import {TypeCasts} from "../../libs/TypeCasts.sol";
library CallLib {
struct StaticCall {
// supporting non EVM targets
bytes32 to;
bytes data;
}
struct Call {
// supporting non EVM targets
bytes32 to;
uint256 value;
bytes data;
}
struct StaticCallWithCallback {
StaticCall _call;
bytes callback;
}
function call(
Call memory _call
) internal returns (bytes memory returnData) {
return
Address.functionCallWithValue(
TypeCasts.bytes32ToAddress(_call.to),
_call.data,
_call.value
);
}
function staticcall(
StaticCall memory _call
) private view returns (bytes memory) {
return
Address.functionStaticCall(
TypeCasts.bytes32ToAddress(_call.to),
_call.data
);
}
function staticcall(
StaticCallWithCallback memory _call
) internal view returns (bytes memory callback) {
return bytes.concat(_call.callback, staticcall(_call._call));
}
function multicall(Call[] memory calls) internal {
uint256 i = 0;
uint256 len = calls.length;
while (i < len) {
call(calls[i]);
unchecked {
++i;
}
}
}
function multistaticcall(
StaticCallWithCallback[] memory _calls
) internal view returns (bytes[] memory) {
uint256 i = 0;
uint256 len = _calls.length;
bytes[] memory callbacks = new bytes[](len);
while (i < len) {
callbacks[i] = staticcall(_calls[i]);
unchecked {
++i;
}
}
return callbacks;
}
function multicallto(address to, bytes[] memory calls) internal {
uint256 i = 0;
uint256 len = calls.length;
while (i < len) {
Address.functionCall(to, calls[i]);
unchecked {
++i;
}
}
}
function build(
bytes32 to,
bytes memory data
) internal pure returns (StaticCall memory) {
return StaticCall(to, data);
}
function build(
address to,
bytes memory data
) internal pure returns (StaticCall memory) {
return build(TypeCasts.addressToBytes32(to), data);
}
function build(
bytes32 to,
uint256 value,
bytes memory data
) internal pure returns (Call memory) {
return Call(to, value, data);
}
function build(
address to,
uint256 value,
bytes memory data
) internal pure returns (Call memory) {
return Call(TypeCasts.addressToBytes32(to), value, data);
}
function build(
bytes32 to,
bytes memory data,
bytes memory callback
) internal pure returns (StaticCallWithCallback memory) {
return StaticCallWithCallback(build(to, data), callback);
}
function build(
address to,
bytes memory data,
bytes memory callback
) internal pure returns (StaticCallWithCallback memory) {
return StaticCallWithCallback(build(to, data), callback);
}
}
"
},
"contracts/libs/MinimalProxy.sol": {
"content": "// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.6.11;
// Library for building bytecode of minimal proxies (see https://eips.ethereum.org/EIPS/eip-1167)
library MinimalProxy {
bytes20 private constant PREFIX =
hex"3d602d80600a3d3981f3363d3d373d3d3d363d73";
bytes15 private constant SUFFIX = hex"5af43d82803e903d91602b57fd5bf3";
function create(address implementation) internal returns (address proxy) {
bytes memory _bytecode = bytecode(implementation);
assembly {
proxy := create(0, add(_bytecode, 32), mload(_bytecode))
}
}
function bytecode(
address implementation
) internal pure returns (bytes memory) {
return abi.encodePacked(PREFIX, bytes20(implementation), SUFFIX);
}
}
"
},
"contracts/libs/TypeCasts.sol": {
"content": "// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.6.11;
library TypeCasts {
// alignment preserving cast
function addressToBytes32(address _addr) internal pure returns (bytes32) {
return bytes32(uint256(uint160(_addr)));
}
// alignment preserving cast
function bytes32ToAddress(bytes32 _buf) internal pure returns (address) {
require(
uint256(_buf) <= uint256(type(uint160).max),
"TypeCasts: bytes32ToAddress overflow"
);
return address(uint160(uint256(_buf)));
}
}
"
},
"contracts/hooks/libs/StandardHookMetadata.sol": {
"content": "// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.0;
/*@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@ HYPERLANE @@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@*/
/**
* Format of metadata:
*
* [0:2] variant
* [2:34] msg.value
* [34:66] Gas limit for message (IGP)
* [66:86] Refund address for message (IGP)
* [86:] Custom metadata
*/
library StandardHookMetadata {
struct Metadata {
uint16 variant;
uint256 msgValue;
uint256 gasLimit;
address refundAddress;
}
uint8 private constant VARIANT_OFFSET = 0;
uint8 private constant MSG_VALUE_OFFSET = 2;
uint8 private constant GAS_LIMIT_OFFSET = 34;
uint8 private constant REFUND_ADDRESS_OFFSET = 66;
uint256 private constant MIN_METADATA_LENGTH = 86;
uint16 public constant VARIANT = 1;
/**
* @notice Returns the variant of the metadata.
* @param _metadata ABI encoded standard hook metadata.
* @return variant of the metadata as uint8.
*/
function variant(bytes calldata _metadata) internal pure returns (uint16) {
if (_metadata.length < VARIANT_OFFSET + 2) return 0;
return uint16(bytes2(_metadata[VARIANT_OFFSET:VARIANT_OFFSET + 2]));
}
/**
* @notice Returns the specified value for the message.
* @param _metadata ABI encoded standard hook metadata.
* @param _default Default fallback value.
* @return Value for the message as uint256.
*/
function msgValue(
bytes calldata _metadata,
uint256 _default
) internal pure returns (uint256) {
if (_metadata.length < MSG_VALUE_OFFSET + 32) return _default;
return
uint256(bytes32(_metadata[MSG_VALUE_OFFSET:MSG_VALUE_OFFSET + 32]));
}
/**
* @notice Returns the specified gas limit for the message.
* @param _metadata ABI encoded standard hook metadata.
* @param _default Default fallback gas limit.
* @return Gas limit for the message as uint256.
*/
function gasLimit(
bytes calldata _metadata,
uint256 _default
) internal pure returns (uint256) {
if (_metadata.length < GAS_LIMIT_OFFSET + 32) return _default;
return
uint256(bytes32(_metadata[GAS_LIMIT_OFFSET:GAS_LIMIT_OFFSET + 32]));
}
function gasLimit(
bytes memory _metadata
) internal pure returns (uint256 _gasLimit) {
if (_metadata.length < GAS_LIMIT_OFFSET + 32) return 50_000;
assembly {
_gasLimit := mload(add(_metadata, add(0x20, GAS_LIMIT_OFFSET)))
}
}
/**
* @notice Returns the specified refund address for the message.
* @param _metadata ABI encoded standard hook metadata.
* @param _default Default fallback refund address.
* @return Refund address for the message as address.
*/
function refundAddress(
bytes calldata _metadata,
address _default
) internal pure returns (address) {
if (_metadata.length < REFUND_ADDRESS_OFFSET + 20) return _default;
return
address(
bytes20(
_metadata[REFUND_ADDRESS_OFFSET:REFUND_ADDRESS_OFFSET + 20]
)
);
}
/**
* @notice Returns any custom metadata.
* @param _metadata ABI encoded standard hook metadata.
* @return Custom metadata.
*/
function getCustomMetadata(
bytes calldata _metadata
) internal pure returns (bytes calldata) {
if (_metadata.length < MIN_METADATA_LENGTH) return _metadata[0:0];
return _metadata[MIN_METADATA_LENGTH:];
}
/**
* @notice Formats the specified gas limit and refund address into standard hook metadata.
* @param _msgValue msg.value for the message.
* @param _gasLimit Gas limit for the message.
* @param _refundAddress Refund address for the message.
* @return ABI encoded standard hook metadata.
*/
function format(
uint256 _msgValue,
uint256 _gasLimit,
address _refundAddress
) internal pure returns (bytes memory) {
return abi.encodePacked(VARIANT, _msgValue, _gasLimit, _refundAddress);
}
/**
/**
* @notice Formats the specified gas limit and refund address into standard hook metadata.
* @param _msgValue msg.value for the message.
* @param _gasLimit Gas limit for the message.
* @param _refundAddress Refund address for the message.
* @param _customMetadata Additional metadata to include in the standard hook metadata.
* @return ABI encoded standard hook metadata.
*/
function formatMetadata(
uint256 _msgValue,
uint256 _gasLimit,
address _refundAddress,
bytes memory _customMetadata
) internal pure returns (bytes memory) {
return
abi.encodePacked(
VARIANT,
_msgValue,
_gasLimit,
_refundAddress,
_customMetadata
);
}
/**
* @notice Formats the specified gas limit and refund address into standard hook metadata.
* @param _msgValue msg.value for the message.
* @return ABI encoded standard hook metadata.
*/
function overrideMsgValue(
uint256 _msgValue
) internal view returns (bytes memory) {
return formatMetadata(_msgValue, uint256(0), msg.sender, "");
}
/**
* @notice Formats the specified gas limit and refund address into standard hook metadata.
* @param _gasLimit Gas limit for the message.
* @return ABI encoded standard hook metadata.
*/
function overrideGasLimit(
uint256 _gasLimit
) internal view returns (bytes memory) {
return formatMetadata(uint256(0), _gasLimit, msg.sender, "");
}
/**
* @notice Formats the specified refund address into standard hook metadata.
* @param _refundAddress Refund address for the message.
* @return ABI encoded standard hook metadata.
*/
function overrideRefundAddress(
address _refundAddress
) internal pure returns (bytes memory) {
return formatMetadata(uint256(0), uint256(0), _refundAddress, "");
}
function getRefundAddress(
bytes memory _metadata,
address _default
) internal pure returns (address) {
if (_metadata.length < REFUND_ADDRESS_OFFSET + 20) return _default;
address result;
assembly {
let data_start_ptr := add(_metadata, 32) // Skip length prefix of _metadata
let mload_ptr := add(data_start_ptr, sub(REFUND_ADDRESS_OFFSET, 12))
result := mload(mload_ptr) // Loads 32 bytes; address takes lower 20 bytes.
}
return result;
}
}
"
},
"contracts/client/Router.sol": {
"content": "// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.6.11;
// ============ Internal Imports ============
import {IMessageRecipient} from "../interfaces/IMessageRecipient.sol";
import {IPostDispatchHook} from "../interfaces/hooks/IPostDispatchHook.sol";
import {MailboxClient} from "./MailboxClient.sol";
import {EnumerableMapExtended} from "../libs/EnumerableMapExtended.sol";
// ============ External Imports ============
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
abstract contract Router is MailboxClient, IMessageRecipient {
using EnumerableMapExtended for EnumerableMapExtended.UintToBytes32Map;
using Strings for uint32;
// ============ Mutable Storage ============
/// @dev Mapping of domain => router. For a given domain we have one router we send/receive messages from.
EnumerableMapExtended.UintToBytes32Map internal _routers;
uint256[48] private __GAP; // gap for upgrade safety
constructor(address _mailbox) MailboxClient(_mailbox) {}
// ============ External functions ============
function domains() external view returns (uint32[] memory) {
return _routers.uint32Keys();
}
/**
* @notice Returns the address of the Router contract for the given domain
* @param _domain The remote domain ID.
* @dev Returns 0 address if no router is enrolled for the given domain
* @return router The address of the Router contract for the given domain
*/
function routers(uint32 _domain) public view virtual returns (bytes32) {
(, bytes32 _router) = _routers.tryGet(_domain);
return _router;
}
/**
* @notice Unregister the domain
* @param _domain The domain of the remote Application Router
*/
function unenrollRemoteRouter(uint32 _domain) external virtual onlyOwner {
_unenrollRemoteRouter(_domain);
}
/**
* @notice Register the address of a Router contract for the same Application on a remote chain
* @param _domain The domain of the remote Application Router
* @param _router The address of the remote Application Router
*/
function enrollRemoteRouter(
uint32 _domain,
bytes32 _router
) external virtual onlyOwner {
_enrollRemoteRouter(_domain, _router);
}
/**
* @notice Batch version of `enrollRemoteRouter`
* @param _domains The domains of the remote Application Routers
* @param _addresses The addresses of the remote Application Routers
*/
function enrollRemoteRouters(
uint32[] calldata _domains,
bytes32[] calldata _addresses
) external virtual onlyOwner {
require(_domains.length == _addresses.length, "!length");
uint256 length = _domains.length;
for (uint256 i = 0; i < length; i += 1) {
_enrollRemoteRouter(_domains[i], _addresses[i]);
}
}
/**
* @notice Batch version of `unenrollRemoteRouter`
* @param _domains The domains of the remote Application Routers
*/
function unenrollRemoteRouters(
uint32[] calldata _domains
) external virtual onlyOwner {
uint256 length = _domains.length;
for (uint256 i = 0; i < length; i += 1) {
_unenrollRemoteRouter(_domains[i]);
}
}
/**
* @notice Handles an incoming message
* @param _origin The origin domain
* @param _sender The sender address
* @param _message The message
*/
function handle(
uint32 _origin,
bytes32 _sender,
bytes calldata _message
) external payable virtual override onlyMailbox {
bytes32 _router = _mustHave
Submitted on: 2025-10-06 12:34:49
Comments
Log in to comment.
No comments yet.