Skip to content

Commit

Permalink
feat: options builder
Browse files Browse the repository at this point in the history
Signed-off-by: Ryan Goulding <[email protected]>
  • Loading branch information
ryandgoulding committed Dec 13, 2023
1 parent 381bc69 commit 9071343
Show file tree
Hide file tree
Showing 13 changed files with 2,304 additions and 78 deletions.
19 changes: 19 additions & 0 deletions packages/omnicounter-utils-evm/src/omnicounter/factory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import pMemoize from 'p-memoize'
import { OmniCounter } from '@/omnicounter/sdk'
import { createEndpointFactory } from '@layerzerolabs/protocol-utils-evm'
import { EndpointFactory } from '@layerzerolabs/protocol-utils'
import { OAppFactory } from '@layerzerolabs/ua-utils'
import { OmniContractFactory } from '@layerzerolabs/utils-evm'

/**
* Syntactic sugar that creates an instance of EVM `OmniCounter` SDK based on an `OmniPoint` with help of an
* `OmniContractFactory` and an (optional) `EndpointFactory`
*
* @param {OmniContractFactory} contractFactory
* @param {EndpointFactory} [endpointFactory]
* @returns {EndpointFactory<Endpoint>}
*/
export const createOmniCounterFactory = (
contractFactory: OmniContractFactory,
endpointFactory: EndpointFactory = createEndpointFactory(contractFactory)
): OAppFactory<OmniCounter> => pMemoize(async (point) => new OmniCounter(await contractFactory(point), endpointFactory))
1 change: 1 addition & 0 deletions packages/omnicounter-utils-evm/src/omnicounter/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './factory'
export * from './sdk'
13 changes: 11 additions & 2 deletions packages/omnicounter-utils-evm/src/omnicounter/sdk.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
import { IOmniCounterApp } from '@layerzerolabs/omnicounter-utils'
import { IOmniCounter } from '@layerzerolabs/omnicounter-utils'
import { EndpointFactory } from '@layerzerolabs/protocol-utils'
import { OApp } from '@layerzerolabs/ua-utils-evm'
import { OmniTransaction } from '@layerzerolabs/utils'
import { OmniContract } from '@layerzerolabs/utils-evm'

export class OmniCounter extends OApp implements IOmniCounter {
public constructor(
public override contract: OmniContract,
protected override endpointFactory: EndpointFactory
) {
super(contract, endpointFactory)
}

export class OmniCounterApp extends OApp implements IOmniCounterApp {
public async increment(eid: number, type: number, options: string): Promise<OmniTransaction> {
const data = this.contract.contract.interface.encodeFunctionData('increment', [eid, type, options])
return super.createTransaction(data)
Expand Down
2 changes: 1 addition & 1 deletion packages/omnicounter-utils/src/omnicounter/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { OmniTransaction } from '@layerzerolabs/utils'
import { EndpointId } from '@layerzerolabs/lz-definitions'

export interface IOmniCounterApp {
export interface IOmniCounter {
increment(eid: EndpointId, type: number, options: string): Promise<OmniTransaction>
}
8 changes: 8 additions & 0 deletions packages/ua-utils-evm-hardhat-test/contracts/OmniCounter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.22;

import { OmniCounter as OmniCounterImpl } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/examples/OmniCounter.sol";

contract OmniCounterOApp is OmniCounterImpl {
constructor(address _endpoint, address _owner) OmniCounterImpl(_endpoint, _owner) {}
}
43 changes: 27 additions & 16 deletions packages/ua-utils-evm-hardhat-test/deploy/001_bootstrap.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,32 @@
import { type DeployFunction } from 'hardhat-deploy/types'
import assert from 'assert'
import { TransactionReceipt, TransactionResponse } from '@ethersproject/providers'
import { formatEid } from '@layerzerolabs/utils'
import { wrapEIP1193Provider } from '@layerzerolabs/utils-evm-hardhat'
import env from 'hardhat'
import { Contract } from 'ethers'
import { TransactionReceipt, TransactionResponse } from '@ethersproject/providers'
import assert from 'assert'
import { Contract, ethers } from 'ethers'
import { HardhatRuntimeEnvironment } from 'hardhat/types'

const DEFAULT_NATIVE_DECIMALS_RATE = '18' //ethers.utils.parseUnits('1', 18).toString()
const DEFAULT_NATIVE_DECIMALS_RATE = ethers.utils.parseUnits('1', 18).toString()

/**
* This deploy function will deploy and configure LayerZero endpoint
* This `deploy` function will deploy and configure LayerZero EndpointV2. This includes:
* - EndpointV2
* - SendUln302
* - ReceiveUln302
* - PriceFeed
* - Executor
* - ExecutorFeeLib
* - DVN
* - DVNFeeLib
*
* @param env `HardhatRuntimeEnvironment`
* @param {HardhatRuntimeEnvironment} env
*/
const deploy: DeployFunction = async ({ getUnnamedAccounts, deployments, network }) => {
const deploy: DeployFunction = async ({ getUnnamedAccounts, deployments, network }: HardhatRuntimeEnvironment) => {
assert(network.config.eid != null, `Missing endpoint ID for network ${network.name}`)

const [deployer] = await getUnnamedAccounts()
assert(deployer, 'Missing deployer')
const signer = wrapEIP1193Provider(network.provider).getSigner()

await deployments.delete('EndpointV2')
const endpointV2Deployment = await deployments.deploy('EndpointV2', {
Expand Down Expand Up @@ -95,13 +104,15 @@ const deploy: DeployFunction = async ({ getUnnamedAccounts, deployments, network
},
})

const signer = wrapEIP1193Provider(env.network.provider).getSigner()
const executorContract = new Contract(executor.address, executor.abi).connect(signer)
const setExecFeeLibResp: TransactionResponse = await executorContract.setWorkerFeeLib?.(executorFeeLib.address, {
from: await signer.getAddress(),
})
const setExecFeeLibReceipt: TransactionReceipt = await setExecFeeLibResp.wait()
const setExecFeeLibResp: TransactionResponse = await executorContract.setWorkerFeeLib(executorFeeLib.address)
const setExecFeeLibReceipt: TransactionReceipt = await setExecFeeLibResp.wait(1)
assert(setExecFeeLibReceipt?.status === 1)
const polledExecutorFeeLib = await executorContract.workerFeeLib?.()
assert(
polledExecutorFeeLib?.toLowerCase() === executorFeeLib.address.toLowerCase(),
'Executor worker fee lib not set correctly'
)

await deployments.delete('DVN')
const dvn = await deployments.deploy('DVN', {
Expand All @@ -123,11 +134,11 @@ const deploy: DeployFunction = async ({ getUnnamedAccounts, deployments, network
})

const dvnContract = new Contract(dvn.address, dvn.abi).connect(signer)
const setDvnFeeLibResp: TransactionResponse = await dvnContract.setWorkerFeeLib?.(dvnFeeLib.address, {
from: await signer.getAddress(),
})
const setDvnFeeLibResp: TransactionResponse = await dvnContract.setWorkerFeeLib?.(dvnFeeLib.address)
const setDvnFeeLibReceipt: TransactionReceipt = await setDvnFeeLibResp.wait()
assert(setDvnFeeLibReceipt?.status === 1)
const polledDvnFeeLib = await dvnContract.workerFeeLib?.()
assert(polledDvnFeeLib?.toLowerCase() === dvnFeeLib.address.toLowerCase(), 'DVN worker fee lib not set correctly')

console.table({
Network: `${network.name} (endpoint ${formatEid(network.config.eid)})`,
Expand Down
32 changes: 32 additions & 0 deletions packages/ua-utils-evm-hardhat-test/deploy/003_omnicounteroapp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { formatEid } from '@layerzerolabs/utils'
import { type DeployFunction } from 'hardhat-deploy/types'
import assert from 'assert'

/**
* This deploy function will deploy and configure LayerZero OmniCounter
*
* @param env `HardhatRuntimeEnvironment`
*/
const deploy: DeployFunction = async ({ getUnnamedAccounts, deployments, network }) => {
assert(network.config.eid != null, `Missing endpoint ID for network ${network.name}`)

const [deployer] = await getUnnamedAccounts()
assert(deployer, 'Missing deployer')

await deployments.delete('OmniCounter')
const endpointV2 = await deployments.get('EndpointV2')
const omniCounterDeployment = await deployments.deploy('OmniCounter', {
from: deployer,
args: [endpointV2.address, deployer],
})

console.table({
Network: `${network.name} (endpoint ${formatEid(network.config.eid)})`,
OmniCounter: omniCounterDeployment.address,
})
}

deploy.tags = ['OmniCounter']
deploy.dependencies = ['Bootstrap']

export default deploy
2 changes: 1 addition & 1 deletion packages/ua-utils-evm-hardhat-test/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testTimeout: 15000,
testTimeout: 60000,
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
},
Expand Down
3 changes: 3 additions & 0 deletions packages/ua-utils-evm-hardhat-test/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
"@layerzerolabs/lz-evm-sdk-v1": "~1.5.72",
"@layerzerolabs/lz-evm-sdk-v2": "~1.5.72",
"@layerzerolabs/lz-utility-v2": "~1.5.72",
"@layerzerolabs/omnicounter-utils": "~0.0.1",
"@layerzerolabs/omnicounter-utils-evm": "~0.0.1",
"@layerzerolabs/protocol-utils": "~0.0.1",
"@layerzerolabs/protocol-utils-evm": "~0.0.1",
"@layerzerolabs/toolbox-hardhat": "~0.0.1",
Expand All @@ -45,6 +47,7 @@
"hardhat": "^2.19.2",
"hardhat-deploy": "^0.11.43",
"jest": "^29.7.0",
"solidity-bytes-utils": "^0.8.0",
"ts-jest": "^29.1.1",
"ts-node": "^10.9.1",
"typescript": "^5.2.2"
Expand Down
21 changes: 21 additions & 0 deletions packages/ua-utils-evm-hardhat-test/test/__utils__/omnicounter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { EndpointId } from '@layerzerolabs/lz-definitions'
import { createNetworkEnvironmentFactory } from '@layerzerolabs/utils-evm-hardhat'

export const deployOmniCounter = async () => {
const environmentFactory = createNetworkEnvironmentFactory()
const eth = await environmentFactory(EndpointId.ETHEREUM_MAINNET)
const avax = await environmentFactory(EndpointId.AVALANCHE_MAINNET)

await Promise.all([
eth.deployments.run('OmniCounter', { writeDeploymentsToFiles: true }),
avax.deployments.run('OmniCounter', { writeDeploymentsToFiles: true }),
])
}

export const deployOmniCounterFixture = async () => {
const environmentFactory = createNetworkEnvironmentFactory()
const eth = await environmentFactory(EndpointId.ETHEREUM_MAINNET)
const avax = await environmentFactory(EndpointId.AVALANCHE_MAINNET)

await Promise.all([eth.deployments.fixture('OmniCounter'), avax.deployments.fixture('OmniCounter')])
}
128 changes: 128 additions & 0 deletions packages/ua-utils-evm-hardhat-test/test/omnicounter/options.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import fc from 'fast-check'
import 'hardhat'
import { EventFragment } from '@ethersproject/abi/src.ts/fragments'
import { Log, TransactionReceipt } from '@ethersproject/providers'
import { EndpointId, MainnetEndpointId } from '@layerzerolabs/lz-definitions'
import { Options } from '@layerzerolabs/lz-utility-v2'
import { createOmniCounterFactory, OmniCounter } from '@layerzerolabs/omnicounter-utils-evm'
import { createEndpointFactory } from '@layerzerolabs/protocol-utils-evm'
import { configureOApp } from '@layerzerolabs/ua-utils'
import { OmniTransaction } from '@layerzerolabs/utils'
import { omniContractToPoint } from '@layerzerolabs/utils-evm'
import {
createConnectedContractFactory,
createSignerFactory,
OmniGraphBuilderHardhat,
OmniGraphHardhat,
} from '@layerzerolabs/utils-evm-hardhat'
import { utils } from 'ethers'
import { keccak256, parseEther, toUtf8Bytes } from 'ethers/lib/utils'
import { setupDefaultEndpoint } from '../__utils__/endpoint'
import { deployOmniCounter } from '../__utils__/omnicounter'

/**
* Find matching emitted events.
* @param {TransactionReceipt} receipt
* @param {EventFragment} frag
* @param {string} contractAddress
* @returns {Log[]}
*/
const findMatchingEvents = (receipt: TransactionReceipt, frag: utils.EventFragment, contractAddress: string): Log[] =>
receipt.logs
.filter((log) => log.topics.includes(keccak256(toUtf8Bytes(frag.format()))))
.filter(
(log) =>
log.address &&
(contractAddress === undefined || log.address.toLowerCase() === contractAddress.toLowerCase())
)

/**
* Parse event logs.
* @param {Log} log
* @param {utils.Interface} context
*/
const parseArgs = (log: Log, context: utils.Interface): utils.Result => context.parseLog(log).args

describe('oapp/options', () => {
const ethContract = { eid: EndpointId.ETHEREUM_MAINNET, contractName: 'OmniCounter' }
const avaxContract = { eid: EndpointId.AVALANCHE_MAINNET, contractName: 'OmniCounter' }

const config: OmniGraphHardhat = {
contracts: [
{
contract: ethContract,
},
{
contract: avaxContract,
},
],
connections: [
{
from: ethContract,
to: avaxContract,
},
{
from: avaxContract,
to: ethContract,
},
],
}

beforeEach(async () => {
await deployOmniCounter()
await setupDefaultEndpoint()
})

it('lzReceive option', async () => {
const contractFactory = createConnectedContractFactory()
const builder = await OmniGraphBuilderHardhat.fromConfig(config)
const sdkFactory = createOmniCounterFactory(contractFactory)
const signerFactory = createSignerFactory()

const ethPoint = omniContractToPoint(await contractFactory(ethContract))
const ethSdk: OmniCounter = await sdkFactory(ethPoint)
const ethSigner = await signerFactory(ethContract.eid)

const avaxPoint = omniContractToPoint(await contractFactory(avaxContract))
const avaxSdk = await sdkFactory(avaxPoint)
const avaxSigner = await signerFactory(avaxContract.eid)

const transactions = await configureOApp(builder.graph, sdkFactory)
for (const transaction of transactions) {
const signer = transaction.point.eid === MainnetEndpointId.ETHEREUM_MAINNET ? ethSigner : avaxSigner
const txResponse = await signer.signAndSend(transaction)
const txReceipt: TransactionReceipt = await txResponse.wait()
expect(txReceipt.status).toBe(1)
}

expect(await ethSdk.hasPeer(avaxPoint.eid, avaxPoint.address)).toBe(true)
expect(await avaxSdk.hasPeer(ethPoint.eid, ethPoint.address)).toBe(true)

await fc.assert(
fc.asyncProperty(fc.integer({ min: 200000, max: 100000000000 }), async (p) => {
const options = Options.newOptions().addExecutorLzReceiveOption(p)
const incrementTx: OmniTransaction = {
...(await ethSdk.increment(avaxPoint.eid, 2, options.toHex())),
gasLimit: 500000,
value: parseEther('0').toString(),
}
const incrementTxResponse = await ethSigner.signAndSend(incrementTx)
const incrementTxReceipt: TransactionReceipt = await incrementTxResponse.wait()
expect(incrementTxReceipt.status).toEqual(1)

const ethEndpointContract = { eid: MainnetEndpointId.ETHEREUM_MAINNET, contractName: 'EndpointV2' }
const ethEndpointPoint = omniContractToPoint(await contractFactory(ethEndpointContract))
const endpointSdkFactory = createEndpointFactory(contractFactory)
const ethEndpointSdk = await endpointSdkFactory(ethEndpointPoint)
const packetReceived = Object.values(
ethEndpointSdk.contract.contract.interface.events as any as EventFragment[]

Check warning on line 118 in packages/ua-utils-evm-hardhat-test/test/omnicounter/options.test.ts

View workflow job for this annotation

GitHub Actions / Check code / Build, Lint & Test

Unexpected any. Specify a different type
).find((frag: EventFragment) => frag.name === 'PacketSent') as EventFragment
const logs = findMatchingEvents(incrementTxReceipt, packetReceived, ethEndpointPoint.address)
if (logs && logs.length > 0) {
const eventArgs = parseArgs(logs[0]!, ethEndpointSdk.contract.contract.interface)
expect(eventArgs.options.toLowerCase() === options.toHex().toLowerCase())
}
})
)
})
})
2 changes: 1 addition & 1 deletion packages/ua-utils-evm/src/oapp/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { OmniSDK } from '@layerzerolabs/utils-evm'
export class OApp extends OmniSDK implements IOApp {
constructor(
contract: OmniContract,
private readonly endpointFactory: EndpointFactory
protected readonly endpointFactory: EndpointFactory
) {
super(contract)
}
Expand Down
Loading

0 comments on commit 9071343

Please sign in to comment.