diff --git a/solidity/contracts/interfaces/middleware/layerzero/ILayerZeroEndpoint.sol b/solidity/contracts/interfaces/middleware/layerzero/ILayerZeroEndpoint.sol new file mode 100644 index 000000000..f31dd03a5 --- /dev/null +++ b/solidity/contracts/interfaces/middleware/layerzero/ILayerZeroEndpoint.sol @@ -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); +} diff --git a/solidity/contracts/interfaces/middleware/layerzero/ILayerZeroReceiver.sol b/solidity/contracts/interfaces/middleware/layerzero/ILayerZeroReceiver.sol new file mode 100644 index 000000000..d02102b8f --- /dev/null +++ b/solidity/contracts/interfaces/middleware/layerzero/ILayerZeroReceiver.sol @@ -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; +} diff --git a/solidity/contracts/interfaces/middleware/layerzero/ILayerZeroUserApplicationConfig.sol b/solidity/contracts/interfaces/middleware/layerzero/ILayerZeroUserApplicationConfig.sol new file mode 100644 index 000000000..8e4886c5d --- /dev/null +++ b/solidity/contracts/interfaces/middleware/layerzero/ILayerZeroUserApplicationConfig.sol @@ -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; +} diff --git a/solidity/contracts/middleware/layerzero/LayerZeroRouter.sol b/solidity/contracts/middleware/layerzero/LayerZeroRouter.sol new file mode 100644 index 000000000..c9ed59273 --- /dev/null +++ b/solidity/contracts/middleware/layerzero/LayerZeroRouter.sol @@ -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()); + } +} diff --git a/solidity/contracts/middleware/layerzero/MockLayerZeroRouter.sol b/solidity/contracts/middleware/layerzero/MockLayerZeroRouter.sol new file mode 100644 index 000000000..e54a9082a --- /dev/null +++ b/solidity/contracts/middleware/layerzero/MockLayerZeroRouter.sol @@ -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 + } +} diff --git a/solidity/test/middleware/layerzero/LayerZeroRouter.t.sol b/solidity/test/middleware/layerzero/LayerZeroRouter.t.sol new file mode 100644 index 000000000..e280cc48c --- /dev/null +++ b/solidity/test/middleware/layerzero/LayerZeroRouter.t.sol @@ -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(); + } +}