diff --git a/packages/ua-utils-evm-hardhat-test/test/endpoint/config.test.ts b/packages/ua-utils-evm-hardhat-test/test/endpoint/config.test.ts new file mode 100644 index 000000000..f58436095 --- /dev/null +++ b/packages/ua-utils-evm-hardhat-test/test/endpoint/config.test.ts @@ -0,0 +1,148 @@ +import { EndpointEdgeConfig, configureEndpoint } from '@layerzerolabs/utils' +import { Endpoint } from '@layerzerolabs/utils-evm' +import { + createContractFactory, + OmniGraphHardhat, + OmniGraphBuilderHardhat, + createConnectedContractFactory, +} from '@layerzerolabs/utils-evm-hardhat' +import type { OmniPoint } from '@layerzerolabs/utils' +import { omniContractToPoint } from '@layerzerolabs/utils-evm' +import { createSignerFactory } from '@layerzerolabs/utils-evm-hardhat' +import { expect } from 'chai' +import { describe } from 'mocha' +import { EndpointId } from '@layerzerolabs/lz-definitions' + +describe('endpoint/config', () => { + const ethEndpoint = { eid: EndpointId.ETHEREUM_MAINNET, contractName: 'EndpointV2' } + const ethUln = { eid: EndpointId.ETHEREUM_MAINNET, contractName: 'SendUln302' } + const avaxEndpoint = { eid: EndpointId.AVALANCHE_MAINNET, contractName: 'EndpointV2' } + const avaxUln = { eid: EndpointId.AVALANCHE_MAINNET, contractName: 'SendUln302' } + + it('should return all setDefaultSendLibrary transactions', async () => { + // This is the required tooling we need to set up + const contractFactory = createContractFactory() + const connectedContractFactory = createConnectedContractFactory(contractFactory) + const sdkFactory = async (point: OmniPoint) => new Endpoint(await connectedContractFactory(point)) + + // At this point we need a config + // + // Since the config values now depend on contracts deployed in the bootstrap, + // the config creation is a bit more involved + const ethUlnPoint = omniContractToPoint(await contractFactory(ethUln)) + const avaxUlnPoint = omniContractToPoint(await contractFactory(avaxUln)) + const config: OmniGraphHardhat = { + contracts: [ + { + contract: ethEndpoint, + config: undefined, + }, + { + contract: avaxEndpoint, + config: undefined, + }, + ], + connections: [ + { + from: ethEndpoint, + to: avaxEndpoint, + config: { + defaultSendLibrary: ethUlnPoint.address, + }, + }, + { + from: avaxEndpoint, + to: ethEndpoint, + config: { + defaultSendLibrary: avaxUlnPoint.address, + }, + }, + ], + } + + const builder = await OmniGraphBuilderHardhat.fromConfig(config, contractFactory) + + // This is where the configuration happens + const transactions = await configureEndpoint(builder.graph, sdkFactory) + + // And finally the test assertions + const ethEndpointPoint = omniContractToPoint(await contractFactory(ethEndpoint)) + const ethEndpointSdk = await sdkFactory(ethEndpointPoint) + + const avaxEndpointPoint = omniContractToPoint(await contractFactory(avaxEndpoint)) + const avaxEndpointSdk = await sdkFactory(avaxEndpointPoint) + + expect(transactions).to.eql([ + await ethEndpointSdk.setDefaultSendLibrary(avaxUlnPoint.eid, avaxUlnPoint.address), + await avaxEndpointSdk.setDefaultSendLibrary(ethUlnPoint.eid, ethUlnPoint.address), + ]) + }) + + it('should exclude setDefaultSendLibrary transactions for libraries that have been set', async () => { + // This is the required tooling we need to set up + // This is the required tooling we need to set up + const contractFactory = createContractFactory() + const connectedContractFactory = createConnectedContractFactory(contractFactory) + const sdkFactory = async (point: OmniPoint) => new Endpoint(await connectedContractFactory(point)) + + // At this point we need a config + // + // Since the config values now depend on contracts deployed in the bootstrap, + // the config creation is a bit more involved + const ethUlnPoint = omniContractToPoint(await contractFactory(ethUln)) + const avaxUlnPoint = omniContractToPoint(await contractFactory(avaxUln)) + const config: OmniGraphHardhat = { + contracts: [ + { + contract: ethEndpoint, + config: undefined, + }, + { + contract: avaxEndpoint, + config: undefined, + }, + ], + connections: [ + { + from: ethEndpoint, + to: avaxEndpoint, + config: { + defaultSendLibrary: ethUlnPoint.address, + }, + }, + { + from: avaxEndpoint, + to: ethEndpoint, + config: { + defaultSendLibrary: avaxUlnPoint.address, + }, + }, + ], + } + + const builder = await OmniGraphBuilderHardhat.fromConfig(config, contractFactory) + + const ethEndpointPoint = omniContractToPoint(await contractFactory(ethEndpoint)) + const ethEndpointSdk = await sdkFactory(ethEndpointPoint) + + const avaxEndpointPoint = omniContractToPoint(await contractFactory(avaxEndpoint)) + const avaxEndpointSdk = await sdkFactory(avaxEndpointPoint) + + // Before we configure the Endpoint, we'll set some defaultSendLibraries + { + const signerFactory = createSignerFactory() + const ethSigner = await signerFactory(ethEndpoint.eid) + const ethTransaction = await ethEndpointSdk.setDefaultSendLibrary(avaxUlnPoint.eid, avaxUlnPoint.address) + const ethResponse = await ethSigner.signAndSend(ethTransaction) + const ethReceipt = await ethResponse.wait() + + expect(ethReceipt.from).to.equal(await ethSigner.signer.getAddress()) + } + + // Now we configure the Endpoint + const transactions = await configureEndpoint(builder.graph, sdkFactory) + + // And we check that the configuration transaction we already submitted is not included + expect(transactions).to.eql([await avaxEndpointSdk.setDefaultSendLibrary(ethUlnPoint.eid, ethUlnPoint.address)]) + }) +}) diff --git a/packages/utils-evm/src/endpoint/index.ts b/packages/utils-evm/src/endpoint/index.ts new file mode 100644 index 000000000..7db67b18a --- /dev/null +++ b/packages/utils-evm/src/endpoint/index.ts @@ -0,0 +1 @@ +export * from './sdk' diff --git a/packages/utils-evm/src/endpoint/sdk.ts b/packages/utils-evm/src/endpoint/sdk.ts new file mode 100644 index 000000000..6a2ec2b29 --- /dev/null +++ b/packages/utils-evm/src/endpoint/sdk.ts @@ -0,0 +1,30 @@ +import type { IEndpoint } from '@layerzerolabs/utils' +import type { Address, OmniTransaction } from '@layerzerolabs/utils' +import type { EndpointId } from '@layerzerolabs/lz-definitions' +import { OmniContract } from '@/omnigraph/types' +import { ignoreZero, makeZero } from '@/address' +import { omniContractToPoint } from '@/omnigraph/coordinates' + +export class Endpoint implements IEndpoint { + constructor(public readonly contract: OmniContract) {} + + async defaultSendLibrary(eid: EndpointId): Promise { + return ignoreZero(await this.contract.contract.defaultSendLibrary(eid)) + } + + async setDefaultSendLibrary(eid: EndpointId, address: Address | null | undefined): Promise { + const data = this.contract.contract.interface.encodeFunctionData('setDefaultSendLibrary', [ + eid, + makeZero(address), + ]) + + return this.createTransaction(data) + } + + protected createTransaction(data: string): OmniTransaction { + return { + point: omniContractToPoint(this.contract), + data, + } + } +} diff --git a/packages/utils-evm/src/index.ts b/packages/utils-evm/src/index.ts index a4a1506fc..a76be5fec 100644 --- a/packages/utils-evm/src/index.ts +++ b/packages/utils-evm/src/index.ts @@ -1,4 +1,5 @@ export * from './address' +export * from './endpoint' export * from './omnigraph' export * from './provider' export * from './signer' diff --git a/packages/utils/src/endpoint/config.ts b/packages/utils/src/endpoint/config.ts new file mode 100644 index 000000000..ed58ca080 --- /dev/null +++ b/packages/utils/src/endpoint/config.ts @@ -0,0 +1,19 @@ +import type { EndpointFactory, EndpointOmniGraph } from './types' +import type { OmniTransaction } from '@/transactions/types' + +export const configureEndpoint = async ( + graph: EndpointOmniGraph, + factory: EndpointFactory +): Promise => { + const setDefaultSendLibraries = await Promise.all( + graph.connections.map(async ({ vector: { from, to }, config }): Promise => { + const instance = await factory(from) + const address = await instance.defaultSendLibrary(to.eid) + + if (config.defaultSendLibrary === address) return [] + return [await instance.setDefaultSendLibrary(to.eid, config.defaultSendLibrary)] + }) + ) + + return [...setDefaultSendLibraries].flat() +} diff --git a/packages/utils/src/endpoint/index.ts b/packages/utils/src/endpoint/index.ts new file mode 100644 index 000000000..39bdac610 --- /dev/null +++ b/packages/utils/src/endpoint/index.ts @@ -0,0 +1,2 @@ +export * from './config' +export * from './types' diff --git a/packages/utils/src/endpoint/types.ts b/packages/utils/src/endpoint/types.ts new file mode 100644 index 000000000..62e05cd22 --- /dev/null +++ b/packages/utils/src/endpoint/types.ts @@ -0,0 +1,16 @@ +import { OmniGraph, OmniPointBasedFactory } from '@/omnigraph/types' +import type { EndpointId } from '@layerzerolabs/lz-definitions' +import type { Address, OmniTransaction } from '@layerzerolabs/utils' + +export interface IEndpoint { + defaultSendLibrary(eid: EndpointId): Promise
+ setDefaultSendLibrary(eid: EndpointId, lib: Address | null | undefined): Promise +} + +export interface EndpointEdgeConfig { + defaultSendLibrary: Address +} + +export type EndpointOmniGraph = OmniGraph + +export type EndpointFactory = OmniPointBasedFactory diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index a8fe8c866..668f90436 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -1,3 +1,4 @@ +export * from './endpoint' export * from './omnigraph' export * from './transactions' export * from './types'