Skip to content

Commit

Permalink
🪚 OmniGraph™ address/bytes utilities & hasPeer OApp SDK method (#72)
Browse files Browse the repository at this point in the history
  • Loading branch information
janjakubnanista authored Dec 7, 2023
1 parent 1305749 commit 795951b
Show file tree
Hide file tree
Showing 16 changed files with 381 additions and 74 deletions.
11 changes: 7 additions & 4 deletions packages/protocol-utils-evm/src/endpoint/sdk.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { IEndpoint } from '@layerzerolabs/protocol-utils'
import { formatEid, type Address, type OmniTransaction } from '@layerzerolabs/utils'
import type { EndpointId } from '@layerzerolabs/lz-definitions'
import { ignoreZero, makeZero, omniContractToPoint, type OmniContract } from '@layerzerolabs/utils-evm'
import { ignoreZero, makeZeroAddress, omniContractToPoint, type OmniContract } from '@layerzerolabs/utils-evm'

export class Endpoint implements IEndpoint {
constructor(public readonly contract: OmniContract) {}
Expand All @@ -17,13 +17,13 @@ export class Endpoint implements IEndpoint {
): Promise<OmniTransaction> {
const data = this.contract.contract.interface.encodeFunctionData('setDefaultReceiveLibrary', [
eid,
makeZero(lib),
makeZeroAddress(lib),
gracePeriod,
])

return {
...this.createTransaction(data),
description: `Setting default receive library for ${formatEid(eid)} to ${makeZero(lib)}`,
description: `Setting default receive library for ${formatEid(eid)} to ${makeZeroAddress(lib)}`,
}
}

Expand All @@ -32,7 +32,10 @@ export class Endpoint implements IEndpoint {
}

async setDefaultSendLibrary(eid: EndpointId, lib: Address | null | undefined): Promise<OmniTransaction> {
const data = this.contract.contract.interface.encodeFunctionData('setDefaultSendLibrary', [eid, makeZero(lib)])
const data = this.contract.contract.interface.encodeFunctionData('setDefaultSendLibrary', [
eid,
makeZeroAddress(lib),
])

return {
...this.createTransaction(data),
Expand Down
6 changes: 3 additions & 3 deletions packages/protocol-utils-evm/src/uln302/sdk.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import type { EndpointId } from '@layerzerolabs/lz-definitions'
import type { IUln302, Uln302ExecutorConfig, Uln302UlnConfig } from '@layerzerolabs/protocol-utils'
import { Address, formatEid, type OmniTransaction } from '@layerzerolabs/utils'
import { omniContractToPoint, type OmniContract, makeZero } from '@layerzerolabs/utils-evm'
import { omniContractToPoint, type OmniContract, makeZeroAddress } from '@layerzerolabs/utils-evm'
import { Uln302ExecutorConfigSchema, Uln302UlnConfigInputSchema, Uln302UlnConfigSchema } from './schema'

export class Uln302 implements IUln302 {
constructor(public readonly contract: OmniContract) {}

async getUlnConfig(eid: EndpointId, address?: Address | null | undefined): Promise<Uln302UlnConfig> {
const config = await this.contract.contract.getUlnConfig(makeZero(address), eid)
const config = await this.contract.contract.getUlnConfig(makeZeroAddress(address), eid)

// Now we convert the ethers-specific object into the common structure
//
Expand All @@ -18,7 +18,7 @@ export class Uln302 implements IUln302 {
}

async getExecutorConfig(eid: EndpointId, address?: Address | null | undefined): Promise<Uln302ExecutorConfig> {
const config = await this.contract.contract.getExecutorConfig(makeZero(address), eid)
const config = await this.contract.contract.getExecutorConfig(makeZeroAddress(address), eid)

// Now we convert the ethers-specific object into the common structure
//
Expand Down
2 changes: 2 additions & 0 deletions packages/test-utils/src/arbitraries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ export const addressArbitrary = fc.string()

export const evmAddressArbitrary = fc.hexaString({ minLength: 40, maxLength: 40 }).map((address) => `0x${address}`)

export const evmBytes32Arbitrary = fc.hexaString({ minLength: 64, maxLength: 64 }).map((address) => `0x${address}`)

export const endpointArbitrary: fc.Arbitrary<EndpointId> = fc.constantFrom(...ENDPOINT_IDS)

export const stageArbitrary: fc.Arbitrary<Stage> = fc.constantFrom(Stage.MAINNET, Stage.TESTNET, Stage.SANDBOX)
Expand Down
4 changes: 2 additions & 2 deletions packages/ua-utils-evm-hardhat-test/contracts/DefaultOApp.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
pragma solidity ^0.8.22;

contract DefaultOApp {
mapping(uint256 => address) public peers;
mapping(uint256 => bytes32) public peers;

function setPeer(uint256 eid, address peer) external {
function setPeer(uint256 eid, bytes32 peer) external {
peers[eid] = peer;
}
}
14 changes: 9 additions & 5 deletions packages/ua-utils-evm/src/oapp/sdk.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import type { IOApp } from '@layerzerolabs/ua-utils'
import type { Address, OmniTransaction } from '@layerzerolabs/utils'
import { omniContractToPoint, OmniContract, ignoreZero, makeZero } from '@layerzerolabs/utils-evm'
import type { Bytes32, Address, OmniTransaction } from '@layerzerolabs/utils'
import { omniContractToPoint, OmniContract, ignoreZero, makeBytes32, areBytes32Equal } from '@layerzerolabs/utils-evm'
import type { EndpointId } from '@layerzerolabs/lz-definitions'

export class OApp implements IOApp {
constructor(public readonly contract: OmniContract) {}

async peers(eid: EndpointId): Promise<string | undefined> {
async peers(eid: EndpointId): Promise<Bytes32 | undefined> {
return ignoreZero(await this.contract.contract.peers(eid))
}

async setPeer(eid: EndpointId, address: Address | null | undefined): Promise<OmniTransaction> {
const data = this.contract.contract.interface.encodeFunctionData('setPeer', [eid, makeZero(address)])
async hasPeer(eid: EndpointId, address: Bytes32 | Address | null | undefined): Promise<boolean> {
return areBytes32Equal(await this.peers(eid), address)
}

async setPeer(eid: EndpointId, address: Bytes32 | Address | null | undefined): Promise<OmniTransaction> {
const data = this.contract.contract.interface.encodeFunctionData('setPeer', [eid, makeBytes32(address)])

return this.createTransaction(data)
}
Expand Down
123 changes: 114 additions & 9 deletions packages/ua-utils-evm/test/oapp/sdk.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import fc from 'fast-check'
import { endpointArbitrary, evmAddressArbitrary } from '@layerzerolabs/test-utils'
import { Contract } from '@ethersproject/contracts'
import { OApp } from '@/oapp/sdk'
import { OmniContract, makeZero } from '@layerzerolabs/utils-evm'
import { OmniContract, isZero, makeZeroAddress } from '@layerzerolabs/utils-evm'
import { makeBytes32 } from '@layerzerolabs/utils-evm'

describe('oapp/sdk', () => {
const nullishAddressArbitrary = fc.constantFrom(null, undefined, makeZeroAddress(), makeBytes32(makeZeroAddress()))
const jestFunctionArbitrary = fc.anything().map(() => jest.fn())

const oappOmniContractArbitrary = fc.record({
Expand Down Expand Up @@ -35,13 +37,11 @@ describe('oapp/sdk', () => {
})

it('should return undefined if peers() returns a zero address, null or undefined', async () => {
const peerArbitrary = fc.constantFrom(null, undefined, makeZero(null))

await fc.assert(
fc.asyncProperty(
omniContractArbitrary,
endpointArbitrary,
peerArbitrary,
nullishAddressArbitrary,
async (omniContract, peerEid, peer) => {
omniContract.contract.peers.mockResolvedValue(peer)

Expand All @@ -54,25 +54,128 @@ describe('oapp/sdk', () => {
})

it('should return undefined if peers() returns null or undefined', async () => {
const peerArbitrary = evmAddressArbitrary

await fc.assert(
fc.asyncProperty(
omniContractArbitrary,
endpointArbitrary,
peerArbitrary,
evmAddressArbitrary,
async (omniContract, peerEid, peer) => {
omniContract.contract.peers.mockResolvedValue(peer)

const sdk = new OApp(omniContract)

expect(sdk.peers(peerEid)).resolves.toBe(peer)
await expect(sdk.peers(peerEid)).resolves.toBe(peer)
}
)
)
})
})

describe('hasPeer', () => {
describe('when called with zeroish address', () => {
it('should return true if peers returns a zero address', async () => {
await fc.assert(
fc.asyncProperty(
omniContractArbitrary,
endpointArbitrary,
nullishAddressArbitrary,
nullishAddressArbitrary,
async (omniContract, peerEid, peer, probePeer) => {
omniContract.contract.peers.mockResolvedValue(peer)

const sdk = new OApp(omniContract)

await expect(sdk.hasPeer(peerEid, probePeer)).resolves.toBe(true)
}
)
)
})

it('should return false if peers returns a non-zero address', async () => {
await fc.assert(
fc.asyncProperty(
omniContractArbitrary,
endpointArbitrary,
nullishAddressArbitrary,
evmAddressArbitrary,
async (omniContract, peerEid, peer, probePeer) => {
fc.pre(!isZero(probePeer))

omniContract.contract.peers.mockResolvedValue(peer)

const sdk = new OApp(omniContract)

await expect(sdk.hasPeer(peerEid, probePeer)).resolves.toBe(false)
}
)
)
})
})

describe('when called non-zeroish address', () => {
it('should return false if peers() returns a zero address, null or undefined', async () => {
await fc.assert(
fc.asyncProperty(
omniContractArbitrary,
endpointArbitrary,
evmAddressArbitrary,
nullishAddressArbitrary,
async (omniContract, peerEid, peer, probePeer) => {
fc.pre(!isZero(peer))

omniContract.contract.peers.mockResolvedValue(peer)

const sdk = new OApp(omniContract)

await expect(sdk.hasPeer(peerEid, probePeer)).resolves.toBe(false)
await expect(sdk.hasPeer(peerEid, makeBytes32(probePeer))).resolves.toBe(false)
}
)
)
})

it('should return true if peers() returns a matching address', async () => {
await fc.assert(
fc.asyncProperty(
omniContractArbitrary,
endpointArbitrary,
evmAddressArbitrary,
async (omniContract, peerEid, peer) => {
fc.pre(!isZero(peer))

omniContract.contract.peers.mockResolvedValue(peer)

const sdk = new OApp(omniContract)

await expect(sdk.hasPeer(peerEid, peer)).resolves.toBe(true)
await expect(sdk.hasPeer(peerEid, makeBytes32(peer))).resolves.toBe(true)
}
)
)
})

it('should return true if peers() returns a matching bytes32', async () => {
await fc.assert(
fc.asyncProperty(
omniContractArbitrary,
endpointArbitrary,
evmAddressArbitrary,
async (omniContract, peerEid, peer) => {
fc.pre(!isZero(peer))

omniContract.contract.peers.mockResolvedValue(makeBytes32(peer))

const sdk = new OApp(omniContract)

await expect(sdk.hasPeer(peerEid, peer)).resolves.toBe(true)
await expect(sdk.hasPeer(peerEid, makeBytes32(peer))).resolves.toBe(true)
}
)
)
})
})
})

describe('setPeer', () => {
it('should encode data for a setPeer call', async () => {
await fc.assert(
Expand All @@ -84,10 +187,12 @@ describe('oapp/sdk', () => {
const sdk = new OApp(omniContract)
const encodeFunctionData = omniContract.contract.interface.encodeFunctionData

;(encodeFunctionData as jest.Mock).mockClear()

await sdk.setPeer(peerEid, peerAddress)

expect(encodeFunctionData).toHaveBeenCalledTimes(1)
expect(encodeFunctionData).toHaveBeenCalledWith('setPeer', [peerEid, makeZero(peerAddress)])
expect(encodeFunctionData).toHaveBeenCalledWith('setPeer', [peerEid, makeBytes32(peerAddress)])
}
)
)
Expand Down
10 changes: 5 additions & 5 deletions packages/ua-utils/src/oapp/config.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import type { OmniTransaction } from '@layerzerolabs/utils'
import type { OAppFactory, OAppOmniGraph } from './types'

export const configureOApp = async (graph: OAppOmniGraph, factory: OAppFactory): Promise<OmniTransaction[]> => {
export const configureOApp = async (graph: OAppOmniGraph, createSdk: OAppFactory): Promise<OmniTransaction[]> => {
const setPeers = await Promise.all(
graph.connections.map(async ({ vector: { from, to } }): Promise<OmniTransaction[]> => {
const instance = await factory(from)
const address = await instance.peers(to.eid)
const sdk = await createSdk(from)
const hasPeer = await sdk.hasPeer(to.eid, to.address)

if (to.address === address) return []
return [await instance.setPeer(to.eid, to.address)]
if (hasPeer) return []
return [await sdk.setPeer(to.eid, to.address)]
})
)

Expand Down
4 changes: 3 additions & 1 deletion packages/ua-utils/src/oapp/types.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import type { EndpointId } from '@layerzerolabs/lz-definitions'
import type { Address, OmniGraph, OmniTransaction } from '@layerzerolabs/utils'
import { Bytes32 } from '@layerzerolabs/utils'
import { OmniPointBasedFactory } from '@layerzerolabs/utils'

export interface IOApp {
peers(eid: EndpointId): Promise<string | undefined>
setPeer(eid: EndpointId, peer: Address): Promise<OmniTransaction>
hasPeer(eid: EndpointId, address: Bytes32 | Address | null | undefined): Promise<boolean>
setPeer(eid: EndpointId, peer: Bytes32 | Address | null | undefined): Promise<OmniTransaction>
}

export type OAppOmniGraph = OmniGraph<unknown, unknown>
Expand Down
28 changes: 0 additions & 28 deletions packages/utils-evm-hardhat/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,31 +62,3 @@ const omniPoint: OmniPoint = {

const omniContract = await omniContractFactory(omniPoint);
```

### Address utilities

#### ignoreZero(address: Address | null | undefined)

Turns EVM zero addresses to `undefined`

```typescript
import { ignoreZero } from "@layerzerolabs/utils-evm";

ignoreZero("0xEe6cF2E1Bc7645F8439d241ce37820305F2BB3F8"); // Returns '0xEe6cF2E1Bc7645F8439d241ce37820305F2BB3F8'
ignoreZero("0x0000000000000000000000000000000000000000"); // Returns undefined
ignoreZero(undefined); // Returns undefined
ignoreZero(null); // Returns undefined
```

#### makeZero(address)

Turns `null` and `undefined` into EVM zero address

```typescript
import { makeZero } from "@layerzerolabs/utils-evm";

makeZero("0xEe6cF2E1Bc7645F8439d241ce37820305F2BB3F8"); // Returns '0xEe6cF2E1Bc7645F8439d241ce37820305F2BB3F8'
makeZero("0x0000000000000000000000000000000000000000"); // Returns '0x0000000000000000000000000000000000000000'
makeZero(undefined); // Returns '0x0000000000000000000000000000000000000000'
makeZero(null); // Returns '0x0000000000000000000000000000000000000000'
```
6 changes: 3 additions & 3 deletions packages/utils-evm-hardhat/test/omnigraph/contracts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { JsonRpcProvider, Web3Provider } from '@ethersproject/providers'
import { createConnectedContractFactory } from '@/omnigraph'
import { pointArbitrary } from '@layerzerolabs/test-utils'
import { Contract } from '@ethersproject/contracts'
import { makeZero } from '@layerzerolabs/utils-evm'
import { makeZeroAddress } from '@layerzerolabs/utils-evm'

// Ethers calls the eth_chainId RPC method when initializing a provider so we mock the result
jest.spyOn(Web3Provider.prototype, 'send').mockResolvedValue('1')
Expand All @@ -28,7 +28,7 @@ describe('omnigraph/contracts', () => {
await fc.assert(
fc.asyncProperty(pointArbitrary, async (point) => {
const error = new Error()
const contractFactory = jest.fn().mockResolvedValue(new Contract(makeZero(undefined), []))
const contractFactory = jest.fn().mockResolvedValue(new Contract(makeZeroAddress(undefined), []))
const providerFactory = jest.fn().mockRejectedValue(error)
const connectedContractFactory = createConnectedContractFactory(contractFactory, providerFactory)

Expand All @@ -40,7 +40,7 @@ describe('omnigraph/contracts', () => {
it('should return a connected contract', async () => {
await fc.assert(
fc.asyncProperty(pointArbitrary, async (point) => {
const contract = new Contract(makeZero(undefined), [])
const contract = new Contract(makeZeroAddress(undefined), [])
const provider = new JsonRpcProvider()
const contractFactory = jest.fn().mockResolvedValue({ eid: point.eid, contract })
const providerFactory = jest.fn().mockResolvedValue(provider)
Expand Down
Loading

0 comments on commit 795951b

Please sign in to comment.