diff --git a/packages/ua-utils-evm-hardhat-test/test/__utils__/endpoint.ts b/packages/ua-utils-evm-hardhat-test/test/__utils__/endpoint.ts new file mode 100644 index 000000000..f7fb6c31a --- /dev/null +++ b/packages/ua-utils-evm-hardhat-test/test/__utils__/endpoint.ts @@ -0,0 +1,171 @@ +import { + createConnectedContractFactory, + createLogger, + createNetworkEnvironmentFactory, + createSignerFactory, + OmniGraphBuilderHardhat, + type OmniGraphHardhat, +} from '@layerzerolabs/utils-evm-hardhat' +import deploy from '../../deploy/001_bootstrap' +import { EndpointId } from '@layerzerolabs/lz-definitions' +import { Endpoint, omniContractToPoint, Uln302 } from '@layerzerolabs/utils-evm' +import { + configureEndpoint, + EndpointEdgeConfig, + EndpointFactory, + Uln302NodeConfig, + formatOmniPoint, + Uln302ExecutorConfig, + configureUln302, + Uln302Factory, + Uln302UlnConfig, +} from '@layerzerolabs/utils' + +export const ethEndpoint = { eid: EndpointId.ETHEREUM_MAINNET, contractName: 'EndpointV2' } +export const ethReceiveUln = { eid: EndpointId.ETHEREUM_MAINNET, contractName: 'ReceiveUln302' } +export const ethSendUln = { eid: EndpointId.ETHEREUM_MAINNET, contractName: 'SendUln302' } +export const avaxEndpoint = { eid: EndpointId.AVALANCHE_MAINNET, contractName: 'EndpointV2' } +export const avaxReceiveUln = { eid: EndpointId.AVALANCHE_MAINNET, contractName: 'ReceiveUln302' } +export const avaxSendUln = { eid: EndpointId.AVALANCHE_MAINNET, contractName: 'SendUln302' } + +export const defaultExecutorConfig: Uln302ExecutorConfig = { + maxMessageSize: 1024, + executor: '0x0000000000000000000000000000000000000001', +} + +export const defaultUlnConfig: Uln302UlnConfig = { + confirmations: 1, + requiredDVNs: ['0x0000000000000000000000000000000000000002', '0x0000000000000000000000000000000000000003'], + optionalDVNs: [], + optionalDVNThreshold: 0, +} + +/** + * Helper function that deploys a fresh endpoint infrastructure: + * + * - EndpointV2 + * - ReceiveUln302 + * - SendUln302 + * + * After deploying, it will wire up the elements with minimal configuration + */ +export const setupDefaultEndpoint = async (): Promise => { + // This is the tooling we are going to need + const logger = createLogger() + const environmentFactory = createNetworkEnvironmentFactory() + const contractFactory = createConnectedContractFactory() + const signerFactory = createSignerFactory() + const endpointSdkFactory: EndpointFactory = async (point) => new Endpoint(await contractFactory(point)) + const ulnSdkFactory: Uln302Factory = async (point) => new Uln302(await contractFactory(point)) + + // First we deploy the endpoint + await deploy(await environmentFactory(EndpointId.ETHEREUM_MAINNET)) + await deploy(await environmentFactory(EndpointId.AVALANCHE_MAINNET)) + + // For the graphs, we'll also need the pointers to the contracts + const ethSendUlnPoint = omniContractToPoint(await contractFactory(ethSendUln)) + const avaxSendUlnPoint = omniContractToPoint(await contractFactory(avaxSendUln)) + const ethReceiveUlnPoint = omniContractToPoint(await contractFactory(ethReceiveUln)) + const avaxReceiveUlnPoint = omniContractToPoint(await contractFactory(avaxReceiveUln)) + + // This is the graph for SendUln302 + const sendUlnConfig: OmniGraphHardhat = { + contracts: [ + { + contract: ethSendUln, + config: { + defaultUlnConfigs: [[EndpointId.AVALANCHE_MAINNET, defaultUlnConfig]], + defaultExecutorConfigs: [[EndpointId.AVALANCHE_MAINNET, defaultExecutorConfig]], + }, + }, + { + contract: avaxSendUln, + config: { + defaultUlnConfigs: [[EndpointId.ETHEREUM_MAINNET, defaultUlnConfig]], + defaultExecutorConfigs: [[EndpointId.ETHEREUM_MAINNET, defaultExecutorConfig]], + }, + }, + ], + connections: [], + } + + // This is the graph for ReceiveUln302 + const receiveUlnConfig: OmniGraphHardhat = { + contracts: [ + { + contract: ethReceiveUln, + config: { + defaultUlnConfigs: [[EndpointId.AVALANCHE_MAINNET, defaultUlnConfig]], + defaultExecutorConfigs: [], + }, + }, + { + contract: avaxReceiveUln, + config: { + defaultUlnConfigs: [[EndpointId.ETHEREUM_MAINNET, defaultUlnConfig]], + defaultExecutorConfigs: [], + }, + }, + ], + connections: [], + } + + // This is the graph for EndpointV2 + const config: OmniGraphHardhat = { + contracts: [ + { + contract: ethEndpoint, + config: undefined, + }, + { + contract: avaxEndpoint, + config: undefined, + }, + ], + connections: [ + { + from: ethEndpoint, + to: avaxEndpoint, + config: { + defaultReceiveLibrary: ethReceiveUlnPoint.address, + defaultSendLibrary: ethSendUlnPoint.address, + }, + }, + { + from: avaxEndpoint, + to: ethEndpoint, + config: { + defaultReceiveLibrary: avaxReceiveUlnPoint.address, + defaultSendLibrary: avaxSendUlnPoint.address, + }, + }, + ], + } + + // Now we compile a list of all the transactions that need to be executed for the ULNs and Endpoints + const builderEndpoint = await OmniGraphBuilderHardhat.fromConfig(config, contractFactory) + const endpointTransactions = await configureEndpoint(builderEndpoint.graph, endpointSdkFactory) + const builderSendUln = await OmniGraphBuilderHardhat.fromConfig(sendUlnConfig, contractFactory) + const sendUlnTransactions = await configureUln302(builderSendUln.graph, ulnSdkFactory) + const builderReceiveUln = await OmniGraphBuilderHardhat.fromConfig(receiveUlnConfig, contractFactory) + const receiveUlnTransactions = await configureUln302(builderReceiveUln.graph, ulnSdkFactory) + + const transactions = [...sendUlnTransactions, ...receiveUlnTransactions, ...endpointTransactions] + + logger.info(`Executing ${transactions.length} transactions`) + + for (const transaction of transactions) { + const signer = await signerFactory(transaction.point.eid) + const description = transaction.description ?? '[no description]' + + logger.info(`${formatOmniPoint(transaction.point)}: ${description}`) + + const response = await signer.signAndSend(transaction) + logger.info(`${formatOmniPoint(transaction.point)}: ${description}: ${response.transactionHash}`) + + const receipt = await response.wait() + logger.info(`${formatOmniPoint(transaction.point)}: ${description}: ${receipt.transactionHash}`) + } + + logger.info(`Done configuring endpoint`) +} 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 index f58436095..1faad360e 100644 --- a/packages/ua-utils-evm-hardhat-test/test/endpoint/config.test.ts +++ b/packages/ua-utils-evm-hardhat-test/test/endpoint/config.test.ts @@ -1,148 +1,76 @@ -import { EndpointEdgeConfig, configureEndpoint } from '@layerzerolabs/utils' -import { Endpoint } from '@layerzerolabs/utils-evm' -import { - createContractFactory, - OmniGraphHardhat, - OmniGraphBuilderHardhat, - createConnectedContractFactory, -} from '@layerzerolabs/utils-evm-hardhat' +import { Endpoint, Uln302 } from '@layerzerolabs/utils-evm' +import { 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' +import { defaultUlnConfig, setupDefaultEndpoint } from '../__utils__/endpoint' describe('endpoint/config', () => { const ethEndpoint = { eid: EndpointId.ETHEREUM_MAINNET, contractName: 'EndpointV2' } - const ethUln = { eid: EndpointId.ETHEREUM_MAINNET, contractName: 'SendUln302' } + const ethReceiveUln = { eid: EndpointId.ETHEREUM_MAINNET, contractName: 'ReceiveUln302' } + const ethSendUln = { 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), - ]) + const avaxReceiveUln = { eid: EndpointId.AVALANCHE_MAINNET, contractName: 'ReceiveUln302' } + const avaxSendUln = { eid: EndpointId.AVALANCHE_MAINNET, contractName: 'SendUln302' } + + beforeEach(async () => { + await setupDefaultEndpoint() }) - 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)]) + describe('endpoint', () => { + it('should have default libraries configured', async () => { + // This is the required tooling we need to set up + const connectedContractFactory = createConnectedContractFactory() + const sdkFactory = async (point: OmniPoint) => new Endpoint(await connectedContractFactory(point)) + + // Now for the purposes of the test, we need to get coordinates of our contracts + const ethEndpointPoint = omniContractToPoint(await connectedContractFactory(ethEndpoint)) + const avaxEndpointPoint = omniContractToPoint(await connectedContractFactory(avaxEndpoint)) + + const ethEndpointSdk = await sdkFactory(ethEndpointPoint) + const avaxEndpointSdk = await sdkFactory(avaxEndpointPoint) + + // First let's check the send libraries + const ethDefaultSendLib = await ethEndpointSdk.defaultSendLibrary(avaxEndpointPoint.eid) + const avaxDefaultSendLib = await avaxEndpointSdk.defaultSendLibrary(ethEndpointPoint.eid) + + const ethSendUlnPoint = omniContractToPoint(await connectedContractFactory(ethSendUln)) + const avaxSendUlnPoint = omniContractToPoint(await connectedContractFactory(avaxSendUln)) + + expect(ethDefaultSendLib).to.equal(ethSendUlnPoint.address) + expect(avaxDefaultSendLib).to.equal(avaxSendUlnPoint.address) + + // Then let's check the receive libraries + const ethDefaultReceiveLib = await ethEndpointSdk.defaultReceiveLibrary(avaxEndpointPoint.eid) + const avaxDefaultReceiveLib = await avaxEndpointSdk.defaultReceiveLibrary(ethEndpointPoint.eid) + + const ethReceiveUlnPoint = omniContractToPoint(await connectedContractFactory(ethReceiveUln)) + const avaxReceiveUlnPoint = omniContractToPoint(await connectedContractFactory(avaxReceiveUln)) + + expect(ethDefaultReceiveLib).to.equal(ethReceiveUlnPoint.address) + expect(avaxDefaultReceiveLib).to.equal(avaxReceiveUlnPoint.address) + }) + }) + + describe('sendUln302', () => { + it('should have default executors configured', async () => { + // This is the required tooling we need to set up + const connectedContractFactory = createConnectedContractFactory() + const sdkFactory = async (point: OmniPoint) => new Uln302(await connectedContractFactory(point)) + + const ethSendUlnPoint = omniContractToPoint(await connectedContractFactory(ethSendUln)) + const avaxSendUlnPoint = omniContractToPoint(await connectedContractFactory(avaxSendUln)) + + const ethSendUlnSdk = await sdkFactory(ethSendUlnPoint) + const avaxSendUlnSdk = await sdkFactory(avaxSendUlnPoint) + + const ethConfig = await ethSendUlnSdk.getUlnConfig(avaxSendUlnPoint.eid, avaxSendUlnPoint.address) + const avaxConfig = await avaxSendUlnSdk.getUlnConfig(ethSendUlnPoint.eid, ethSendUlnPoint.address) + + expect(ethConfig).to.eql(defaultUlnConfig) + expect(avaxConfig).to.eql(defaultUlnConfig) + }) }) }) 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 635230ae3..e69e39f1b 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 @@ -12,6 +12,7 @@ import { omniContractToPoint } from '@layerzerolabs/utils-evm' import { expect } from 'chai' import { describe } from 'mocha' import { EndpointId } from '@layerzerolabs/lz-definitions' +import { setupDefaultEndpoint } from '../__utils__/endpoint' describe('oapp/config', () => { const ethContract = { eid: EndpointId.ETHEREUM_MAINNET, contractName: 'DefaultOApp' } @@ -43,6 +44,10 @@ describe('oapp/config', () => { ], } + beforeEach(async () => { + await setupDefaultEndpoint() + }) + it('should return all setPeer transactions', async () => { // This is the required tooling we need to set up const contractFactory = createContractFactory() diff --git a/packages/utils-evm/src/endpoint/sdk.ts b/packages/utils-evm/src/endpoint/sdk.ts index 6a2ec2b29..a50fa3b32 100644 --- a/packages/utils-evm/src/endpoint/sdk.ts +++ b/packages/utils-evm/src/endpoint/sdk.ts @@ -1,4 +1,4 @@ -import type { IEndpoint } from '@layerzerolabs/utils' +import { formatEid, type IEndpoint } from '@layerzerolabs/utils' import type { Address, OmniTransaction } from '@layerzerolabs/utils' import type { EndpointId } from '@layerzerolabs/lz-definitions' import { OmniContract } from '@/omnigraph/types' @@ -8,17 +8,51 @@ 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 defaultReceiveLibrary(eid: EndpointId): Promise { + return ignoreZero(await this.contract.contract.defaultReceiveLibrary(eid)) } - async setDefaultSendLibrary(eid: EndpointId, address: Address | null | undefined): Promise { - const data = this.contract.contract.interface.encodeFunctionData('setDefaultSendLibrary', [ + async setDefaultReceiveLibrary( + eid: EndpointId, + lib: string | null | undefined, + gracePeriod: number = 0 + ): Promise { + const data = this.contract.contract.interface.encodeFunctionData('setDefaultReceiveLibrary', [ eid, - makeZero(address), + makeZero(lib), + gracePeriod, ]) - return this.createTransaction(data) + return { + ...this.createTransaction(data), + description: `Setting default receive library for ${formatEid(eid)} to ${makeZero(lib)}`, + } + } + + async defaultSendLibrary(eid: EndpointId): Promise { + return ignoreZero(await this.contract.contract.defaultSendLibrary(eid)) + } + + async setDefaultSendLibrary(eid: EndpointId, lib: Address | null | undefined): Promise { + const data = this.contract.contract.interface.encodeFunctionData('setDefaultSendLibrary', [eid, makeZero(lib)]) + + return { + ...this.createTransaction(data), + description: `Setting default send library for ${formatEid(eid)} to ${lib}`, + } + } + + isRegisteredLibrary(lib: Address): Promise { + return this.contract.contract.isRegisteredLibrary(lib) + } + + async registerLibrary(lib: string): Promise { + const data = this.contract.contract.interface.encodeFunctionData('registerLibrary', [lib]) + + return { + ...this.createTransaction(data), + description: `Registering library ${lib}`, + } } protected createTransaction(data: string): OmniTransaction { diff --git a/packages/utils-evm/src/index.ts b/packages/utils-evm/src/index.ts index a76be5fec..fc14f8b3a 100644 --- a/packages/utils-evm/src/index.ts +++ b/packages/utils-evm/src/index.ts @@ -3,3 +3,4 @@ export * from './endpoint' export * from './omnigraph' export * from './provider' export * from './signer' +export * from './uln302' diff --git a/packages/utils-evm/src/uln302/index.ts b/packages/utils-evm/src/uln302/index.ts new file mode 100644 index 000000000..7db67b18a --- /dev/null +++ b/packages/utils-evm/src/uln302/index.ts @@ -0,0 +1 @@ +export * from './sdk' diff --git a/packages/utils-evm/src/uln302/sdk.ts b/packages/utils-evm/src/uln302/sdk.ts new file mode 100644 index 000000000..12d53598f --- /dev/null +++ b/packages/utils-evm/src/uln302/sdk.ts @@ -0,0 +1,55 @@ +import { formatEid, type IUln302, type Uln302ExecutorConfig, type Uln302UlnConfig } from '@layerzerolabs/utils' +import type { OmniTransaction } from '@layerzerolabs/utils' +import type { EndpointId } from '@layerzerolabs/lz-definitions' +import { OmniContract } from '@/omnigraph/types' +import { omniContractToPoint } from '@/omnigraph/coordinates' + +export class Uln302 implements IUln302 { + constructor(public readonly contract: OmniContract) {} + + async getUlnConfig(eid: EndpointId, address: string): Promise { + const config = await this.contract.contract.getUlnConfig(address, eid) + + return { + confirmations: config.confirmations.toNumber(), + requiredDVNs: config.requiredDVNs, + optionalDVNs: config.optionalDVNs, + optionalDVNThreshold: config.optionalDVNThreshold, + } + } + + async setDefaultExecutorConfig(eid: EndpointId, config: Uln302ExecutorConfig): Promise { + const data = this.contract.contract.interface.encodeFunctionData('setDefaultExecutorConfigs', [ + [{ eid, config }], + ]) + + return this.createTransaction(data) + } + + async setDefaultUlnConfig(eid: EndpointId, config: Uln302UlnConfig): Promise { + const data = this.contract.contract.interface.encodeFunctionData('setDefaultUlnConfigs', [ + [ + { + eid, + config: { + ...config, + requiredDVNCount: config.requiredDVNs.length, + optionalDVNCount: config.optionalDVNs.length, + }, + }, + ], + ]) + + return { + ...this.createTransaction(data), + description: `Setting default ULN config for ${formatEid(eid)}: ${JSON.stringify(config)}`, + } + } + + protected createTransaction(data: string): OmniTransaction { + return { + point: omniContractToPoint(this.contract), + data, + } + } +} diff --git a/packages/utils/src/endpoint/config.ts b/packages/utils/src/endpoint/config.ts index ed58ca080..417a34740 100644 --- a/packages/utils/src/endpoint/config.ts +++ b/packages/utils/src/endpoint/config.ts @@ -1,19 +1,59 @@ +import { flattenTransactions } from '@/transactions/utils' 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)] - }) +export type EndpointConfigurator = (graph: EndpointOmniGraph, createSdk: EndpointFactory) => Promise + +export const configureEndpoint: EndpointConfigurator = async (graph, createSdk) => + flattenTransactions([ + ...(await configureEndpointDefaultReceiveLibraries(graph, createSdk)), + ...(await configureEndpointDefaultSendLibraries(graph, createSdk)), + ]) + +export const configureEndpointDefaultReceiveLibraries: EndpointConfigurator = async (graph, createSdk) => + flattenTransactions( + await Promise.all( + graph.connections.map(async ({ vector: { from, to }, config }): Promise => { + const sdk = await createSdk(from) + const address = await sdk.defaultReceiveLibrary(to.eid) + + // If the library is already set as default, do nothing + if (config.defaultReceiveLibrary === address) return [] + + // We need to check whether the library has been registered before we set is as default + const isRegistered = await sdk.isRegisteredLibrary(config.defaultReceiveLibrary) + + return flattenTransactions([ + // We only want to register the library if it has not been registered yet + isRegistered ? undefined : await sdk.registerLibrary(config.defaultReceiveLibrary), + await sdk.setDefaultReceiveLibrary( + to.eid, + config.defaultReceiveLibrary, + config.defaultReceiveLibraryGracePeriod + ), + ]) + }) + ) ) - return [...setDefaultSendLibraries].flat() -} +export const configureEndpointDefaultSendLibraries: EndpointConfigurator = async (graph, createSdk) => + flattenTransactions( + await Promise.all( + graph.connections.map(async ({ vector: { from, to }, config }): Promise => { + const sdk = await createSdk(from) + const address = await sdk.defaultSendLibrary(to.eid) + + // If the library is already set as default, do nothing + if (config.defaultSendLibrary === address) return [] + + // We need to check whether the library has been registered before we set is as default + const isRegistered = await sdk.isRegisteredLibrary(config.defaultSendLibrary) + + return flattenTransactions([ + // We only want to register the library if it has not been registered yet + isRegistered ? undefined : await sdk.registerLibrary(config.defaultSendLibrary), + await sdk.setDefaultSendLibrary(to.eid, config.defaultSendLibrary), + ]) + }) + ) + ) diff --git a/packages/utils/src/endpoint/types.ts b/packages/utils/src/endpoint/types.ts index ba16ac64a..c93fc05bf 100644 --- a/packages/utils/src/endpoint/types.ts +++ b/packages/utils/src/endpoint/types.ts @@ -4,11 +4,23 @@ import type { Address } from '@/types' import type { EndpointId } from '@layerzerolabs/lz-definitions' export interface IEndpoint { + defaultReceiveLibrary(eid: EndpointId): Promise
+ setDefaultReceiveLibrary( + eid: EndpointId, + lib: Address | null | undefined, + gracePeriod?: number + ): Promise + defaultSendLibrary(eid: EndpointId): Promise
setDefaultSendLibrary(eid: EndpointId, lib: Address | null | undefined): Promise + + isRegisteredLibrary(lib: Address): Promise + registerLibrary(lib: Address): Promise } export interface EndpointEdgeConfig { + defaultReceiveLibrary: Address + defaultReceiveLibraryGracePeriod?: number defaultSendLibrary: Address } diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 668f90436..eec1c23d2 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -2,3 +2,4 @@ export * from './endpoint' export * from './omnigraph' export * from './transactions' export * from './types' +export * from './uln302' diff --git a/packages/utils/src/transactions/types.ts b/packages/utils/src/transactions/types.ts index a3b23e8e8..a89ad5ae7 100644 --- a/packages/utils/src/transactions/types.ts +++ b/packages/utils/src/transactions/types.ts @@ -4,6 +4,7 @@ import { EndpointBasedFactory } from '@/types' export interface OmniTransaction { point: OmniPoint data: string + description?: string } export interface OmniTransactionResponse { diff --git a/packages/utils/src/uln302/config.ts b/packages/utils/src/uln302/config.ts new file mode 100644 index 000000000..ad684edfa --- /dev/null +++ b/packages/utils/src/uln302/config.ts @@ -0,0 +1,37 @@ +import { flattenTransactions } from '@/transactions/utils' +import type { Uln302Factory, Uln302OmniGraph } from './types' +import type { OmniTransaction } from '@/transactions/types' + +export type Uln302Configurator = (graph: Uln302OmniGraph, createSdk: Uln302Factory) => Promise + +export const configureUln302: Uln302Configurator = async (graph, createSdk) => + flattenTransactions([ + ...(await configureUln302DefaultExecutorConfigs(graph, createSdk)), + ...(await configureUln302DefaultUlnConfigs(graph, createSdk)), + ]) + +export const configureUln302DefaultExecutorConfigs: Uln302Configurator = async (graph, createSdk) => + flattenTransactions( + await Promise.all( + graph.contracts.map(async ({ point, config }): Promise => { + const sdk = await createSdk(point) + + return Promise.all( + config.defaultExecutorConfigs.map(([eid, config]) => sdk.setDefaultExecutorConfig(eid, config)) + ) + }) + ) + ) + +export const configureUln302DefaultUlnConfigs: Uln302Configurator = async (graph, createSdk) => + flattenTransactions( + await Promise.all( + graph.contracts.map(async ({ point, config }): Promise => { + const sdk = await createSdk(point) + + return Promise.all( + config.defaultUlnConfigs.map(([eid, config]) => sdk.setDefaultUlnConfig(eid, config)) + ) + }) + ) + ) diff --git a/packages/utils/src/uln302/index.ts b/packages/utils/src/uln302/index.ts new file mode 100644 index 000000000..39bdac610 --- /dev/null +++ b/packages/utils/src/uln302/index.ts @@ -0,0 +1,2 @@ +export * from './config' +export * from './types' diff --git a/packages/utils/src/uln302/types.ts b/packages/utils/src/uln302/types.ts new file mode 100644 index 000000000..7c501c2c4 --- /dev/null +++ b/packages/utils/src/uln302/types.ts @@ -0,0 +1,39 @@ +import type { OmniGraph, OmniPointBasedFactory } from '@/omnigraph/types' +import type { OmniTransaction } from '@/transactions/types' +import type { Address } from '@/types' +import type { EndpointId } from '@layerzerolabs/lz-definitions' + +export interface IUln302 { + getUlnConfig(eid: EndpointId, address: Address): Promise + setDefaultExecutorConfig(eid: EndpointId, config: Uln302ExecutorConfig): Promise + setDefaultUlnConfig(eid: EndpointId, config: Uln302UlnConfig): Promise +} + +export interface Uln302ExecutorConfig { + maxMessageSize: number + executor: string +} + +export interface Uln302UlnConfig { + confirmations: number + optionalDVNThreshold: number + requiredDVNs: string[] + optionalDVNs: string[] + + // uint64 confirmations; + // we store the length of required DVNs and optional DVNs instead of using DVN.length directly to save gas + // uint8 requiredDVNCount; // 0 indicate DEFAULT, NIL_DVN_COUNT indicate NONE (to override the value of default) + // uint8 optionalDVNCount; // 0 indicate DEFAULT, NIL_DVN_COUNT indicate NONE (to override the value of default) + // uint8 optionalDVNThreshold; // (0, optionalDVNCount] + // address[] requiredDVNs; // no duplicates. sorted an an ascending order. allowed overlap with optionalDVNs + // address[] optionalDVNs; +} + +export interface Uln302NodeConfig { + defaultExecutorConfigs: [eid: EndpointId, config: Uln302ExecutorConfig][] + defaultUlnConfigs: [eid: EndpointId, config: Uln302UlnConfig][] +} + +export type Uln302OmniGraph = OmniGraph + +export type Uln302Factory = OmniPointBasedFactory