-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: multichain hyperlane teller contract
- Loading branch information
Showing
5 changed files
with
489 additions
and
0 deletions.
There are no files selected for viewing
141 changes: 141 additions & 0 deletions
141
src/base/Roles/CrossChain/Hyperlane/StandardHookMetadata.sol
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,141 @@ | ||
// 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])); | ||
} | ||
|
||
/** | ||
* @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. | ||
* @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, ""); | ||
} | ||
} |
136 changes: 136 additions & 0 deletions
136
src/base/Roles/CrossChain/MultiChainHyperlaneTellerWithMultiAssetSupport.sol
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,136 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity 0.8.21; | ||
|
||
import { | ||
MultiChainTellerBase, | ||
MultiChainTellerBase_MessagesNotAllowedFrom, | ||
MultiChainTellerBase_MessagesNotAllowedFromSender, | ||
Chain | ||
} from "./MultiChainTellerBase.sol"; | ||
import { BridgeData, ERC20 } from "./CrossChainTellerBase.sol"; | ||
import { StandardHookMetadata } from "./Hyperlane/StandardHookMetadata.sol"; | ||
import { IMailbox } from "../../../interfaces/hyperlane/IMailbox.sol"; | ||
|
||
/** | ||
* @title MultiChainHyperlaneTellerWithMultiAssetSupport | ||
* @notice Hyperlane implementation of MultiChainTeller | ||
* @custom:security-contact [email protected] | ||
*/ | ||
contract MultiChainHyperlaneTellerWithMultiAssetSupport is MultiChainTellerBase { | ||
// ========================================= STATE ========================================= | ||
|
||
/** | ||
* @notice The hyperlane mailbox contract. | ||
*/ | ||
IMailbox public immutable mailbox; | ||
|
||
/** | ||
* @notice A nonce used to generate unique message IDs. | ||
*/ | ||
uint128 public nonce; | ||
|
||
//============================== ERRORS =============================== | ||
|
||
error MultiChainHyperlaneTeller_InvalidToken(); | ||
error MultiChainHyperlaneTeller_CallerMustBeMailbox(address caller); | ||
|
||
constructor( | ||
address _owner, | ||
address _vault, | ||
address _accountant, | ||
IMailbox _mailbox | ||
) | ||
MultiChainTellerBase(_owner, _vault, _accountant) | ||
{ | ||
mailbox = _mailbox; | ||
} | ||
|
||
/** | ||
* @notice function override to return the fee quote | ||
* @param shareAmount to be sent as a message | ||
* @param data Bridge data | ||
* @returns fee to be paid for bridging | ||
*/ | ||
function _quote(uint256 shareAmount, BridgeData calldata data) internal view override returns (uint256) { | ||
bytes memory _payload = abi.encode(shareAmount, data.destinationChainReceiver); | ||
bytes32 msgRecipient = _addressToBytes32(selectorToChains[data.chainSelector].targetTeller); | ||
|
||
return mailbox.quoteDispatch(data.chainSelector, msgRecipient, _payload); // TODO Should there be hook metadata | ||
// for quoteDispatch? | ||
} | ||
|
||
/** | ||
* @notice Called when data is received from the protocol. It overrides the equivalent function in the parent | ||
* contract. | ||
* Protocol messages are defined as packets, comprised of the following parameters. | ||
* @param origin A struct containing information about where the packet came from. | ||
* @param sender The contract that sent this message. | ||
* @param payload Encoded message. | ||
*/ | ||
function handle(uint32 origin, bytes32 sender, bytes calldata payload) external payable { | ||
_beforeReceive(); | ||
|
||
Chain memory chain = selectorToChains[origin]; | ||
|
||
// Three things must be checked. | ||
// 1. This function must only be called by the mailbox | ||
// 2. The sender must be the teller from the source chain | ||
// 3. The origin aka chainSelector must be allowed to send message to this | ||
// contract through the `Chain` config. | ||
|
||
// TODO How does setting the ISM work? Is it necessary? | ||
if (msg.sender != address(mailbox)) { | ||
revert MultiChainHyperlaneTeller_CallerMustBeMailbox(msg.sender); | ||
} | ||
|
||
// TODO check that bytes32 to address works properly | ||
if (sender != _addressToBytes32(chain.targetTeller)) { | ||
revert MultiChainTellerBase_MessagesNotAllowedFromSender(uint256(origin), _bytes32ToAddress(sender)); | ||
} | ||
|
||
if (!chain.allowMessagesFrom) { | ||
revert MultiChainTellerBase_MessagesNotAllowedFrom(origin); | ||
} | ||
|
||
(uint256 shareAmount, address receiver, bytes32 messageId) = abi.decode(payload, (uint256, address, bytes32)); | ||
vault.enter(address(0), ERC20(address(0)), 0, receiver, shareAmount); | ||
|
||
_afterReceive(shareAmount, receiver, messageId); | ||
} | ||
|
||
/** | ||
* @notice bridge override to allow bridge logic to be done for bridge() and depositAndBridge() | ||
* @param shareAmount to be moved across chain | ||
* @param data BridgeData | ||
* @return messageId a unique hash for the message | ||
*/ | ||
function _bridge(uint256 shareAmount, BridgeData calldata data) internal override returns (bytes32 messageId) { | ||
unchecked { | ||
messageId = keccak256(abi.encodePacked(++nonce, address(this), block.chainid)); | ||
} | ||
|
||
bytes memory _payload = abi.encode(shareAmount, data.destinationChainReceiver, messageId); | ||
|
||
// Unlike L0 that has a built in peer check, this contract must | ||
// constrain the message recipient itself. We do this by our own | ||
// configuration. | ||
bytes32 msgRecipient = _addressToBytes32(selectorToChains[data.chainSelector].targetTeller); | ||
|
||
bytes32 messageId = mailbox.dispatch{ value: msg.value }( | ||
data.chainSelector, // must be `destinationDomain` on hyperlane | ||
msgRecipient, // must be the teller address left-padded to bytes32 | ||
_payload, | ||
StandardHookMetadata.overrideGasLimit(data.messageGas) // Sets the refund address to msg.sender, sets | ||
// `_msgValue` | ||
// to zero | ||
); | ||
} | ||
|
||
function _addressToBytes32(address _address) internal pure returns (bytes32) { | ||
return bytes32(uint256(uint160(_address))); | ||
} | ||
|
||
function _bytes32ToAddress(bytes32 _address) internal pure returns (address) { | ||
return address(uint160(uint256(_address))); | ||
} | ||
} |
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,40 @@ | ||
// SPDX-License-Identifier: MIT OR Apache-2.0 | ||
pragma solidity >=0.6.11; | ||
|
||
interface IInterchainSecurityModule { | ||
enum Types { | ||
UNUSED, | ||
ROUTING, | ||
AGGREGATION, | ||
LEGACY_MULTISIG, | ||
MERKLE_ROOT_MULTISIG, | ||
MESSAGE_ID_MULTISIG, | ||
NULL, // used with relayer carrying no metadata | ||
CCIP_READ, | ||
ARB_L2_TO_L1, | ||
WEIGHTED_MERKLE_ROOT_MULTISIG, | ||
WEIGHTED_MESSAGE_ID_MULTISIG, | ||
OP_L2_TO_L1 | ||
} | ||
|
||
/** | ||
* @notice Returns an enum that represents the type of security model | ||
* encoded by this ISM. | ||
* @dev Relayers infer how to fetch and format metadata. | ||
*/ | ||
function moduleType() external view returns (uint8); | ||
|
||
/** | ||
* @notice Defines a security model responsible for verifying interchain | ||
* messages based on the provided metadata. | ||
* @param _metadata Off-chain metadata provided by a relayer, specific to | ||
* the security model encoded by the module (e.g. validator signatures) | ||
* @param _message Hyperlane encoded interchain message | ||
* @return True if the message was verified | ||
*/ | ||
function verify(bytes calldata _metadata, bytes calldata _message) external returns (bool); | ||
} | ||
|
||
interface ISpecifiesInterchainSecurityModule { | ||
function interchainSecurityModule() external view returns (IInterchainSecurityModule); | ||
} |
Oops, something went wrong.