Skip to content

Commit

Permalink
🚧 Hardhat CLI Argument parsers don't have access to HardhatRuntimeEnv…
Browse files Browse the repository at this point in the history
…ironment (#311)
  • Loading branch information
janjakubnanista authored Jan 31, 2024
1 parent 9d4f775 commit 17c8a23
Show file tree
Hide file tree
Showing 12 changed files with 160 additions and 43 deletions.
7 changes: 7 additions & 0 deletions .changeset/ninety-mangos-glow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@layerzerolabs/ua-devtools-evm-hardhat-test": patch
"@layerzerolabs/ua-devtools-evm-hardhat": patch
"@layerzerolabs/devtools-evm-hardhat": patch
---

Fix problems with --networks hardhat CLI argument parser
26 changes: 1 addition & 25 deletions packages/devtools-evm-hardhat/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { HardhatError } from 'hardhat/internal/core/errors'
import { ERRORS } from 'hardhat/internal/core/errors-list'
import type { CLIArgumentType } from 'hardhat/types'
import { z } from 'zod'
import { getEidsByNetworkName } from './runtime'
import { LogLevel } from '@layerzerolabs/io-devtools'

/**
Expand Down Expand Up @@ -39,29 +38,6 @@ const csv: CLIArgumentType<string[]> = {
validate() {},
}

/**
* Hardhat CLI type for a comma separated list of network names
*/
const networks: CLIArgumentType<string[]> = {
name: 'networks',
parse(name: string, value: string) {
const networkNames = csv.parse(name, value)
const allDefinedNetworks = getEidsByNetworkName()
const networks = networkNames.map((networkName) => {
if (networkName in allDefinedNetworks) return networkName

throw new HardhatError(ERRORS.ARGUMENTS.INVALID_VALUE_FOR_TYPE, {
value: networkName,
name: name,
type: 'network',
})
})

return networks
},
validate() {},
}

/**
* Hardhat CLI type for a log level argument
*
Expand All @@ -84,4 +60,4 @@ const logLevel: CLIArgumentType<LogLevel> = {
validate() {},
}

export const types = { csv, networks, logLevel, ...builtInTypes }
export const types = { csv, logLevel, ...builtInTypes }
32 changes: 31 additions & 1 deletion packages/devtools-evm-hardhat/src/internal/assertions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import assert from 'assert'
import { getDefaultRuntimeEnvironment, getEidsByNetworkName } from '@/runtime'
import assert, { AssertionError } from 'assert'
import 'hardhat-deploy/dist/src/type-extensions'
import { DeploymentsExtension } from 'hardhat-deploy/dist/types'
import { HardhatRuntimeEnvironment } from 'hardhat/types'
Expand All @@ -7,8 +8,37 @@ export interface HardhatRuntimeEnvironmentWithDeployments extends HardhatRuntime
deployments: DeploymentsExtension
}

/**
* Helper utility to make sure hardhat-deploy is being used by the project
*
* @param {HardhatRuntimeEnvironment} hre
*/
export function assertHardhatDeploy(
hre: HardhatRuntimeEnvironment
): asserts hre is HardhatRuntimeEnvironmentWithDeployments {
assert(hre.deployments, `You don't seem to be using hardhat-deploy in your project`)
}

/**
* Helper utility to make sure that all the networks passed
* to this function have been defined in the config
*
* @param {Iterable<string>} networkNames
* @param {HardhatRuntimeEnvironment} hre
*/
export function assertDefinedNetworks<TNetworkNames extends Iterable<string>>(
networkNames: TNetworkNames,
hre: HardhatRuntimeEnvironment = getDefaultRuntimeEnvironment()
): TNetworkNames {
const definedNetworkNames = new Set(Object.keys(getEidsByNetworkName(hre)))

for (const networkName of networkNames) {
if (definedNetworkNames.has(networkName)) continue

throw new AssertionError({
message: `Network '${networkName}' has not been defined. Defined networks are ${Array.from(definedNetworkNames).join(', ')}`,
})
}

return networkNames
}
6 changes: 5 additions & 1 deletion packages/devtools-evm-hardhat/src/tasks/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { createLogger, setDefaultLogLevel } from '@layerzerolabs/io-devtools'

import { printLogo } from '@layerzerolabs/io-devtools/swag'
import { types } from '@/cli'
import { assertDefinedNetworks } from '@/internal/assertions'

interface TaskArgs {
networks?: string[]
Expand All @@ -19,6 +20,9 @@ const action: ActionType<TaskArgs> = async ({
}): Promise<void> => {
printLogo()

// Make sure to check that the networks are defined
assertDefinedNetworks(networksArgument ?? [])

// We'll set the global logging level to get as much info as needed
setDefaultLogLevel(logLevel)

Expand All @@ -41,7 +45,7 @@ if (process.env.LZ_ENABLE_EXPERIMENTAL_TASK_LZ_DEPLOY) {
'networks',
'List of comma-separated networks. If not provided, all networks will be deployed',
undefined,
types.networks,
types.csv,
true
)
.addParam('logLevel', 'Logging level. One of: error, warn, info, verbose, debug, silly', 'info', types.logLevel)
Expand Down
49 changes: 49 additions & 0 deletions packages/devtools-evm-hardhat/test/internal/assertions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import 'hardhat'
import { assertDefinedNetworks } from '@/internal'
import fc from 'fast-check'

describe('internal/assertions', () => {
describe('assertDefinedNetworks', () => {
const definedNetworks = ['ethereum-mainnet', 'ethereum-testnet', 'bsc-testnet']
const definedNetworkArbitrary = fc.constantFrom(...definedNetworks)

const definedNetworksArrayArbitrary = fc.array(definedNetworkArbitrary)
const definedNetworksSetArbitrary = definedNetworksArrayArbitrary.map((networks) => new Set(networks))

it('should not throw if called with an array of networks defined in hardhat config', () => {
fc.assert(
fc.property(definedNetworksArrayArbitrary, (networks) => {
expect(assertDefinedNetworks(networks)).toBe(networks)
})
)
})

it('should not throw if called with a Set of networks defined in hardhat config', () => {
fc.assert(
fc.property(definedNetworksSetArbitrary, (networks) => {
expect(assertDefinedNetworks(networks)).toBe(networks)
})
)
})

it('should throw if called if a network has not been defined in an array', () => {
fc.assert(
fc.property(definedNetworksArrayArbitrary, fc.string(), (networks, network) => {
fc.pre(!definedNetworks.includes(network))

expect(() => assertDefinedNetworks([...networks, network])).toThrow()
})
)
})

it('should throw if called if a network has not been defined in a Set', () => {
fc.assert(
fc.property(definedNetworksSetArbitrary, fc.string(), (networks, network) => {
fc.pre(!definedNetworks.includes(network))

expect(() => assertDefinedNetworks(networks.add(network))).toThrow()
})
)
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ import { createLogger, printJson, printRecord } from '@layerzerolabs/io-devtools
import { getReceiveConfig, getSendConfig } from '@/utils/taskHelpers'
import { TASK_LZ_OAPP_CONFIG_GET_DEFAULT } from '@/constants'
import { setDefaultLogLevel } from '@layerzerolabs/io-devtools'
import { getEidForNetworkName, getEidsByNetworkName, types } from '@layerzerolabs/devtools-evm-hardhat'
import {
assertDefinedNetworks,
getEidForNetworkName,
getEidsByNetworkName,
types,
} from '@layerzerolabs/devtools-evm-hardhat'
import { OAppEdgeConfig } from '@layerzerolabs/ua-devtools'

interface TaskArgs {
Expand All @@ -21,9 +26,11 @@ export const getDefaultConfig: ActionType<TaskArgs> = async (
setDefaultLogLevel(logLevel)
const logger = createLogger()

const networks =
networksArgument ??
Object.entries(getEidsByNetworkName(hre)).flatMap(([networkName, eid]) => (eid == null ? [] : [networkName]))
const networks = networksArgument
? // Here we need to check whether the networks have been defined in hardhat config
assertDefinedNetworks(networksArgument)
: // But here a=we are taking them from hardhat config so no assertion is necessary
Object.entries(getEidsByNetworkName(hre)).flatMap(([networkName, eid]) => (eid == null ? [] : [networkName]))

const configs: Record<string, Record<string, unknown>> = {}
for (const localNetworkName of networks) {
Expand Down Expand Up @@ -116,7 +123,7 @@ task(
TASK_LZ_OAPP_CONFIG_GET_DEFAULT,
'Outputs the default Send and Receive Messaging Library versions and the default application config'
)
.addParam('networks', 'Comma-separated list of networks', undefined, types.networks, true)
.addParam('networks', 'Comma-separated list of networks', undefined, types.csv, true)
.addParam('logLevel', 'Logging level. One of: error, warn, info, verbose, debug, silly', 'info', types.logLevel)
.addParam(
'json',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { printRecord } from '@layerzerolabs/io-devtools'
import { getExecutorDstConfig } from '@/utils/taskHelpers'
import { TASK_LZ_OAPP_CONFIG_GET_EXECUTOR } from '@/constants'
import { setDefaultLogLevel } from '@layerzerolabs/io-devtools'
import { getEidsByNetworkName, types } from '@layerzerolabs/devtools-evm-hardhat'
import { assertDefinedNetworks, getEidsByNetworkName, types } from '@layerzerolabs/devtools-evm-hardhat'

interface TaskArgs {
logLevel?: string
Expand All @@ -18,9 +18,11 @@ export const getExecutorConfig: ActionType<TaskArgs> = async (
// We'll set the global logging level to get as much info as needed
setDefaultLogLevel(logLevel)

const networks =
networksArgument ??
Object.entries(getEidsByNetworkName(hre)).flatMap(([networkName, eid]) => (eid == null ? [] : [networkName]))
const networks = networksArgument
? // Here we need to check whether the networks have been defined in hardhat config
assertDefinedNetworks(networksArgument)
: // But here a=we are taking them from hardhat config so no assertion is necessary
Object.entries(getEidsByNetworkName(hre)).flatMap(([networkName, eid]) => (eid == null ? [] : [networkName]))

const configs: Record<string, Record<string, unknown>> = {}
for (const localNetworkName of networks) {
Expand All @@ -47,6 +49,6 @@ task(
TASK_LZ_OAPP_CONFIG_GET_EXECUTOR,
'Outputs the Executors destination configurations including the native max cap amount '
)
.addParam('networks', 'Comma-separated list of networks', undefined, types.networks, true)
.addParam('networks', 'Comma-separated list of networks', undefined, types.csv, true)
.addParam('logLevel', 'Logging level. One of: error, warn, info, verbose, debug, silly', 'info', types.logLevel)
.setAction(getExecutorConfig)
1 change: 1 addition & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions tests/ua-devtools-evm-hardhat-test/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ module.exports = {
transform: {
'^.+\\.(t|j)sx?$': '@swc/jest',
},
transformIgnorePatterns: ['node_modules/(?!zx)'],
};
19 changes: 19 additions & 0 deletions tests/ua-devtools-evm-hardhat-test/test/__utils__/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { createGetHreByEid } from '@layerzerolabs/devtools-evm-hardhat'
import { EndpointId } from '@layerzerolabs/lz-definitions'
import type { HardhatRuntimeEnvironment } from 'hardhat/types'

export const clearDeployments = async (hre: HardhatRuntimeEnvironment) => {
const deployments = await hre.deployments.all()
const deploymentNames = Object.keys(deployments)

await Promise.all(deploymentNames.map((name) => hre.deployments.delete(name)))
}

export const cleanAllDeployments = async () => {
const environmentFactory = createGetHreByEid()
const eth = await environmentFactory(EndpointId.ETHEREUM_V2_MAINNET)
const avax = await environmentFactory(EndpointId.AVALANCHE_V2_MAINNET)
const bsc = await environmentFactory(EndpointId.BSC_V2_MAINNET)

await Promise.all([clearDeployments(eth), clearDeployments(avax), clearDeployments(bsc)])
}
6 changes: 3 additions & 3 deletions tests/ua-devtools-evm-hardhat-test/test/__utils__/endpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,9 @@ export const bscDvn_Opt3 = { eid: EndpointId.BSC_V2_MAINNET, contractName: 'DVN_
export const MAX_MESSAGE_SIZE = 10000 // match on-chain value

const defaultPriceData: PriceData = {
priceRatio: '100000000000000000000',
gasPriceInUnit: 1,
gasPerByte: 1,
priceRatio: BigInt('100000000000000000000'),
gasPriceInUnit: BigInt(1),
gasPerByte: BigInt(1),
}

export const defaultExecutorDstConfig: ExecutorDstConfig = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { TASK_LZ_OAPP_CONFIG_GET_DEFAULT } from '@layerzerolabs/ua-devtools-evm-
import { omniContractToPoint } from '@layerzerolabs/devtools-evm'
import { printJson } from '@layerzerolabs/io-devtools'
import { OAppEdgeConfig } from '@layerzerolabs/ua-devtools'
import { spawnSync } from 'child_process'
import { cleanAllDeployments } from '../../__utils__/common'

jest.mock('@layerzerolabs/io-devtools', () => {
const original = jest.requireActual('@layerzerolabs/io-devtools')
Expand All @@ -21,13 +23,21 @@ jest.mock('@layerzerolabs/io-devtools', () => {
})

describe(`task ${TASK_LZ_OAPP_CONFIG_GET_DEFAULT}`, () => {
const networks = ['vengaboys', 'britney', 'tango']

beforeEach(async () => {
await deployEndpoint()
// We'll deploy the endpoint and save the deployments to the filesystem
// since we want to be able to tun the task using spawnSync
await deployEndpoint(true)
await setupDefaultEndpoint()
})

afterAll(async () => {
// We'll be good citizens and clean the endpoint deployments
await cleanAllDeployments()
})

it('should return default configurations with passed in networks param', async () => {
const networks = ['vengaboys', 'britney', 'tango']
const getDefaultConfigTask = await hre.run(TASK_LZ_OAPP_CONFIG_GET_DEFAULT, { networks })
const contractFactory = createContractFactory()
for (const localNetwork of networks) {
Expand All @@ -53,7 +63,6 @@ describe(`task ${TASK_LZ_OAPP_CONFIG_GET_DEFAULT}`, () => {
})

it('should print out default config in json form', async () => {
const networks = ['vengaboys', 'britney', 'tango']
const getDefaultConfigTask = await hre.run(TASK_LZ_OAPP_CONFIG_GET_DEFAULT, {
networks,
json: true,
Expand Down Expand Up @@ -95,4 +104,16 @@ describe(`task ${TASK_LZ_OAPP_CONFIG_GET_DEFAULT}`, () => {
}
}
})

it(`should fail if not defined networks have been passed`, async () => {
const result = spawnSync('npx', ['hardhat', TASK_LZ_OAPP_CONFIG_GET_DEFAULT, '--networks', 'whatever,yomama'])

expect(result.status).toBe(1)
})

it(`should not fail if defined networks have been passed`, async () => {
const result = spawnSync('npx', ['hardhat', TASK_LZ_OAPP_CONFIG_GET_DEFAULT, `--networks`, networks.join(',')])

expect(result.status).toBe(0)
})
})

0 comments on commit 17c8a23

Please sign in to comment.