Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🪚 OmniGraph™ OmniPoint transformation utility #99

Merged
merged 1 commit into from
Dec 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/utils-evm-hardhat/src/omnigraph/builder.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { OmniGraphHardhat } from './types'
import type { OmniGraphHardhat, OmniGraphHardhatTransformer } from './types'
import { OmniGraphBuilder } from '@layerzerolabs/utils'
import assert from 'assert'
import { OmniGraphHardhatTransformer, createOmniGraphHardhatTransformer } from './transformations'
import { createOmniGraphHardhatTransformer } from './transformations'

/**
* OmniGraphBuilderHardhat houses all hardhat-specific utilities for building OmniGraphs
Expand Down
70 changes: 47 additions & 23 deletions packages/utils-evm-hardhat/src/omnigraph/transformations.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,57 @@
import type { OmniEdge, OmniGraph, OmniNode } from '@layerzerolabs/utils'
import type { OmniEdge, OmniNode } from '@layerzerolabs/utils'
import { isOmniPoint } from '@layerzerolabs/utils'
import { omniContractToPoint } from '@layerzerolabs/utils-evm'
import { createContractFactory } from './coordinates'
import type { OmniContractFactoryHardhat, OmniEdgeHardhat, OmniGraphHardhat, OmniNodeHardhat } from './types'
import type {
OmniContractFactoryHardhat,
OmniEdgeHardhat,
OmniGraphHardhatTransformer,
OmniNodeHardhat,
OmniPointHardhatTransformer,
} from './types'

export const createOmniNodeHardhatTransformer =
(contractFactory: OmniContractFactoryHardhat = createContractFactory()) =>
async <TNodeConfig>({ contract, config }: OmniNodeHardhat<TNodeConfig>): Promise<OmniNode<TNodeConfig>> => {
const point = isOmniPoint(contract) ? contract : omniContractToPoint(await contractFactory(contract))
/**
* Create a function capable of transforming `OmniPointHardhat` to a regular `OmniPoint`
* with some help from an `OmniContractFactoryHardhat`
*
* @param {OmniContractFactoryHardhat} contractFactory
* @returns {OmniPointHardhatTransformer}
*/
export const createOmniPointHardhatTransformer =
(contractFactory: OmniContractFactoryHardhat = createContractFactory()): OmniPointHardhatTransformer =>
async (point) =>
isOmniPoint(point) ? point : omniContractToPoint(await contractFactory(point))

return { point, config: config as TNodeConfig }
}
/**
* Create a function capable of transforming `OmniNodeHardhat` to a regular `OmniNode`
* with some help from an `OmniPointHardhatTransformer`
*
* @param {OmniPointHardhatTransformer} [pointTransformer]
* @returns
*/
export const createOmniNodeHardhatTransformer =
(pointTransformer = createOmniPointHardhatTransformer()) =>
async <TNodeConfig>({ contract, config }: OmniNodeHardhat<TNodeConfig>): Promise<OmniNode<TNodeConfig>> => ({
point: await pointTransformer(contract),
config: config as TNodeConfig,
})

/**
* Create a function capable of transforming `OmniEdgeHardhat` to a regular `OmniEdge`
* with some help from an `OmniPointHardhatTransformer`
*
* @param {OmniPointHardhatTransformer} [pointTransformer]
* @returns
*/
export const createOmniEdgeHardhatTransformer =
(contractFactory: OmniContractFactoryHardhat = createContractFactory()) =>
async <TEdgeConfig>({
from: fromContract,
to: toContract,
config,
}: OmniEdgeHardhat<TEdgeConfig>): Promise<OmniEdge<TEdgeConfig>> => {
const from = isOmniPoint(fromContract) ? fromContract : omniContractToPoint(await contractFactory(fromContract))
const to = isOmniPoint(toContract) ? toContract : omniContractToPoint(await contractFactory(toContract))

return { vector: { from, to }, config: config as TEdgeConfig }
}

export type OmniGraphHardhatTransformer<TNodeConfig = unknown, TEdgeConfig = unknown> = (
graph: OmniGraphHardhat<TNodeConfig, TEdgeConfig>
) => Promise<OmniGraph<TNodeConfig, TEdgeConfig>>
(pointTransformer = createOmniPointHardhatTransformer()) =>
async <TEdgeConfig>({ from, to, config }: OmniEdgeHardhat<TEdgeConfig>): Promise<OmniEdge<TEdgeConfig>> => ({
vector: {
from: await pointTransformer(from),
to: await pointTransformer(to),
},
config: config as TEdgeConfig,
})

export const createOmniGraphHardhatTransformer =
<TNodeConfig, TEdgeConfig>(
Expand Down
8 changes: 7 additions & 1 deletion packages/utils-evm-hardhat/src/omnigraph/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { OmniPoint, WithEid, WithOptionals } from '@layerzerolabs/utils'
import type { OmniGraph, OmniPoint, WithEid, WithOptionals } from '@layerzerolabs/utils'
import type { OmniContract } from '@layerzerolabs/utils-evm'
import type { Deployment } from 'hardhat-deploy/dist/types'

Expand Down Expand Up @@ -50,3 +50,9 @@ export interface OmniGraphHardhat<TNodeConfig = unknown, TEdgeConfig = unknown>
}

export type OmniContractFactoryHardhat = (point: OmniPointHardhat) => OmniContract | Promise<OmniContract>

export type OmniPointHardhatTransformer = (point: OmniPointHardhat | OmniPoint) => Promise<OmniPoint>

export type OmniGraphHardhatTransformer<TNodeConfig = unknown, TEdgeConfig = unknown> = (
graph: OmniGraphHardhat<TNodeConfig, TEdgeConfig>
) => Promise<OmniGraph<TNodeConfig, TEdgeConfig>>
130 changes: 42 additions & 88 deletions packages/utils-evm-hardhat/test/omnigraph/transformations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
createOmniEdgeHardhatTransformer,
createOmniGraphHardhatTransformer,
createOmniNodeHardhatTransformer,
createOmniPointHardhatTransformer,
} from '@/omnigraph/transformations'
import { Contract } from '@ethersproject/contracts'
import { endpointArbitrary, evmAddressArbitrary, nullableArbitrary, pointArbitrary } from '@layerzerolabs/test-utils'
Expand All @@ -16,140 +17,93 @@ describe('omnigraph/transformations', () => {
address: nullableArbitrary(evmAddressArbitrary),
})

describe('createOmniNodeHardhatTransformer', () => {
describe('createOmniPointHardhatTransformer', () => {
it('should pass the original value if contract is already an OmniPoint', async () => {
await fc.assert(
fc.asyncProperty(pointArbitrary, fc.anything(), async (point, config) => {
fc.asyncProperty(pointArbitrary, async (point) => {
const contractFactory = jest.fn().mockRejectedValue('Oh no')
const transformer = createOmniNodeHardhatTransformer(contractFactory)
const transformer = createOmniPointHardhatTransformer(contractFactory)

const node = await transformer({ contract: point, config })
const transformed = await transformer(point)

expect(node).toEqual({ point, config })
expect(transformed).toBe(point)
expect(contractFactory).not.toHaveBeenCalled()
})
)
})

it('should call the contractFactory if contract is not an OmniPoint', async () => {
await fc.assert(
fc.asyncProperty(
pointHardhatArbitrary,
evmAddressArbitrary,
fc.anything(),
async (point, address, config) => {
fc.pre(!isOmniPoint(point))

const contract = new Contract(address, [])
const contractFactory = jest
.fn()
.mockImplementation(async (point: OmniPointHardhat) => ({ eid: point.eid, contract }))
const transformer = createOmniNodeHardhatTransformer(contractFactory)

const node = await transformer({ contract: point, config })

expect(node).toEqual({ point: { eid: point.eid, address }, config })
expect(contractFactory).toHaveBeenCalledTimes(1)
expect(contractFactory).toHaveBeenCalledWith(point)
}
)
)
})
})
fc.asyncProperty(pointHardhatArbitrary, evmAddressArbitrary, async (point, address) => {
fc.pre(!isOmniPoint(point))

describe('createOmniEdgeHardhatTransformer', () => {
it('should pass the original values if from and to are already OmniPoints', async () => {
await fc.assert(
fc.asyncProperty(pointArbitrary, pointArbitrary, fc.anything(), async (from, to, config) => {
const contractFactory = jest.fn().mockRejectedValue('Oh no')
const transformer = createOmniEdgeHardhatTransformer(contractFactory)
const contract = new Contract(address, [])
const contractFactory = jest
.fn()
.mockImplementation(async (point: OmniPointHardhat) => ({ eid: point.eid, contract }))
const transformer = createOmniPointHardhatTransformer(contractFactory)

const edge = await transformer({ from, to, config })
const transformed = await transformer(point)

expect(edge).toEqual({ vector: { from, to }, config })
expect(contractFactory).not.toHaveBeenCalled()
expect(transformed).toEqual({ eid: point.eid, address })
expect(contractFactory).toHaveBeenCalledTimes(1)
expect(contractFactory).toHaveBeenCalledWith(point)
})
)
})
})

it('should call the contractFactory if from is not an OmniPoint', async () => {
await fc.assert(
fc.asyncProperty(
pointHardhatArbitrary,
pointArbitrary,
evmAddressArbitrary,
fc.anything(),
async (from, to, address, config) => {
fc.pre(!isOmniPoint(from))

const contract = new Contract(address, [])
const contractFactory = jest
.fn()
.mockImplementation(async (point: OmniPointHardhat) => ({ eid: point.eid, contract }))
const transformer = createOmniEdgeHardhatTransformer(contractFactory)

const edge = await transformer({ from, to, config })

expect(edge).toEqual({ vector: { from: { eid: from.eid, address }, to }, config })
expect(contractFactory).toHaveBeenCalledTimes(1)
expect(contractFactory).toHaveBeenCalledWith(from)
}
)
)
})

it('should call the contractFactory if to is not an OmniPoint', async () => {
describe('createOmniNodeHardhatTransformer', () => {
it('should call the pointTransformer on the point', async () => {
await fc.assert(
fc.asyncProperty(
pointArbitrary,
pointHardhatArbitrary,
evmAddressArbitrary,
fc.anything(),
async (from, to, address, config) => {
fc.pre(!isOmniPoint(to))
async (point, address, config) => {
fc.pre(!isOmniPoint(point))

const contract = new Contract(address, [])
const contractFactory = jest
.fn()
.mockImplementation(async (point: OmniPointHardhat) => ({ eid: point.eid, contract }))
const transformer = createOmniEdgeHardhatTransformer(contractFactory)
const pointTransformer = jest.fn().mockImplementation(async (point: OmniPointHardhat) => ({
eid: point.eid,
address: address,
}))
const transformer = createOmniNodeHardhatTransformer(pointTransformer)

const edge = await transformer({ from, to, config })
const node = await transformer({ contract: point, config })

expect(edge).toEqual({ vector: { from, to: { eid: to.eid, address } }, config })
expect(contractFactory).toHaveBeenCalledTimes(1)
expect(contractFactory).toHaveBeenCalledWith(to)
expect(node).toEqual({ point: { eid: point.eid, address }, config })
expect(pointTransformer).toHaveBeenCalledTimes(1)
expect(pointTransformer).toHaveBeenCalledWith(point)
}
)
)
})
})

it('should call the contractFactory if from & to are not OmniPoints', async () => {
describe('createOmniEdgeHardhatTransformer', () => {
it('should call the pointTransformer on from and to', async () => {
await fc.assert(
fc.asyncProperty(
pointHardhatArbitrary,
pointHardhatArbitrary,
evmAddressArbitrary,
fc.anything(),
async (from, to, address, config) => {
fc.pre(!isOmniPoint(from))
fc.pre(!isOmniPoint(to))

const contract = new Contract(address, [])
const contractFactory = jest
.fn()
.mockImplementation(async (point: OmniPointHardhat) => ({ eid: point.eid, contract }))
const transformer = createOmniEdgeHardhatTransformer(contractFactory)
const pointTransformer = jest.fn().mockImplementation(async (point: OmniPointHardhat) => ({
eid: point.eid,
address,
}))
const transformer = createOmniEdgeHardhatTransformer(pointTransformer)

const edge = await transformer({ from, to, config })

expect(edge).toEqual({
vector: { from: { eid: from.eid, address }, to: { eid: to.eid, address } },
config,
})
expect(contractFactory).toHaveBeenCalledTimes(2)
expect(contractFactory).toHaveBeenCalledWith(from)
expect(contractFactory).toHaveBeenCalledWith(to)
expect(pointTransformer).toHaveBeenCalledTimes(2)
expect(pointTransformer).toHaveBeenCalledWith(from)
expect(pointTransformer).toHaveBeenCalledWith(to)
}
)
)
Expand Down
Loading