import { spawnSync } from "child_process"; import fs from "fs"; import path from "path"; export interface GateToolValidateOptions { root: string; network: string; proxyAddress: string; oldContractName?: string; newContractName: string; outputPath: string; constructorArgs?: unknown[]; unsafeAllow?: string[]; forkRpcUrl?: string; } export interface GateToolUpgradeOutput { proxyAddress: string; oldImplementation: string; newImplementation: string; deployment?: { method?: string; factoryAddress?: string | null; factorySalt?: string | null; txHash?: string | null; }; upgradeToAndCall: { target: string; calldata: string; }; timestamp?: string; network?: string; } const resolveGateToolCommand = ( root: string, ): { command: string; argsPrefix: string[] } => { const localBin = path.join( root, "node_modules", ".bin", process.platform === "win32" ? "gate-tool.cmd" : "gate-tool", ); if (fs.existsSync(localBin)) { return { command: localBin, argsPrefix: [] }; } return { command: process.platform === "win32" ? "npx.cmd" : "npx", argsPrefix: ["--no-install", "gate-tool"], }; }; const pushOptional = ( args: string[], flag: string, value: string | undefined, ): void => { if (value) { args.push(flag, value); } }; const readGateToolOutput = (outputPath: string): GateToolUpgradeOutput => { const output = JSON.parse(fs.readFileSync(outputPath, "utf8")) as GateToolUpgradeOutput; if (!output.upgradeToAndCall?.target || !output.upgradeToAndCall?.calldata) { throw new Error(`gate-tool validate output is missing upgradeToAndCall: ${outputPath}`); } if (!output.newImplementation) { throw new Error(`gate-tool validate output is missing newImplementation: ${outputPath}`); } return output; }; export const runGateToolValidate = ( options: GateToolValidateOptions, ): GateToolUpgradeOutput => { const { command, argsPrefix } = resolveGateToolCommand(options.root); const args = [ ...argsPrefix, "validate", "--proxy", options.proxyAddress, "--new", options.newContractName, "--network", options.network, "--output", options.outputPath, ]; pushOptional(args, "--old", options.oldContractName); if (options.constructorArgs?.length) { args.push("--constructor-args", JSON.stringify(options.constructorArgs)); } if (options.unsafeAllow?.length) { args.push("--unsafeAllow-args", JSON.stringify(options.unsafeAllow)); } fs.mkdirSync(path.dirname(options.outputPath), { recursive: true }); const result = spawnSync(command, args, { cwd: options.root, env: { ...process.env, HARDHAT_NETWORK: options.network, ...(options.forkRpcUrl ? { HARDHAT_FORK_RPC_URL: options.forkRpcUrl } : {}), }, stdio: "inherit", }); if (result.error) { throw result.error; } if (result.status !== 0) { throw new Error(`gate-tool validate failed with exit code ${result.status}`); } return readGateToolOutput(options.outputPath); };