diff --git a/packages/ua-utils-evm-hardhat-test/test/oapp/config.test.ts b/packages/ua-utils-evm-hardhat-test/test/oapp/config.test.ts index 9b4c0a5a7..635230ae3 100644 --- a/packages/ua-utils-evm-hardhat-test/test/oapp/config.test.ts +++ b/packages/ua-utils-evm-hardhat-test/test/oapp/config.test.ts @@ -1,14 +1,14 @@ import { configureOApp } from '@layerzerolabs/ua-utils' import { OApp } from '@layerzerolabs/ua-utils-evm' import { + createConnectedContractFactory, createContractFactory, - createProviderFactory, createSignerFactory, OmniGraphBuilderHardhat, } from '@layerzerolabs/utils-evm-hardhat' import type { OmniGraphHardhat } from '@layerzerolabs/utils-evm-hardhat' import type { OmniPoint } from '@layerzerolabs/utils' -import { omniContractToPoint, connectOmniContract } from '@layerzerolabs/utils-evm' +import { omniContractToPoint } from '@layerzerolabs/utils-evm' import { expect } from 'chai' import { describe } from 'mocha' import { EndpointId } from '@layerzerolabs/lz-definitions' @@ -45,17 +45,12 @@ describe('oapp/config', () => { it('should return all setPeer transactions', async () => { // This is the required tooling we need to set up - const providerFactory = createProviderFactory() const contractFactory = createContractFactory() + const connectedContractFactory = createConnectedContractFactory(contractFactory) const builder = await OmniGraphBuilderHardhat.fromConfig(config, contractFactory) // This so far the only non-oneliner, a function that returns an SDK for a contract on a network - const sdkFactory = async (point: OmniPoint) => { - const provider = await providerFactory(point.eid) - const contract = await contractFactory(point) - - return new OApp(connectOmniContract(contract, provider)) - } + const sdkFactory = async (point: OmniPoint) => new OApp(await connectedContractFactory(point)) // This is where the configuration happens const transactions = await configureOApp(builder.graph, sdkFactory) @@ -75,17 +70,12 @@ describe('oapp/config', () => { it('should exclude setPeer transactions for peers that have been set', async () => { // This is the required tooling we need to set up - const providerFactory = createProviderFactory() const contractFactory = createContractFactory() + const connectedContractFactory = createConnectedContractFactory(contractFactory) const builder = await OmniGraphBuilderHardhat.fromConfig(config, contractFactory) // This so far the only non-oneliner, a function that returns an SDK for a contract on a network - const sdkFactory = async (point: OmniPoint) => { - const provider = await providerFactory(point.eid) - const contract = await contractFactory(point) - - return new OApp(connectOmniContract(contract, provider)) - } + const sdkFactory = async (point: OmniPoint) => new OApp(await connectedContractFactory(point)) const ethPoint = omniContractToPoint(await contractFactory(ethContract)) const ethSdk = await sdkFactory(ethPoint) diff --git a/packages/ua-utils/src/oapp/config.ts b/packages/ua-utils/src/oapp/config.ts index d767b9ec4..528da52d2 100644 --- a/packages/ua-utils/src/oapp/config.ts +++ b/packages/ua-utils/src/oapp/config.ts @@ -1,12 +1,7 @@ -import type { OmniGraph, OmniPoint, OmniTransaction } from '@layerzerolabs/utils' -import type { IOApp } from './types' +import type { OmniTransaction } from '@layerzerolabs/utils' +import type { OAppFactory, OAppOmniGraph } from './types' -export type OAppFactory = (point: OmniPoint) => IOApp | Promise - -export const configureOApp = async ( - graph: OmniGraph, - factory: OAppFactory -): Promise => { +export const configureOApp = async (graph: OAppOmniGraph, factory: OAppFactory): Promise => { const setPeers = await Promise.all( graph.connections.map(async ({ vector: { from, to } }): Promise => { const instance = await factory(from) diff --git a/packages/ua-utils/src/oapp/types.ts b/packages/ua-utils/src/oapp/types.ts index 230ecaa07..6abbcdb8d 100644 --- a/packages/ua-utils/src/oapp/types.ts +++ b/packages/ua-utils/src/oapp/types.ts @@ -1,7 +1,12 @@ import type { EndpointId } from '@layerzerolabs/lz-definitions' -import type { Address, OmniTransaction } from '@layerzerolabs/utils' +import type { Address, OmniGraph, OmniTransaction } from '@layerzerolabs/utils' +import { OmniPointBasedFactory } from '@layerzerolabs/utils' export interface IOApp { peers(eid: EndpointId): Promise setPeer(eid: EndpointId, peer: Address): Promise } + +export type OAppOmniGraph = OmniGraph + +export type OAppFactory = OmniPointBasedFactory diff --git a/packages/utils-evm-hardhat/src/omnigraph/contracts.ts b/packages/utils-evm-hardhat/src/omnigraph/contracts.ts new file mode 100644 index 000000000..ce43bb690 --- /dev/null +++ b/packages/utils-evm-hardhat/src/omnigraph/contracts.ts @@ -0,0 +1,16 @@ +import pMemoize from 'p-memoize' +import { ProviderFactory, connectOmniContract } from '@layerzerolabs/utils-evm' +import { createContractFactory } from '@/omnigraph/coordinates' +import type { OmniContractFactory } from '@/omnigraph/types' +import { createProviderFactory } from '@/provider' + +export const createConnectedContractFactory = ( + contractFactory: OmniContractFactory = createContractFactory(), + providerFactory: ProviderFactory = createProviderFactory() +): OmniContractFactory => + pMemoize(async (point) => { + const contract = await contractFactory(point) + const provider = await providerFactory(point.eid) + + return connectOmniContract(contract, provider) + }) diff --git a/packages/utils-evm-hardhat/src/omnigraph/index.ts b/packages/utils-evm-hardhat/src/omnigraph/index.ts index 499126ca7..037cfacf9 100644 --- a/packages/utils-evm-hardhat/src/omnigraph/index.ts +++ b/packages/utils-evm-hardhat/src/omnigraph/index.ts @@ -1,3 +1,4 @@ export * from './builder' +export * from './contracts' export * from './coordinates' export * from './types' diff --git a/packages/utils-evm-hardhat/test/omnigraph/contracts.test.ts b/packages/utils-evm-hardhat/test/omnigraph/contracts.test.ts new file mode 100644 index 000000000..f8e13ef52 --- /dev/null +++ b/packages/utils-evm-hardhat/test/omnigraph/contracts.test.ts @@ -0,0 +1,59 @@ +import fc from 'fast-check' +import 'hardhat' +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' + +// Ethers calls the eth_chainId RPC method when initializing a provider so we mock the result +jest.spyOn(Web3Provider.prototype, 'send').mockResolvedValue('1') +jest.spyOn(JsonRpcProvider.prototype, 'send').mockResolvedValue('1') + +describe('omnigraph/contracts', () => { + describe('createConnectedContractFactory', () => { + it('should reject if contractFactory rejects', async () => { + await fc.assert( + fc.asyncProperty(pointArbitrary, async (point) => { + const error = new Error() + const contractFactory = jest.fn().mockRejectedValue(error) + const connectedContractFactory = createConnectedContractFactory(contractFactory) + + await expect(connectedContractFactory(point)).rejects.toBe(error) + }) + ) + }) + + it('should reject if providerFactory rejects', async () => { + await fc.assert( + fc.asyncProperty(pointArbitrary, async (point) => { + const error = new Error() + const contractFactory = jest.fn().mockResolvedValue(new Contract(makeZero(undefined), [])) + const providerFactory = jest.fn().mockRejectedValue(error) + const connectedContractFactory = createConnectedContractFactory(contractFactory, providerFactory) + + await expect(connectedContractFactory(point)).rejects.toBe(error) + }) + ) + }) + + it('should return a connected contract', async () => { + await fc.assert( + fc.asyncProperty(pointArbitrary, async (point) => { + const contract = new Contract(makeZero(undefined), []) + const provider = new JsonRpcProvider() + const contractFactory = jest.fn().mockResolvedValue({ eid: point.eid, contract }) + const providerFactory = jest.fn().mockResolvedValue(provider) + const connectedContractFactory = createConnectedContractFactory(contractFactory, providerFactory) + + const connectedOmniContract = await connectedContractFactory(point) + + expect(connectedOmniContract.eid).toBe(point.eid) + expect(connectedOmniContract.contract).not.toBe(contract) + expect(connectedOmniContract.contract).toBeInstanceOf(Contract) + expect(connectedOmniContract.contract.provider).toBe(provider) + }) + ) + }) + }) +}) diff --git a/packages/utils-evm-hardhat/test/provider.test.ts b/packages/utils-evm-hardhat/test/provider.test.ts index c4ff4dc4c..961e24fe6 100644 --- a/packages/utils-evm-hardhat/test/provider.test.ts +++ b/packages/utils-evm-hardhat/test/provider.test.ts @@ -8,10 +8,7 @@ import { Web3Provider } from '@ethersproject/providers' jest.spyOn(Web3Provider.prototype, 'send').mockResolvedValue('1') describe('provider', () => { - // Web3Provider.prototype.s describe('createProviderFactory', () => { - beforeAll(() => {}) - it('should reject with an endpoint that is not in the hardhat config', async () => { await expect(createProviderFactory(hre)(EndpointId.CATHAY_TESTNET)).rejects.toBeTruthy() }) diff --git a/packages/utils/src/omnigraph/types.ts b/packages/utils/src/omnigraph/types.ts index 82f91c8cd..860202f8d 100644 --- a/packages/utils/src/omnigraph/types.ts +++ b/packages/utils/src/omnigraph/types.ts @@ -50,3 +50,9 @@ export interface OmniGraph { contracts: OmniNode[] connections: OmniEdge[] } + +/** + * OmniPointBasedFactory is a basis for all factories that can create objects + * based on OmniPoints - by their character these are typically contract or deployment factories + */ +export type OmniPointBasedFactory = (point: OmniPoint) => TValue | Promise