diff --git a/tests/ua-devtools-evm-hardhat-test/test/task/oapp/config.init.test.ts b/tests/ua-devtools-evm-hardhat-test/test/task/oapp/config.init.test.ts index 11811237d..dfbc4bed7 100644 --- a/tests/ua-devtools-evm-hardhat-test/test/task/oapp/config.init.test.ts +++ b/tests/ua-devtools-evm-hardhat-test/test/task/oapp/config.init.test.ts @@ -3,7 +3,6 @@ import { TASK_LZ_OAPP_CONFIG_INIT, TASK_LZ_OAPP_CONFIG_GET_DEFAULT, TASK_LZ_OAPP_WIRE, - TASK_LZ_OAPP_CONFIG_GET_READ, } from '@layerzerolabs/ua-devtools-evm-hardhat' import { deployContract, setupDefaultEndpointV2 } from '@layerzerolabs/test-setup-devtools-evm-hardhat' import { getTestHre } from '@layerzerolabs/test-devtools-evm-hardhat' @@ -141,86 +140,13 @@ describe(`task ${TASK_LZ_OAPP_CONFIG_INIT}`, () => { }) }) -describe(`task ${TASK_LZ_OAPP_CONFIG_INIT} with lzRead`, () => { - const actual = './layerzero_actual.config.js' - const expected = './layerzero_expected.config.js' - - const getPromptMocks = async () => { - const { promptToContinue, promptToSelectMultiple } = await import('@layerzerolabs/io-devtools') - - return { - promptToContinueMock: promptToContinue as jest.Mock, - promptToSelectMultipleMock: promptToSelectMultiple as jest.Mock, - } - } - - beforeEach(async () => { - // We'll deploy the endpoint and save the deployments to the filesystem - // since we want to be able to tun the task using spawnSync - await deployContract('EndpointV2', true) - await setupDefaultEndpointV2() - await deployContract('OAppRead') - }) - - afterEach(async () => { - // Delete the test file after each test - fs.existsSync(actual) && fs.unlinkSync(actual) - fs.existsSync(expected) && fs.unlinkSync(expected) - }) - - it('should generate a LayerZero Read Configuration with the two selected networks', async () => { - const { promptToSelectMultipleMock } = await getPromptMocks() - const networks = ['britney', 'tango'] - promptToSelectMultipleMock.mockResolvedValue(networks) - const contractName = 'MyOAppRead' - await hre.run(TASK_LZ_OAPP_CONFIG_INIT, { contractName: contractName, oappConfig: actual, lzRead: true }) - - // generate an expected LayerZero Config file to compare - await generateExpectedLzConfig(expected, networks, contractName, true) - const expectedContent = fs.readFileSync(expected, 'utf-8') - const actualContent = fs.readFileSync(actual, 'utf-8') - expect(expectedContent).toEqual(actualContent) - }) - - it('should generate a LayerZero Read Configuration with the three selected networks and then wire using generated config', async () => { - const { promptToSelectMultipleMock } = await getPromptMocks() - const networks = ['britney', 'tango', 'vengaboys'] - promptToSelectMultipleMock.mockResolvedValue(networks) - const contractName = 'DefaultOAppRead' - await hre.run(TASK_LZ_OAPP_CONFIG_INIT, { contractName: contractName, oappConfig: actual, lzRead: true }) - - // generate an expected LayerZero Config file to compare - await generateExpectedLzConfig(expected, networks, contractName, true) - - const expectedContent = fs.readFileSync(expected, 'utf-8') - const actualContent = fs.readFileSync(actual, 'utf-8') - expect(expectedContent).toEqual(actualContent) - - // wire using generated config and expect no errors - const oappConfig = actual - - promptToContinueMock - .mockResolvedValueOnce(false) // We don't want to see the list - .mockResolvedValueOnce(true) // We want to continue - - const [, errors] = await hre.run(TASK_LZ_OAPP_WIRE, { oappConfig }) - expect(errors).toEqual([]) - }) -}) - /** * Builds expected LayerZero config to compare against * @param {string} filename filename * @param {string[]} networks selected networks * @param {string} contractName name of contract - * @param {boolean} lzRead boolean if it is an lzRead config */ -async function generateExpectedLzConfig( - filename: string, - networks: string[], - contractName: string, - lzRead: boolean = false -) { +async function generateExpectedLzConfig(filename: string, networks: string[], contractName: string) { const endpointIdImportStatement = `import { EndpointId } from "@layerzerolabs/lz-definitions";\n` const networkContractVariables = networks .map( @@ -228,9 +154,7 @@ async function generateExpectedLzConfig( `const ${network}Contract = {\n eid: ${getTestEndpoint(network)},\n contractName: "${contractName}"\n};` ) .join('\n') - const contractsArray = lzRead - ? await getReadContractsArray(networks) - : networks.map((network) => `{ contract: ${network}Contract }`).join(', ') + const contractsArray = networks.map((network) => `{ contract: ${network}Contract }`).join(', ') const connectionsArray = await getConnectionsArray(networks ?? []) const exportContent = `\nexport default { contracts: [${contractsArray}], connections: [${connectionsArray}] };\n` const lzConfigContent = endpointIdImportStatement + networkContractVariables + exportContent @@ -293,38 +217,6 @@ async function getConnectionsArray(networks: string[]) { return connectionsContent } -/** - * Builds contract array string using passed in selected networks. - * Calls TASK_LZ_OAPP_CONFIG_GET_DEFAULT to get default values for all pathways - * @param {string[]} networks - * @return {string} string representation of the connections array - * - * connections: [ - * { - * from: firstContract, - * to: secondContract, - * config:{...} - * }, - * { - * from: secondContract, - * to: firstContract, - * config:{...} - * } - * ] - */ -async function getReadContractsArray(networks: string[]) { - const getDefaultConfigTask = await hre.run(TASK_LZ_OAPP_CONFIG_GET_READ, { - networks, - }) - - let contractsContent = '' - for (const from of networks) { - contractsContent += `{ contract: ${from}Contract, config: ${buildDefaultReadConfig(getDefaultConfigTask[from])} }, ` - } - contractsContent = contractsContent.substring(0, contractsContent.length - 2) - return contractsContent -} - /** * Builds config string with passed in defaults. * @@ -350,34 +242,6 @@ function buildDefaultConfig(defaultConfig: Record>} defaultConfig - * @return {string} string representing config - * - * config: { - * readChannelConfigs: [ - * { - * channelId: 1, - * readLibrary: "0x0000000000000000000000000000000000000000", - * ulnConfig: {...}, - * } - * ] - * } - */ -function buildDefaultReadConfig(defaultConfig: Record>>): string { - let readChannelConfigs = '' - - for (const [channelId, config] of Object.entries(defaultConfig)) { - readChannelConfigs += `{ channelId: ${channelId}, readLibrary: "${config.defaultReadLibrary}", ulnConfig: { executor: "${config.readUlnConfig?.executor}", requiredDVNs: ${handleDvns(config.readUlnConfig?.requiredDVNs as string[])}, optionalDVNs: ${handleDvns(config.readUlnConfig?.optionalDVNs as string[])}, optionalDVNThreshold: ${config.readUlnConfig?.optionalDVNThreshold ?? 0} } }, ` - } - - readChannelConfigs = readChannelConfigs.substring(0, readChannelConfigs.length - 2) - - return `{ readChannelConfigs: [` + readChannelConfigs + `] }` -} - /** * Handles converting array of DVNs (required or optional) to a string * @param {string[]} dvnArray diff --git a/tests/ua-devtools-evm-hardhat-test/test/task/oapp/read.config.init.test.ts b/tests/ua-devtools-evm-hardhat-test/test/task/oapp/read.config.init.test.ts new file mode 100644 index 000000000..8ac67f453 --- /dev/null +++ b/tests/ua-devtools-evm-hardhat-test/test/task/oapp/read.config.init.test.ts @@ -0,0 +1,264 @@ +import hre from 'hardhat' +import { + TASK_LZ_OAPP_CONFIG_GET_DEFAULT, + TASK_LZ_OAPP_READ_WIRE, + TASK_LZ_OAPP_READ_CONFIG_GET_CHANNEL, + TASK_LZ_OAPP_READ_CONFIG_INIT, +} from '@layerzerolabs/ua-devtools-evm-hardhat' +import { deployContract, setupDefaultEndpointV2 } from '@layerzerolabs/test-setup-devtools-evm-hardhat' +import * as fs from 'fs' +import { promptToContinue } from '@layerzerolabs/io-devtools' + +jest.mock('@layerzerolabs/io-devtools', () => { + const original = jest.requireActual('@layerzerolabs/io-devtools') + + return { + ...original, + promptToContinue: jest.fn().mockRejectedValue('Not mocked: promptToContinue'), + promptToSelectMultiple: jest.fn().mockRejectedValue('Not mocked: promptToSelectMultiple'), + } +}) + +const promptToContinueMock = promptToContinue as jest.Mock + +describe(`task ${TASK_LZ_OAPP_READ_CONFIG_INIT}`, () => { + const actual = './layerzero_actual.config.js' + const expected = './layerzero_expected.config.js' + + const getPromptMocks = async () => { + const { promptToContinue, promptToSelectMultiple } = await import('@layerzerolabs/io-devtools') + + return { + promptToContinueMock: promptToContinue as jest.Mock, + promptToSelectMultipleMock: promptToSelectMultiple as jest.Mock, + } + } + + beforeEach(async () => { + // We'll deploy the endpoint and save the deployments to the filesystem + // since we want to be able to tun the task using spawnSync + await deployContract('EndpointV2', true) + await setupDefaultEndpointV2() + await deployContract('OAppRead') + }) + + afterEach(async () => { + // Delete the test file after each test + fs.existsSync(actual) && fs.unlinkSync(actual) + fs.existsSync(expected) && fs.unlinkSync(expected) + }) + + it('should generate a LayerZero Read Configuration with the two selected networks', async () => { + const { promptToSelectMultipleMock } = await getPromptMocks() + const networks = ['britney', 'tango'] + promptToSelectMultipleMock.mockResolvedValue(networks) + const contractName = 'MyOAppRead' + await hre.run(TASK_LZ_OAPP_READ_CONFIG_INIT, { contractName: contractName, oappConfig: actual }) + + // generate an expected LayerZero Config file to compare + await generateExpectedLzConfig(expected, networks, contractName, true) + const expectedContent = fs.readFileSync(expected, 'utf-8') + const actualContent = fs.readFileSync(actual, 'utf-8') + expect(expectedContent).toEqual(actualContent) + }) + + it('should generate a LayerZero Read Configuration with the three selected networks and then wire using generated config', async () => { + const { promptToSelectMultipleMock } = await getPromptMocks() + const networks = ['britney', 'tango', 'vengaboys'] + promptToSelectMultipleMock.mockResolvedValue(networks) + const contractName = 'DefaultOAppRead' + await hre.run(TASK_LZ_OAPP_READ_CONFIG_INIT, { contractName: contractName, oappConfig: actual }) + + // generate an expected LayerZero Config file to compare + await generateExpectedLzConfig(expected, networks, contractName, true) + + const expectedContent = fs.readFileSync(expected, 'utf-8') + const actualContent = fs.readFileSync(actual, 'utf-8') + expect(expectedContent).toEqual(actualContent) + + // wire using generated config and expect no errors + const oappConfig = actual + + promptToContinueMock + .mockResolvedValueOnce(false) // We don't want to see the list + .mockResolvedValueOnce(true) // We want to continue + + const [, errors] = await hre.run(TASK_LZ_OAPP_READ_WIRE, { oappConfig }) + expect(errors).toEqual([]) + }) +}) + +/** + * Builds expected LayerZero config to compare against + * @param {string} filename filename + * @param {string[]} networks selected networks + * @param {string} contractName name of contract + */ +async function generateExpectedLzConfig(filename: string, networks: string[], contractName: string) { + const endpointIdImportStatement = `import { EndpointId } from "@layerzerolabs/lz-definitions";\n` + const networkContractVariables = networks + .map( + (network) => + `const ${network}Contract = {\n eid: ${getTestEndpoint(network)},\n contractName: "${contractName}"\n};` + ) + .join('\n') + const contractsArray = await getReadContractsArray(networks) + const connectionsArray = await getConnectionsArray(networks ?? []) + const exportContent = `\nexport default { contracts: [${contractsArray}], connections: [${connectionsArray}] };\n` + const lzConfigContent = endpointIdImportStatement + networkContractVariables + exportContent + fs.writeFileSync(filename, lzConfigContent) +} + +/** + * Gets string representation of test endpoint enum + * @param {string} network + * @return {string} string representation of test endpoint enum + */ +function getTestEndpoint(network: string): string { + switch (network) { + case 'vengaboys': + return 'EndpointId.ETHEREUM_V2_MAINNET' + case 'britney': + return 'EndpointId.AVALANCHE_V2_MAINNET' + case 'tango': + return 'EndpointId.BSC_V2_MAINNET' + default: + throw new Error('test network does not exist.') + } +} + +/** + * Builds connection array string using passed in selected networks. + * Calls TASK_LZ_OAPP_CONFIG_GET_DEFAULT to get default values for all pathways + * @param {string[]} networks + * @return {string} string representation of the connections array + * + * connections: [ + * { + * from: firstContract, + * to: secondContract, + * config:{...} + * }, + * { + * from: secondContract, + * to: firstContract, + * config:{...} + * } + * ] + */ +async function getConnectionsArray(networks: string[]) { + const getDefaultConfigTask = await hre.run(TASK_LZ_OAPP_CONFIG_GET_DEFAULT, { + networks, + }) + + let connectionsContent = '' + for (const from of networks) { + for (const to of networks) { + if (from === to) { + continue + } + const defaultConfig = getDefaultConfigTask[from][to] + connectionsContent += `{ from: ${from}Contract, to: ${to}Contract, config: ${buildDefaultConfig(defaultConfig)} }, ` + } + } + connectionsContent = connectionsContent.substring(0, connectionsContent.length - 2) + return connectionsContent +} + +/** + * Builds contract array string using passed in selected networks. + * Calls TASK_LZ_OAPP_CONFIG_GET_DEFAULT to get default values for all pathways + * @param {string[]} networks + * @return {string} string representation of the connections array + * + * connections: [ + * { + * from: firstContract, + * to: secondContract, + * config:{...} + * }, + * { + * from: secondContract, + * to: firstContract, + * config:{...} + * } + * ] + */ +async function getReadContractsArray(networks: string[]) { + const getDefaultConfigTask = await hre.run(TASK_LZ_OAPP_READ_CONFIG_GET_CHANNEL, { + networks, + }) + + let contractsContent = '' + for (const from of networks) { + contractsContent += `{ contract: ${from}Contract, config: ${buildDefaultReadConfig(getDefaultConfigTask[from])} }, ` + } + contractsContent = contractsContent.substring(0, contractsContent.length - 2) + return contractsContent +} + +/** + * Builds config string with passed in defaults. + * + * @param {Record>} defaultConfig + * @return {string} string representing config + * + * config: { + * sendLibrary: "0x0000000000000000000000000000000000000000", + * receiveLibraryConfig: {...}, + * sendConfig: {...}, + * receiveConfig: {...} + * } + */ +function buildDefaultConfig(defaultConfig: Record>): string { + return ( + `{ ` + + `sendLibrary: "${defaultConfig.defaultSendLibrary}", ` + + `receiveLibraryConfig: { receiveLibrary: "${defaultConfig.defaultReceiveLibrary}", gracePeriod: 0 }, ` + + `sendConfig: { executorConfig: { maxMessageSize: ${defaultConfig.sendExecutorConfig?.maxMessageSize}, executor: "${defaultConfig.sendExecutorConfig?.executor}" }, ` + + `ulnConfig: { confirmations: ${defaultConfig.sendUlnConfig?.confirmations}, requiredDVNs: ${handleDvns(defaultConfig.sendUlnConfig?.requiredDVNs as string[])}, optionalDVNs: ${handleDvns(defaultConfig.sendUlnConfig?.optionalDVNs as string[])}, optionalDVNThreshold: ${defaultConfig.sendUlnConfig?.optionalDVNThreshold ?? 0} } }, ` + + `receiveConfig: { ulnConfig: { confirmations: ${defaultConfig.receiveUlnConfig?.confirmations}, requiredDVNs: ${handleDvns(defaultConfig.receiveUlnConfig?.requiredDVNs as string[])}, optionalDVNs: ${handleDvns(defaultConfig.receiveUlnConfig?.optionalDVNs as string[])}, optionalDVNThreshold: ${defaultConfig.receiveUlnConfig?.optionalDVNThreshold ?? 0} } }` + + ` }` + ) +} + +/** + * Builds config string with passed in defaults for ReadLib. + * + * @param {Record>} defaultConfig + * @return {string} string representing config + * + * config: { + * readChannelConfigs: [ + * { + * channelId: 1, + * readLibrary: "0x0000000000000000000000000000000000000000", + * ulnConfig: {...}, + * } + * ] + * } + */ +function buildDefaultReadConfig(defaultConfig: Record>>): string { + let readChannelConfigs = '' + + for (const [channelId, config] of Object.entries(defaultConfig)) { + readChannelConfigs += `{ channelId: ${channelId}, readLibrary: "${config.defaultReadLibrary}", ulnConfig: { executor: "${config.readUlnConfig?.executor}", requiredDVNs: ${handleDvns(config.readUlnConfig?.requiredDVNs as string[])}, optionalDVNs: ${handleDvns(config.readUlnConfig?.optionalDVNs as string[])}, optionalDVNThreshold: ${config.readUlnConfig?.optionalDVNThreshold ?? 0} } }, ` + } + + readChannelConfigs = readChannelConfigs.substring(0, readChannelConfigs.length - 2) + + return `{ readChannelConfigs: [` + readChannelConfigs + `] }` +} + +/** + * Handles converting array of DVNs (required or optional) to a string + * @param {string[]} dvnArray + * @return {string} string representing dvns + */ +function handleDvns(dvnArray: string[]): string { + if (dvnArray.length > 0) { + return '[' + dvnArray.map((str) => `"${str}"`).join(', ') + ']' + } else { + return '[]' + } +} diff --git a/tests/ua-devtools-evm-hardhat-test/test/task/oapp/read.wire.test.ts b/tests/ua-devtools-evm-hardhat-test/test/task/oapp/read.wire.test.ts new file mode 100644 index 000000000..c2ef2e030 --- /dev/null +++ b/tests/ua-devtools-evm-hardhat-test/test/task/oapp/read.wire.test.ts @@ -0,0 +1,78 @@ +import hre from 'hardhat' +import { isFile, promptToContinue } from '@layerzerolabs/io-devtools' +import { join, relative } from 'path' +import { TASK_LZ_OAPP_READ_WIRE } from '@layerzerolabs/ua-devtools-evm-hardhat' +import { cwd } from 'process' +import { deployContract, setupDefaultEndpointV2 } from '@layerzerolabs/test-setup-devtools-evm-hardhat' +import { createGnosisSignerFactory, createSignerFactory } from '@layerzerolabs/devtools-evm-hardhat' + +jest.mock('@layerzerolabs/io-devtools', () => { + const original = jest.requireActual('@layerzerolabs/io-devtools') + + return { + ...original, + promptToContinue: jest.fn().mockRejectedValue('Not mocked'), + } +}) + +jest.mock('@layerzerolabs/devtools-evm-hardhat', () => { + const original = jest.requireActual('@layerzerolabs/devtools-evm-hardhat') + + return { + ...original, + createGnosisSignerFactory: jest.fn(original.createGnosisSignerFactory), + createSignerFactory: jest.fn(original.createSignerFactory), + } +}) + +const hreRunSpy = jest.spyOn(hre, 'run') +const promptToContinueMock = promptToContinue as jest.Mock +const createGnosisSignerFactoryMock = createGnosisSignerFactory as jest.Mock +const createSignerFactoryMock = createSignerFactory as jest.Mock + +describe(`task ${TASK_LZ_OAPP_READ_WIRE}`, () => { + // Helper matcher object that checks for OmniPoint objects + const expectOmniPoint = { address: expect.any(String), eid: expect.any(Number) } + // Helper matcher object that checks for OmniTransaction objects + const expectTransaction = { data: expect.any(String), point: expectOmniPoint, description: expect.any(String) } + const expectTransactionWithReceipt = { receipt: expect.any(Object), transaction: expectTransaction } + + const CONFIGS_BASE_DIR = relative(cwd(), join(__dirname, '__data__', 'configs')) + const configPathFixture = (fileName: string): string => { + const path = join(CONFIGS_BASE_DIR, fileName) + + expect(isFile(path)).toBeTruthy() + + return path + } + + beforeAll(async () => { + await deployContract('EndpointV2') + await setupDefaultEndpointV2() + }) + + beforeEach(async () => { + promptToContinueMock.mockReset() + createGnosisSignerFactoryMock.mockClear() + createSignerFactoryMock.mockClear() + hreRunSpy.mockClear() + }) + + describe('with invalid configs', () => { + beforeAll(async () => { + await deployContract('OApp') + }) + + it('should work', async () => { + await deployContract('OAppRead') + const oappConfig = configPathFixture('valid.config.read.js') + + promptToContinueMock.mockResolvedValue(false) + + const result = await hre.run(TASK_LZ_OAPP_READ_WIRE, { oappConfig, ci: true }) + + expect(result).toEqual([[expectTransactionWithReceipt, expectTransactionWithReceipt], [], []]) + expect(promptToContinueMock).not.toHaveBeenCalled() + }) + }) +}) diff --git a/tests/ua-devtools-evm-hardhat-test/test/task/oapp/wire.test.ts b/tests/ua-devtools-evm-hardhat-test/test/task/oapp/wire.test.ts index ceee19d41..6380f155b 100644 --- a/tests/ua-devtools-evm-hardhat-test/test/task/oapp/wire.test.ts +++ b/tests/ua-devtools-evm-hardhat-test/test/task/oapp/wire.test.ts @@ -398,18 +398,6 @@ describe(`task ${TASK_LZ_OAPP_WIRE}`, () => { expect(promptToContinueMock).not.toHaveBeenCalled() }) - it('should work if the lzRead flag is used', async () => { - await deployContract('OAppRead') - const oappConfig = configPathFixture('valid.config.read.js') - - promptToContinueMock.mockResolvedValue(false) - - const result = await hre.run(TASK_LZ_OAPP_WIRE, { oappConfig, ci: true, lzRead: true }) - - expect(result).toEqual([[expectTransactionWithReceipt, expectTransactionWithReceipt], [], []]) - expect(promptToContinueMock).not.toHaveBeenCalled() - }) - describe('if a transaction fails', () => { let sendTransactionMock: jest.SpyInstance