diff --git a/.changeset/blue-sheep-visit.md b/.changeset/blue-sheep-visit.md new file mode 100644 index 000000000..807d61514 --- /dev/null +++ b/.changeset/blue-sheep-visit.md @@ -0,0 +1,5 @@ +--- +"@layerzerolabs/io-devtools": patch +--- + +Rename description to hint for select prompts diff --git a/.changeset/large-ligers-visit.md b/.changeset/large-ligers-visit.md new file mode 100644 index 000000000..f2b595063 --- /dev/null +++ b/.changeset/large-ligers-visit.md @@ -0,0 +1,8 @@ +--- +"@layerzerolabs/ua-devtools-evm-hardhat-test": patch +"@layerzerolabs/ua-devtools-evm-hardhat": patch +"@layerzerolabs/devtools-evm-hardhat-test": patch +"@layerzerolabs/devtools-evm-hardhat": patch +--- + +Add lz:deploy task; Add createClearDeployments utility diff --git a/packages/devtools-evm-hardhat/src/deployments.ts b/packages/devtools-evm-hardhat/src/deployments.ts new file mode 100644 index 000000000..2b98ff54b --- /dev/null +++ b/packages/devtools-evm-hardhat/src/deployments.ts @@ -0,0 +1,35 @@ +import { EndpointId } from '@layerzerolabs/lz-definitions' +import { createGetHreByEid } from './runtime' +import { Deployment } from 'hardhat-deploy/dist/types' + +const alwaysTrue = () => true + +/** + * Creates a function for clearing existing deployments from filesystem. + * + * This is mostly useful for cleanups after tests but might come in handy + * in other scenarios as well. + * + * ``` + * const clearDeployments = createClearDeployments() + * + * // Clear all deployments for Fuji + * await clearDeployments(EndpointId.AVALANCHE_V2_TESTNET) + * + * // Clear MyOFT deployments for Fuji + * await clearDeployments(EndpointId.AVALANCHE_V2_TESTNET, (contractName) => contractName === 'MyOFT') + * ``` + * + * @param getHreByEid + * @returns + */ +export const createClearDeployments = + (getHreByEid = createGetHreByEid()) => + async (eid: EndpointId, filter: (contractName: string, deployment: Deployment) => boolean = alwaysTrue) => { + const hre = await getHreByEid(eid) + const contractNames: string[] = Object.entries(await hre.deployments.all()).flatMap( + ([contractName, deployment]) => (filter(contractName, deployment) ? [contractName] : []) + ) + + await Promise.all(contractNames.map((contractName) => hre.deployments.delete(contractName))) + } diff --git a/packages/devtools-evm-hardhat/src/index.ts b/packages/devtools-evm-hardhat/src/index.ts index 941612ce2..0ce5d3ac3 100644 --- a/packages/devtools-evm-hardhat/src/index.ts +++ b/packages/devtools-evm-hardhat/src/index.ts @@ -5,6 +5,7 @@ import './type-extensions' export * from './artifacts' export * from './cli' export * from './config' +export * from './deployments' export * from './constants' export * from './errors' export * from './internal' diff --git a/packages/devtools-evm-hardhat/src/tasks/deploy.ts b/packages/devtools-evm-hardhat/src/tasks/deploy.ts index d7243484b..2ba14aa09 100644 --- a/packages/devtools-evm-hardhat/src/tasks/deploy.ts +++ b/packages/devtools-evm-hardhat/src/tasks/deploy.ts @@ -1,23 +1,64 @@ import { task } from 'hardhat/config' import type { ActionType } from 'hardhat/types' import { TASK_LZ_DEPLOY } from '@/constants/tasks' -import { createLogger, setDefaultLogLevel } from '@layerzerolabs/io-devtools' +import { + PromptOption, + createLogger, + pluralizeNoun, + printBoolean, + promptToContinue, + promptToSelectMultiple, + setDefaultLogLevel, +} from '@layerzerolabs/io-devtools' -import { printLogo } from '@layerzerolabs/io-devtools/swag' +import { createProgressBar, printLogo, printRecords, render } from '@layerzerolabs/io-devtools/swag' +import { formatEid } from '@layerzerolabs/devtools' +import { getEidsByNetworkName, getHreByNetworkName } from '@/runtime' import { types } from '@/cli' -import { assertDefinedNetworks } from '@/internal/assertions' +import { promptForText } from '@layerzerolabs/io-devtools' +import { Deployment } from 'hardhat-deploy/dist/types' +import { assertDefinedNetworks, assertHardhatDeploy } from '@/internal/assertions' +import { splitCommaSeparated } from '@layerzerolabs/devtools' interface TaskArgs { networks?: string[] + tags?: string[] logLevel?: string ci?: boolean } +/** + * Result of this task, a map of `NetworkDeployResult` objects keyed by network names + * + * @see {@link NetworkDeployResult} + */ +type DeployResults = Record + +/** + * Result of a deployment for one particular network. + * + * Unfortunately, when deployment fails partially, + * there is now way of getting the partial deployment result from hardhat-deploy + * and just an error is returned instead + */ +type NetworkDeployResult = + // A successful result will contain a map of deployments by their contract names + | { + contracts: Record + error?: never + } + // A failed result will only contain an error + | { + contracts?: never + error: unknown + } + const action: ActionType = async ({ networks: networksArgument, + tags: tagsArgument = [], logLevel = 'info', ci = false, -}): Promise => { +}): Promise => { printLogo() // Make sure to check that the networks are defined @@ -33,27 +74,181 @@ const action: ActionType = async ({ const isInteractive = !ci logger.debug(isInteractive ? 'Running in interactive mode' : 'Running in non-interactive (CI) mode') - // First we'll deal with the networks - if (networksArgument == null) { - logger.verbose('No --networks argument provided, will use all networks') + // We grab a mapping between network names and endpoint IDs + const eidsByNetworks = Object.entries(getEidsByNetworkName()) + const configuredNetworkNames = eidsByNetworks.flatMap(([name, eid]) => (eid == null ? [] : [name])) + + // We'll use all the configured network names as the default for the networks argument + const networks: string[] = networksArgument ?? configuredNetworkNames + + // Here we'll store the final value for the networks we'd like to deploy + let selectedNetworks: string[] + + let selectedTags: string[] + + if (isInteractive) { + // In the interactive mode, we'll ask the user to confirm which networks they want to deploy + + // We'll preselect the networks passed as --networks argument and we'll do it in O(1) + const networksSet = new Set(networks) + + const options: PromptOption[] = eidsByNetworks + .map(([networkName, eid]) => ({ + title: networkName, + value: networkName, + disabled: eid == null, + selected: networksSet.has(networkName), + hint: eid == null ? undefined : `Connected to ${formatEid(eid)}`, + })) + .sort( + (a, b) => + // We want to show the enabled networks first + Number(a.disabled) - Number(b.disabled) || + // And sort the rest by their name + a.title.localeCompare(b.title) + ) + + // Now we ask the user to confirm the network selection + selectedNetworks = await promptToSelectMultiple('Which networks would you like to deploy?', { options }) + + // And we ask to confirm the tags to deploy + selectedTags = await promptForText('Which deploy script tags would you like to use?', { + defaultValue: tagsArgument?.join(','), + hint: 'Leave empty to use all deploy scripts', + }).then(splitCommaSeparated) + } else { + // In the non-interactive mode we'll use whatever we got on the CLI + selectedNetworks = networks + selectedTags = tagsArgument } -} -if (process.env.LZ_ENABLE_EXPERIMENTAL_TASK_LZ_DEPLOY) { - task(TASK_LZ_DEPLOY, 'Deploy LayerZero contracts') - .addParam( - 'networks', - 'List of comma-separated networks. If not provided, all networks will be deployed', - undefined, - types.csv, - true + // If no networks have been selected, we exit + if (selectedNetworks.length === 0) return logger.warn(`No networks selected, exiting`), {} + + // We'll tell the user what's about to happen + logger.info( + pluralizeNoun( + selectedNetworks.length, + `Will deploy 1 network: ${selectedNetworks.join(',')}`, + `Will deploy ${selectedNetworks.length} networks: ${selectedNetworks.join(', ')}` ) - .addParam('logLevel', 'Logging level. One of: error, warn, info, verbose, debug, silly', 'info', types.logLevel) - .addParam( - 'ci', - 'Continuous integration (non-interactive) mode. Will not ask for any input from the user', - false, - types.boolean + ) + + if (selectedTags.length === 0) { + // Deploying all tags might not be what the user wants so we'll warn them about it + logger.warn(`Will use all deployment scripts`) + } else { + logger.info(`Will use deploy scripts tagged with ${selectedTags.join(', ')}`) + } + + // Now we confirm with the user that they want to continue + const shouldDeploy = isInteractive ? await promptToContinue() : true + if (!shouldDeploy) return logger.verbose(`User cancelled the operation, exiting`), {} + + // We talk we talk we talk + logger.verbose(`Running deployment scripts`) + + // Now we render a progressbar to monitor the deployment progress + const progressBar = render(createProgressBar({ before: 'Deploying... ', after: ` 0/${selectedNetworks.length}` })) + + // For now we'll use a very simple deployment logic with no retries + // + // For display purposes, we'll track the number of networks we deployed + let numProcessed: number = 0 + + // And for diplay purposes we'll also track the failures + const results: DeployResults = {} + + // Now we run all the deployments + await Promise.all( + selectedNetworks.map(async (networkName) => { + // First we grab the hre for that network + const env = await getHreByNetworkName(networkName) + + try { + // We need to make sure the user has enabled hardhat-deploy + assertHardhatDeploy(env) + + // The core of this task, running the hardhat deploy scripts + const contracts = await env.deployments.run(selectedTags, { + writeDeploymentsToFiles: true, + }) + + results[networkName] = { contracts } + + logger.debug(`Successfully deployed network ${networkName}`) + } catch (error: unknown) { + // If we fail to deploy, we just store the error and continue + // + // Unfortunately, there is no way of knowing whether the failure was total + // or partial so we don't know whether there are any contracts that got deployed + results[networkName] = { error } + + logger.debug(`Failed deploying network ${networkName}: ${error}`) + } finally { + numProcessed++ + + // Now we update the progressbar + progressBar.rerender( + createProgressBar({ + before: 'Deploying... ', + after: ` ${numProcessed}/${selectedNetworks.length}`, + progress: numProcessed, + }) + ) + } + }) + ) + + // We drop the progressbar and continue + progressBar.clear() + + // We check whether we got any errors + const errors = Object.entries(results).flatMap(([networkName, { error }]) => + error == null ? [] : [{ networkName, error }] + ) + + // If nothing went wrong we just exit + if (errors.length === 0) return logger.info(`${printBoolean(true)} Your contracts are now deployed`), results + + // We log the fact that there were some errors + logger.error( + `${printBoolean(false)} ${pluralizeNoun(errors.length, 'Failed to deploy 1 network', `Failed to deploy ${errors.length} networks`)}` + ) + + // If some of the deployments failed, we let the user know + const previewErrors = isInteractive ? await promptToContinue(`Would you like to see the deployment errors?`) : true + if (previewErrors) + printRecords( + errors.map(({ networkName, error }) => ({ + Network: networkName, + Error: String(error), + })) ) - .setAction(action) + + return results } + +task(TASK_LZ_DEPLOY, 'Deploy LayerZero contracts') + .addParam( + 'networks', + 'List of comma-separated networks. If not provided, all networks will be deployed', + undefined, + types.csv, + true + ) + .addParam( + 'tags', + 'List of comma-separated deploy script tags to deploy. If not provided, all deploy scripts will be executed', + undefined, + types.csv, + true + ) + .addParam('logLevel', 'Logging level. One of: error, warn, info, verbose, debug, silly', 'info', types.logLevel) + .addParam( + 'ci', + 'Continuous integration (non-interactive) mode. Will not ask for any input from the user', + false, + types.boolean + ) + .setAction(action) diff --git a/packages/devtools-evm-hardhat/test/tasks/deploy.test.ts b/packages/devtools-evm-hardhat/test/tasks/deploy.test.ts deleted file mode 100644 index 4c5d0c946..000000000 --- a/packages/devtools-evm-hardhat/test/tasks/deploy.test.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { HardhatContext } from 'hardhat/internal/context' -import type { HardhatRuntimeEnvironment } from 'hardhat/types' -import { TASK_LZ_DEPLOY } from '@/constants' - -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'), - } -}) - -describe(`task ${TASK_LZ_DEPLOY}`, () => { - /** - * Helper utility that loads hardhat in isolation, - * ensuring that a new copy of hardhat will be loaded - * everytime we call this. - * - * This is useful since we are testing a behavior of feature flagged task - * whose presence is determined by a env variable when hardhat is loaded - * - * @param {(hre: HardhatRuntimeEnvironment) => void | Promise} callback Callback to be executed - * @returns {Promise} - */ - const useIsolatedHre = (callback: (hre: HardhatRuntimeEnvironment) => void | Promise): Promise => - jest.isolateModulesAsync(async () => callback(await import('hardhat'))) - - afterEach(() => { - jest.resetModules() - - // On top of isolating the import of hardhat, we also need to wipe the HardhatContext - // after every test, otherwise the context will be preserved and the new hardhat import - // will fail since it will try to re-register already registered tasks - HardhatContext.deleteHardhatContext() - }) - - describe('when LZ_ENABLE_EXPERIMENTAL_TASK_LZ_DEPLOY env feature flag is not set', () => { - beforeEach(() => { - process.env.LZ_ENABLE_EXPERIMENTAL_TASK_LZ_DEPLOY = '' - }) - - it('should not be available', async () => { - await useIsolatedHre((hre) => { - expect(hre.tasks[TASK_LZ_DEPLOY]).toBeUndefined() - }) - }) - }) - - describe('when LZ_ENABLE_EXPERIMENTAL_TASK_LZ_DEPLOY env feature flag is set', () => { - beforeEach(() => { - process.env.LZ_ENABLE_EXPERIMENTAL_TASK_LZ_DEPLOY = '1' - }) - - it('should be available', async () => { - await useIsolatedHre((hre) => { - expect(hre.tasks[TASK_LZ_DEPLOY]).not.toBeUndefined() - }) - }) - - it('should not fail if no networks are provided', async () => { - await useIsolatedHre(async (hre) => { - await expect(hre.run(TASK_LZ_DEPLOY, {})).resolves.toBeUndefined() - }) - }) - }) -}) diff --git a/packages/io-devtools/src/stdio/prompts.ts b/packages/io-devtools/src/stdio/prompts.ts index c9752b3c1..a8bd94bfa 100644 --- a/packages/io-devtools/src/stdio/prompts.ts +++ b/packages/io-devtools/src/stdio/prompts.ts @@ -52,17 +52,22 @@ export const promptToContinue = async ( type PromptValidator = (value: TValue) => string | boolean | Promise interface TextProps { + /** + * Additional message to show to the user + */ + hint?: string defaultValue?: string validate?: PromptValidator } export const promptForText = async ( message: string = 'Do you want to continue?', - { defaultValue, validate }: TextProps = {} + { defaultValue, hint, validate }: TextProps = {} ) => { const { value } = await prompts({ type: 'text', name: 'value', + hint, message, onState: handlePromptState, validate, @@ -74,7 +79,7 @@ export const promptForText = async ( export interface PromptOption { title: string - description?: string + hint?: string disabled?: boolean selected?: boolean value?: TValue diff --git a/packages/ua-devtools-evm-hardhat/src/tasks/oapp/config.init.ts b/packages/ua-devtools-evm-hardhat/src/tasks/oapp/config.init.ts index 6897c6111..8afff240a 100644 --- a/packages/ua-devtools-evm-hardhat/src/tasks/oapp/config.init.ts +++ b/packages/ua-devtools-evm-hardhat/src/tasks/oapp/config.init.ts @@ -64,7 +64,7 @@ const action: ActionType = async ({ logLevel = 'info' }, hre): Promise title: networkName, value: networkName, disabled: eid == null, - description: eid == null ? undefined : `Connected to ${formatEid(eid)}`, + hint: eid == null ? undefined : `Connected to ${formatEid(eid)}`, })) .sort( (a, b) => diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9f775fb23..7c4c24006 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -157,7 +157,7 @@ importers: version: 0.8.2 ts-node: specifier: ^10.9.2 - version: 10.9.2(@swc/core@1.4.0)(@types/node@20.11.16)(typescript@5.3.3) + version: 10.9.2(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.3.3) typescript: specifier: ^5.3.3 version: 5.3.3 @@ -250,7 +250,7 @@ importers: version: 0.8.2 ts-node: specifier: ^10.9.2 - version: 10.9.2(@swc/core@1.4.0)(@types/node@20.11.16)(typescript@5.3.3) + version: 10.9.2(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.3.3) typescript: specifier: ^5.3.3 version: 5.3.3 @@ -305,7 +305,7 @@ importers: version: 17.0.2 ts-node: specifier: ^10.9.2 - version: 10.9.2(@swc/core@1.4.0)(@types/node@20.11.16)(typescript@5.3.3) + version: 10.9.2(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.3.3) tsup: specifier: ~8.0.1 version: 8.0.1(@swc/core@1.4.0)(ts-node@10.9.2)(typescript@5.3.3) @@ -366,7 +366,7 @@ importers: version: 4.0.3(ink@3.2.0)(react@17.0.2) jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@20.11.16)(ts-node@10.9.2) + version: 29.7.0(@types/node@18.18.14)(ts-node@10.9.2) prompts: specifier: ^2.4.2 version: 2.4.2 @@ -378,7 +378,7 @@ importers: version: 2.12.6 ts-node: specifier: ^10.9.2 - version: 10.9.2(@swc/core@1.4.0)(@types/node@20.11.16)(typescript@5.3.3) + version: 10.9.2(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.3.3) tsup: specifier: ~8.0.1 version: 8.0.1(@swc/core@1.4.0)(ts-node@10.9.2)(typescript@5.3.3) @@ -424,13 +424,13 @@ importers: version: 3.15.1 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@20.11.16)(ts-node@10.9.2) + version: 29.7.0(@types/node@18.18.14)(ts-node@10.9.2) jest-extended: specifier: ^4.0.2 version: 4.0.2(jest@29.7.0) ts-node: specifier: ^10.9.2 - version: 10.9.2(@swc/core@1.4.0)(@types/node@20.11.16)(typescript@5.3.3) + version: 10.9.2(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.3.3) tslib: specifier: ~2.6.2 version: 2.6.2 @@ -500,13 +500,13 @@ importers: version: 3.15.1 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@20.11.16)(ts-node@10.9.2) + version: 29.7.0(@types/node@18.18.14)(ts-node@10.9.2) jest-extended: specifier: ^4.0.2 version: 4.0.2(jest@29.7.0) ts-node: specifier: ^10.9.2 - version: 10.9.2(@swc/core@1.4.0)(@types/node@20.11.16)(typescript@5.3.3) + version: 10.9.2(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.3.3) tslib: specifier: ~2.6.2 version: 2.6.2 @@ -591,13 +591,13 @@ importers: version: 0.11.45 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@20.11.16)(ts-node@10.9.2) + version: 29.7.0(@types/node@18.18.14)(ts-node@10.9.2) sinon: specifier: ^17.0.1 version: 17.0.1 ts-node: specifier: ^10.9.2 - version: 10.9.2(@swc/core@1.4.0)(@types/node@20.11.16)(typescript@5.3.3) + version: 10.9.2(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.3.3) tslib: specifier: ~2.6.2 version: 2.6.2 @@ -658,13 +658,13 @@ importers: version: 3.1.0(ink@3.2.0)(react@17.0.2) jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@20.11.16)(ts-node@10.9.2) + version: 29.7.0(@types/node@18.18.14)(ts-node@10.9.2) react: specifier: ^17.0.2 version: 17.0.2 ts-node: specifier: ^10.9.2 - version: 10.9.2(@swc/core@1.4.0)(@types/node@20.11.16)(typescript@5.3.3) + version: 10.9.2(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.3.3) tslib: specifier: ~2.6.2 version: 2.6.2 @@ -700,10 +700,10 @@ importers: version: 29.5.12 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@20.11.16)(ts-node@10.9.2) + version: 29.7.0(@types/node@18.18.14)(ts-node@10.9.2) ts-node: specifier: ^10.9.2 - version: 10.9.2(@swc/core@1.4.0)(@types/node@20.11.16)(typescript@5.3.3) + version: 10.9.2(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.3.3) tslib: specifier: ~2.6.2 version: 2.6.2 @@ -758,10 +758,10 @@ importers: version: 29.5.12 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@20.11.16)(ts-node@10.9.2) + version: 29.7.0(@types/node@18.18.14)(ts-node@10.9.2) ts-node: specifier: ^10.9.2 - version: 10.9.2(@swc/core@1.4.0)(@types/node@20.11.16)(typescript@5.3.3) + version: 10.9.2(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.3.3) tslib: specifier: ~2.6.2 version: 2.6.2 @@ -800,10 +800,10 @@ importers: version: 3.15.1 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@20.11.16)(ts-node@10.9.2) + version: 29.7.0(@types/node@18.18.14)(ts-node@10.9.2) ts-node: specifier: ^10.9.2 - version: 10.9.2(@swc/core@1.4.0)(@types/node@20.11.16)(typescript@5.3.3) + version: 10.9.2(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.3.3) tslib: specifier: ~2.6.2 version: 2.6.2 @@ -876,10 +876,10 @@ importers: version: 3.15.1 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@20.11.16)(ts-node@10.9.2) + version: 29.7.0(@types/node@18.18.14)(ts-node@10.9.2) ts-node: specifier: ^10.9.2 - version: 10.9.2(@swc/core@1.4.0)(@types/node@20.11.16)(typescript@5.3.3) + version: 10.9.2(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.3.3) tslib: specifier: ~2.6.2 version: 2.6.2 @@ -903,7 +903,7 @@ importers: version: 3.15.1 ts-node: specifier: ^10.9.2 - version: 10.9.2(@swc/core@1.4.0)(@types/node@20.11.16)(typescript@5.3.3) + version: 10.9.2(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.3.3) tslib: specifier: ~2.6.2 version: 2.6.2 @@ -939,7 +939,7 @@ importers: version: 18.18.14 ts-node: specifier: ^10.9.2 - version: 10.9.2(@types/node@18.18.14)(typescript@5.3.3) + version: 10.9.2(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.3.3) typescript: specifier: ^5.3.3 version: 5.3.3 @@ -972,7 +972,7 @@ importers: version: 0.8.2 ts-node: specifier: ^10.9.2 - version: 10.9.2(@swc/core@1.4.0)(@types/node@20.11.16)(typescript@5.3.3) + version: 10.9.2(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.3.3) tslib: specifier: ~2.6.2 version: 2.6.2 @@ -996,13 +996,13 @@ importers: version: 29.5.12 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@20.11.16)(ts-node@10.9.2) + version: 29.7.0(@types/node@18.18.14)(ts-node@10.9.2) solidity-bytes-utils: specifier: ^0.8.2 version: 0.8.2 ts-node: specifier: ^10.9.2 - version: 10.9.2(@swc/core@1.4.0)(@types/node@20.11.16)(typescript@5.3.3) + version: 10.9.2(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.3.3) tsup: specifier: ~8.0.1 version: 8.0.1(@swc/core@1.4.0)(ts-node@10.9.2)(typescript@5.3.3) @@ -1071,7 +1071,7 @@ importers: version: link:../ua-devtools-evm-hardhat '@nomicfoundation/hardhat-ethers': specifier: ^3.0.2 - version: 3.0.5(ethers@5.7.2)(hardhat@2.19.4) + version: 3.0.5(ethers@5.7.2)(hardhat@2.19.5) ink: specifier: ^3.2.0 version: 3.2.0(@types/react@17.0.75)(react@17.0.2) @@ -1111,10 +1111,10 @@ importers: version: 0.11.45 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@20.11.16)(ts-node@10.9.2) + version: 29.7.0(@types/node@18.18.14)(ts-node@10.9.2) ts-node: specifier: ^10.9.2 - version: 10.9.2(@swc/core@1.4.0)(@types/node@20.11.16)(typescript@5.3.3) + version: 10.9.2(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.3.3) tsup: specifier: ~8.0.1 version: 8.0.1(@swc/core@1.4.0)(ts-node@10.9.2)(typescript@5.3.3) @@ -1156,10 +1156,10 @@ importers: version: 3.15.1 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@20.11.16)(ts-node@10.9.2) + version: 29.7.0(@types/node@18.18.14)(ts-node@10.9.2) ts-node: specifier: ^10.9.2 - version: 10.9.2(@swc/core@1.4.0)(@types/node@20.11.16)(typescript@5.3.3) + version: 10.9.2(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.3.3) tslib: specifier: ~2.6.2 version: 2.6.2 @@ -1229,10 +1229,10 @@ importers: version: 3.15.1 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@20.11.16)(ts-node@10.9.2) + version: 29.7.0(@types/node@18.18.14)(ts-node@10.9.2) ts-node: specifier: ^10.9.2 - version: 10.9.2(@swc/core@1.4.0)(@types/node@20.11.16)(typescript@5.3.3) + version: 10.9.2(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.3.3) tslib: specifier: ~2.6.2 version: 2.6.2 @@ -1320,10 +1320,10 @@ importers: version: 0.11.45 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@20.11.16)(ts-node@10.9.2) + version: 29.7.0(@types/node@18.18.14)(ts-node@10.9.2) ts-node: specifier: ^10.9.2 - version: 10.9.2(@swc/core@1.4.0)(@types/node@20.11.16)(typescript@5.3.3) + version: 10.9.2(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.3.3) tsup: specifier: ^8.0.1 version: 8.0.1(@swc/core@1.4.0)(ts-node@10.9.2)(typescript@5.3.3) @@ -1449,13 +1449,16 @@ importers: version: 0.11.45 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@20.11.16)(ts-node@10.9.2) + version: 29.7.0(@types/node@18.18.14)(ts-node@10.9.2) + jest-extended: + specifier: ^4.0.2 + version: 4.0.2(jest@29.7.0) solidity-bytes-utils: specifier: ^0.8.2 version: 0.8.2 ts-node: specifier: ^10.9.2 - version: 10.9.2(@swc/core@1.4.0)(@types/node@20.11.16)(typescript@5.3.3) + version: 10.9.2(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.3.3) typescript: specifier: ^5.3.3 version: 5.3.3 @@ -1515,10 +1518,10 @@ importers: version: 2.19.5(ts-node@10.9.2)(typescript@5.3.3) jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@20.11.16)(ts-node@10.9.2) + version: 29.7.0(@types/node@18.18.14)(ts-node@10.9.2) ts-node: specifier: ^10.9.2 - version: 10.9.2(@swc/core@1.4.0)(@types/node@20.11.16)(typescript@5.3.3) + version: 10.9.2(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.3.3) typescript: specifier: ^5.3.3 version: 5.3.3 @@ -1530,7 +1533,7 @@ importers: version: 2.19.5(ts-node@10.9.2)(typescript@5.3.3) ts-node: specifier: ^10.9.2 - version: 10.9.2(@swc/core@1.4.0)(@types/node@20.11.16)(typescript@5.3.3) + version: 10.9.2(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.3.3) tslib: specifier: ~2.6.2 version: 2.6.2 @@ -1656,7 +1659,7 @@ importers: version: 0.11.45 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@20.11.16)(ts-node@10.9.2) + version: 29.7.0(@types/node@18.18.14)(ts-node@10.9.2) jest-extended: specifier: ^4.0.2 version: 4.0.2(jest@29.7.0) @@ -1665,7 +1668,7 @@ importers: version: 0.8.2 ts-node: specifier: ^10.9.2 - version: 10.9.2(@swc/core@1.4.0)(@types/node@20.11.16)(typescript@5.3.3) + version: 10.9.2(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.3.3) tslib: specifier: ~2.6.2 version: 2.6.2 @@ -3613,20 +3616,6 @@ packages: - supports-color - utf-8-validate - /@nomicfoundation/hardhat-ethers@3.0.5(ethers@5.7.2)(hardhat@2.19.4): - resolution: {integrity: sha512-RNFe8OtbZK6Ila9kIlHp0+S80/0Bu/3p41HUpaRIoHLm6X3WekTd83vob3rE54Duufu1edCiBDxspBzi2rxHHw==} - peerDependencies: - ethers: ^6.1.0 - hardhat: ^2.0.0 - dependencies: - debug: 4.3.4(supports-color@8.1.1) - ethers: 5.7.2 - hardhat: 2.19.4(ts-node@10.9.2)(typescript@5.3.3) - lodash.isequal: 4.5.0 - transitivePeerDependencies: - - supports-color - dev: false - /@nomicfoundation/hardhat-ethers@3.0.5(ethers@5.7.2)(hardhat@2.19.5): resolution: {integrity: sha512-RNFe8OtbZK6Ila9kIlHp0+S80/0Bu/3p41HUpaRIoHLm6X3WekTd83vob3rE54Duufu1edCiBDxspBzi2rxHHw==} peerDependencies: @@ -3639,7 +3628,6 @@ packages: lodash.isequal: 4.5.0 transitivePeerDependencies: - supports-color - dev: true /@nomicfoundation/solidity-analyzer-darwin-arm64@0.1.1: resolution: {integrity: sha512-KcTodaQw8ivDZyF+D76FokN/HdpgGpfjc/gFCImdLUyqB6eSWVaZPazMbeAjmfhx3R0zm/NYVzxwAokFKgrc0w==} @@ -4074,31 +4062,12 @@ packages: resolution: {integrity: sha512-Nko8R0/kUo391jsEHHxrGM07QFdnPGvlmox4rmH0kNiNAashItAilhy4Mv4pK5gQmW5f4sXAF58fwJbmlkGcVw==} dev: true - /@swc/core-darwin-arm64@1.3.105: - resolution: {integrity: sha512-buWeweLVDXXmcnfIemH4PGnpjwsDTUGitnPchdftb0u1FU8zSSP/lw/pUCBDG/XvWAp7c/aFxgN4CyG0j7eayA==} - engines: {node: '>=10'} - cpu: [arm64] - os: [darwin] - requiresBuild: true - dev: false - optional: true - /@swc/core-darwin-arm64@1.4.0: resolution: {integrity: sha512-UTJ/Vz+s7Pagef6HmufWt6Rs0aUu+EJF4Pzuwvr7JQQ5b1DZeAAUeUtkUTFx/PvCbM8Xfw4XdKBUZfrIKCfW8A==} engines: {node: '>=10'} cpu: [arm64] os: [darwin] requiresBuild: true - dev: true - optional: true - - /@swc/core-darwin-x64@1.3.105: - resolution: {integrity: sha512-hFmXPApqjA/8sy/9NpljHVaKi1OvL9QkJ2MbbTCCbJERuHMpMUeMBUWipHRfepGHFhU+9B9zkEup/qJaJR4XIg==} - engines: {node: '>=10'} - cpu: [x64] - os: [darwin] - requiresBuild: true - dev: false optional: true /@swc/core-darwin-x64@1.4.0: @@ -4107,16 +4076,6 @@ packages: cpu: [x64] os: [darwin] requiresBuild: true - dev: true - optional: true - - /@swc/core-linux-arm-gnueabihf@1.3.105: - resolution: {integrity: sha512-mwXyMC41oMKkKrPpL8uJpOxw7fyfQoVtIw3Y5p0Blabk+espNYqix0E8VymHdRKuLmM//z5wVmMsuHdGBHvZeg==} - engines: {node: '>=10'} - cpu: [arm] - os: [linux] - requiresBuild: true - dev: false optional: true /@swc/core-linux-arm-gnueabihf@1.4.0: @@ -4125,16 +4084,6 @@ packages: cpu: [arm] os: [linux] requiresBuild: true - dev: true - optional: true - - /@swc/core-linux-arm64-gnu@1.3.105: - resolution: {integrity: sha512-H7yEIVydnUtqBSUxwmO6vpIQn7j+Rr0DF6ZOORPyd/SFzQJK9cJRtmJQ3ZMzlJ1Bb+1gr3MvjgLEnmyCYEm2Hg==} - engines: {node: '>=10'} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: false optional: true /@swc/core-linux-arm64-gnu@1.4.0: @@ -4143,16 +4092,6 @@ packages: cpu: [arm64] os: [linux] requiresBuild: true - dev: true - optional: true - - /@swc/core-linux-arm64-musl@1.3.105: - resolution: {integrity: sha512-Jg7RTFT3pGFdGt5elPV6oDkinRy7q9cXpenjXnJnM2uvx3jOwnsAhexPyCDHom8SHL0j+9kaLLC66T3Gz1E4UA==} - engines: {node: '>=10'} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: false optional: true /@swc/core-linux-arm64-musl@1.4.0: @@ -4161,16 +4100,6 @@ packages: cpu: [arm64] os: [linux] requiresBuild: true - dev: true - optional: true - - /@swc/core-linux-x64-gnu@1.3.105: - resolution: {integrity: sha512-DJghplpyusAmp1X5pW/y93MmS/u83Sx5GrpJxI6KLPa82+NItTgMcl8KBQmW5GYAJpVKZyaIvBanS5TdR8aN2w==} - engines: {node: '>=10'} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: false optional: true /@swc/core-linux-x64-gnu@1.4.0: @@ -4179,16 +4108,6 @@ packages: cpu: [x64] os: [linux] requiresBuild: true - dev: true - optional: true - - /@swc/core-linux-x64-musl@1.3.105: - resolution: {integrity: sha512-wD5jL2dZH/5nPNssBo6jhOvkI0lmWnVR4vnOXWjuXgjq1S0AJpO5jdre/6pYLmf26hft3M42bteDnjR4AAZ38w==} - engines: {node: '>=10'} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: false optional: true /@swc/core-linux-x64-musl@1.4.0: @@ -4197,16 +4116,6 @@ packages: cpu: [x64] os: [linux] requiresBuild: true - dev: true - optional: true - - /@swc/core-win32-arm64-msvc@1.3.105: - resolution: {integrity: sha512-UqJtwILUHRw2+3UTPnRkZrzM/bGdQtbR4UFdp79mZQYfryeOUVNg7aJj/bWUTkKtLiZ3o+FBNrM/x2X1mJX5bA==} - engines: {node: '>=10'} - cpu: [arm64] - os: [win32] - requiresBuild: true - dev: false optional: true /@swc/core-win32-arm64-msvc@1.4.0: @@ -4215,16 +4124,6 @@ packages: cpu: [arm64] os: [win32] requiresBuild: true - dev: true - optional: true - - /@swc/core-win32-ia32-msvc@1.3.105: - resolution: {integrity: sha512-Z95C6vZgBEJ1snidYyjVKnVWiy/ZpPiIFIXGWkDr4ZyBgL3eZX12M6LzZ+NApHKffrbO4enbFyFomueBQgS2oA==} - engines: {node: '>=10'} - cpu: [ia32] - os: [win32] - requiresBuild: true - dev: false optional: true /@swc/core-win32-ia32-msvc@1.4.0: @@ -4233,16 +4132,6 @@ packages: cpu: [ia32] os: [win32] requiresBuild: true - dev: true - optional: true - - /@swc/core-win32-x64-msvc@1.3.105: - resolution: {integrity: sha512-3J8fkyDPFsS3mszuYUY4Wfk7/B2oio9qXUwF3DzOs2MK+XgdyMLIptIxL7gdfitXJBH8k39uVjrIw1JGJDjyFA==} - engines: {node: '>=10'} - cpu: [x64] - os: [win32] - requiresBuild: true - dev: false optional: true /@swc/core-win32-x64-msvc@1.4.0: @@ -4251,34 +4140,8 @@ packages: cpu: [x64] os: [win32] requiresBuild: true - dev: true optional: true - /@swc/core@1.3.105: - resolution: {integrity: sha512-me2VZyr3OjqRpFrYQJJYy7x/zbFSl9nt+MAGnIcBtjDsN00iTVqEaKxBjPBFQV9BDAgPz2SRWes/DhhVm5SmMw==} - engines: {node: '>=10'} - requiresBuild: true - peerDependencies: - '@swc/helpers': ^0.5.0 - peerDependenciesMeta: - '@swc/helpers': - optional: true - dependencies: - '@swc/counter': 0.1.2 - '@swc/types': 0.1.5 - optionalDependencies: - '@swc/core-darwin-arm64': 1.3.105 - '@swc/core-darwin-x64': 1.3.105 - '@swc/core-linux-arm-gnueabihf': 1.3.105 - '@swc/core-linux-arm64-gnu': 1.3.105 - '@swc/core-linux-arm64-musl': 1.3.105 - '@swc/core-linux-x64-gnu': 1.3.105 - '@swc/core-linux-x64-musl': 1.3.105 - '@swc/core-win32-arm64-msvc': 1.3.105 - '@swc/core-win32-ia32-msvc': 1.3.105 - '@swc/core-win32-x64-msvc': 1.3.105 - dev: false - /@swc/core@1.4.0: resolution: {integrity: sha512-wc5DMI5BJftnK0Fyx9SNJKkA0+BZSJQx8430yutWmsILkHMBD3Yd9GhlMaxasab9RhgKqZp7Ht30hUYO5ZDvQg==} engines: {node: '>=10'} @@ -4302,7 +4165,6 @@ packages: '@swc/core-win32-arm64-msvc': 1.4.0 '@swc/core-win32-ia32-msvc': 1.4.0 '@swc/core-win32-x64-msvc': 1.4.0 - dev: true /@swc/counter@0.1.2: resolution: {integrity: sha512-9F4ys4C74eSTEUNndnER3VJ15oru2NumfQxS8geE+f3eB5xvfxpWyqE5XlVnxb/R14uoXi6SLbBwwiDSkv+XEw==} @@ -4463,12 +4325,6 @@ packages: dependencies: undici-types: 5.26.5 - /@types/node@20.11.16: - resolution: {integrity: sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ==} - dependencies: - undici-types: 5.26.5 - dev: true - /@types/node@20.11.5: resolution: {integrity: sha512-g557vgQjUUfN76MZAN/dt1z3dzcUsimuysco0KeluHgrPdJXkP/XdAURgyO2W9fZWHRtRBiVKzKn8vyOAwlG+w==} dependencies: @@ -4840,7 +4696,6 @@ packages: resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} dependencies: string-width: 4.2.3 - dev: true /ansi-colors@4.1.1: resolution: {integrity: sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==} @@ -5197,7 +5052,6 @@ packages: type-fest: 0.20.2 widest-line: 3.1.0 wrap-ansi: 7.0.0 - dev: true /brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} @@ -5718,7 +5572,7 @@ packages: safe-buffer: 5.2.1 sha.js: 2.4.11 - /create-jest@29.7.0(@types/node@20.11.16)(ts-node@10.9.2): + /create-jest@29.7.0(@types/node@18.18.14)(ts-node@10.9.2): resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -5727,7 +5581,7 @@ packages: chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@20.11.16)(ts-node@10.9.2) + jest-config: 29.7.0(@types/node@18.18.14)(ts-node@10.9.2) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -7303,74 +7157,6 @@ packages: - utf-8-validate dev: true - /hardhat@2.19.4(ts-node@10.9.2)(typescript@5.3.3): - resolution: {integrity: sha512-fTQJpqSt3Xo9Mn/WrdblNGAfcANM6XC3tAEi6YogB4s02DmTf93A8QsGb8uR0KR8TFcpcS8lgiW4ugAIYpnbrQ==} - hasBin: true - peerDependencies: - ts-node: '*' - typescript: '*' - peerDependenciesMeta: - ts-node: - optional: true - typescript: - optional: true - dependencies: - '@ethersproject/abi': 5.7.0 - '@metamask/eth-sig-util': 4.0.1 - '@nomicfoundation/ethereumjs-block': 5.0.2 - '@nomicfoundation/ethereumjs-blockchain': 7.0.2 - '@nomicfoundation/ethereumjs-common': 4.0.2 - '@nomicfoundation/ethereumjs-evm': 2.0.2 - '@nomicfoundation/ethereumjs-rlp': 5.0.2 - '@nomicfoundation/ethereumjs-statemanager': 2.0.2 - '@nomicfoundation/ethereumjs-trie': 6.0.2 - '@nomicfoundation/ethereumjs-tx': 5.0.2 - '@nomicfoundation/ethereumjs-util': 9.0.2 - '@nomicfoundation/ethereumjs-vm': 7.0.2 - '@nomicfoundation/solidity-analyzer': 0.1.1 - '@sentry/node': 5.30.0 - '@types/bn.js': 5.1.5 - '@types/lru-cache': 5.1.1 - adm-zip: 0.4.16 - aggregate-error: 3.1.0 - ansi-escapes: 4.3.2 - chalk: 2.4.2 - chokidar: 3.5.3 - ci-info: 2.0.0 - debug: 4.3.4(supports-color@8.1.1) - enquirer: 2.4.1 - env-paths: 2.2.1 - ethereum-cryptography: 1.2.0 - ethereumjs-abi: 0.6.8 - find-up: 2.1.0 - fp-ts: 1.19.3 - fs-extra: 7.0.1 - glob: 7.2.0 - immutable: 4.3.4 - io-ts: 1.10.4 - keccak: 3.0.4 - lodash: 4.17.21 - mnemonist: 0.38.5 - mocha: 10.2.0 - p-map: 4.0.0 - raw-body: 2.5.2 - resolve: 1.17.0 - semver: 6.3.1 - solc: 0.7.3(debug@4.3.4) - source-map-support: 0.5.21 - stacktrace-parser: 0.1.10 - ts-node: 10.9.2(@swc/core@1.3.105)(@types/node@18.18.14)(typescript@5.3.3) - tsort: 0.0.1 - typescript: 5.3.3 - undici: 5.28.2 - uuid: 8.3.2 - ws: 7.5.9 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - dev: false - /hardhat@2.19.5(ts-node@10.9.2)(typescript@5.3.3): resolution: {integrity: sha512-vx8R7zWCYVgM56vA6o0Wqx2bIIptkN4TMs9QwDqZVNGRhMzBfzqUeEYbp+69gxWp1neg2V2nYQUaaUv7aom1kw==} hasBin: true @@ -7428,7 +7214,7 @@ packages: solc: 0.7.3(debug@4.3.4) source-map-support: 0.5.21 stacktrace-parser: 0.1.10 - ts-node: 10.9.2(@swc/core@1.4.0)(@types/node@20.11.16)(typescript@5.3.3) + ts-node: 10.9.2(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.3.3) tsort: 0.0.1 typescript: 5.3.3 undici: 5.28.2 @@ -7438,7 +7224,6 @@ packages: - bufferutil - supports-color - utf-8-validate - dev: true /has-bigints@1.0.2: resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} @@ -8160,7 +7945,7 @@ packages: - supports-color dev: true - /jest-cli@29.7.0(@types/node@20.11.16)(ts-node@10.9.2): + /jest-cli@29.7.0(@types/node@18.18.14)(ts-node@10.9.2): resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -8174,10 +7959,10 @@ packages: '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@20.11.16)(ts-node@10.9.2) + create-jest: 29.7.0(@types/node@18.18.14)(ts-node@10.9.2) exit: 0.1.2 import-local: 3.1.0 - jest-config: 29.7.0(@types/node@20.11.16)(ts-node@10.9.2) + jest-config: 29.7.0(@types/node@18.18.14)(ts-node@10.9.2) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -8223,48 +8008,7 @@ packages: pretty-format: 29.7.0 slash: 3.0.0 strip-json-comments: 3.1.1 - ts-node: 10.9.2(@swc/core@1.4.0)(@types/node@20.11.16)(typescript@5.3.3) - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - dev: true - - /jest-config@29.7.0(@types/node@20.11.16)(ts-node@10.9.2): - resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - '@types/node': '*' - ts-node: '>=9.0.0' - peerDependenciesMeta: - '@types/node': - optional: true - ts-node: - optional: true - dependencies: - '@babel/core': 7.23.9 - '@jest/test-sequencer': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 20.11.16 - babel-jest: 29.7.0(@babel/core@7.23.9) - chalk: 4.1.2 - ci-info: 3.9.0 - deepmerge: 4.3.1 - glob: 7.2.3 - graceful-fs: 4.2.11 - jest-circus: 29.7.0 - jest-environment-node: 29.7.0 - jest-get-type: 29.6.3 - jest-regex-util: 29.6.3 - jest-resolve: 29.7.0 - jest-runner: 29.7.0 - jest-util: 29.7.0 - jest-validate: 29.7.0 - micromatch: 4.0.5 - parse-json: 5.2.0 - pretty-format: 29.7.0 - slash: 3.0.0 - strip-json-comments: 3.1.1 - ts-node: 10.9.2(@swc/core@1.4.0)(@types/node@20.11.16)(typescript@5.3.3) + ts-node: 10.9.2(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.3.3) transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -8319,7 +8063,7 @@ packages: jest: optional: true dependencies: - jest: 29.7.0(@types/node@20.11.16)(ts-node@10.9.2) + jest: 29.7.0(@types/node@18.18.14)(ts-node@10.9.2) jest-diff: 29.7.0 jest-get-type: 29.6.3 dev: true @@ -8567,7 +8311,7 @@ packages: supports-color: 8.1.1 dev: true - /jest@29.7.0(@types/node@20.11.16)(ts-node@10.9.2): + /jest@29.7.0(@types/node@18.18.14)(ts-node@10.9.2): resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -8580,7 +8324,7 @@ packages: '@jest/core': 29.7.0(ts-node@10.9.2) '@jest/types': 29.6.3 import-local: 3.1.0 - jest-cli: 29.7.0(@types/node@20.11.16)(ts-node@10.9.2) + jest-cli: 29.7.0(@types/node@18.18.14)(ts-node@10.9.2) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -9659,7 +9403,7 @@ packages: optional: true dependencies: lilconfig: 3.0.0 - ts-node: 10.9.2(@swc/core@1.4.0)(@types/node@20.11.16)(typescript@5.3.3) + ts-node: 10.9.2(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.3.3) yaml: 2.3.4 dev: true @@ -10866,39 +10610,7 @@ packages: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} dev: true - /ts-node@10.9.2(@swc/core@1.3.105)(@types/node@18.18.14)(typescript@5.3.3): - resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} - hasBin: true - peerDependencies: - '@swc/core': '>=1.2.50' - '@swc/wasm': '>=1.2.50' - '@types/node': '*' - typescript: '>=2.7' - peerDependenciesMeta: - '@swc/core': - optional: true - '@swc/wasm': - optional: true - dependencies: - '@cspotcode/source-map-support': 0.8.1 - '@swc/core': 1.3.105 - '@tsconfig/node10': 1.0.9 - '@tsconfig/node12': 1.0.11 - '@tsconfig/node14': 1.0.3 - '@tsconfig/node16': 1.0.4 - '@types/node': 18.18.14 - acorn: 8.11.3 - acorn-walk: 8.3.2 - arg: 4.1.3 - create-require: 1.1.1 - diff: 4.0.2 - make-error: 1.3.6 - typescript: 5.3.3 - v8-compile-cache-lib: 3.0.1 - yn: 3.1.1 - dev: false - - /ts-node@10.9.2(@swc/core@1.4.0)(@types/node@20.11.16)(typescript@5.3.3): + /ts-node@10.9.2(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.3.3): resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} hasBin: true peerDependencies: @@ -10918,37 +10630,6 @@ packages: '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 20.11.16 - acorn: 8.11.3 - acorn-walk: 8.3.2 - arg: 4.1.3 - create-require: 1.1.1 - diff: 4.0.2 - make-error: 1.3.6 - typescript: 5.3.3 - v8-compile-cache-lib: 3.0.1 - yn: 3.1.1 - dev: true - - /ts-node@10.9.2(@types/node@18.18.14)(typescript@5.3.3): - resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} - hasBin: true - peerDependencies: - '@swc/core': '>=1.2.50' - '@swc/wasm': '>=1.2.50' - '@types/node': '*' - typescript: '>=2.7' - peerDependenciesMeta: - '@swc/core': - optional: true - '@swc/wasm': - optional: true - dependencies: - '@cspotcode/source-map-support': 0.8.1 - '@tsconfig/node10': 1.0.9 - '@tsconfig/node12': 1.0.11 - '@tsconfig/node14': 1.0.3 - '@tsconfig/node16': 1.0.4 '@types/node': 18.18.14 acorn: 8.11.3 acorn-walk: 8.3.2 @@ -10959,7 +10640,6 @@ packages: typescript: 5.3.3 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 - dev: true /tsconfig-paths@3.15.0: resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} @@ -11139,7 +10819,6 @@ packages: /type-fest@0.20.2: resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} engines: {node: '>=10'} - dev: true /type-fest@0.21.3: resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} diff --git a/tests/devtools-evm-hardhat-test/jest.config.js b/tests/devtools-evm-hardhat-test/jest.config.js index 5afc43159..92cbf9896 100644 --- a/tests/devtools-evm-hardhat-test/jest.config.js +++ b/tests/devtools-evm-hardhat-test/jest.config.js @@ -3,6 +3,7 @@ module.exports = { cache: false, testEnvironment: 'node', testTimeout: 150_000, + setupFilesAfterEnv: ['/jest.setup.js'], moduleNameMapper: { '^@/(.*)$': '/src/$1', }, diff --git a/tests/devtools-evm-hardhat-test/jest.setup.js b/tests/devtools-evm-hardhat-test/jest.setup.js new file mode 100644 index 000000000..84d638324 --- /dev/null +++ b/tests/devtools-evm-hardhat-test/jest.setup.js @@ -0,0 +1,3 @@ +// add all jest-extended matchers +// eslint-disable-next-line @typescript-eslint/no-var-requires +expect.extend(require('jest-extended')); diff --git a/tests/devtools-evm-hardhat-test/package.json b/tests/devtools-evm-hardhat-test/package.json index 239df45b1..8785c177f 100644 --- a/tests/devtools-evm-hardhat-test/package.json +++ b/tests/devtools-evm-hardhat-test/package.json @@ -55,6 +55,7 @@ "hardhat": "^2.19.5", "hardhat-deploy": "^0.11.45", "jest": "^29.7.0", + "jest-extended": "^4.0.2", "solidity-bytes-utils": "^0.8.2", "ts-node": "^10.9.2", "typescript": "^5.3.3" diff --git a/tests/devtools-evm-hardhat-test/test/task/deploy.test.ts b/tests/devtools-evm-hardhat-test/test/task/deploy.test.ts new file mode 100644 index 000000000..03db352ce --- /dev/null +++ b/tests/devtools-evm-hardhat-test/test/task/deploy.test.ts @@ -0,0 +1,262 @@ +/// + +import hre from 'hardhat' +import { + TASK_LZ_DEPLOY, + createClearDeployments, + createGetHreByEid, + getEidForNetworkName, +} from '@layerzerolabs/devtools-evm-hardhat' +import { promptForText, promptToContinue, promptToSelectMultiple } from '@layerzerolabs/io-devtools' + +jest.mock('@layerzerolabs/io-devtools', () => { + const original = jest.requireActual('@layerzerolabs/io-devtools') + + return { + ...original, + promptForText: jest.fn(), + promptToContinue: jest.fn(), + promptToSelectMultiple: jest.fn(), + } +}) + +const promptForTextMock = promptForText as jest.Mock +const promptToContinueMock = promptToContinue as jest.Mock +const promptToSelectMultipleMock = promptToSelectMultiple as jest.Mock + +describe(`task ${TASK_LZ_DEPLOY}`, () => { + const expectDeployment = expect.objectContaining({ + abi: expect.any(Array), + args: expect.any(Array), + address: expect.any(String), + bytecode: expect.any(String), + metadata: expect.any(String), + }) + + beforeEach(() => { + promptForTextMock.mockRejectedValue('Not mocked: promptForText') + promptToContinueMock.mockRejectedValue('Not mocked: promptToContinue') + promptToSelectMultipleMock.mockRejectedValue('Not mocked: promptToSelectMultiple') + }) + + afterEach(async () => { + promptForTextMock.mockReset() + promptToContinueMock.mockReset() + promptToSelectMultipleMock.mockReset() + + const getHreByEid = createGetHreByEid(hre) + const clearDeployments = createClearDeployments(getHreByEid) + + await clearDeployments(getEidForNetworkName('britney')) + await clearDeployments(getEidForNetworkName('tango')) + await clearDeployments(getEidForNetworkName('vengaboys')) + }) + + it('should be available', () => { + expect(hre.tasks[TASK_LZ_DEPLOY]).not.toBeUndefined() + }) + + describe('in interactive mode', () => { + it('should ask for networks & tags', async () => { + // We want to say yes to deployment + promptToContinueMock.mockResolvedValueOnce(true) + // We want to deploy two imaginary tags + promptForTextMock.mockResolvedValueOnce('tag1,tag2') + // And we want to select two networks + promptToSelectMultipleMock.mockResolvedValueOnce(['vengaboys', 'tango']) + + // Since we provided two made up tags, nothing should get deployed + await expect(hre.run(TASK_LZ_DEPLOY, {})).resolves.toEqual({ + tango: { + contracts: {}, + }, + vengaboys: { + contracts: {}, + }, + }) + + expect(promptToSelectMultipleMock).toHaveBeenCalledTimes(1) + expect(promptToSelectMultipleMock).toHaveBeenCalledWith( + 'Which networks would you like to deploy?', + expect.objectContaining({ + options: [ + { + disabled: false, + hint: 'Connected to AVALANCHE_V2_MAINNET', + selected: true, + title: 'britney', + value: 'britney', + }, + { + disabled: false, + hint: 'Connected to BSC_V2_MAINNET', + selected: true, + title: 'tango', + value: 'tango', + }, + { + disabled: false, + hint: 'Connected to ETHEREUM_V2_MAINNET', + selected: true, + title: 'vengaboys', + value: 'vengaboys', + }, + { disabled: true, hint: undefined, selected: false, title: 'hardhat', value: 'hardhat' }, + { disabled: true, hint: undefined, selected: false, title: 'localhost', value: 'localhost' }, + ], + }) + ) + + expect(promptForTextMock).toHaveBeenCalledTimes(1) + expect(promptForTextMock).toHaveBeenCalledWith('Which deploy script tags would you like to use?', { + defaultValue: '', + hint: 'Leave empty to use all deploy scripts', + }) + + expect(promptToContinueMock).toHaveBeenCalledTimes(1) + }) + + it('should not deploy anything if no networks have been selected', async () => { + // We want to say yes to deployment + promptToContinueMock.mockResolvedValueOnce(true) + // We want to deploy two imaginary tags + promptForTextMock.mockResolvedValueOnce('tag1,tag2') + // And we want to select two networks + promptToSelectMultipleMock.mockResolvedValueOnce([]) + + // Since we provided selected no networks, we should get an empty object back + await expect(hre.run(TASK_LZ_DEPLOY, {})).resolves.toEqual({}) + }) + + it('should not deploy anything if the user says no', async () => { + // We want to say no to deployment + promptToContinueMock.mockResolvedValueOnce(false) + // We want to deploy two imaginary tags + promptForTextMock.mockResolvedValueOnce('tag1,tag2') + // And we want to select two networks + promptToSelectMultipleMock.mockResolvedValueOnce(['vengaboys', 'tango']) + + // Since the user said no to the development, we should get an empty object back + await expect(hre.run(TASK_LZ_DEPLOY, {})).resolves.toEqual({}) + }) + + it('should deploy everything if the user provides no tags', async () => { + // We want to say yes to deployment + promptToContinueMock.mockResolvedValueOnce(true) + // We want to deploy two imaginary tags + promptForTextMock.mockResolvedValueOnce('') + // And we want to select two networks + promptToSelectMultipleMock.mockResolvedValueOnce(['vengaboys', 'tango']) + + // Since we provided selected no tags, everything will be deployed + await expect(hre.run(TASK_LZ_DEPLOY, {})).resolves.toEqual({ + tango: { + contracts: expect.objectContaining({ + Thrower: expectDeployment, + TestProxy: expectDeployment, + }), + }, + vengaboys: { + contracts: expect.objectContaining({ + Thrower: expectDeployment, + TestProxy: expectDeployment, + }), + }, + }) + }) + + it('should only deploy the provided tags', async () => { + // We want to say yes to deployment + promptToContinueMock.mockResolvedValueOnce(true) + // We want to deploy two imaginary tags + promptForTextMock.mockResolvedValueOnce('Thrower') + // And we want to select two networks + promptToSelectMultipleMock.mockResolvedValueOnce(['vengaboys', 'tango']) + + const { tango, vengaboys } = await hre.run(TASK_LZ_DEPLOY, {}) + + expect(Object.keys(tango.contracts)).toEqual(['Thrower']) + expect(Object.keys(vengaboys.contracts)).toEqual(['Thrower']) + }) + }) + + describe('in CI mode', () => { + it('should use all available networks & tags if networks argument is undefined', async () => { + await expect( + hre.run(TASK_LZ_DEPLOY, { + ci: true, + }) + ).resolves.toEqual({ + britney: { + contracts: expect.objectContaining({ + Thrower: expectDeployment, + TestProxy: expectDeployment, + }), + }, + tango: { + contracts: expect.objectContaining({ + Thrower: expectDeployment, + TestProxy: expectDeployment, + }), + }, + vengaboys: { + contracts: expect.objectContaining({ + Thrower: expectDeployment, + TestProxy: expectDeployment, + }), + }, + }) + + expect(promptToSelectMultipleMock).not.toHaveBeenCalled() + expect(promptForTextMock).not.toHaveBeenCalled() + expect(promptToContinueMock).not.toHaveBeenCalled() + }) + + it('should not deploy anything if an empty array of networks was provided', async () => { + // Since we provided an empty array of networks, we should get an empty object back + await expect( + hre.run(TASK_LZ_DEPLOY, { + ci: true, + networks: [], + }) + ).resolves.toEqual({}) + }) + + it('should deploy everything if an empty array of tags was provided', async () => { + // Since we provided an empty array of tags, we should use all the deployment scripts + await expect( + hre.run(TASK_LZ_DEPLOY, { + ci: true, + tags: [], + }) + ).resolves.toEqual({ + britney: { + contracts: expect.objectContaining({ + Thrower: expectDeployment, + TestProxy: expectDeployment, + }), + }, + tango: { + contracts: expect.objectContaining({ + Thrower: expectDeployment, + TestProxy: expectDeployment, + }), + }, + vengaboys: { + contracts: expect.objectContaining({ + Thrower: expectDeployment, + TestProxy: expectDeployment, + }), + }, + }) + }) + + it('should deploy only the tags provided', async () => { + const { tango, vengaboys, britney } = await hre.run(TASK_LZ_DEPLOY, { ci: true, tags: ['Thrower'] }) + + expect(Object.keys(britney.contracts)).toEqual(['Thrower']) + expect(Object.keys(tango.contracts)).toEqual(['Thrower']) + expect(Object.keys(vengaboys.contracts)).toEqual(['Thrower']) + }) + }) +}) diff --git a/tests/ua-devtools-evm-hardhat-test/test/task/deploy.test.ts b/tests/ua-devtools-evm-hardhat-test/test/task/deploy.test.ts deleted file mode 100644 index 8d8602e60..000000000 --- a/tests/ua-devtools-evm-hardhat-test/test/task/deploy.test.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { HardhatContext } from 'hardhat/internal/context' -import type { HardhatRuntimeEnvironment } from 'hardhat/types' -import { TASK_LZ_DEPLOY } from '@layerzerolabs/devtools-evm-hardhat' - -/** - * Here we only check that the tasks from devtools-evm-hardhat are present, - * the functional tests are colocated with the task code - */ -describe(`task ${TASK_LZ_DEPLOY}`, () => { - /** - * Helper utility that loads hardhat in isolation, - * ensuring that a new copy of hardhat will be loaded - * everytime we call this. - * - * This is useful since we are testing a behavior of feature flagged task - * whose presence is determined by a env variable when hardhat is loaded - * - * @param {(hre: HardhatRuntimeEnvironment) => void | Promise} callback Callback to be executed - * @returns {Promise} - */ - const useIsolatedHre = (callback: (hre: HardhatRuntimeEnvironment) => void | Promise): Promise => - jest.isolateModulesAsync(async () => callback(await import('hardhat'))) - - afterEach(() => { - jest.resetModules() - - // On top of isolating the import of hardhat, we also need to wipe the HardhatContext - // after every test, otherwise the context will be preserved and the new hardhat import - // will fail since it will try to re-register already registered tasks - HardhatContext.deleteHardhatContext() - }) - - describe('when LZ_ENABLE_EXPERIMENTAL_TASK_LZ_DEPLOY env feature flag is not set', () => { - beforeEach(() => { - process.env.LZ_ENABLE_EXPERIMENTAL_TASK_LZ_DEPLOY = '' - }) - - it('should not be available', async () => { - await useIsolatedHre((hre) => { - expect(hre.tasks[TASK_LZ_DEPLOY]).toBeUndefined() - }) - }) - }) - - describe('when LZ_ENABLE_EXPERIMENTAL_TASK_LZ_DEPLOY env feature flag is set', () => { - beforeEach(() => { - process.env.LZ_ENABLE_EXPERIMENTAL_TASK_LZ_DEPLOY = '1' - }) - - it('should be available', async () => { - await useIsolatedHre((hre) => { - expect(hre.tasks[TASK_LZ_DEPLOY]).not.toBeUndefined() - }) - }) - }) -}) 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 f3ba523dd..f1b870688 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 @@ -97,19 +97,19 @@ describe(`task ${TASK_LZ_OAPP_CONFIG_INIT}`, () => { title: 'britney', value: 'britney', disabled: false, - description: `Connected to ${formatEid(getEidForNetworkName('britney'))}`, + hint: `Connected to ${formatEid(getEidForNetworkName('britney'))}`, }, { title: 'tango', value: 'tango', disabled: false, - description: `Connected to ${formatEid(getEidForNetworkName('tango'))}`, + hint: `Connected to ${formatEid(getEidForNetworkName('tango'))}`, }, { title: 'vengaboys', value: 'vengaboys', disabled: false, - description: `Connected to ${formatEid(getEidForNetworkName('vengaboys'))}`, + hint: `Connected to ${formatEid(getEidForNetworkName('vengaboys'))}`, }, { title: 'hardhat',