diff --git a/.changeset/gorgeous-cobras-pull.md b/.changeset/gorgeous-cobras-pull.md new file mode 100644 index 0000000000..253cc4079d --- /dev/null +++ b/.changeset/gorgeous-cobras-pull.md @@ -0,0 +1,9 @@ +--- +"@layerzerolabs/ua-devtools-evm-hardhat": patch +"@layerzerolabs/lzapp-migration-example": patch +"@layerzerolabs/oapp-solana-example": patch +"@layerzerolabs/oft-solana-example": patch +"@layerzerolabs/devtools": patch +--- + +fix misleading message after running init-config diff --git a/docs/EXAMPLES_SPECS.md b/docs/EXAMPLES_SPECS.md index 4f45924ac8..e016767c19 100644 --- a/docs/EXAMPLES_SPECS.md +++ b/docs/EXAMPLES_SPECS.md @@ -122,6 +122,10 @@ Currently, this document will only detail the structure for the READMEs of the e - Goal: Resolve errors and setup issues - Contents: Link to general troubleshooting + local fixes + 16.7. **Audits** _(include only if audits exist)_ + - Goal: Link to audit reports for transparency + - Contents: Commit hash audited + links to audit report PDFs + Any sections that don't appear in the above list should be considered for removal. Before removing, ask the user for confirmation. diff --git a/examples/lzapp-migration/tasks/solana/initConfig.ts b/examples/lzapp-migration/tasks/solana/initConfig.ts index 794a4dbf21..e49163070c 100644 --- a/examples/lzapp-migration/tasks/solana/initConfig.ts +++ b/examples/lzapp-migration/tasks/solana/initConfig.ts @@ -25,9 +25,11 @@ interface Args { // This task will use the `initOFTAccounts` configurator that initializes the Solana accounts const initConfigTask = wireLikeTask('lz:oft:solana:init-config') as ConfigurableTaskDefinition -// This task will use the `initOFTAccounts` configurator that initializes the Solana accounts -initConfigTask - .setDescription('Initialize OFT accounts for Solana') - .setAction(async (args: Args, hre) => - hre.run(TASK_LZ_OAPP_WIRE, { ...args, isSolanaInitConfig: true, internalConfigurator: initOFTAccounts }) - ) +initConfigTask.setDescription('Initialize OFT accounts for Solana').setAction(async (args: Args, hre) => + hre.run(TASK_LZ_OAPP_WIRE, { + ...args, + isSolanaInitConfig: true, + internalConfigurator: initOFTAccounts, + noActionMessage: 'Pathway config already initialized', + }) +) diff --git a/examples/oapp-solana/hardhat.config.ts b/examples/oapp-solana/hardhat.config.ts index e735ada7e8..0b31f5a97b 100644 --- a/examples/oapp-solana/hardhat.config.ts +++ b/examples/oapp-solana/hardhat.config.ts @@ -1,10 +1,3 @@ -// Force ts-node to use CommonJS mode -// This must be set before any imports -process.env.TS_NODE_COMPILER_OPTIONS = JSON.stringify({ - module: 'commonjs', - esModuleInterop: true, -}) - // Get the environment configuration from .env file // // To make use of automatic environment setup: diff --git a/examples/oapp-solana/tasks/solana/initConfig.ts b/examples/oapp-solana/tasks/solana/initConfig.ts index c1b01905a7..d4dd6ca1cf 100644 --- a/examples/oapp-solana/tasks/solana/initConfig.ts +++ b/examples/oapp-solana/tasks/solana/initConfig.ts @@ -24,9 +24,11 @@ interface Args { // This task will use the `initOFTAccounts` configurator that initializes the Solana accounts const initConfigTask = wireLikeTask('lz:oapp:solana:init-config') as ConfigurableTaskDefinition -// TODO: currently the message for 'already done' state is "OApp is already wired." which is misleading -> should be changed to "Pathway Config already initialized" -initConfigTask - .setDescription('Initialize OApp accounts for Solana') - .setAction(async (args: Args, hre) => - hre.run(TASK_LZ_OAPP_WIRE, { ...args, internalConfigurator: initOAppAccounts, isSolanaInitConfig: true }) - ) +initConfigTask.setDescription('Initialize OApp accounts for Solana').setAction(async (args: Args, hre) => + hre.run(TASK_LZ_OAPP_WIRE, { + ...args, + internalConfigurator: initOAppAccounts, + isSolanaInitConfig: true, + noActionMessage: 'Pathway config already initialized', + }) +) diff --git a/examples/oft-solana/README.md b/examples/oft-solana/README.md index b773a558cd..d9d812e5ef 100644 --- a/examples/oft-solana/README.md +++ b/examples/oft-solana/README.md @@ -537,3 +537,17 @@ Refer to [Verify the OFT Program](https://docs.layerzero.network/v2/developers/s ### Troubleshooting Refer to the [Solana Troubleshooting page on the LayerZero Docs](https://docs.layerzero.network/v2/developers/solana/troubleshooting/common-errors) to see how to solve common error when deploying Solana OFTs. + +### Audits + +For the Solana OFT program, the last change was via commit [6a07bb7](https://github.com/LayerZero-Labs/devtools/commit/6a07bb7e089349995ce4b30978512aff001cc131) + +The `6a07bb7` commit hash audited in the following 2 reports: + +1. SolanaOFTandDevTools-Ottersec-24SEPT2024.pdf + + - https://github.com/LayerZero-Labs/Audits/blob/main/audits/SolanaOFTandDevTools-Ottersec-24SEPT2024.pdf + +2. SolanaOFTandDevTools-Pashov-17SEPT2024.pdf + + - https://github.com/LayerZero-Labs/Audits/blob/main/audits/SolanaOFTandDevTools-Pashov-17SEPT2024.pdf diff --git a/examples/oft-solana/hardhat.config.ts b/examples/oft-solana/hardhat.config.ts index 9b21b274c8..1b5fc29d08 100644 --- a/examples/oft-solana/hardhat.config.ts +++ b/examples/oft-solana/hardhat.config.ts @@ -1,10 +1,3 @@ -// Force ts-node to use CommonJS mode -// This must be set before any imports -process.env.TS_NODE_COMPILER_OPTIONS = JSON.stringify({ - module: 'commonjs', - esModuleInterop: true, -}) - // Get the environment configuration from .env file // // To make use of automatic environment setup: diff --git a/examples/oft-solana/tasks/solana/initConfig.ts b/examples/oft-solana/tasks/solana/initConfig.ts index 202d18a367..c041cbcbe2 100644 --- a/examples/oft-solana/tasks/solana/initConfig.ts +++ b/examples/oft-solana/tasks/solana/initConfig.ts @@ -23,9 +23,11 @@ interface Args { // This task will use the `initOFTAccounts` configurator that initializes the Solana accounts const initConfigTask = wireLikeTask('lz:oft:solana:init-config') as ConfigurableTaskDefinition -// TODO: currently the message for 'already done' state is "OApp is already wired." which is misleading -> should be changed to "Pathway Config already initialized" -initConfigTask - .setDescription('Initialize OFT accounts for Solana') - .setAction(async (args: Args, hre) => - hre.run(TASK_LZ_OAPP_WIRE, { ...args, internalConfigurator: initOFTAccounts, isSolanaInitConfig: true }) - ) +initConfigTask.setDescription('Initialize OFT accounts for Solana').setAction(async (args: Args, hre) => + hre.run(TASK_LZ_OAPP_WIRE, { + ...args, + internalConfigurator: initOFTAccounts, + isSolanaInitConfig: true, + noActionMessage: 'Pathway config already initialized', + }) +) diff --git a/packages/devtools-evm-hardhat/src/tasks/deploy.ts b/packages/devtools-evm-hardhat/src/tasks/deploy.ts index d45c83843a..d2ed0091e7 100644 --- a/packages/devtools-evm-hardhat/src/tasks/deploy.ts +++ b/packages/devtools-evm-hardhat/src/tasks/deploy.ts @@ -1,5 +1,5 @@ import { task } from 'hardhat/config' -import type { ActionType } from 'hardhat/types' +import type { ActionType, HardhatRuntimeEnvironment } from 'hardhat/types' import { TASK_COMPILE } from 'hardhat/builtin-tasks/task-names' import { TASK_LZ_DEPLOY } from '@/constants/tasks' import { @@ -17,11 +17,57 @@ import { formatEid } from '@layerzerolabs/devtools' import { getEidsByNetworkName, getHreByNetworkName } from '@/runtime' import { types } from '@/cli' import { promptForText } from '@layerzerolabs/io-devtools' -import { Deployment } from 'hardhat-deploy/dist/types' +import { Deployment, DeployFunction } from 'hardhat-deploy/dist/types' import { assertDefinedNetworks, assertHardhatDeploy } from '@/internal/assertions' import { splitCommaSeparated } from '@layerzerolabs/devtools' import { isDeepEqual } from '@layerzerolabs/devtools' import { Stage, endpointIdToStage } from '@layerzerolabs/lz-definitions' +import { readdirSync, statSync } from 'fs' +import { join, extname } from 'path' + +/** + * Get all available tags from deploy scripts in the given deploy paths. + * Uses require() with ts-node/esm loader to handle TypeScript files. + */ +const getAvailableTagsFromDeployScripts = async (hre: HardhatRuntimeEnvironment): Promise> => { + const tags = new Set() + const deployPaths = hre.config.paths.deploy + + for (const deployPath of deployPaths) { + try { + const files = readdirSync(deployPath) + for (const file of files) { + const filePath = join(deployPath, file) + // Skip directories and non-script files + if (statSync(filePath).isDirectory()) { + continue + } + const ext = extname(file) + if (!['.js', '.ts'].includes(ext)) { + continue + } + + try { + // Use require which works with ts-node/register in hardhat context + // eslint-disable-next-line @typescript-eslint/no-var-requires + const deployScript = require(filePath) + const deployFunc: DeployFunction = deployScript.default ?? deployScript + if (deployFunc?.tags) { + for (const tag of deployFunc.tags) { + tags.add(tag) + } + } + } catch { + // Skip files that can't be imported + } + } + } catch { + // Skip paths that don't exist + } + } + + return tags +} interface TaskArgs { networks?: string[] @@ -166,6 +212,16 @@ const action: ActionType = async ( logger.warn(`Will use all deployment scripts`) } else { logger.info(`Will use deploy scripts tagged with ${selectedTags.join(', ')}`) + + // Check if selected tags match any available deploy script tags + const availableTags = await getAvailableTagsFromDeployScripts(hre) + const unmatchedTags = selectedTags.filter((tag) => !availableTags.has(tag)) + + if (unmatchedTags.length > 0) { + logger.warn( + `The following tags do not match any deploy scripts: ${unmatchedTags.join(', ')}. Available tags: ${[...availableTags].join(', ') || 'none'}` + ) + } } // Now we confirm with the user that they want to continue @@ -261,8 +317,23 @@ const action: ActionType = async ( error == null ? [] : [{ networkName, error }] ) - // If nothing went wrong we just exit + // We check whether any contracts were actually deployed + const totalContractsDeployed = Object.values(results).reduce( + (sum, { contracts }) => sum + Object.keys(contracts ?? {}).length, + 0 + ) + + // If nothing went wrong we check if any contracts were deployed if (errors.length === 0) { + if (totalContractsDeployed === 0) { + return ( + logger.warn( + `${printBoolean(false)} No contracts were deployed. This could mean the deploy script tags don't match any scripts, or all contracts were already deployed.` + ), + results + ) + } + return logger.info(`${printBoolean(true)} Your contracts are now deployed`), results } diff --git a/packages/devtools/src/flows/wire.ts b/packages/devtools/src/flows/wire.ts index 2568201915..84500bb517 100644 --- a/packages/devtools/src/flows/wire.ts +++ b/packages/devtools/src/flows/wire.ts @@ -11,6 +11,11 @@ export interface CreateWireFlowArgs { logger?: Logger executeConfig: ConfigExecuteFlow signAndSend: SignAndSendFlow + /** + * Custom message to display when no transactions are needed. + * Defaults to "The OApp is wired, no action is necessary" + */ + noActionMessage?: string } export interface WireFlowArgs { @@ -25,6 +30,7 @@ export const createWireFlow = logger = createLogger(), executeConfig, signAndSend, + noActionMessage = 'The OApp is wired, no action is necessary', }: CreateWireFlowArgs) => async ({ graph, @@ -55,7 +61,7 @@ export const createWireFlow = // If there are no transactions that need to be executed, we'll just exit if (transactions.length === 0) { - logger.info(`The OApp is wired, no action is necessary`) + logger.info(noActionMessage) return [[], [], []] } diff --git a/packages/ua-devtools-evm-hardhat/src/tasks/oapp/wire/index.ts b/packages/ua-devtools-evm-hardhat/src/tasks/oapp/wire/index.ts index 4d345fd153..16207f73e8 100644 --- a/packages/ua-devtools-evm-hardhat/src/tasks/oapp/wire/index.ts +++ b/packages/ua-devtools-evm-hardhat/src/tasks/oapp/wire/index.ts @@ -58,6 +58,11 @@ interface TaskArgs { * Exclude connections that originate from the specified EndpointIds. */ skipConnectionsFromEids?: string[] + /** + * Custom message to display when no transactions are needed. + * Defaults to "The OApp is wired, no action is necessary" + */ + noActionMessage?: string } const action: ActionType = async ( @@ -74,6 +79,7 @@ const action: ActionType = async ( signAndSendSubtask = SUBTASK_LZ_SIGN_AND_SEND, outputFilename, skipConnectionsFromEids, + noActionMessage, }, hre ): Promise => { @@ -105,6 +111,7 @@ const action: ActionType = async ( // Then create the wire flow const wireFlow = createWireFlow({ logger, + noActionMessage, // We use hardhat subtasks to provide the option to override certain behaviors on a more granular level executeConfig: ({ graph }) => hre.run(configureSubtask, { graph } satisfies SubtaskConfigureTaskArgs), signAndSend: ({ transactions }) => { diff --git a/tests/devtools-evm-hardhat-test/test/task/deploy.test.expectations/deploy-all-missing-tag.exp b/tests/devtools-evm-hardhat-test/test/task/deploy.test.expectations/deploy-all-missing-tag.exp index fb6a053772..9ee5e380be 100755 --- a/tests/devtools-evm-hardhat-test/test/task/deploy.test.expectations/deploy-all-missing-tag.exp +++ b/tests/devtools-evm-hardhat-test/test/task/deploy.test.expectations/deploy-all-missing-tag.exp @@ -40,10 +40,11 @@ send -- "\r" expect "Will deploy 3 networks: britney, tango, vengaboys" expect "Will use deploy scripts tagged with MeNoExist" +expect "do not match any deploy scripts" expect "Do you want to continue?" send -- "\r" expect "Deploying..." -expect "Your contracts are now deployed" +expect "No contracts were deployed" expect eof diff --git a/turbo.json b/turbo.json index 420acc6970..b46c1fab48 100644 --- a/turbo.json +++ b/turbo.json @@ -49,6 +49,8 @@ "globalDependencies": ["tsconfig.json"], "globalEnv": ["NODE_ENV"], "globalPassThroughEnv": [ + "TS_NODE_FILES", + "TS_NODE_PROJECT", "LZ_DEVTOOLS_ENABLE_DEPLOY_LOGGING", "LZ_DEVTOOLS_ENABLE_SOLANA_TESTS", "LZ_ENABLE_EXPERIMENTAL_BATCHED_SEND",