Skip to content

Commit

Permalink
🪚 OApp example (#214)
Browse files Browse the repository at this point in the history
Co-authored-by: Jan Nanista <[email protected]>
  • Loading branch information
St0rmBr3w and janjakubnanista authored Jan 19, 2024
1 parent 983a141 commit 73b37d0
Show file tree
Hide file tree
Showing 45 changed files with 1,210 additions and 21 deletions.
7 changes: 7 additions & 0 deletions .changeset/six-masks-roll.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@layerzerolabs/toolbox-foundry": patch
"@layerzerolabs/oapp-example": patch
"@layerzerolabs/oft-example": patch
---

Include solidity-bytes-utils in toolbox-foundry
6 changes: 6 additions & 0 deletions .changeset/thick-moons-invite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@layerzerolabs/toolbox-foundry": patch
"@layerzerolabs/oapp-example": patch
---

Include forgotten libs in toolbox-foundry
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ node_modules
*.md
*.sol
*.toml
Makefile
pnpm-lock.yaml
3 changes: 3 additions & 0 deletions .github/workflows/actions/setup-environment/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,6 @@ runs:
with:
node-version-file: ".nvmrc"
cache: "pnpm"

- name: Setup Foundry
uses: foundry-rs/foundry-toolchain@v1
4 changes: 2 additions & 2 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[submodule "toolbox-foundry/forge-std"]
path = packages/toolbox-foundry/lib/forge-std
path = packages/toolbox-foundry/src/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "toolbox-foundry/ds-test"]
path = packages/toolbox-foundry/lib/ds-test
path = packages/toolbox-foundry/src/ds-test
url = https://github.com/dapphub/ds-test
5 changes: 5 additions & 0 deletions examples/oapp/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
artifacts
cache
dist
node_modules
out
5 changes: 5 additions & 0 deletions examples/oapp/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
require('@rushstack/eslint-patch/modern-module-resolution');

module.exports = {
extends: ['@layerzerolabs/eslint-config-next/recommended'],
};
16 changes: 16 additions & 0 deletions examples/oapp/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
node_modules
.env
coverage
coverage.json
typechain
typechain-types

# Hardhat files
cache
artifacts

# foundry test compilation files
out

# pnpm
pnpm-error.log
5 changes: 5 additions & 0 deletions examples/oapp/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
artifacts/
cache/
dist/
node_modules/
out/
3 changes: 3 additions & 0 deletions examples/oapp/.prettierrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
...require('@layerzerolabs/prettier-config-next'),
};
27 changes: 27 additions & 0 deletions examples/oapp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<p align="center">
<a href="https://layerzero.network">
<img alt="LayerZero" style="max-width: 500px" src="https://d3a2dpnnrypp5h.cloudfront.net/bridge-app/lz.png"/>
</a>
</p>

<h1 align="center">@layerzerolabs/oapp-example</h1>

## Template repository for getting started with LayerZero using either Hardhat or Foundry in one project.

### Getting Started

#### Using Foundry

```bash
forge install
forge build
forge test
```

#### Using Hardhat

```bash
pnpm
pnpm hardhat compile
pnpm hardhat test
```
66 changes: 66 additions & 0 deletions examples/oapp/contracts/MyOApp.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.22;

import { OApp, MessagingFee, Origin } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/OApp.sol";
import { MessagingReceipt } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/OAppSender.sol";

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

string public data = "Nothing received yet.";

/**
* @notice Sends a message from the source chain to a destination chain.
* @param _dstEid The endpoint ID of the destination chain.
* @param _message The message string to be sent.
* @param _options Additional options for message execution.
* @dev Encodes the message as bytes and sends it using the `_lzSend` internal function.
* @return receipt A `MessagingReceipt` struct containing details of the message sent.
*/
function send(
uint32 _dstEid,
string memory _message,
bytes calldata _options
) external payable returns (MessagingReceipt memory receipt) {
bytes memory _payload = abi.encode(_message);
receipt = _lzSend(_dstEid, _payload, _options, MessagingFee(msg.value, 0), payable(msg.sender));
}

/**
* @notice Quotes the gas needed to pay for the full omnichain transaction in native gas or ZRO token.
* @param _dstEid Destination chain's endpoint ID.
* @param _message The message.
* @param _options Message execution options (e.g., for sending gas to destination).
* @param _payInLzToken Whether to return fee in ZRO token.
*/
function quote(
uint32 _dstEid,
string memory _message,
bytes memory _options,
bool _payInLzToken
) public view returns (MessagingFee memory fee) {
bytes memory payload = abi.encode(_message);
fee = _quote(_dstEid, payload, _options, _payInLzToken);
}

/**
* @dev Internal function override to handle incoming messages from another chain.
* @param _origin A struct containing information about the message sender.
* @param _guid A unique global packet identifier for the message.
* @param payload The encoded message payload being received.
* @param _executor The address of the Executor responsible for processing the message.
* @param _extraData Arbitrary data appended by the Executor to the message.
*
* Decodes the received payload and processes it as per the business logic defined in the function.
*/
function _lzReceive(
Origin calldata _origin,
bytes32 _guid,
bytes calldata payload,
address _executor,
bytes calldata _extraData
) internal override {
data = abi.decode(payload, (string));
}
}
47 changes: 47 additions & 0 deletions examples/oapp/deploy/MyOApp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { type DeployFunction } from 'hardhat-deploy/types'

// TODO declare your contract name here
const contractName = 'MyOApp'

const deploy: DeployFunction = async (hre) => {
const { getNamedAccounts, deployments } = hre

const { deploy } = deployments
const { deployer } = await getNamedAccounts()

console.log(`Network: ${hre.network.name}`)
console.log(`Deployer: ${deployer}`)

// This is an external deployment pulled in from @layerzerolabs/lz-evm-sdk-v2
//
// @layerzerolabs/toolbox-hardhat takes care of plugging in the external deployments
// from @layerzerolabs packages based on the configuration in your hardhat config
//
// For this to work correctly, your network config must define an eid property
// set to `EndpointId` as defined in @layerzerolabs/lz-definitions
//
// For example:
//
// networks: {
// fuji: {
// ...
// eid: EndpointId.AVALANCHE_V2_TESTNET
// }
// }
const endpointV2Deployment = await hre.deployments.get('EndpointV2')

const { address } = await deploy(contractName, {
from: deployer,
args: [
endpointV2Deployment.address, // LayerZero's EndpointV2 address
deployer, // owner
],
log: true,
skipIfAlreadyDeployed: false,
})

console.log(`Deployed contract: ${contractName}, network: ${hre.network.name}, address: ${address}`)
}

deploy.tags = [contractName]
export default deploy
11 changes: 11 additions & 0 deletions examples/oapp/foundry.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[profile.default]
src = 'contracts'
out = 'out'
test = 'test/foundry'
cache_path = 'cache'
libs = ['node_modules', 'node_modules/@layerzerolabs/toolbox-foundry/lib']

remappings = [
'@layerzerolabs/=node_modules/@layerzerolabs',
'@openzeppelin/=node_modules/@openzeppelin/',
]
19 changes: 19 additions & 0 deletions examples/oapp/hardhat.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import 'hardhat-deploy'
import 'hardhat-contract-sizer'
import '@nomiclabs/hardhat-ethers'
import '@layerzerolabs/toolbox-hardhat'
import { HardhatUserConfig } from 'hardhat/types'

import './tasks/'

const config: HardhatUserConfig = {
solidity: '0.8.22',

namedAccounts: {
deployer: {
default: 0, // wallet address of index[0], of the mnemonic in .env
},
},
}

export default config
47 changes: 47 additions & 0 deletions examples/oapp/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"name": "@layerzerolabs/oapp-example",
"version": "0.0.2",
"private": true,
"license": "MIT",
"scripts": {
"compile": "$npm_execpath compile:forge && $npm_execpath compile:hardhat",
"compile:forge": "forge build",
"compile:hardhat": "$npm_execpath hardhat compile",
"lint": "$npm_execpath lint:js && $npm_execpath lint:sol",
"lint:fix": "$npm_execpath prettier --write . && solhint 'contracts/**/*.sol' --fix --noPrompt",
"lint:js": "$npm_execpath eslint '**/*.js' && $npm_execpath prettier --check .",
"lint:sol": "solhint 'contracts/**/*.sol'",
"test": "$npm_execpath test:forge && $npm_execpath test:hardhat",
"test:forge": "forge test",
"test:hardhat": "$npm_execpath hardhat test"
},
"devDependencies": {
"@babel/core": "^7.23.7",
"@layerzerolabs/eslint-config-next": "^2.0.7",
"@layerzerolabs/lz-definitions": "~2.0.7",
"@layerzerolabs/lz-evm-messagelib-v2": "~2.0.7",
"@layerzerolabs/lz-evm-oapp-v2": "~2.0.7",
"@layerzerolabs/lz-evm-protocol-v2": "~2.0.7",
"@layerzerolabs/lz-evm-v1-0.7": "~2.0.7",
"@layerzerolabs/prettier-config-next": "^2.0.7",
"@layerzerolabs/solhint-config": "^2.0.7",
"@layerzerolabs/toolbox-foundry": "~0.0.1",
"@layerzerolabs/toolbox-hardhat": "~0.0.3",
"@nomicfoundation/hardhat-ethers": "^3.0.5",
"@nomiclabs/hardhat-ethers": "^2.2.3",
"@openzeppelin/contracts": "^4.9.5",
"@openzeppelin/contracts-upgradeable": "^4.9.5",
"@rushstack/eslint-patch": "^1.6.1",
"@types/mocha": "^10.0.6",
"ethers": "^5.7.0",
"hardhat": "^2.19.4",
"hardhat-contract-sizer": "^2.10.0",
"hardhat-deploy": "^0.11.45",
"mocha": "^10.2.0",
"prettier": "^3.1.1",
"solhint": "^4.0.0",
"solidity-bytes-utils": "^0.8.1",
"ts-node": "^10.9.2",
"typescript": "^5.3.3"
}
}
1 change: 1 addition & 0 deletions examples/oapp/solhint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('@layerzerolabs/solhint-config');
21 changes: 21 additions & 0 deletions examples/oapp/tasks/getSigners.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { task, types } from 'hardhat/config'
import { type ActionType } from 'hardhat/types'

// TODO Figure out a way so this doesnt need to be defined in two places
interface TaskArguments {
n: number
}

const action: ActionType<TaskArguments> = async (taskArgs, hre) => {
const signers = await hre.ethers.getSigners()
for (let i = 0; i < taskArgs.n; ++i) {
console.log(`${i}) ${signers[i].address}`)
}
}

task('getSigners', 'show the signers of the current mnemonic', action).addOptionalParam(
'n',
'how many to show',
3,
types.int
)
2 changes: 2 additions & 0 deletions examples/oapp/tasks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import './getSigners'
// TODO get rid of index.ts somehow so we only need to define it in one place
63 changes: 63 additions & 0 deletions examples/oapp/test/foundry/MyOApp.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.8.22;

import { Packet } from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ISendLib.sol";
import { OptionsBuilder } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/libs/OptionsBuilder.sol";
import { MessagingFee } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/OApp.sol";
import { MessagingReceipt } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/OAppSender.sol";
import { MyOApp } from "../../contracts/MyOApp.sol";
import { TestHelper } from "./TestHelper.sol";

import "forge-std/console.sol";

/// @notice Unit test for MyOApp using the TestHelper.
/// @dev Inherits from TestHelper to utilize its setup and utility functions.
contract MyOAppTest is TestHelper {
using OptionsBuilder for bytes;

// Declaration of mock endpoint IDs.
uint16 aEid = 1;
uint16 bEid = 2;

// Declaration of mock contracts.
MyOApp aMyOApp; // OApp A
MyOApp bMyOApp; // OApp B

/// @notice Calls setUp from TestHelper and initializes contract instances for testing.
function setUp() public virtual override {
super.setUp();

// Setup function to initialize 2 Mock Endpoints with Mock MessageLib.
setUpEndpoints(2, LibraryType.UltraLightNode);

// Initializes 2 MyOApps; one on chain A, one on chain B.
address[] memory sender = setupOApps(type(MyOApp).creationCode, 1, 2);
aMyOApp = MyOApp(payable(sender[0]));
bMyOApp = MyOApp(payable(sender[1]));
}

/// @notice Tests the send and multi-compose functionality of MyOApp.
/// @dev Simulates message passing from A -> B and checks for data integrity.
function test_send() public {
// Setup variable for data values before calling send().
string memory dataBefore = aMyOApp.data();

// Generates 1 lzReceive execution option via the OptionsBuilder library.
// STEP 0: Estimating message gas fees via the quote function.
bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(150000, 0);
MessagingFee memory fee = aMyOApp.quote(bEid, "test message", options, false);

// STEP 1: Sending a message via the _lzSend() method.
MessagingReceipt memory receipt = aMyOApp.send{ value: fee.nativeFee }(bEid, "test message", options);

// Asserting that the receiving OApps have NOT had data manipulated.
assertEq(bMyOApp.data(), dataBefore, "shouldn't be changed until lzReceive packet is verified");

// STEP 2 & 3: Deliver packet to bMyOApp manually.
verifyPackets(bEid, addressToBytes32(address(bMyOApp)));

// Asserting that the data variable has updated in the receiving OApp.
assertEq(bMyOApp.data(), "test message", "lzReceive data assertion failure");
}
}
Loading

0 comments on commit 73b37d0

Please sign in to comment.