diff --git a/.changeset/ten-kids-crash.md b/.changeset/ten-kids-crash.md new file mode 100644 index 000000000..c50305d42 --- /dev/null +++ b/.changeset/ten-kids-crash.md @@ -0,0 +1,6 @@ +--- +"@layerzerolabs/devtools-cli-test": patch +"@layerzerolabs/devtools-cli": patch +--- + +Add configuration logic to devtools-cli diff --git a/packages/devtools-cli/package.json b/packages/devtools-cli/package.json index b1c4301d3..1d363f9ed 100644 --- a/packages/devtools-cli/package.json +++ b/packages/devtools-cli/package.json @@ -45,6 +45,7 @@ "devDependencies": { "@layerzerolabs/devtools": "~0.3.17", "@layerzerolabs/io-devtools": "~0.1.11", + "@layerzerolabs/ua-devtools": "~0.3.20", "@types/prompts": "^2.4.9", "@types/react": "^17.0.74", "commander": "^11.1.0", diff --git a/packages/devtools-cli/src/commands/oapp/wire/index.ts b/packages/devtools-cli/src/commands/oapp/wire/index.ts index 76995f2ba..be20870e3 100644 --- a/packages/devtools-cli/src/commands/oapp/wire/index.ts +++ b/packages/devtools-cli/src/commands/oapp/wire/index.ts @@ -1,36 +1,160 @@ import { Command } from 'commander' -import { printLogo } from '@layerzerolabs/io-devtools/swag' -import { createLogger, setDefaultLogLevel } from '@layerzerolabs/io-devtools' +import { printLogo, printRecords } from '@layerzerolabs/io-devtools/swag' import { + createConfigLoader, + createLogger, + printBoolean, + printJson, + setDefaultLogLevel, +} from '@layerzerolabs/io-devtools' +import { configureOApp, OAppOmniGraphSchema } from '@layerzerolabs/ua-devtools' +import { + WithAssertFlag, + WithDryRunFlag, type WithLogLevelOption, + WithOAppConfigOption, type WithSetupOption, WithTsConfigOption, + createAssertFlag, + createDryRunFlag, createLogLevelOption, + createOAppConfigFileOption, createSetupFileOption, createTsConfigFileOption, } from '@/commands/options' -import { setupTypescript } from '@/setup' +import { createSetupLoader, setupTypescript } from '@/setup' +import { Configurator, formatOmniTransaction, OmniGraph, OmniTransaction } from '@layerzerolabs/devtools' +import { CLISetup } from '@/types' -interface Args extends WithLogLevelOption, WithSetupOption, WithTsConfigOption {} +interface Args + extends WithLogLevelOption, + WithSetupOption, + WithOAppConfigOption, + WithAssertFlag, + WithDryRunFlag, + WithTsConfigOption {} export const wire = new Command('wire') + .addOption(createLogLevelOption()) .addOption(createSetupFileOption()) + .addOption(createOAppConfigFileOption()) .addOption(createTsConfigFileOption()) - .addOption(createLogLevelOption()) - .action(async ({ setup, logLevel, tsConfig: tsConfigPath }: Args) => { - printLogo() + .addOption(createAssertFlag()) + .addOption(createDryRunFlag()) + .action( + async ({ + setup: setupPath, + logLevel, + oappConfig: oappConfigPath, + assert = false, + dryRun = false, + tsConfig: tsConfigPath, + }: Args) => { + printLogo() + + // We'll set the global logging level to get as much info as needed + setDefaultLogLevel(logLevel) + + // We'll setup TypeScript support so that we can dynamically load TypeScript config files + setupTypescript(tsConfigPath) + + // We'll need a logger + const logger = createLogger(logLevel) + + // And since this command is not complete yet, we'll warn the user + logger.warn( + `This command is just a placeholder. Please use @layerzerolabs/toolbox-hardhat package for the time being.` + ) + + if (assert) { + logger.info(`Running in assertion mode`) + } else if (dryRun) { + logger.info(`Running in dry run mode`) + } + + let setup: CLISetup + + // Now it's time to load the setup file tht contains all the bits required to do the wiring + const loadSetup = createSetupLoader() + + try { + logger.verbose(`Loading setup from ${setupPath}`) + + setup = await loadSetup(setupPath) + + logger.verbose(`Loaded setup from ${setupPath}`) + } catch (error) { + logger.error(`Failed to load setup from ${setupPath}:\n\n${error}`) + + process.exit(1) + } + + // The first thing we'll need is the graph of our app + // + // In order to get the graph, we'll first need some means of loading it. + // By default, the OApp graph loader will be used but users might choose to supply their own + // config loader + logger.verbose( + setup.loadConfig == null + ? `Using default OApp config loader` + : `Using custom config loader from ${setupPath}` + ) + + let graph: OmniGraph + + // Now it's time to load the config file + const loadConfig = setup.loadConfig ?? createConfigLoader(OAppOmniGraphSchema) + + try { + logger.info(`Loading config file from ${oappConfigPath}`) + + graph = await loadConfig(oappConfigPath) + + logger.info(`${printBoolean(true)} Successfully loaded config file from ${oappConfigPath}`) + logger.debug(`Loaded config file from ${oappConfigPath}:\n\n${printJson(graph)}`) + } catch (error) { + logger.error(`Failed to load config from ${setupPath}:\n\n${error}`) + + process.exit(1) + } + + // With the setup & config in place, the only missing piece to get the OApp configuration + // is the configurator that will get the list of transactions needed + const configure: Configurator = + setup.configure == null + ? (logger.verbose(`Using default OApp configurator`), configureOApp) + : (logger.verbose(`Using custom configurator from ${setupPath}`), setup.configure) + + let transactions: OmniTransaction[] + + try { + logger.info(`Checking configuration`) + + transactions = await configure(graph, setup.createSdk) + } catch (error) { + logger.error(`Failed to check OApp configuration:\n\n${error}`) - // We'll set the global logging level to get as much info as needed - setDefaultLogLevel(logLevel) + process.exit(1) + } - // We'll setup TypeScript support so that we can dynamically load TypeScript config files - setupTypescript(tsConfigPath) + // If there are no transactions that need to be executed, we'll just exit + if (transactions.length === 0) { + return logger.info(`The OApp is wired, no action is necessary`), undefined + } else if (assert) { + // If we are in assertion mode, we'll print out the transactions and exit with code 1 + // if there is anything left to configure + logger.error(`The OApp is not fully wired, following transactions are necessary:`) - const logger = createLogger(logLevel) + // Print the outstanding transactions + printRecords(transactions.map(formatOmniTransaction)) - logger.debug(`Loading setup from ${setup}`) + // Exit with non-zero error code + process.exit(1) + } - logger.warn( - `This command is just a placeholder. Please use @layerzerolabs/toolbox-hardhat package for the time being.` - ) - }) + // If we are in dry run mode, we'll just print the transactions and exit + if (dryRun) { + printRecords(transactions.map(formatOmniTransaction)) + } + } + ) diff --git a/packages/devtools-cli/src/commands/options.ts b/packages/devtools-cli/src/commands/options.ts index 293027250..f69bba083 100644 --- a/packages/devtools-cli/src/commands/options.ts +++ b/packages/devtools-cli/src/commands/options.ts @@ -7,6 +7,13 @@ export interface WithSetupOption { export const createSetupFileOption = () => new Option('-s,--setup ', 'Path to a setup file').makeOptionMandatory() +export interface WithOAppConfigOption { + oappConfig: string +} + +export const createOAppConfigFileOption = () => + new Option('--oapp-config ', 'Path to an OApp config file').makeOptionMandatory() + export interface WithLogLevelOption { logLevel: LogLevel } @@ -22,6 +29,22 @@ export const createLogLevelOption = () => return value }) +export interface WithAssertFlag { + assert?: boolean +} + +export const createAssertFlag = () => + new Option( + '--assert', + 'Will not execute any transactions and fail if there are any transactions required to configure the OApp' + ) + +export interface WithDryRunFlag { + dryRun?: boolean +} + +export const createDryRunFlag = () => new Option('--dry-run', 'Will not execute any transactions') + export interface WithTsConfigOption { tsConfig?: string } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0bed5f3e1..58b85fba0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -659,6 +659,9 @@ importers: '@layerzerolabs/io-devtools': specifier: ~0.1.11 version: link:../io-devtools + '@layerzerolabs/ua-devtools': + specifier: ~0.3.20 + version: link:../ua-devtools '@types/prompts': specifier: ^2.4.9 version: 2.4.9 @@ -1986,6 +1989,147 @@ importers: specifier: ^3.22.4 version: 3.22.4 + tests/devtools-cli-test: + devDependencies: + '@babel/core': + specifier: ^7.23.9 + version: 7.23.9 + '@ethersproject/abi': + specifier: ^5.7.0 + version: 5.7.0 + '@ethersproject/abstract-signer': + specifier: ^5.7.0 + version: 5.7.0 + '@ethersproject/address': + specifier: ~5.7.0 + version: 5.7.0 + '@ethersproject/constants': + specifier: ^5.7.0 + version: 5.7.0 + '@ethersproject/contracts': + specifier: ^5.7.0 + version: 5.7.0 + '@ethersproject/providers': + specifier: ^5.7.2 + version: 5.7.2 + '@ethersproject/wallet': + specifier: ^5.7.0 + version: 5.7.0 + '@layerzerolabs/devtools': + specifier: ~0.3.18 + version: link:../../packages/devtools + '@layerzerolabs/devtools-cli': + specifier: ~0.0.1 + version: link:../../packages/devtools-cli + '@layerzerolabs/devtools-evm': + specifier: ~0.3.13 + version: link:../../packages/devtools-evm + '@layerzerolabs/devtools-evm-hardhat': + specifier: ~0.3.21 + version: link:../../packages/devtools-evm-hardhat + '@layerzerolabs/io-devtools': + specifier: ~0.1.11 + version: link:../../packages/io-devtools + '@layerzerolabs/lz-definitions': + specifier: ^2.3.25 + version: 2.3.25 + '@layerzerolabs/lz-evm-messagelib-v2': + specifier: ^2.3.25 + version: 2.3.25(@axelar-network/axelar-gmp-sdk-solidity@5.9.0)(@chainlink/contracts-ccip@0.7.6)(@eth-optimism/contracts@0.6.0)(@layerzerolabs/lz-evm-protocol-v2@2.3.25)(@layerzerolabs/lz-evm-v1-0.7@2.3.29)(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.2)(hardhat-deploy@0.12.4)(solidity-bytes-utils@0.8.2) + '@layerzerolabs/lz-evm-oapp-v2': + specifier: ^2.3.25 + version: 2.3.29(@layerzerolabs/lz-evm-messagelib-v2@2.3.25)(@layerzerolabs/lz-evm-protocol-v2@2.3.25)(@layerzerolabs/lz-evm-v1-0.7@2.3.29)(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.2)(hardhat-deploy@0.12.4)(solidity-bytes-utils@0.8.2) + '@layerzerolabs/lz-evm-protocol-v2': + specifier: ^2.3.25 + version: 2.3.25(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.2)(hardhat-deploy@0.12.4)(solidity-bytes-utils@0.8.2) + '@layerzerolabs/lz-evm-sdk-v1': + specifier: ^2.3.25 + version: 2.3.25 + '@layerzerolabs/lz-evm-sdk-v2': + specifier: ^2.3.25 + version: 2.3.25 + '@layerzerolabs/lz-v2-utilities': + specifier: ^2.3.25 + version: 2.3.25 + '@layerzerolabs/omnicounter-devtools': + specifier: ~0.3.7 + version: link:../../packages/omnicounter-devtools + '@layerzerolabs/omnicounter-devtools-evm': + specifier: ~0.3.7 + version: link:../../packages/omnicounter-devtools-evm + '@layerzerolabs/protocol-devtools': + specifier: ~0.3.9 + version: link:../../packages/protocol-devtools + '@layerzerolabs/protocol-devtools-evm': + specifier: ~0.3.10 + version: link:../../packages/protocol-devtools-evm + '@layerzerolabs/test-devtools-evm-hardhat': + specifier: ~0.2.5 + version: link:../../packages/test-devtools-evm-hardhat + '@layerzerolabs/test-setup-devtools-evm-hardhat': + specifier: ~0.0.6 + version: link:../test-setup-devtools-evm-hardhat + '@layerzerolabs/toolbox-hardhat': + specifier: ~0.2.35 + version: link:../../packages/toolbox-hardhat + '@layerzerolabs/ua-devtools': + specifier: ~0.3.19 + version: link:../../packages/ua-devtools + '@layerzerolabs/ua-devtools-evm': + specifier: ~0.3.15 + version: link:../../packages/ua-devtools-evm + '@layerzerolabs/ua-devtools-evm-hardhat': + specifier: ~0.3.19 + version: link:../../packages/ua-devtools-evm-hardhat + '@nomicfoundation/hardhat-ethers': + specifier: ^3.0.5 + version: 3.0.5(ethers@5.7.2)(hardhat@2.22.3) + '@nomiclabs/hardhat-ethers': + specifier: ^2.2.3 + version: 2.2.3(ethers@5.7.2)(hardhat@2.22.3) + '@openzeppelin/contracts': + specifier: ^5.0.1 + version: 5.0.2 + '@swc/core': + specifier: ^1.4.0 + version: 1.4.0 + '@swc/jest': + specifier: ^0.2.36 + version: 0.2.36(@swc/core@1.4.0) + '@types/jest': + specifier: ^29.5.12 + version: 29.5.12 + ethers: + specifier: ^5.7.2 + version: 5.7.2 + fast-check: + specifier: ^3.15.1 + version: 3.15.1 + hardhat: + specifier: ^2.22.3 + version: 2.22.3(ts-node@10.9.2)(typescript@5.5.3) + hardhat-deploy: + specifier: ^0.12.1 + version: 0.12.4 + jest: + specifier: ^29.7.0 + 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@18.18.14)(typescript@5.5.3) + tslib: + specifier: ~2.6.2 + version: 2.6.3 + typescript: + specifier: ^5.3.3 + version: 5.5.3 + tests/devtools-evm-hardhat-test: devDependencies: '@babel/core': @@ -4575,6 +4719,34 @@ packages: solidity-bytes-utils: 0.8.2 dev: true + /@layerzerolabs/lz-evm-messagelib-v2@2.3.25(@axelar-network/axelar-gmp-sdk-solidity@5.9.0)(@chainlink/contracts-ccip@0.7.6)(@eth-optimism/contracts@0.6.0)(@layerzerolabs/lz-evm-protocol-v2@2.3.25)(@layerzerolabs/lz-evm-v1-0.7@2.3.29)(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.2)(hardhat-deploy@0.12.4)(solidity-bytes-utils@0.8.2): + resolution: {integrity: sha512-QcIqNry0qia7Rf/7PR1t4VibQT2QGZdyLzsiKICzb2k8EpwiUYnHriIX6VBeFklVOBc3D9ffwZ/bBvsg9NykKg==} + peerDependencies: + '@arbitrum/nitro-contracts': ^1.1.0 + '@axelar-network/axelar-gmp-sdk-solidity': ^5.6.4 + '@chainlink/contracts-ccip': ^0.7.6 + '@eth-optimism/contracts': ^0.6.0 + '@layerzerolabs/lz-evm-protocol-v2': ^2.3.25 + '@layerzerolabs/lz-evm-v1-0.7': ^2.3.25 + '@openzeppelin/contracts': ^4.8.1 || ^5.0.0 + '@openzeppelin/contracts-upgradeable': ^4.8.1 || ^5.0.0 + hardhat-deploy: ^0.12.1 + solidity-bytes-utils: ^0.8.0 + peerDependenciesMeta: + '@arbitrum/nitro-contracts': + optional: true + dependencies: + '@axelar-network/axelar-gmp-sdk-solidity': 5.9.0 + '@chainlink/contracts-ccip': 0.7.6(ethers@5.7.2) + '@eth-optimism/contracts': 0.6.0(ethers@5.7.2) + '@layerzerolabs/lz-evm-protocol-v2': 2.3.25(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.2)(hardhat-deploy@0.12.4)(solidity-bytes-utils@0.8.2) + '@layerzerolabs/lz-evm-v1-0.7': 2.3.29(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.2)(hardhat-deploy@0.12.4) + '@openzeppelin/contracts': 5.0.2 + '@openzeppelin/contracts-upgradeable': 5.0.2(@openzeppelin/contracts@5.0.2) + hardhat-deploy: 0.12.4 + solidity-bytes-utils: 0.8.2 + dev: true + /@layerzerolabs/lz-evm-messagelib-v2@2.3.25(@axelar-network/axelar-gmp-sdk-solidity@5.9.0)(@chainlink/contracts-ccip@0.7.6)(@eth-optimism/contracts@0.6.0)(@layerzerolabs/lz-evm-protocol-v2@2.3.29)(@layerzerolabs/lz-evm-v1-0.7@2.3.29)(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.2)(hardhat-deploy@0.12.1)(solidity-bytes-utils@0.8.2): resolution: {integrity: sha512-QcIqNry0qia7Rf/7PR1t4VibQT2QGZdyLzsiKICzb2k8EpwiUYnHriIX6VBeFklVOBc3D9ffwZ/bBvsg9NykKg==} peerDependencies: @@ -4752,6 +4924,26 @@ packages: solidity-bytes-utils: 0.8.2 dev: true + /@layerzerolabs/lz-evm-oapp-v2@2.3.29(@layerzerolabs/lz-evm-messagelib-v2@2.3.25)(@layerzerolabs/lz-evm-protocol-v2@2.3.25)(@layerzerolabs/lz-evm-v1-0.7@2.3.29)(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.2)(hardhat-deploy@0.12.4)(solidity-bytes-utils@0.8.2): + resolution: {integrity: sha512-HFuHVCUjZNOjKuhRDBbK2NSGA7sMwFhXRqy0veym9kECDwtHdazLfYmOGhu1amRfomc+eq+KBakJl2XI8njM6Q==} + peerDependencies: + '@layerzerolabs/lz-evm-messagelib-v2': ^2.3.29 + '@layerzerolabs/lz-evm-protocol-v2': ^2.3.29 + '@layerzerolabs/lz-evm-v1-0.7': ^2.3.29 + '@openzeppelin/contracts': ^4.8.1 || ^5.0.0 + '@openzeppelin/contracts-upgradeable': ^4.8.1 || ^5.0.0 + hardhat-deploy: ^0.12.1 + solidity-bytes-utils: ^0.8.0 + dependencies: + '@layerzerolabs/lz-evm-messagelib-v2': 2.3.25(@axelar-network/axelar-gmp-sdk-solidity@5.9.0)(@chainlink/contracts-ccip@0.7.6)(@eth-optimism/contracts@0.6.0)(@layerzerolabs/lz-evm-protocol-v2@2.3.25)(@layerzerolabs/lz-evm-v1-0.7@2.3.29)(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.2)(hardhat-deploy@0.12.4)(solidity-bytes-utils@0.8.2) + '@layerzerolabs/lz-evm-protocol-v2': 2.3.25(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.2)(hardhat-deploy@0.12.4)(solidity-bytes-utils@0.8.2) + '@layerzerolabs/lz-evm-v1-0.7': 2.3.29(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.2)(hardhat-deploy@0.12.4) + '@openzeppelin/contracts': 5.0.2 + '@openzeppelin/contracts-upgradeable': 5.0.2(@openzeppelin/contracts@5.0.2) + hardhat-deploy: 0.12.4 + solidity-bytes-utils: 0.8.2 + dev: true + /@layerzerolabs/lz-evm-oapp-v2@2.3.29(@layerzerolabs/lz-evm-messagelib-v2@2.3.29)(@layerzerolabs/lz-evm-protocol-v2@2.3.29)(@layerzerolabs/lz-evm-v1-0.7@2.3.29)(@openzeppelin/contracts-upgradeable@4.9.5)(@openzeppelin/contracts@4.9.5)(hardhat-deploy@0.12.4)(solidity-bytes-utils@0.8.2): resolution: {integrity: sha512-HFuHVCUjZNOjKuhRDBbK2NSGA7sMwFhXRqy0veym9kECDwtHdazLfYmOGhu1amRfomc+eq+KBakJl2XI8njM6Q==} peerDependencies: @@ -4960,6 +5152,18 @@ packages: hardhat-deploy: 0.12.1 dev: true + /@layerzerolabs/lz-evm-v1-0.7@2.3.29(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.2)(hardhat-deploy@0.12.4): + resolution: {integrity: sha512-Vsqk0VBih1GTAzmfiwBo/MfU9qHn8x5E3OiLhZoi7cCIDmqF3v2gCldcSrMdUdb9k/TT2zifL4lnxVU2rO+Zkw==} + peerDependencies: + '@openzeppelin/contracts': 3.4.2-solc-0.7 || ^3.4.2 || ^4.0.0 || ^5.0.0 + '@openzeppelin/contracts-upgradeable': 3.4.2-solc-0.7 || ^3.4.2 || ^4.0.0 || ^5.0.0 + hardhat-deploy: ^0.12.1 + dependencies: + '@openzeppelin/contracts': 5.0.2 + '@openzeppelin/contracts-upgradeable': 5.0.2(@openzeppelin/contracts@5.0.2) + hardhat-deploy: 0.12.4 + dev: true + /@layerzerolabs/lz-v2-utilities@2.3.25: resolution: {integrity: sha512-IzqcK49NPBOEs/Q7d6+fJEpIfjicR4huaeayyK7oAVCQ5X/dfM9zT8qTbpbQ9ju52OmJKCyifOJglK2xXg4zZg==} dependencies: @@ -10433,6 +10637,70 @@ packages: - utf-8-validate dev: true + /hardhat@2.22.3(ts-node@10.9.2)(typescript@5.5.3): + resolution: {integrity: sha512-k8JV2ECWNchD6ahkg2BR5wKVxY0OiKot7fuxiIpRK0frRqyOljcR2vKwgWSLw6YIeDcNNA4xybj7Og7NSxr2hA==} + 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/edr': 0.3.5 + '@nomicfoundation/ethereumjs-common': 4.0.4 + '@nomicfoundation/ethereumjs-tx': 5.0.4 + '@nomicfoundation/ethereumjs-util': 9.0.4 + '@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 + boxen: 5.1.2 + chalk: 2.4.2 + chokidar: 3.6.0 + 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.4.0)(@types/node@18.18.14)(typescript@5.5.3) + tsort: 0.0.1 + typescript: 5.5.3 + undici: 5.28.2 + uuid: 8.3.2 + ws: 7.5.9 + transitivePeerDependencies: + - bufferutil + - c-kzg + - supports-color + - utf-8-validate + dev: true + /hardhat@2.22.3(typescript@5.2.2): resolution: {integrity: sha512-k8JV2ECWNchD6ahkg2BR5wKVxY0OiKot7fuxiIpRK0frRqyOljcR2vKwgWSLw6YIeDcNNA4xybj7Og7NSxr2hA==} hasBin: true diff --git a/tests-user/tests/devtools-cli.bats b/tests-user/tests/devtools-cli.bats index 4218b391e..14ddc2fcf 100644 --- a/tests-user/tests/devtools-cli.bats +++ b/tests-user/tests/devtools-cli.bats @@ -23,12 +23,20 @@ teardown() { rm -rf "$PROJECTS_DIRECTORY" } +@test "should output version" { + npx --yes @layerzerolabs/devtools-cli --version +} + +@test "should output help" { + npx --yes @layerzerolabs/devtools-cli --help +} + @test "should work with pnpm & oapp example in CI mode" { local DESTINATION="$PROJECTS_DIRECTORY/pnpm-oapp" npx --yes create-lz-oapp --ci --example oapp --destination $DESTINATION --package-manager pnpm cd "$DESTINATION" - run npx --yes @layerzerolabs/devtools-cli oapp wire --setup ./imaginary.layerzero.setup.ts + run npx --yes @layerzerolabs/devtools-cli oapp wire --setup ./imaginary.layerzero.setup.ts --oapp-config ./layerzero.config.ts --dry-run assert_output --partial "This command is just a placeholder. Please use @layerzerolabs/toolbox-hardhat package for the time being." } \ No newline at end of file diff --git a/tests/devtools-cli-test/.eslintignore b/tests/devtools-cli-test/.eslintignore new file mode 100644 index 000000000..d50e6da80 --- /dev/null +++ b/tests/devtools-cli-test/.eslintignore @@ -0,0 +1,5 @@ +artifacts +cache +deployments +dist +node_modules \ No newline at end of file diff --git a/tests/devtools-cli-test/.eslintrc.json b/tests/devtools-cli-test/.eslintrc.json new file mode 100644 index 000000000..be97c53fb --- /dev/null +++ b/tests/devtools-cli-test/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "../../.eslintrc.json" +} diff --git a/tests/devtools-cli-test/.gitignore b/tests/devtools-cli-test/.gitignore new file mode 100644 index 000000000..2da127def --- /dev/null +++ b/tests/devtools-cli-test/.gitignore @@ -0,0 +1,4 @@ +artifacts +cache +deployments +.layerzero diff --git a/tests/devtools-cli-test/.prettierignore b/tests/devtools-cli-test/.prettierignore new file mode 100644 index 000000000..d94b0d361 --- /dev/null +++ b/tests/devtools-cli-test/.prettierignore @@ -0,0 +1,5 @@ +artifacts/ +cache/ +deployments/ +dist/ +node_modules/ \ No newline at end of file diff --git a/tests/devtools-cli-test/README.md b/tests/devtools-cli-test/README.md new file mode 100644 index 000000000..751b6cb4e --- /dev/null +++ b/tests/devtools-cli-test/README.md @@ -0,0 +1,13 @@ +

+ + LayerZero + +

+ +

@layerzerolabs/devtools-cli-test

+ +## Development + +This package provides integration tests for `@layerzerolabs/devtools-cli` executed within a containerized setup. These tests are dependent on two networks that are bootstrapped in the root `docker-compose.yaml` and will only run when executed within that environment. + +Please see the root [`README.md`](../../README.md) for more information. diff --git a/tests/devtools-cli-test/contracts/DefaultOApp.sol b/tests/devtools-cli-test/contracts/DefaultOApp.sol new file mode 100644 index 000000000..81b2e7535 --- /dev/null +++ b/tests/devtools-cli-test/contracts/DefaultOApp.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.22; + +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; +import { OApp, Origin } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/OApp.sol"; +import { OAppOptionsType3 } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/libs/OAppOptionsType3.sol"; + +contract DefaultOApp is OApp, OAppOptionsType3 { + event CallerBpsCapSet(uint256 callerBpsCap); + + uint256 public callerBpsCap; + + constructor(address _endpoint, address _delegate) OApp(_endpoint, _delegate) Ownable(_delegate) {} + + function _lzReceive(Origin calldata, bytes32, bytes calldata, address, bytes calldata) internal virtual override {} + + /** + Copied from https://github.com/stargate-protocol/stargate-v2/blob/3556e333b197b4f3706c68aa2b5cb4060c9c3812/packages/stg-evm-v2/src/peripheral/oft-wrapper/OFTWrapper.sol#L38-L42 + (except validations) + */ + function setCallerBpsCap(uint256 _callerBpsCap) external onlyOwner { + callerBpsCap = _callerBpsCap; + emit CallerBpsCapSet(_callerBpsCap); + } +} diff --git a/tests/devtools-cli-test/deploy/001_bootstrap.ts b/tests/devtools-cli-test/deploy/001_bootstrap.ts new file mode 100644 index 000000000..6365e9d1d --- /dev/null +++ b/tests/devtools-cli-test/deploy/001_bootstrap.ts @@ -0,0 +1,8 @@ +import { type DeployFunction } from 'hardhat-deploy/types' +import { createDeployEndpointV2 } from '@layerzerolabs/test-setup-devtools-evm-hardhat' + +const deploy: DeployFunction = createDeployEndpointV2() + +deploy.tags = ['Bootstrap', 'EndpointV2'] + +export default deploy diff --git a/tests/devtools-cli-test/deploy/002_oapp.ts b/tests/devtools-cli-test/deploy/002_oapp.ts new file mode 100644 index 000000000..6c689ddd0 --- /dev/null +++ b/tests/devtools-cli-test/deploy/002_oapp.ts @@ -0,0 +1,31 @@ +import { formatEid } from '@layerzerolabs/devtools' +import { type DeployFunction } from 'hardhat-deploy/types' +import assert from 'assert' + +/** + * This deploy function will deploy and configure EndpointV2 and DefaultOApp + * + * @param env `HardhatRuntimeEnvironment` + */ +const deploy: DeployFunction = async ({ getUnnamedAccounts, deployments, network }) => { + assert(network.config.eid != null, `Missing endpoint ID for network ${network.name}`) + + const [deployer] = await getUnnamedAccounts() + assert(deployer, 'Missing deployer') + + await deployments.delete('DefaultOApp') + const endpointV2 = await deployments.get('EndpointV2') + const defaultOAppDeployment = await deployments.deploy('DefaultOApp', { + from: deployer, + args: [endpointV2.address, deployer], + }) + + console.table({ + Network: `${network.name} (endpoint ${formatEid(network.config.eid)})`, + DefaultOApp: defaultOAppDeployment.address, + }) +} + +deploy.tags = ['OApp', 'DefaultOApp'] + +export default deploy diff --git a/tests/devtools-cli-test/hardhat.config.ts b/tests/devtools-cli-test/hardhat.config.ts new file mode 100644 index 000000000..0a9387565 --- /dev/null +++ b/tests/devtools-cli-test/hardhat.config.ts @@ -0,0 +1,28 @@ +import 'hardhat-deploy' +import '@layerzerolabs/toolbox-hardhat' +import type { HardhatUserConfig } from 'hardhat/types' + +// These tasks are only for when you want to play with this setup +// using your own keyboard (using exposed networks) +import './tasks' +import { createTestNetworkConfigV2 } from '@layerzerolabs/test-setup-devtools-evm-hardhat' + +const mnemonic = process.env.MNEMONIC ?? '' + +/** + * This is a dummy hardhat config that enables us to test + * hardhat functionality without mocking too much + */ +const config: HardhatUserConfig = { + layerZero: { + // Since we are deploying our own protocol contracts, + // we'll skip the external deployment imports + deploymentSourcePackages: [], + }, + solidity: { + version: '0.8.22', + }, + networks: createTestNetworkConfigV2({ mnemonic, initialIndex: 40 }), +} + +export default config diff --git a/tests/devtools-cli-test/jest.config.js b/tests/devtools-cli-test/jest.config.js new file mode 100644 index 000000000..72e55d459 --- /dev/null +++ b/tests/devtools-cli-test/jest.config.js @@ -0,0 +1,15 @@ +/** @type {import('ts-jest').JestConfigWithTsJest} */ +module.exports = { + cache: false, + reporters: [['github-actions', { silent: false }], 'default'], + testEnvironment: 'node', + testTimeout: 300_000, + setupFilesAfterEnv: ['/jest.setup.js'], + moduleNameMapper: { + '^@/(.*)$': '/src/$1', + }, + transform: { + '^.+\\.(t|j)sx?$': '@swc/jest', + }, + transformIgnorePatterns: ['node_modules/(?!zx)'], +}; diff --git a/tests/devtools-cli-test/jest.setup.js b/tests/devtools-cli-test/jest.setup.js new file mode 100644 index 000000000..d0fa95a14 --- /dev/null +++ b/tests/devtools-cli-test/jest.setup.js @@ -0,0 +1,11 @@ +import { rmSync } from 'fs'; +import * as jestExtended from 'jest-extended'; + +// add all jest-extended matchers +expect.extend(jestExtended); + +// clear all deployments before & after every test +const clearDeployments = () => rmSync('./deployments', { force: true, recursive: true }); + +beforeEach(clearDeployments); +afterEach(clearDeployments); diff --git a/tests/devtools-cli-test/package.json b/tests/devtools-cli-test/package.json new file mode 100644 index 000000000..9b66437ce --- /dev/null +++ b/tests/devtools-cli-test/package.json @@ -0,0 +1,67 @@ +{ + "name": "@layerzerolabs/devtools-cli-test", + "version": "0.2.14", + "private": true, + "description": "Integration tests for devtools-cli for V2", + "repository": { + "type": "git", + "url": "git+https://github.com/LayerZero-Labs/devtools.git", + "directory": "tests/devtools-cli-test" + }, + "license": "MIT", + "scripts": { + "clean": "rm -rf artifacts cache deployments", + "compile": "$npm_execpath hardhat compile", + "lint": "$npm_execpath eslint '**/*.{js,ts,json}'", + "lint:fix": "eslint --fix '**/*.{js,ts,json}'", + "test": "jest --ci --runInBand --forceExit" + }, + "devDependencies": { + "@babel/core": "^7.23.9", + "@ethersproject/abi": "^5.7.0", + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "~5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/contracts": "^5.7.0", + "@ethersproject/providers": "^5.7.2", + "@ethersproject/wallet": "^5.7.0", + "@layerzerolabs/devtools": "~0.3.18", + "@layerzerolabs/devtools-cli": "~0.0.1", + "@layerzerolabs/devtools-evm": "~0.3.13", + "@layerzerolabs/devtools-evm-hardhat": "~0.3.21", + "@layerzerolabs/io-devtools": "~0.1.11", + "@layerzerolabs/lz-definitions": "^2.3.25", + "@layerzerolabs/lz-evm-messagelib-v2": "^2.3.25", + "@layerzerolabs/lz-evm-oapp-v2": "^2.3.25", + "@layerzerolabs/lz-evm-protocol-v2": "^2.3.25", + "@layerzerolabs/lz-evm-sdk-v1": "^2.3.25", + "@layerzerolabs/lz-evm-sdk-v2": "^2.3.25", + "@layerzerolabs/lz-v2-utilities": "^2.3.25", + "@layerzerolabs/omnicounter-devtools": "~0.3.7", + "@layerzerolabs/omnicounter-devtools-evm": "~0.3.7", + "@layerzerolabs/protocol-devtools": "~0.3.9", + "@layerzerolabs/protocol-devtools-evm": "~0.3.10", + "@layerzerolabs/test-devtools-evm-hardhat": "~0.2.5", + "@layerzerolabs/test-setup-devtools-evm-hardhat": "~0.0.6", + "@layerzerolabs/toolbox-hardhat": "~0.2.35", + "@layerzerolabs/ua-devtools": "~0.3.19", + "@layerzerolabs/ua-devtools-evm": "~0.3.15", + "@layerzerolabs/ua-devtools-evm-hardhat": "~0.3.19", + "@nomicfoundation/hardhat-ethers": "^3.0.5", + "@nomiclabs/hardhat-ethers": "^2.2.3", + "@openzeppelin/contracts": "^5.0.1", + "@swc/core": "^1.4.0", + "@swc/jest": "^0.2.36", + "@types/jest": "^29.5.12", + "ethers": "^5.7.2", + "fast-check": "^3.15.1", + "hardhat": "^2.22.3", + "hardhat-deploy": "^0.12.1", + "jest": "^29.7.0", + "jest-extended": "^4.0.2", + "solidity-bytes-utils": "^0.8.2", + "ts-node": "^10.9.2", + "tslib": "~2.6.2", + "typescript": "^5.3.3" + } +} diff --git a/tests/devtools-cli-test/tasks/index.ts b/tests/devtools-cli-test/tasks/index.ts new file mode 100644 index 000000000..740329fec --- /dev/null +++ b/tests/devtools-cli-test/tasks/index.ts @@ -0,0 +1,29 @@ +import { task } from 'hardhat/config' +import { deployContract, setupDefaultEndpointV2 } from '@layerzerolabs/test-setup-devtools-evm-hardhat' + +/** + * This will deploy and wire up the endpoints. + */ +const deployAndWireEndpoint = async () => { + await deployContract('EndpointV2', true) + await setupDefaultEndpointV2() +} + +/** + * Task that will: + * + * - Deploy EndpointV2-related infrastructure + * - Wire the EndpointV2-related infrastructure with default configuration + * - Deploy the DefaultOApp + * + * If you want to expose the networks locally + * and deploy your own endpoints, this task is just for you! + * + * See the root README.md section for info about how to expose networks locally. + */ +task('lz:test:oapp:deploy', 'Deploy the test OApp on default EndpointV2 infrastructure', async () => { + await deployAndWireEndpoint() + + // Deploy the DefaultOApp + await deployContract('OApp', true) +}) diff --git a/tests/devtools-cli-test/test/__utils__/cli.ts b/tests/devtools-cli-test/test/__utils__/cli.ts new file mode 100644 index 000000000..9824c1f42 --- /dev/null +++ b/tests/devtools-cli-test/test/__utils__/cli.ts @@ -0,0 +1,7 @@ +import { spawnSync } from 'child_process' + +export const runCli = (args: string[] = []) => + spawnSync(`npx`, ['@layerzerolabs/devtools-cli', ...args], { + encoding: 'utf8', + stdio: 'pipe', + }) diff --git a/tests/devtools-cli-test/test/commands/oapp/__data__/configs/invalid.config.001.js b/tests/devtools-cli-test/test/commands/oapp/__data__/configs/invalid.config.001.js new file mode 100644 index 000000000..4336b4fa6 --- /dev/null +++ b/tests/devtools-cli-test/test/commands/oapp/__data__/configs/invalid.config.001.js @@ -0,0 +1,15 @@ +// eslint-disable-next-line @typescript-eslint/no-var-requires +const { EndpointId } = require('@layerzerolabs/lz-definitions'); + +module.exports = { + contracts: [ + { + eid: EndpointId.EON_MAINNET, + contractName: 'DefaultOApp', + }, + { + eid: 'Invalid EndpointId', + contractName: 'DefaultOApp', + }, + ], +}; diff --git a/tests/devtools-cli-test/test/commands/oapp/__data__/configs/invalid.config.empty.js b/tests/devtools-cli-test/test/commands/oapp/__data__/configs/invalid.config.empty.js new file mode 100644 index 000000000..e69de29bb diff --git a/tests/devtools-cli-test/test/commands/oapp/__data__/configs/invalid.config.empty.json b/tests/devtools-cli-test/test/commands/oapp/__data__/configs/invalid.config.empty.json new file mode 100644 index 000000000..e69de29bb diff --git a/tests/devtools-cli-test/test/commands/oapp/__data__/configs/valid.config.connected.js b/tests/devtools-cli-test/test/commands/oapp/__data__/configs/valid.config.connected.js new file mode 100644 index 000000000..c17030330 --- /dev/null +++ b/tests/devtools-cli-test/test/commands/oapp/__data__/configs/valid.config.connected.js @@ -0,0 +1,33 @@ +// eslint-disable-next-line @typescript-eslint/no-var-requires +const { EndpointId } = require('@layerzerolabs/lz-definitions'); + +const ethContract = { + eid: EndpointId.ETHEREUM_V2_MAINNET, + contractName: 'DefaultOApp', +}; + +const avaxContract = { + eid: EndpointId.AVALANCHE_V2_MAINNET, + contractName: 'DefaultOApp', +}; + +module.exports = { + contracts: [ + { + contract: avaxContract, + }, + { + contract: ethContract, + }, + ], + connections: [ + { + from: avaxContract, + to: ethContract, + }, + { + from: ethContract, + to: avaxContract, + }, + ], +}; diff --git a/tests/devtools-cli-test/test/commands/oapp/__data__/configs/valid.config.empty.js b/tests/devtools-cli-test/test/commands/oapp/__data__/configs/valid.config.empty.js new file mode 100644 index 000000000..6b37271a9 --- /dev/null +++ b/tests/devtools-cli-test/test/commands/oapp/__data__/configs/valid.config.empty.js @@ -0,0 +1,4 @@ +module.exports = { + contracts: [], + connections: [], +}; diff --git a/tests/devtools-cli-test/test/commands/oapp/__data__/configs/valid.config.empty.ts b/tests/devtools-cli-test/test/commands/oapp/__data__/configs/valid.config.empty.ts new file mode 100644 index 000000000..eeddbaa38 --- /dev/null +++ b/tests/devtools-cli-test/test/commands/oapp/__data__/configs/valid.config.empty.ts @@ -0,0 +1,8 @@ +import { OAppOmniGraphHardhat } from '@layerzerolabs/ua-devtools-evm-hardhat' + +const config: OAppOmniGraphHardhat = { + contracts: [], + connections: [], +} + +export default config diff --git a/tests/devtools-cli-test/test/commands/oapp/__data__/configs/valid.config.misconfigured.001.js b/tests/devtools-cli-test/test/commands/oapp/__data__/configs/valid.config.misconfigured.001.js new file mode 100644 index 000000000..7713588b9 --- /dev/null +++ b/tests/devtools-cli-test/test/commands/oapp/__data__/configs/valid.config.misconfigured.001.js @@ -0,0 +1,33 @@ +// eslint-disable-next-line @typescript-eslint/no-var-requires +const { EndpointId } = require('@layerzerolabs/lz-definitions'); + +const ethContract = { + eid: EndpointId.ETHEREUM_V2_MAINNET, + contractName: 'DefaultOApp', +}; + +const avaxContract = { + eid: EndpointId.AVALANCHE_V2_MAINNET, + contractName: 'NonExistent', +}; + +module.exports = { + contracts: [ + { + contract: avaxContract, + }, + { + contract: ethContract, + }, + ], + connections: [ + { + from: avaxContract, + to: ethContract, + }, + { + from: ethContract, + to: avaxContract, + }, + ], +}; diff --git a/tests/devtools-cli-test/test/commands/oapp/__data__/setups/invalid.setup.001.js b/tests/devtools-cli-test/test/commands/oapp/__data__/setups/invalid.setup.001.js new file mode 100644 index 000000000..128352b14 --- /dev/null +++ b/tests/devtools-cli-test/test/commands/oapp/__data__/setups/invalid.setup.001.js @@ -0,0 +1,3 @@ +module.exports = { + createSdk: [], +}; diff --git a/tests/devtools-cli-test/test/commands/oapp/__data__/setups/invalid.setup.empty.js b/tests/devtools-cli-test/test/commands/oapp/__data__/setups/invalid.setup.empty.js new file mode 100644 index 000000000..e69de29bb diff --git a/tests/devtools-cli-test/test/commands/oapp/__data__/setups/valid.setup.ts b/tests/devtools-cli-test/test/commands/oapp/__data__/setups/valid.setup.ts new file mode 100644 index 000000000..24e7e9885 --- /dev/null +++ b/tests/devtools-cli-test/test/commands/oapp/__data__/setups/valid.setup.ts @@ -0,0 +1,26 @@ +import { + createConnectedContractFactory, + createSignerFactory, + createDefaultContext, +} from '@layerzerolabs/devtools-evm-hardhat' +import { createOAppFactory } from '@layerzerolabs/ua-devtools-evm' + +import type { CLISetup } from '@layerzerolabs/devtools-cli' + +/** + * Since we are not in hardhat CLI, we'll need to create the context first + */ +createDefaultContext() + +/** + * This is a setup file for @layerzerolabs/devtools-cli. + * + * At the moment, @layerzerolabs/devtools-cli is in development + * and will be available + */ +const setup: CLISetup = { + createSdk: createOAppFactory(createConnectedContractFactory()), + createSigner: createSignerFactory(), +} + +export default setup diff --git a/tests/devtools-cli-test/test/commands/oapp/__snapshots__/wire.test.ts.snap b/tests/devtools-cli-test/test/commands/oapp/__snapshots__/wire.test.ts.snap new file mode 100644 index 000000000..e2539f12b --- /dev/null +++ b/tests/devtools-cli-test/test/commands/oapp/__snapshots__/wire.test.ts.snap @@ -0,0 +1,225 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`command oapp wire with invalid options with invalid config option should fail with a malformed JS file (001) 1`] = ` +" + + + + + + ********** + ************** + ****************** + ******************** + ********* ********* + ********* ********* + ********* ********* + ****** ********* + ******** ********* + ********* ******** + ********* ****** + ********* ********* + ********* ********* + ********* ********* + ******************** + ****************** + **************** + ********** + + + + + + + +warn: This command is just a placeholder. Please use @layerzerolabs/toolbox-hardhat package for the time being. +info: Loading config file from test/commands/oapp/__data__/configs/invalid.config.001.js +error: Failed to load config from test/commands/oapp/__data__/setups/valid.setup.ts: + +Error: Config from file 'test/commands/oapp/__data__/configs/invalid.config.001.js' is malformed. Please fix the following errors: + +Property 'contracts.0.point': Required +Property 'contracts.0.config': Required +Property 'contracts.1.point': Required +Property 'contracts.1.config': Required +Property 'connections': Required +" +`; + +exports[`command oapp wire with invalid options with invalid config option should fail with a misconfigured file (001) 1`] = ` +" + + + + + + ********** + ************** + ****************** + ******************** + ********* ********* + ********* ********* + ********* ********* + ****** ********* + ******** ********* + ********* ******** + ********* ****** + ********* ********* + ********* ********* + ********* ********* + ******************** + ****************** + **************** + ********** + + + + + + + +warn: This command is just a placeholder. Please use @layerzerolabs/toolbox-hardhat package for the time being. +info: Loading config file from test/commands/oapp/__data__/configs/valid.config.misconfigured.001.js +error: Failed to load config from test/commands/oapp/__data__/setups/valid.setup.ts: + +Error: Config from file 'test/commands/oapp/__data__/configs/valid.config.misconfigured.001.js' is malformed. Please fix the following errors: + +Property 'contracts.0.point': Required +Property 'contracts.0.config': Required +Property 'contracts.1.point': Required +Property 'contracts.1.config': Required +Property 'connections.0.vector': Required +Property 'connections.0.config': Required +Property 'connections.1.vector': Required +Property 'connections.1.config': Required +" +`; + +exports[`command oapp wire with invalid options with invalid config option should fail with an empty JS file 1`] = ` +" + + + + + + ********** + ************** + ****************** + ******************** + ********* ********* + ********* ********* + ********* ********* + ****** ********* + ******** ********* + ********* ******** + ********* ****** + ********* ********* + ********* ********* + ********* ********* + ******************** + ****************** + **************** + ********** + + + + + + + +warn: This command is just a placeholder. Please use @layerzerolabs/toolbox-hardhat package for the time being. +info: Loading config file from test/commands/oapp/__data__/configs/invalid.config.empty.js +error: Failed to load config from test/commands/oapp/__data__/setups/valid.setup.ts: + +Error: Config from file 'test/commands/oapp/__data__/configs/invalid.config.empty.js' is malformed. Please fix the following errors: + +Property 'contracts': Required +Property 'connections': Required +" +`; + +exports[`command oapp wire with invalid options with invalid setup option should fail with a malformed JS file (001) 1`] = ` +" + + + + + + ********** + ************** + ****************** + ******************** + ********* ********* + ********* ********* + ********* ********* + ****** ********* + ******** ********* + ********* ******** + ********* ****** + ********* ********* + ********* ********* + ********* ********* + ******************** + ****************** + **************** + ********** + + + + + + + +warn: This command is just a placeholder. Please use @layerzerolabs/toolbox-hardhat package for the time being. +error: Failed to load setup from test/commands/oapp/__data__/setups/invalid.setup.001.js: + +Error: Setup from file 'test/commands/oapp/__data__/setups/invalid.setup.001.js' is malformed. Please fix the following errors: + +Property 'createSdk': Expected function, received array +Property 'createSigner': Required +" +`; + +exports[`command oapp wire with invalid options with invalid setup option should fail with an empty JS file 1`] = ` +" + + + + + + ********** + ************** + ****************** + ******************** + ********* ********* + ********* ********* + ********* ********* + ****** ********* + ******** ********* + ********* ******** + ********* ****** + ********* ********* + ********* ********* + ********* ********* + ******************** + ****************** + **************** + ********** + + + + + + + +warn: This command is just a placeholder. Please use @layerzerolabs/toolbox-hardhat package for the time being. +error: Failed to load setup from test/commands/oapp/__data__/setups/invalid.setup.empty.js: + +Error: Setup from file 'test/commands/oapp/__data__/setups/invalid.setup.empty.js' is malformed. Please fix the following errors: + +Property 'createSdk': Required +Property 'createSigner': Required +" +`; + +exports[`command oapp wire with valid configs should not understand hardhat config 1`] = `""`; diff --git a/tests/devtools-cli-test/test/commands/oapp/wire.test.ts b/tests/devtools-cli-test/test/commands/oapp/wire.test.ts new file mode 100644 index 000000000..bd2ec7823 --- /dev/null +++ b/tests/devtools-cli-test/test/commands/oapp/wire.test.ts @@ -0,0 +1,193 @@ +import 'hardhat' +import { isFile } from '@layerzerolabs/io-devtools' +import { dirname, join, relative } from 'path' +import { cwd } from 'process' +import { deployContract, setupDefaultEndpointV2 } from '@layerzerolabs/test-setup-devtools-evm-hardhat' +import { runCli } from '../../__utils__/cli' + +describe(`command oapp wire`, () => { + const runWire = (args: string[] = []) => runCli(['oapp', 'wire', ...args]) + + const CONFIGS_BASE_DIR = relative(cwd(), join(__dirname, '__data__', 'configs')) + const SETUPS_BASE_DIR = relative(cwd(), join(__dirname, '__data__', 'setups')) + + const configPathFixture = (fileName: string): string => { + const path = join(CONFIGS_BASE_DIR, fileName) + + expect(isFile(path)).toBeTruthy() + + return path + } + + const setupPathFixture = (fileName: string): string => { + const path = join(SETUPS_BASE_DIR, fileName) + + expect(isFile(path)).toBeTruthy() + + return path + } + + beforeAll(async () => { + await deployContract('EndpointV2') + await setupDefaultEndpointV2() + }) + + describe('with missing options', () => { + it('should fail if the setup file is not passed', async () => { + const result = runWire(['--oapp-config', './does-not-exist.js']) + + expect(result.stderr).toMatch("required option '-s,--setup ' not specified") + expect(result.status).toBe(1) + }) + + it('should fail if the config file is not passed', async () => { + const result = runWire(['--setup', './does-not-exist.js']) + + expect(result.stderr).toMatch("required option '--oapp-config ' not specified") + expect(result.status).toBe(1) + }) + }) + + describe('with invalid options', () => { + beforeAll(async () => { + await deployContract('OApp') + }) + + describe('with invalid config option', () => { + it('should fail if the config file does not exist', async () => { + const result = runWire([ + '--oapp-config', + './does-not-exist.js', + '--setup', + setupPathFixture('valid.setup.ts'), + ]) + + expect(result.stdout).toMatch(`Unable to read config file './does-not-exist.js'`) + expect(result.status).not.toBe(0) + }) + + it('should fail if the config file is not a file', async () => { + const oappConfig = dirname(configPathFixture('invalid.config.empty.json')) + + const result = runWire(['--oapp-config', oappConfig, '--setup', setupPathFixture('valid.setup.ts')]) + expect(result.stdout).toMatch(`Unable to read config file '${oappConfig}`) + expect(result.status).not.toBe(0) + }) + + it('should fail if the config file is not a valid JSON or JS file', async () => { + const oappConfig = 'README.md' + + expect(isFile(oappConfig)).toBeTruthy() + + const result = runWire(['--oapp-config', oappConfig, '--setup', setupPathFixture('valid.setup.ts')]) + expect(result.stdout).toMatch(`Unable to read config file '${oappConfig}`) + expect(result.status).not.toBe(0) + }) + + it('should fail with an empty JSON file', async () => { + const oappConfig = configPathFixture('invalid.config.empty.json') + + const result = runWire(['--oapp-config', oappConfig, '--setup', setupPathFixture('valid.setup.ts')]) + expect(result.stdout).toMatch(`Unable to read config file '${oappConfig}'`) + expect(result.stdout).toMatch(`Unexpected end of JSON input`) + expect(result.status).not.toBe(0) + }) + + it('should fail with an empty JS file', async () => { + const oappConfig = configPathFixture('invalid.config.empty.js') + + const result = runWire(['--oapp-config', oappConfig, '--setup', setupPathFixture('valid.setup.ts')]) + expect(result.stdout).toMatchSnapshot() + expect(result.status).not.toBe(0) + }) + + it('should fail with a malformed JS file (001)', async () => { + const oappConfig = configPathFixture('invalid.config.001.js') + + const result = runWire(['--oapp-config', oappConfig, '--setup', setupPathFixture('valid.setup.ts')]) + expect(result.stdout).toMatchSnapshot() + expect(result.status).not.toBe(0) + }) + + it('should fail with a misconfigured file (001)', async () => { + const oappConfig = configPathFixture('valid.config.misconfigured.001.js') + + const result = runWire(['--oapp-config', oappConfig, '--setup', setupPathFixture('valid.setup.ts')]) + expect(result.stdout).toMatchSnapshot() + expect(result.status).not.toBe(0) + }) + }) + + describe('with invalid setup option', () => { + it('should fail if the setup file does not exist', async () => { + const result = runWire([ + '--oapp-config', + configPathFixture('valid.config.empty.js'), + '--setup', + './does-not-exist.js', + ]) + + expect(result.stdout).toMatch(`Unable to read setup file './does-not-exist.js'`) + expect(result.status).not.toBe(0) + }) + + it('should fail if the config file is not a file', async () => { + const setup = dirname(configPathFixture('invalid.config.empty.json')) + + const result = runWire(['--oapp-config', configPathFixture('valid.config.empty.js'), '--setup', setup]) + expect(result.stdout).toMatch(`Unable to read setup file '${setup}`) + expect(result.status).not.toBe(0) + }) + + it('should fail if the config file is not a valid JSON or JS file', async () => { + const setup = 'README.md' + + expect(isFile(setup)).toBeTruthy() + + const result = runWire(['--oapp-config', configPathFixture('valid.config.empty.js'), '--setup', setup]) + expect(result.stdout).toMatch(`Unable to read setup file '${setup}`) + expect(result.status).not.toBe(0) + }) + + it('should fail with an empty JS file', async () => { + const setup = setupPathFixture('invalid.setup.empty.js') + + const result = runWire(['--oapp-config', configPathFixture('valid.config.empty.js'), '--setup', setup]) + expect(result.stdout).toMatchSnapshot() + expect(result.status).not.toBe(0) + }) + + it('should fail with a malformed JS file (001)', async () => { + const setup = setupPathFixture('invalid.setup.001.js') + + const result = runWire(['--oapp-config', configPathFixture('valid.config.empty.js'), '--setup', setup]) + expect(result.stdout).toMatchSnapshot() + expect(result.status).not.toBe(0) + }) + }) + }) + + describe('with valid configs', () => { + beforeEach(async () => { + await deployContract('OApp') + }) + + it('should exit with an empty config', async () => { + const oappConfig = configPathFixture('valid.config.empty.ts') + const setup = setupPathFixture('valid.setup.ts') + + const result = runWire(['--oapp-config', oappConfig, '--setup', setup]) + expect(result.stdout).toMatch(`The OApp is wired, no action is necessary`) + expect(result.status).toBe(0) + }) + + it('should not understand hardhat config', async () => { + const oappConfig = configPathFixture('valid.config.connected.js') + const setup = setupPathFixture('valid.setup.ts') + + const result = runWire(['--oapp-config', oappConfig, '--setup', setup]) + expect(result.stderr).toMatchSnapshot() + expect(result.status).not.toBe(0) + }) + }) +}) diff --git a/tests/devtools-cli-test/tsconfig.json b/tests/devtools-cli-test/tsconfig.json new file mode 100644 index 000000000..dff69db7f --- /dev/null +++ b/tests/devtools-cli-test/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "exclude": ["dist", "node_modules"], + "include": ["src", "test", "deploy", "*.config.ts", "*.setup.ts"], + "compilerOptions": { + "module": "commonjs", + "types": ["node", "jest"] + } +}