-
Notifications
You must be signed in to change notification settings - Fork 19
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
3fed585
commit 22fa681
Showing
2 changed files
with
356 additions
and
0 deletions.
There are no files selected for viewing
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,239 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.0; | ||
|
||
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; | ||
import "@layerzero/lzApp/interfaces/ILayerZeroReceiver.sol"; | ||
import "@layerzero/lzApp/interfaces/ILayerZeroUserApplicationConfig.sol"; | ||
import "@layerzero/lzApp/interfaces/ILayerZeroEndpoint.sol"; | ||
import "@layerzero/libraries/BytesLib.sol"; | ||
|
||
/* | ||
* a generic LzReceiver implementation | ||
*/ | ||
abstract contract LzApp is | ||
OwnableUpgradeable, | ||
ILayerZeroReceiver, | ||
ILayerZeroUserApplicationConfig | ||
{ | ||
using BytesLib for bytes; | ||
|
||
// ua can not send payload larger than this by default, but it can be changed by the ua owner | ||
uint public constant DEFAULT_PAYLOAD_SIZE_LIMIT = 10000; | ||
|
||
ILayerZeroEndpoint public lzEndpoint; | ||
mapping(uint16 => bytes) public trustedRemoteLookup; | ||
mapping(uint16 => mapping(uint16 => uint)) public minDstGasLookup; | ||
mapping(uint16 => uint) public payloadSizeLimitLookup; | ||
address public precrime; | ||
|
||
event SetPrecrime(address precrime); | ||
event SetTrustedRemote(uint16 _remoteChainId, bytes _path); | ||
event SetTrustedRemoteAddress(uint16 _remoteChainId, bytes _remoteAddress); | ||
event SetMinDstGas(uint16 _dstChainId, uint16 _type, uint _minDstGas); | ||
|
||
function __LzApp_init(address _endpoint) internal { | ||
if (address(lzEndpoint) != address(0)) return; | ||
lzEndpoint = ILayerZeroEndpoint(ILayerZeroEndpoint(_endpoint)); | ||
} | ||
|
||
function lzReceive( | ||
uint16 _srcChainId, | ||
bytes calldata _srcAddress, | ||
uint64 _nonce, | ||
bytes calldata _payload | ||
) public virtual override { | ||
// lzReceive must be called by the endpoint for security | ||
require( | ||
_msgSender() == address(lzEndpoint), | ||
"LzApp: invalid endpoint caller" | ||
); | ||
|
||
bytes memory trustedRemote = trustedRemoteLookup[_srcChainId]; | ||
// if will still block the message pathway from (srcChainId, srcAddress). should not receive message from untrusted remote. | ||
require( | ||
_srcAddress.length == trustedRemote.length && | ||
trustedRemote.length > 0 && | ||
keccak256(_srcAddress) == keccak256(trustedRemote), | ||
"LzApp: invalid source sending contract" | ||
); | ||
|
||
_blockingLzReceive(_srcChainId, _srcAddress, _nonce, _payload); | ||
} | ||
|
||
// abstract function - the default behaviour of LayerZero is blocking. See: NonblockingLzApp if you dont need to enforce ordered messaging | ||
function _blockingLzReceive( | ||
uint16 _srcChainId, | ||
bytes memory _srcAddress, | ||
uint64 _nonce, | ||
bytes memory _payload | ||
) internal virtual; | ||
|
||
function _lzSend( | ||
uint16 _dstChainId, | ||
bytes memory _payload, | ||
address payable _refundAddress, | ||
address _zroPaymentAddress, | ||
bytes memory _adapterParams, | ||
uint _nativeFee | ||
) internal virtual { | ||
bytes memory trustedRemote = trustedRemoteLookup[_dstChainId]; | ||
require( | ||
trustedRemote.length != 0, | ||
"LzApp: destination chain is not a trusted source" | ||
); | ||
_checkPayloadSize(_dstChainId, _payload.length); | ||
lzEndpoint.send{value: _nativeFee}( | ||
_dstChainId, | ||
trustedRemote, | ||
_payload, | ||
_refundAddress, | ||
_zroPaymentAddress, | ||
_adapterParams | ||
); | ||
} | ||
|
||
function _checkGasLimit( | ||
uint16 _dstChainId, | ||
uint16 _type, | ||
bytes memory _adapterParams, | ||
uint _extraGas | ||
) internal view virtual { | ||
uint providedGasLimit = _getGasLimit(_adapterParams); | ||
uint minGasLimit = minDstGasLookup[_dstChainId][_type]; | ||
require(minGasLimit > 0, "LzApp: minGasLimit not set"); | ||
require( | ||
providedGasLimit >= minGasLimit + _extraGas, | ||
"LzApp: gas limit is too low" | ||
); | ||
} | ||
|
||
function _getGasLimit( | ||
bytes memory _adapterParams | ||
) internal pure virtual returns (uint gasLimit) { | ||
require(_adapterParams.length >= 34, "LzApp: invalid adapterParams"); | ||
assembly { | ||
gasLimit := mload(add(_adapterParams, 34)) | ||
} | ||
} | ||
|
||
function _checkPayloadSize( | ||
uint16 _dstChainId, | ||
uint _payloadSize | ||
) internal view virtual { | ||
uint payloadSizeLimit = payloadSizeLimitLookup[_dstChainId]; | ||
if (payloadSizeLimit == 0) { | ||
// use default if not set | ||
payloadSizeLimit = DEFAULT_PAYLOAD_SIZE_LIMIT; | ||
} | ||
require( | ||
_payloadSize <= payloadSizeLimit, | ||
"LzApp: payload size is too large" | ||
); | ||
} | ||
|
||
//---------------------------UserApplication config---------------------------------------- | ||
function getConfig( | ||
uint16 _version, | ||
uint16 _chainId, | ||
address, | ||
uint _configType | ||
) external view returns (bytes memory) { | ||
return | ||
lzEndpoint.getConfig( | ||
_version, | ||
_chainId, | ||
address(this), | ||
_configType | ||
); | ||
} | ||
|
||
// generic config for LayerZero user Application | ||
function setConfig( | ||
uint16 _version, | ||
uint16 _chainId, | ||
uint _configType, | ||
bytes calldata _config | ||
) external override onlyOwner { | ||
lzEndpoint.setConfig(_version, _chainId, _configType, _config); | ||
} | ||
|
||
function setLzEndpoint(address _lzEndpoint) internal { | ||
lzEndpoint = ILayerZeroEndpoint(_lzEndpoint); | ||
} | ||
|
||
function setSendVersion(uint16 _version) external override onlyOwner { | ||
lzEndpoint.setSendVersion(_version); | ||
} | ||
|
||
function setReceiveVersion(uint16 _version) external override onlyOwner { | ||
lzEndpoint.setReceiveVersion(_version); | ||
} | ||
|
||
function forceResumeReceive( | ||
uint16 _srcChainId, | ||
bytes calldata _srcAddress | ||
) external override onlyOwner { | ||
lzEndpoint.forceResumeReceive(_srcChainId, _srcAddress); | ||
} | ||
|
||
// _path = abi.encodePacked(remoteAddress, localAddress) | ||
// this function set the trusted path for the cross-chain communication | ||
function setTrustedRemote( | ||
uint16 _remoteChainId, | ||
bytes calldata _path | ||
) external onlyOwner { | ||
trustedRemoteLookup[_remoteChainId] = _path; | ||
emit SetTrustedRemote(_remoteChainId, _path); | ||
} | ||
|
||
function setTrustedRemoteAddress( | ||
uint16 _remoteChainId, | ||
bytes memory _remoteAddress | ||
) internal { | ||
trustedRemoteLookup[_remoteChainId] = abi.encodePacked( | ||
_remoteAddress, | ||
address(this) | ||
); | ||
emit SetTrustedRemoteAddress(_remoteChainId, _remoteAddress); | ||
} | ||
|
||
function getTrustedRemoteAddress( | ||
uint16 _remoteChainId | ||
) external view returns (bytes memory) { | ||
bytes memory path = trustedRemoteLookup[_remoteChainId]; | ||
require(path.length != 0, "LzApp: no trusted path record"); | ||
return path.slice(0, path.length - 20); // the last 20 bytes should be address(this) | ||
} | ||
|
||
function setPrecrime(address _precrime) external onlyOwner { | ||
precrime = _precrime; | ||
emit SetPrecrime(_precrime); | ||
} | ||
|
||
function setMinDstGas( | ||
uint16 _dstChainId, | ||
uint16 _packetType, | ||
uint _minGas | ||
) external onlyOwner { | ||
minDstGasLookup[_dstChainId][_packetType] = _minGas; | ||
emit SetMinDstGas(_dstChainId, _packetType, _minGas); | ||
} | ||
|
||
// if the size is 0, it means default size limit | ||
function setPayloadSizeLimit( | ||
uint16 _dstChainId, | ||
uint _size | ||
) external onlyOwner { | ||
payloadSizeLimitLookup[_dstChainId] = _size; | ||
} | ||
|
||
//--------------------------- VIEW FUNCTION ---------------------------------------- | ||
function isTrustedRemote( | ||
uint16 _srcChainId, | ||
bytes calldata _srcAddress | ||
) external view returns (bool) { | ||
bytes memory trustedSource = trustedRemoteLookup[_srcChainId]; | ||
return keccak256(trustedSource) == keccak256(_srcAddress); | ||
} | ||
} |
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,117 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.0; | ||
|
||
import "./LzApp.sol"; | ||
import "@layerzero/libraries/ExcessivelySafeCall.sol"; | ||
|
||
/* | ||
* the default LayerZero messaging behaviour is blocking, i.e. any failed message will block the channel | ||
* this abstract class try-catch all fail messages and store locally for future retry. hence, non-blocking | ||
* NOTE: if the srcAddress is not configured properly, it will still block the message pathway from (srcChainId, srcAddress) | ||
*/ | ||
abstract contract NonblockingLzApp is LzApp { | ||
using ExcessivelySafeCall for address; | ||
|
||
mapping(uint16 => mapping(bytes => mapping(uint64 => bytes32))) | ||
public failedMessages; | ||
|
||
event MessageFailed( | ||
uint16 _srcChainId, | ||
bytes _srcAddress, | ||
uint64 _nonce, | ||
bytes _payload, | ||
bytes _reason | ||
); | ||
event RetryMessageSuccess( | ||
uint16 _srcChainId, | ||
bytes _srcAddress, | ||
uint64 _nonce, | ||
bytes32 _payloadHash | ||
); | ||
|
||
// overriding the virtual function in LzReceiver | ||
function _blockingLzReceive( | ||
uint16 _srcChainId, | ||
bytes memory _srcAddress, | ||
uint64 _nonce, | ||
bytes memory _payload | ||
) internal virtual override { | ||
(bool success, bytes memory reason) = address(this).excessivelySafeCall( | ||
gasleft(), | ||
150, | ||
abi.encodeWithSelector( | ||
this.nonblockingLzReceive.selector, | ||
_srcChainId, | ||
_srcAddress, | ||
_nonce, | ||
_payload | ||
) | ||
); | ||
if (!success) { | ||
_storeFailedMessage( | ||
_srcChainId, | ||
_srcAddress, | ||
_nonce, | ||
_payload, | ||
reason | ||
); | ||
} | ||
} | ||
|
||
function _storeFailedMessage( | ||
uint16 _srcChainId, | ||
bytes memory _srcAddress, | ||
uint64 _nonce, | ||
bytes memory _payload, | ||
bytes memory _reason | ||
) internal virtual { | ||
failedMessages[_srcChainId][_srcAddress][_nonce] = keccak256(_payload); | ||
emit MessageFailed(_srcChainId, _srcAddress, _nonce, _payload, _reason); | ||
} | ||
|
||
function nonblockingLzReceive( | ||
uint16 _srcChainId, | ||
bytes calldata _srcAddress, | ||
uint64 _nonce, | ||
bytes calldata _payload | ||
) public virtual { | ||
// only internal transaction | ||
require( | ||
_msgSender() == address(this), | ||
"NonblockingLzApp: caller must be LzApp" | ||
); | ||
_nonblockingLzReceive(_srcChainId, _srcAddress, _nonce, _payload); | ||
} | ||
|
||
//@notice override this function | ||
function _nonblockingLzReceive( | ||
uint16 _srcChainId, | ||
bytes memory _srcAddress, | ||
uint64 _nonce, | ||
bytes memory _payload | ||
) internal virtual; | ||
|
||
function retryMessage( | ||
uint16 _srcChainId, | ||
bytes calldata _srcAddress, | ||
uint64 _nonce, | ||
bytes calldata _payload | ||
) public payable virtual { | ||
// assert there is message to retry | ||
bytes32 payloadHash = failedMessages[_srcChainId][_srcAddress][_nonce]; | ||
require( | ||
payloadHash != bytes32(0), | ||
"NonblockingLzApp: no stored message" | ||
); | ||
require( | ||
keccak256(_payload) == payloadHash, | ||
"NonblockingLzApp: invalid payload" | ||
); | ||
// clear the stored message | ||
failedMessages[_srcChainId][_srcAddress][_nonce] = bytes32(0); | ||
// execute the message. revert if it fails again | ||
_nonblockingLzReceive(_srcChainId, _srcAddress, _nonce, _payload); | ||
emit RetryMessageSuccess(_srcChainId, _srcAddress, _nonce, payloadHash); | ||
} | ||
} |