Skip to content

Commit

Permalink
feat: oapp contracts
Browse files Browse the repository at this point in the history
  • Loading branch information
zouguangxian committed Dec 14, 2023
1 parent d36b847 commit 6c09f46
Show file tree
Hide file tree
Showing 50 changed files with 3,946 additions and 2 deletions.
21 changes: 21 additions & 0 deletions oapp/contracts/oapp/OApp.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

import { OAppSender } from "./OAppSender.sol";
// @dev import the origin so its exposed to OApp implementers
import { OAppReceiver, Origin } from "./OAppReceiver.sol";
import { OAppCore } from "./OAppCore.sol";

abstract contract OApp is OAppSender, OAppReceiver {
constructor(address _endpoint, address _owner) OAppCore(_endpoint, _owner) {}

function oAppVersion()
public
pure
virtual
override(OAppSender, OAppReceiver)
returns (uint64 senderVersion, uint64 receiverVersion)
{
return (SENDER_VERSION, RECEIVER_VERSION);
}
}
33 changes: 33 additions & 0 deletions oapp/contracts/oapp/OAppCore.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { IOAppCore, ILayerZeroEndpointV2 } from "./interfaces/IOAppCore.sol";

abstract contract OAppCore is IOAppCore, Ownable {
ILayerZeroEndpointV2 public immutable endpoint;
mapping(uint32 eid => bytes32 peer) public peers;

// TODO see if we can move to open zeppelin 5 with ownable(_owner) constructor
constructor(address _endpoint, address _owner) {
_transferOwnership(_owner);
endpoint = ILayerZeroEndpointV2(_endpoint);
endpoint.setDelegate(_owner); // by default, the owner is the delegate
}

// @dev must-have configurations for standard OApps
function setPeer(uint32 _eid, bytes32 _peer) public virtual onlyOwner {
peers[_eid] = _peer;
emit PeerSet(_eid, _peer);
}

function _getPeerOrRevert(uint32 _eid) internal view virtual returns (bytes32) {
bytes32 peer = peers[_eid];
if (peer == bytes32(0)) revert NoPeer(_eid);
return peer;
}

function setDelegate(address _delegate) public onlyOwner {
endpoint.setDelegate(_delegate);
}
}
47 changes: 47 additions & 0 deletions oapp/contracts/oapp/OAppReceiver.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

import { ILayerZeroReceiver, Origin } from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroReceiver.sol";
import { OAppCore } from "./OAppCore.sol";

abstract contract OAppReceiver is ILayerZeroReceiver, OAppCore {
error OnlyEndpoint(address addr);

uint64 internal constant RECEIVER_VERSION = 1;

function allowInitializePath(Origin calldata origin) public view virtual returns (bool) {
return peers[origin.srcEid] == origin.sender;
}

/// @dev path nonce starts from 1. if 0 it means that there is no specific nonce enforcement
/// @dev only used to guide the executor actions if the app specify the msg execution to be ordered.
function nextNonce(uint32 /*_srcEid*/, bytes32 /*_sender*/) public view virtual returns (uint64 nonce) {
return 0;
}

function oAppVersion() public view virtual returns (uint64 senderVersion, uint64 receiverVersion) {
return (0, RECEIVER_VERSION);
}

// @dev entry point for receiving msg/packet from the endpoint
function lzReceive(
Origin calldata _origin,
bytes32 _guid,
bytes calldata _message,
address _executor,
bytes calldata _extraData
) public payable virtual {
if (address(endpoint) != msg.sender) revert OnlyEndpoint(msg.sender);
if (_getPeerOrRevert(_origin.srcEid) != _origin.sender) revert OnlyPeer(_origin.srcEid, _origin.sender);
_lzReceive(_origin, _guid, _message, _executor, _extraData);
}

// @dev post basic parameter validation logic implemented in this, must be overriden by OApp
function _lzReceive(
Origin calldata _origin,
bytes32 _guid,
bytes calldata _message,
address _executor,
bytes calldata _extraData
) internal virtual;
}
69 changes: 69 additions & 0 deletions oapp/contracts/oapp/OAppSender.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

import { SafeERC20, IERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { MessagingParams, MessagingFee, MessagingReceipt } from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol";
import { OAppCore } from "./OAppCore.sol";

abstract contract OAppSender is OAppCore {
using SafeERC20 for IERC20;

error NotEnoughNative(uint256 msgValue);
error LzTokenUnavailable();

uint64 internal constant SENDER_VERSION = 1;

function oAppVersion() public view virtual returns (uint64 senderVersion, uint64 receiverVersion) {
return (SENDER_VERSION, 0);
}

/// @dev the generic quote interface to interact with the LayerZero EndpointV2.quote()
function _quote(
uint32 _dstEid,
bytes memory _message,
bytes memory _options,
bool _payInLzToken
) internal view virtual returns (MessagingFee memory fee) {
return
endpoint.quote(
MessagingParams(_dstEid, _getPeerOrRevert(_dstEid), _message, _options, _payInLzToken),
address(this)
);
}

/// @dev generic send interface to interact with the LayerZero EndpointV2.send()
function _lzSend(
uint32 _dstEid,
bytes memory _message,
bytes memory _options,
MessagingFee memory _fee,
address _refundAddress
) internal virtual returns (MessagingReceipt memory receipt) {
// @dev push corresponding fees to the endpoint, any excess is sent back to the _refundAddress from the endpoint
uint256 messageValue = _payNative(_fee.nativeFee);
if (_fee.lzTokenFee > 0) _payLzToken(_fee.lzTokenFee);

return
endpoint.send{ value: messageValue }(
MessagingParams(_dstEid, _getPeerOrRevert(_dstEid), _message, _options, _fee.lzTokenFee > 0),
_refundAddress
);
}

// @dev needs to provide a return value in the event endpoint is nativeErc20 enabled,
// because message value to endpoint would be 0 in that case
// TODO further explanation of how alt token works
function _payNative(uint _nativeFee) internal virtual returns (uint256 nativeFee) {
if (msg.value < _nativeFee) revert NotEnoughNative(msg.value);
return _nativeFee;
}

function _payLzToken(uint _lzTokenFee) internal virtual {
// @dev cant cache this because it is mutable inside of the endpoint
address lzToken = endpoint.lzToken();
if (lzToken == address(0x0)) revert LzTokenUnavailable();

// @dev pay lzToken fee by sending tokens to the endpoint
IERC20(lzToken).safeTransferFrom(msg.sender, address(endpoint), _lzTokenFee);
}
}
20 changes: 20 additions & 0 deletions oapp/contracts/oapp/examples/ExampleOApp.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

import { OApp, Origin } from "../OApp.sol";

contract ExampleOApp is OApp {
constructor(address _endpoint, address _owner) OApp(_endpoint, _owner) {}

/// @dev needs to be implemented by the OApp
/// @dev basic security checks are already performed
function _lzReceive(
Origin calldata _origin,
bytes32 _guid,
bytes calldata _message,
address _executor,
bytes calldata _extraData
) internal virtual override {
// @dev Do something
}
}
38 changes: 38 additions & 0 deletions oapp/contracts/oapp/examples/ExampleOAppPreCrimeSimulator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

import { OApp, Origin } from "../OApp.sol";
import { OAppPreCrimeSimulator } from "../../precrime/OAppPreCrimeSimulator.sol";

contract ExampleOAppPreCrimeSimulator is OApp, OAppPreCrimeSimulator {
constructor(address _endpoint, address _owner) OApp(_endpoint, _owner) {}

/// @dev needs to be implemented by the OApp
/// @dev basic security checks are already performed
function _lzReceive(
Origin calldata _origin,
bytes32 _guid,
bytes calldata _message,
address _executor,
bytes calldata _extraData
) internal virtual override {
// @dev Do something
}

// @dev IF you want preCrime simulator enabled, you need to implement this function as is.
// @dev routes the call down from the OAppPreCrimeSimulator, and up to the OApp
function _lzReceiveSimulate(
Origin calldata _origin,
bytes32 _guid,
bytes calldata _message,
address _executor,
bytes calldata _extraData
) internal virtual override {
_lzReceive(_origin, _guid, _message, _executor, _extraData);
}

// @dev IF you want preCrime enabled, you need to implement this function
function isPeer(uint32 _eid, bytes32 _peer) public view virtual override returns (bool) {
return peers[_eid] != _peer;
}
}
20 changes: 20 additions & 0 deletions oapp/contracts/oapp/examples/ExampleOAppReceiver.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

import { OAppReceiver, OAppCore, Origin } from "../OAppReceiver.sol";

contract ExampleOAppReceiver is OAppReceiver {
constructor(address _endpoint, address _owner) OAppCore(_endpoint, _owner) {}

/// @dev needs to be implemented by the OApp
/// @dev basic security checks are already performed
function _lzReceive(
Origin calldata _origin,
bytes32 _guid,
bytes calldata _message,
address _executor,
bytes calldata _extraData
) internal virtual override {
// @dev Do something
}
}
8 changes: 8 additions & 0 deletions oapp/contracts/oapp/examples/ExampleOAppSender.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

import { OAppSender, OAppCore } from "../OAppSender.sol";

contract ExampleOAppSender is OAppSender {
constructor(address _endpoint, address _owner) OAppCore(_endpoint, _owner) {}
}
Loading

0 comments on commit 6c09f46

Please sign in to comment.