parent
f6ad1e9a95
commit
943b0162fa
@ -0,0 +1,137 @@ |
|||||||
|
// SPDX-License-Identifier: MIT |
||||||
|
|
||||||
|
pragma solidity >=0.5.0; |
||||||
|
|
||||||
|
import "./ILayerZeroUserApplicationConfig.sol"; |
||||||
|
|
||||||
|
interface ILayerZeroEndpoint is ILayerZeroUserApplicationConfig { |
||||||
|
// @notice send a LayerZero message to the specified address at a LayerZero endpoint. |
||||||
|
// @param _dstChainId - the destination chain identifier |
||||||
|
// @param _destination - the address on destination chain (in bytes). address length/format may vary by chains |
||||||
|
// @param _payload - a custom bytes payload to send to the destination contract |
||||||
|
// @param _refundAddress - if the source transaction is cheaper than the amount of value passed, refund the additional amount to this address |
||||||
|
// @param _zroPaymentAddress - the address of the ZRO token holder who would pay for the transaction |
||||||
|
// @param _adapterParams - parameters for custom functionality. e.g. receive airdropped native gas from the relayer on destination |
||||||
|
function send( |
||||||
|
uint16 _dstChainId, |
||||||
|
bytes calldata _destination, |
||||||
|
bytes calldata _payload, |
||||||
|
address payable _refundAddress, |
||||||
|
address _zroPaymentAddress, |
||||||
|
bytes calldata _adapterParams |
||||||
|
) external payable; |
||||||
|
|
||||||
|
// @notice used by the messaging library to publish verified payload |
||||||
|
// @param _srcChainId - the source chain identifier |
||||||
|
// @param _srcAddress - the source contract (as bytes) at the source chain |
||||||
|
// @param _dstAddress - the address on destination chain |
||||||
|
// @param _nonce - the unbound message ordering nonce |
||||||
|
// @param _gasLimit - the gas limit for external contract execution |
||||||
|
// @param _payload - verified payload to send to the destination contract |
||||||
|
function receivePayload( |
||||||
|
uint16 _srcChainId, |
||||||
|
bytes calldata _srcAddress, |
||||||
|
address _dstAddress, |
||||||
|
uint64 _nonce, |
||||||
|
uint256 _gasLimit, |
||||||
|
bytes calldata _payload |
||||||
|
) external; |
||||||
|
|
||||||
|
// @notice get the inboundNonce of a lzApp from a source chain which could be EVM or non-EVM chain |
||||||
|
// @param _srcChainId - the source chain identifier |
||||||
|
// @param _srcAddress - the source chain contract address |
||||||
|
function getInboundNonce(uint16 _srcChainId, bytes calldata _srcAddress) |
||||||
|
external |
||||||
|
view |
||||||
|
returns (uint64); |
||||||
|
|
||||||
|
// @notice get the outboundNonce from this source chain which, consequently, is always an EVM |
||||||
|
// @param _srcAddress - the source chain contract address |
||||||
|
function getOutboundNonce(uint16 _dstChainId, address _srcAddress) |
||||||
|
external |
||||||
|
view |
||||||
|
returns (uint64); |
||||||
|
|
||||||
|
// @notice gets a quote in source native gas, for the amount that send() requires to pay for message delivery |
||||||
|
// @param _dstChainId - the destination chain identifier |
||||||
|
// @param _userApplication - the user app address on this EVM chain |
||||||
|
// @param _payload - the custom message to send over LayerZero |
||||||
|
// @param _payInZRO - if false, user app pays the protocol fee in native token |
||||||
|
// @param _adapterParam - parameters for the adapter service, e.g. send some dust native token to dstChain |
||||||
|
function estimateFees( |
||||||
|
uint16 _dstChainId, |
||||||
|
address _userApplication, |
||||||
|
bytes calldata _payload, |
||||||
|
bool _payInZRO, |
||||||
|
bytes calldata _adapterParam |
||||||
|
) external view returns (uint256 nativeFee, uint256 zroFee); |
||||||
|
|
||||||
|
// @notice get this Endpoint's immutable source identifier |
||||||
|
function getChainId() external view returns (uint16); |
||||||
|
|
||||||
|
// @notice the interface to retry failed message on this Endpoint destination |
||||||
|
// @param _srcChainId - the source chain identifier |
||||||
|
// @param _srcAddress - the source chain contract address |
||||||
|
// @param _payload - the payload to be retried |
||||||
|
function retryPayload( |
||||||
|
uint16 _srcChainId, |
||||||
|
bytes calldata _srcAddress, |
||||||
|
bytes calldata _payload |
||||||
|
) external; |
||||||
|
|
||||||
|
// @notice query if any STORED payload (message blocking) at the endpoint. |
||||||
|
// @param _srcChainId - the source chain identifier |
||||||
|
// @param _srcAddress - the source chain contract address |
||||||
|
function hasStoredPayload(uint16 _srcChainId, bytes calldata _srcAddress) |
||||||
|
external |
||||||
|
view |
||||||
|
returns (bool); |
||||||
|
|
||||||
|
// @notice query if the _libraryAddress is valid for sending msgs. |
||||||
|
// @param _userApplication - the user app address on this EVM chain |
||||||
|
function getSendLibraryAddress(address _userApplication) |
||||||
|
external |
||||||
|
view |
||||||
|
returns (address); |
||||||
|
|
||||||
|
// @notice query if the _libraryAddress is valid for receiving msgs. |
||||||
|
// @param _userApplication - the user app address on this EVM chain |
||||||
|
function getReceiveLibraryAddress(address _userApplication) |
||||||
|
external |
||||||
|
view |
||||||
|
returns (address); |
||||||
|
|
||||||
|
// @notice query if the non-reentrancy guard for send() is on |
||||||
|
// @return true if the guard is on. false otherwise |
||||||
|
function isSendingPayload() external view returns (bool); |
||||||
|
|
||||||
|
// @notice query if the non-reentrancy guard for receive() is on |
||||||
|
// @return true if the guard is on. false otherwise |
||||||
|
function isReceivingPayload() external view returns (bool); |
||||||
|
|
||||||
|
// @notice get the configuration of the LayerZero messaging library of the specified version |
||||||
|
// @param _version - messaging library version |
||||||
|
// @param _chainId - the chainId for the pending config change |
||||||
|
// @param _userApplication - the contract address of the user application |
||||||
|
// @param _configType - type of configuration. every messaging library has its own convention. |
||||||
|
function getConfig( |
||||||
|
uint16 _version, |
||||||
|
uint16 _chainId, |
||||||
|
address _userApplication, |
||||||
|
uint256 _configType |
||||||
|
) external view returns (bytes memory); |
||||||
|
|
||||||
|
// @notice get the send() LayerZero messaging library version |
||||||
|
// @param _userApplication - the contract address of the user application |
||||||
|
function getSendVersion(address _userApplication) |
||||||
|
external |
||||||
|
view |
||||||
|
returns (uint16); |
||||||
|
|
||||||
|
// @notice get the lzReceive() LayerZero messaging library version |
||||||
|
// @param _userApplication - the contract address of the user application |
||||||
|
function getReceiveVersion(address _userApplication) |
||||||
|
external |
||||||
|
view |
||||||
|
returns (uint16); |
||||||
|
} |
@ -0,0 +1,17 @@ |
|||||||
|
// SPDX-License-Identifier: MIT |
||||||
|
|
||||||
|
pragma solidity >=0.5.0; |
||||||
|
|
||||||
|
interface ILayerZeroReceiver { |
||||||
|
// @notice LayerZero endpoint will invoke this function to deliver the message on the destination |
||||||
|
// @param _srcChainId - the source endpoint identifier |
||||||
|
// @param _srcAddress - the source sending contract address from the source chain |
||||||
|
// @param _nonce - the ordered message nonce |
||||||
|
// @param _payload - the signed payload is the UA bytes has encoded to be sent |
||||||
|
function lzReceive( |
||||||
|
uint16 _srcChainId, |
||||||
|
bytes calldata _srcAddress, |
||||||
|
uint64 _nonce, |
||||||
|
bytes calldata _payload |
||||||
|
) external; |
||||||
|
} |
@ -0,0 +1,31 @@ |
|||||||
|
// SPDX-License-Identifier: MIT |
||||||
|
|
||||||
|
pragma solidity >=0.5.0; |
||||||
|
|
||||||
|
interface ILayerZeroUserApplicationConfig { |
||||||
|
// @notice set the configuration of the LayerZero messaging library of the specified version |
||||||
|
// @param _version - messaging library version |
||||||
|
// @param _chainId - the chainId for the pending config change |
||||||
|
// @param _configType - type of configuration. every messaging library has its own convention. |
||||||
|
// @param _config - configuration in the bytes. can encode arbitrary content. |
||||||
|
function setConfig( |
||||||
|
uint16 _version, |
||||||
|
uint16 _chainId, |
||||||
|
uint256 _configType, |
||||||
|
bytes calldata _config |
||||||
|
) external; |
||||||
|
|
||||||
|
// @notice set the send() LayerZero messaging library version to _version |
||||||
|
// @param _version - new messaging library version |
||||||
|
function setSendVersion(uint16 _version) external; |
||||||
|
|
||||||
|
// @notice set the lzReceive() LayerZero messaging library version to _version |
||||||
|
// @param _version - new messaging library version |
||||||
|
function setReceiveVersion(uint16 _version) external; |
||||||
|
|
||||||
|
// @notice Only when the UA needs to resume the message flow in blocking mode and clear the stored payload |
||||||
|
// @param _srcChainId - the chainId of the source chain |
||||||
|
// @param _srcAddress - the contract address of the source contract at the source chain |
||||||
|
function forceResumeReceive(uint16 _srcChainId, bytes calldata _srcAddress) |
||||||
|
external; |
||||||
|
} |
@ -0,0 +1,350 @@ |
|||||||
|
// SPDX-License-Identifier: Apache-2.0 |
||||||
|
pragma solidity ^0.8.13; |
||||||
|
|
||||||
|
import {Router} from "../../Router.sol"; |
||||||
|
import {TypeCasts} from "../../libs/TypeCasts.sol"; |
||||||
|
import {IMessageRecipient} from "../../interfaces/IMessageRecipient.sol"; |
||||||
|
import {ILayerZeroEndpoint} from "../../interfaces/middleware/layerzero/ILayerZeroEndpoint.sol"; |
||||||
|
import {ILayerZeroReceiver} from "../../interfaces/middleware/layerzero/ILayerZeroReceiver.sol"; |
||||||
|
|
||||||
|
/** |
||||||
|
* @title LayerZeroRouter |
||||||
|
* @notice Example of middleware to use hyperlane in a layerzero app on layerZero |
||||||
|
* @dev Implemented send() and a virtual lzReceive(). |
||||||
|
* @dev Please make sure to edit lzReceive() and setEstGasAmount() to match gas usage of lzReceive() in your app |
||||||
|
* @dev Run `forge test --match-contract LayerZeroRouterTest` to see tests |
||||||
|
*/ |
||||||
|
|
||||||
|
abstract contract LayerZeroRouter is Router, ILayerZeroEndpoint { |
||||||
|
mapping(uint16 => uint32) layerZeroToHyperlaneDomain; |
||||||
|
mapping(uint32 => uint16) hyperlaneToLayerZeroDomain; |
||||||
|
|
||||||
|
ILayerZeroReceiver public layerZeroReceiver; |
||||||
|
|
||||||
|
error LayerZeroDomainNotMapped(uint16); |
||||||
|
error HyperlaneDomainNotMapped(uint32); |
||||||
|
|
||||||
|
uint256 estGasAmount; |
||||||
|
|
||||||
|
function initialize(address _owner, address _mailbox) public initializer { |
||||||
|
_transferOwnership(_owner); |
||||||
|
__Router_initialize(_mailbox); |
||||||
|
} |
||||||
|
|
||||||
|
function initialize( |
||||||
|
address _owner, |
||||||
|
address _mailbox, |
||||||
|
address _interchainGasPaymaster |
||||||
|
) public initializer { |
||||||
|
_transferOwnership(_owner); |
||||||
|
__Router_initialize(_mailbox, _interchainGasPaymaster); |
||||||
|
} |
||||||
|
|
||||||
|
function initialize( |
||||||
|
address _owner, |
||||||
|
address _mailbox, |
||||||
|
address _interchainGasPaymaster, |
||||||
|
address _interchainSecurityModule |
||||||
|
) public initializer { |
||||||
|
_transferOwnership(_owner); |
||||||
|
__Router_initialize( |
||||||
|
_mailbox, |
||||||
|
_interchainGasPaymaster, |
||||||
|
_interchainSecurityModule |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @notice Adds a domain ID mapping from layerZeroDomain/hyperlaneDomain domain IDs and vice versa |
||||||
|
* @param _layerZeroDomains An array of layerZeroDomain domain IDs |
||||||
|
* @param _hyperlaneDomains An array of hyperlaneDomain domain IDs |
||||||
|
*/ |
||||||
|
function mapDomains( |
||||||
|
uint16[] calldata _layerZeroDomains, |
||||||
|
uint32[] calldata _hyperlaneDomains |
||||||
|
) external onlyOwner { |
||||||
|
for (uint256 i = 0; i < _layerZeroDomains.length; i += 1) { |
||||||
|
layerZeroToHyperlaneDomain[ |
||||||
|
_layerZeroDomains[i] |
||||||
|
] = _hyperlaneDomains[i]; |
||||||
|
hyperlaneToLayerZeroDomain[ |
||||||
|
_hyperlaneDomains[i] |
||||||
|
] = _layerZeroDomains[i]; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @notice Gets layerZero domain ID from hyperlane domain ID |
||||||
|
* @param _hyperlaneDomain The hyperlane domain ID |
||||||
|
*/ |
||||||
|
function getLayerZeroDomain(uint32 _hyperlaneDomain) |
||||||
|
public |
||||||
|
view |
||||||
|
returns (uint16 layerZeroDomain) |
||||||
|
{ |
||||||
|
layerZeroDomain = hyperlaneToLayerZeroDomain[_hyperlaneDomain]; |
||||||
|
if (layerZeroDomain == 0) { |
||||||
|
revert HyperlaneDomainNotMapped(_hyperlaneDomain); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @notice Gets hyperlane domain ID from layerZero domain ID |
||||||
|
* @param _layerZeroDomain The layerZero domain ID |
||||||
|
*/ |
||||||
|
function getHyperlaneDomain(uint16 _layerZeroDomain) |
||||||
|
public |
||||||
|
view |
||||||
|
returns (uint32 hyperlaneDomain) |
||||||
|
{ |
||||||
|
hyperlaneDomain = layerZeroToHyperlaneDomain[_layerZeroDomain]; |
||||||
|
if (hyperlaneDomain == 0) { |
||||||
|
revert LayerZeroDomainNotMapped(_layerZeroDomain); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @notice handles the version Adapter Parameters for LayerZero |
||||||
|
* @param _adapterParams The adapter params used in LayerZero sends |
||||||
|
*/ |
||||||
|
function _interpretAdapterParamsV1(bytes memory _adapterParams) |
||||||
|
internal |
||||||
|
pure |
||||||
|
returns (uint256 gasAmount) |
||||||
|
{ |
||||||
|
uint16 version; |
||||||
|
require(_adapterParams.length == 34, "Please check your adapterparams"); |
||||||
|
(version, gasAmount) = abi.decode(_adapterParams, (uint16, uint256)); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @notice handles the version Adapter Parameters for LayerZero |
||||||
|
* @param _adapterParams The adapter params used in LayerZero sends |
||||||
|
*/ |
||||||
|
function _interpretAdapterParamsV2(bytes memory _adapterParams) |
||||||
|
internal |
||||||
|
pure |
||||||
|
returns ( |
||||||
|
uint256 gasAmount, |
||||||
|
uint256 nativeForDst, |
||||||
|
address addressOnDst |
||||||
|
) |
||||||
|
{ |
||||||
|
require(_adapterParams.length == 86, "Please check your adapterparams"); |
||||||
|
uint16 version; |
||||||
|
(version, gasAmount, nativeForDst, addressOnDst) = abi.decode( |
||||||
|
_adapterParams, |
||||||
|
(uint16, uint256, uint256, address) |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
function splitAddress(bytes memory hexString) |
||||||
|
public |
||||||
|
pure |
||||||
|
returns (address, address) |
||||||
|
{ |
||||||
|
// bytes memory byteArray = bytes(hexString); |
||||||
|
require( |
||||||
|
hexString.length == 40, |
||||||
|
"Input string must be 40 characters long" |
||||||
|
); |
||||||
|
|
||||||
|
bytes20 firstAddress; |
||||||
|
bytes20 secondAddress; |
||||||
|
|
||||||
|
assembly { |
||||||
|
firstAddress := mload(add(hexString, 0x20)) |
||||||
|
secondAddress := mload(add(hexString, 0x30)) |
||||||
|
} |
||||||
|
|
||||||
|
return (address(firstAddress), address(secondAddress)); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @notice Sends a hyperlane message using LayerZero endpoint interface |
||||||
|
* @dev NOTE: Layerzero's documentation is inconsistent in github vs docs. Following: https://layerzero.gitbook.io/docs/evm-guides/master/how-to-send-a-message |
||||||
|
* @param _dstChainId - the destination chain identifier |
||||||
|
* @param _remoteAndLocalAddresses - remote address concated with local address packed into 40 bytes |
||||||
|
* @param _payload - the payload to be sent to the destination chain |
||||||
|
* @param _refundAddress - the address to refund the gas fees to |
||||||
|
* @param _zroPaymentAddress - not used (only for LayerZero) |
||||||
|
* @param _adapterParams - the adapter params used in LayerZero sends |
||||||
|
*/ |
||||||
|
function send( |
||||||
|
uint16 _dstChainId, |
||||||
|
bytes memory _remoteAndLocalAddresses, |
||||||
|
bytes calldata _payload, |
||||||
|
address payable _refundAddress, |
||||||
|
address _zroPaymentAddress, |
||||||
|
bytes memory _adapterParams |
||||||
|
) external payable override { |
||||||
|
uint32 dstChainId32 = layerZeroToHyperlaneDomain[_dstChainId]; |
||||||
|
_mustHaveRemoteRouter(dstChainId32); |
||||||
|
address remoteAddr; |
||||||
|
address localAddr; |
||||||
|
if (_remoteAndLocalAddresses.length == 40) { |
||||||
|
(remoteAddr, localAddr) = splitAddress(_remoteAndLocalAddresses); |
||||||
|
} else if (_remoteAndLocalAddresses.length == 32) { |
||||||
|
remoteAddr = abi.decode(_remoteAndLocalAddresses, (address)); |
||||||
|
} else { |
||||||
|
revert("Invalid remote and local addresses"); |
||||||
|
} |
||||||
|
|
||||||
|
bytes memory adapterParams; |
||||||
|
uint256 gasFees; |
||||||
|
if (_adapterParams.length > 0) { |
||||||
|
if (_adapterParams.length == 33) { |
||||||
|
gasFees = _interpretAdapterParamsV1(_adapterParams); |
||||||
|
} else if (_adapterParams.length == 86) { |
||||||
|
uint256 nativeForDst; |
||||||
|
address addressOnDst; |
||||||
|
( |
||||||
|
gasFees, |
||||||
|
nativeForDst, |
||||||
|
addressOnDst |
||||||
|
) = _interpretAdapterParamsV2(_adapterParams); |
||||||
|
} else { |
||||||
|
revert("Invalid adapter params"); |
||||||
|
} |
||||||
|
} else { |
||||||
|
(gasFees, ) = estimateFees( |
||||||
|
_dstChainId, |
||||||
|
msg.sender, |
||||||
|
_payload, |
||||||
|
_zroPaymentAddress != address(0x0), |
||||||
|
adapterParams |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
require(msg.value >= gasFees, "Not enough fee for gas"); |
||||||
|
|
||||||
|
bytes32 _messageId = mailbox.dispatch( |
||||||
|
dstChainId32, |
||||||
|
TypeCasts.addressToBytes32(remoteAddr), |
||||||
|
_payload |
||||||
|
); |
||||||
|
|
||||||
|
interchainGasPaymaster.payForGas{value: msg.value}( |
||||||
|
_messageId, |
||||||
|
dstChainId32, |
||||||
|
gasFees, |
||||||
|
_refundAddress |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @notice The internal Router `handle` function which extracts the true recipient of the message and passes the translated hyperlane domain ID to lzReceive |
||||||
|
* @param _originHyperlaneDomain the origin domain as specified by Hyperlane |
||||||
|
* @param _sender The sender address |
||||||
|
* @param _message The wrapped message to include sender and recipient |
||||||
|
*/ |
||||||
|
function handle( |
||||||
|
uint32 _originHyperlaneDomain, |
||||||
|
bytes32 _sender, |
||||||
|
bytes calldata _message |
||||||
|
) |
||||||
|
public |
||||||
|
override |
||||||
|
onlyMailbox |
||||||
|
onlyRemoteRouter(_originHyperlaneDomain, _sender) |
||||||
|
{ |
||||||
|
_handle(_originHyperlaneDomain, _sender, _message); |
||||||
|
} |
||||||
|
|
||||||
|
function _handle( |
||||||
|
uint32 _originHyperlaneDomain, |
||||||
|
bytes32 _sender, |
||||||
|
bytes calldata _message |
||||||
|
) internal override { |
||||||
|
uint16 srcChainId = getLayerZeroDomain(_originHyperlaneDomain); |
||||||
|
lzReceive(srcChainId, _sender, 0, _message); //Note nonce does not exist in hyperlane on the destination chain |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @notice Originally LayerZero endpoint which will be evoked by this contract's handle function |
||||||
|
* @dev override from ILayerZeroEndpoint.sol |
||||||
|
* @param _srcChainId - the source endpoint identifier |
||||||
|
* @param _srcAddress - the source sending contract address from the source chain |
||||||
|
* @param _nonce - the ordered message nonce (not used in Hyperlane) |
||||||
|
* @param _payload - the signed payload is the UA bytes has encoded to be sent |
||||||
|
*/ |
||||||
|
function lzReceive( |
||||||
|
uint16 _srcChainId, |
||||||
|
bytes32 _srcAddress, |
||||||
|
uint64 _nonce, |
||||||
|
bytes memory _payload |
||||||
|
) public virtual {} |
||||||
|
|
||||||
|
/** |
||||||
|
* @notice Gets a quote in source native gas, for the amount that send() requires to pay for message delivery |
||||||
|
* @dev override from ILayerZeroEndpoint.sol |
||||||
|
* @param _dstChainId - the destination chain identifier |
||||||
|
* @param _userApplication - the user app address on this EVM chain |
||||||
|
* @param _payload - the custom message to send over LayerZero |
||||||
|
* @param _payInZRO - if false, user app pays the protocol fee in native token |
||||||
|
* @param _adapterParams - parameters for the adapter service, e.g. send some dust native token to dstChain |
||||||
|
*/ |
||||||
|
function estimateFees( |
||||||
|
uint16 _dstChainId, |
||||||
|
address _userApplication, |
||||||
|
bytes memory _payload, |
||||||
|
bool _payInZRO, |
||||||
|
bytes memory _adapterParams |
||||||
|
) public view override returns (uint256 nativeFee, uint256 zroFee) { |
||||||
|
require(estGasAmount > 0, "Please set gas amount"); |
||||||
|
return ( |
||||||
|
interchainGasPaymaster.quoteGasPayment( |
||||||
|
layerZeroToHyperlaneDomain[_dstChainId], |
||||||
|
estGasAmount |
||||||
|
), |
||||||
|
0 |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @notice Sets the gas amount for the estimateFees function since this will depend upon gas your lzreceive() uses |
||||||
|
* @dev Used for showcase and testing suggest editing getGasAmount |
||||||
|
* @param _gas The amount of gas to set |
||||||
|
*/ |
||||||
|
|
||||||
|
function setEstGasAmount(uint256 _gas) external onlyOwner { |
||||||
|
estGasAmount = _gas; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @notice Gets the gas amount for the estimateFees function |
||||||
|
* @dev Please override this to however you wish to calculate your gas usage on destiniation chain |
||||||
|
* @param _payload The payload to be sent to the destination chain |
||||||
|
*/ |
||||||
|
|
||||||
|
function getEstGasAmount(bytes memory _payload) |
||||||
|
public |
||||||
|
view |
||||||
|
returns (uint256) |
||||||
|
{ |
||||||
|
return estGasAmount; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @notice Gets the chain ID of the current chain |
||||||
|
* @dev override from ILayerZeroEndpoint.sol -- NOTE OVERFLOW RISK |
||||||
|
*/ |
||||||
|
function getChainId() external view override returns (uint16) { |
||||||
|
return hyperlaneToLayerZeroDomain[mailbox.localDomain()]; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @notice Gets the mailbox count this source chain since hyperlane does not have nonce |
||||||
|
* @dev override from ILayerZeroEndpoint.sol |
||||||
|
* @param _dstChainId - the destination chain identifier |
||||||
|
* @param _srcAddress - the source chain contract address |
||||||
|
* |
||||||
|
*/ |
||||||
|
function getOutboundNonce(uint16 _dstChainId, address _srcAddress) |
||||||
|
external |
||||||
|
view |
||||||
|
returns (uint64) |
||||||
|
{ |
||||||
|
return uint64(mailbox.count()); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,161 @@ |
|||||||
|
// SPDX-License-Identifier: Apache-2.0 |
||||||
|
pragma solidity ^0.8.13; |
||||||
|
|
||||||
|
import {Router} from "../../Router.sol"; |
||||||
|
import {TypeCasts} from "../../libs/TypeCasts.sol"; |
||||||
|
import {IMessageRecipient} from "../../interfaces/IMessageRecipient.sol"; |
||||||
|
import {ILayerZeroEndpoint} from "../../interfaces/middleware/layerzero/ILayerZeroEndpoint.sol"; |
||||||
|
import {ILayerZeroReceiver} from "../../interfaces/middleware/layerzero/ILayerZeroReceiver.sol"; |
||||||
|
import {LayerZeroRouter} from "./LayerZeroRouter.sol"; |
||||||
|
|
||||||
|
/** |
||||||
|
* @title MockLayerZeroRouter |
||||||
|
* @dev Used for testing LayerZeroRouter |
||||||
|
*/ |
||||||
|
|
||||||
|
contract MockLayerZeroRouter is LayerZeroRouter { |
||||||
|
/** |
||||||
|
* @notice Originally LayerZero endpoint which will be evoked by this contract's handle function |
||||||
|
* @dev override from ILayerZeroEndpoint.sol -- NEED UPDATING |
||||||
|
* @param _srcChainId - the source endpoint identifier |
||||||
|
* @param _srcAddress - the source sending contract address from the source chain |
||||||
|
* @param _nonce - the ordered message nonce (not used in Hyperlane) |
||||||
|
* @param _payload - the signed payload is the UA bytes has encoded to be sent |
||||||
|
*/ |
||||||
|
function lzReceive( |
||||||
|
uint16 _srcChainId, |
||||||
|
bytes32 _srcAddress, |
||||||
|
uint64 _nonce, |
||||||
|
bytes memory _payload |
||||||
|
) public override {} |
||||||
|
|
||||||
|
/** |
||||||
|
* @dev Below are the functions that are not supported in the interface "ILayerZeroEndpoint" due to way hyperlane is structured |
||||||
|
*/ |
||||||
|
|
||||||
|
function receivePayload( |
||||||
|
uint16 _srcChainId, |
||||||
|
bytes calldata _srcAddress, |
||||||
|
address _dstAddress, |
||||||
|
uint64 _nonce, |
||||||
|
uint256 _gasLimit, |
||||||
|
bytes calldata _payload |
||||||
|
) external override { |
||||||
|
//Not supported |
||||||
|
} |
||||||
|
|
||||||
|
function getInboundNonce(uint16 _srcChainId, bytes calldata _srcAddress) |
||||||
|
external |
||||||
|
view |
||||||
|
override |
||||||
|
returns (uint64) |
||||||
|
{ |
||||||
|
//Not supported |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
function retryPayload( |
||||||
|
uint16 _srcChainId, |
||||||
|
bytes calldata _srcAddress, |
||||||
|
bytes calldata _payload |
||||||
|
) external override { |
||||||
|
//Not supported |
||||||
|
} |
||||||
|
|
||||||
|
function hasStoredPayload(uint16 _srcChainId, bytes calldata _srcAddress) |
||||||
|
external |
||||||
|
view |
||||||
|
override |
||||||
|
returns (bool) |
||||||
|
{ |
||||||
|
//Not supported |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
function getSendLibraryAddress(address _userApplication) |
||||||
|
external |
||||||
|
view |
||||||
|
override |
||||||
|
returns (address) |
||||||
|
{ |
||||||
|
//Not supported |
||||||
|
return address(0); |
||||||
|
} |
||||||
|
|
||||||
|
function getReceiveLibraryAddress(address _userApplication) |
||||||
|
external |
||||||
|
view |
||||||
|
override |
||||||
|
returns (address) |
||||||
|
{ |
||||||
|
//Not supported |
||||||
|
return address(0); |
||||||
|
} |
||||||
|
|
||||||
|
function isSendingPayload() external view override returns (bool) { |
||||||
|
//Not supported |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
function isReceivingPayload() external view override returns (bool) { |
||||||
|
//Not supported |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
function getConfig( |
||||||
|
uint16 _version, |
||||||
|
uint16 _chainId, |
||||||
|
address _userApplication, |
||||||
|
uint256 _configType |
||||||
|
) external view returns (bytes memory) { |
||||||
|
//Not supported |
||||||
|
return ""; |
||||||
|
} |
||||||
|
|
||||||
|
function getSendVersion(address _userApplication) |
||||||
|
external |
||||||
|
view |
||||||
|
override |
||||||
|
returns (uint16) |
||||||
|
{ |
||||||
|
//Not supported |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
function getReceiveVersion(address _userApplication) |
||||||
|
external |
||||||
|
view |
||||||
|
override |
||||||
|
returns (uint16) |
||||||
|
{ |
||||||
|
//Not supported |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @dev Below are the functions that are not supported in the interface "ILayerZeroUserApplicationConfig" due to way hyperlane is structured |
||||||
|
*/ |
||||||
|
function setConfig( |
||||||
|
uint16 _version, |
||||||
|
uint16 _chainId, |
||||||
|
uint256 _configType, |
||||||
|
bytes calldata _config |
||||||
|
) external override { |
||||||
|
//Not supported |
||||||
|
} |
||||||
|
|
||||||
|
function setSendVersion(uint16 _version) external override { |
||||||
|
//Not supported |
||||||
|
} |
||||||
|
|
||||||
|
function setReceiveVersion(uint16 _version) external override { |
||||||
|
//Not supported |
||||||
|
} |
||||||
|
|
||||||
|
function forceResumeReceive(uint16 _srcChainId, bytes calldata _srcAddress) |
||||||
|
external |
||||||
|
override |
||||||
|
{ |
||||||
|
//Not supported |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,195 @@ |
|||||||
|
// SPDX-License-Identifier: Apache-2.0 |
||||||
|
pragma solidity ^0.8.13; |
||||||
|
|
||||||
|
import "forge-std/Test.sol"; |
||||||
|
import "../../../contracts/test/TestRecipient.sol"; |
||||||
|
import {MockLayerZeroRouter} from "../../../contracts/middleware/layerzero/MockLayerZeroRouter.sol"; |
||||||
|
import {MockHyperlaneEnvironment, MockMailbox} from "../../../contracts/mock/MockHyperlaneEnvironment.sol"; |
||||||
|
|
||||||
|
import {TypeCasts} from "../../../contracts/libs/TypeCasts.sol"; |
||||||
|
|
||||||
|
contract LayerZeroRouterTest is Test { |
||||||
|
MockHyperlaneEnvironment testEnvironment; |
||||||
|
|
||||||
|
MockLayerZeroRouter originRouter; |
||||||
|
MockLayerZeroRouter destinationRouter; |
||||||
|
|
||||||
|
TestRecipient recipient; |
||||||
|
|
||||||
|
uint16 lzOriginDomain = 123; |
||||||
|
uint16 lzDestinationDomain = 321; |
||||||
|
|
||||||
|
uint32 hlOriginDomain = 456; |
||||||
|
uint32 hlDestinationDomain = 654; |
||||||
|
|
||||||
|
address owner = vm.addr(123); |
||||||
|
|
||||||
|
function setUp() public { |
||||||
|
console.log("Owner Address: %s", owner); |
||||||
|
|
||||||
|
originRouter = new MockLayerZeroRouter(); |
||||||
|
destinationRouter = new MockLayerZeroRouter(); |
||||||
|
|
||||||
|
testEnvironment = new MockHyperlaneEnvironment( |
||||||
|
hlOriginDomain, |
||||||
|
hlDestinationDomain |
||||||
|
); |
||||||
|
|
||||||
|
console.log("Origin Router Address: %s", address(originRouter)); |
||||||
|
console.log( |
||||||
|
"Origin Mailbox: %s", |
||||||
|
address(testEnvironment.mailboxes(hlOriginDomain)) |
||||||
|
); |
||||||
|
console.log( |
||||||
|
"Destination Router Address: %s", |
||||||
|
address(destinationRouter) |
||||||
|
); |
||||||
|
console.log( |
||||||
|
"Destination Mailbox: %s", |
||||||
|
address(testEnvironment.mailboxes(hlDestinationDomain)) |
||||||
|
); |
||||||
|
|
||||||
|
originRouter.initialize( |
||||||
|
owner, |
||||||
|
address(testEnvironment.mailboxes(hlOriginDomain)), |
||||||
|
address(testEnvironment.igps(hlOriginDomain)), |
||||||
|
address(testEnvironment.isms(hlOriginDomain)) |
||||||
|
); |
||||||
|
|
||||||
|
destinationRouter.initialize( |
||||||
|
owner, |
||||||
|
address(testEnvironment.mailboxes(hlDestinationDomain)), |
||||||
|
address(testEnvironment.igps(hlDestinationDomain)), |
||||||
|
address(testEnvironment.isms(hlDestinationDomain)) |
||||||
|
); |
||||||
|
|
||||||
|
uint16[] memory lzDomains = new uint16[](2); |
||||||
|
lzDomains[0] = lzOriginDomain; |
||||||
|
lzDomains[1] = lzDestinationDomain; |
||||||
|
|
||||||
|
uint32[] memory hlDomains = new uint32[](2); |
||||||
|
hlDomains[0] = hlOriginDomain; |
||||||
|
hlDomains[1] = hlDestinationDomain; |
||||||
|
|
||||||
|
originRouter.mapDomains(lzDomains, hlDomains); |
||||||
|
destinationRouter.mapDomains(lzDomains, hlDomains); |
||||||
|
|
||||||
|
originRouter.enrollRemoteRouter( |
||||||
|
hlDestinationDomain, |
||||||
|
TypeCasts.addressToBytes32(address(destinationRouter)) |
||||||
|
); |
||||||
|
destinationRouter.enrollRemoteRouter( |
||||||
|
hlOriginDomain, |
||||||
|
TypeCasts.addressToBytes32(address(originRouter)) |
||||||
|
); |
||||||
|
|
||||||
|
recipient = new TestRecipient(); |
||||||
|
|
||||||
|
//Set expected gas usage |
||||||
|
uint256 gasEstimate = 0.01 ether; |
||||||
|
originRouter.setEstGasAmount(gasEstimate); |
||||||
|
destinationRouter.setEstGasAmount(gasEstimate); |
||||||
|
} |
||||||
|
|
||||||
|
function testCanSendMessage(bytes calldata _messageBody) public { |
||||||
|
uint256 gasPrice = 100000000000000000000; //Need fix here |
||||||
|
|
||||||
|
address a = address(recipient); |
||||||
|
address b = msg.sender; |
||||||
|
bytes memory destination = abi.encodePacked(a, b); |
||||||
|
|
||||||
|
console.log("Recipient: %s", a); |
||||||
|
console.log("Sender: %s", b); |
||||||
|
|
||||||
|
bytes memory payload = abi.encode("abc"); |
||||||
|
|
||||||
|
originRouter.send{value: gasPrice}( |
||||||
|
lzDestinationDomain, |
||||||
|
destination, |
||||||
|
payload, |
||||||
|
payable(address(0)), |
||||||
|
address(0), |
||||||
|
"" |
||||||
|
); |
||||||
|
|
||||||
|
bytes32 senderAsBytes32 = TypeCasts.addressToBytes32( |
||||||
|
address(originRouter) |
||||||
|
); |
||||||
|
|
||||||
|
vm.expectCall( |
||||||
|
address(recipient), |
||||||
|
abi.encodeWithSelector( |
||||||
|
recipient.handle.selector, |
||||||
|
hlOriginDomain, |
||||||
|
senderAsBytes32, |
||||||
|
payload |
||||||
|
) |
||||||
|
); |
||||||
|
testEnvironment.processNextPendingMessage(); |
||||||
|
|
||||||
|
assertEq(recipient.lastData(), payload); |
||||||
|
assertEq(recipient.lastSender(), senderAsBytes32); |
||||||
|
|
||||||
|
bytes memory destinationAs32 = abi.encode(address(recipient)); |
||||||
|
|
||||||
|
originRouter.send{value: gasPrice}( |
||||||
|
lzDestinationDomain, |
||||||
|
destinationAs32, |
||||||
|
payload, |
||||||
|
payable(address(0)), |
||||||
|
address(0), |
||||||
|
"" |
||||||
|
); |
||||||
|
|
||||||
|
vm.expectCall( |
||||||
|
address(recipient), |
||||||
|
abi.encodeWithSelector( |
||||||
|
recipient.handle.selector, |
||||||
|
hlOriginDomain, |
||||||
|
senderAsBytes32, |
||||||
|
payload |
||||||
|
) |
||||||
|
); |
||||||
|
testEnvironment.processNextPendingMessage(); |
||||||
|
|
||||||
|
assertEq(recipient.lastData(), payload); |
||||||
|
assertEq(recipient.lastSender(), senderAsBytes32); |
||||||
|
} |
||||||
|
|
||||||
|
function testCanReceiveMessage(bytes calldata _messageBody) public { |
||||||
|
uint256 gasPrice = 100000000000000000000; //Need fix here |
||||||
|
|
||||||
|
address a = address(destinationRouter); |
||||||
|
address b = msg.sender; |
||||||
|
bytes memory destination = abi.encodePacked(a, b); |
||||||
|
|
||||||
|
console.log("Recipient: %s", a); |
||||||
|
console.log("Sender: %s", b); |
||||||
|
|
||||||
|
bytes memory payload = abi.encode("abc"); |
||||||
|
|
||||||
|
originRouter.send{value: gasPrice}( |
||||||
|
lzDestinationDomain, |
||||||
|
destination, |
||||||
|
payload, |
||||||
|
payable(address(0)), |
||||||
|
address(0), |
||||||
|
"" |
||||||
|
); |
||||||
|
|
||||||
|
bytes32 senderAsBytes32 = TypeCasts.addressToBytes32( |
||||||
|
address(originRouter) |
||||||
|
); |
||||||
|
|
||||||
|
vm.expectCall( |
||||||
|
address(destinationRouter), |
||||||
|
abi.encodeWithSelector( |
||||||
|
destinationRouter.handle.selector, |
||||||
|
hlOriginDomain, |
||||||
|
senderAsBytes32, |
||||||
|
payload |
||||||
|
) |
||||||
|
); |
||||||
|
testEnvironment.processNextPendingMessage(); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue