import crypto from "crypto"; import path from "path"; import { JsonRpcProvider, type Provider } from "ethers"; import { getStorageLayout, getVersion, } from "@openzeppelin/upgrades-core"; import { readValidations } from "@openzeppelin/hardhat-upgrades/dist/utils/validations"; import hre, { artifacts, config as hardhatConfig, ethers as hardhatEthers, network as hardhatNetwork, } from "hardhat"; import { loadOpenZeppelinManifestDir } from "../../../config/openzeppelin"; import { loadConfig } from "../config"; import { loadContractInfo } from "../deployments"; import { loadDeploymentInfo } from "../deploymentInfo"; import { getERC1967Implementation } from "../erc1967"; import type { CheckCodeDependencies } from "../checkCodeRunner"; import { runCheckCodeCommand } from "./command"; export interface CheckCodeRuntimeDependencies { dependencies: CheckCodeDependencies; dispose: () => void; } const normalize = (value: unknown): unknown => { if (Array.isArray(value)) return value.map(normalize); if (value && typeof value === "object") { return Object.fromEntries(Object.keys(value as Record) .sort() .map((key) => [key, normalize((value as Record)[key])])); } return value; }; export const createCheckCodeDependencies = ( root: string, ): CheckCodeRuntimeDependencies => { const workflowConfig = loadConfig(root); const rpcProviders = new Map(); const providerFor = ( networkName: string, ): Pick => { if (networkName === hardhatNetwork.name) return hardhatEthers.provider; const existing = rpcProviders.get(networkName); if (existing) return existing; const networkConfig = hardhatConfig.networks[networkName]; if (!networkConfig || !("url" in networkConfig) || typeof networkConfig.url !== "string") { throw new Error( "Hardhat network " + networkName + " does not define an HTTP RPC URL for CheckCode", ); } const provider = new JsonRpcProvider(networkConfig.url); rpcProviders.set(networkName, provider); return provider; }; return { dependencies: { runCommand: runCheckCodeCommand, loadContractInfo: () => loadContractInfo(root, workflowConfig), loadDeploymentInfo: () => loadDeploymentInfo(root), resolveManifestDir: (networkName) => ( loadOpenZeppelinManifestDir(root, networkName) ), resolveManifestFile: async (networkName, manifestDir) => { const chainId = Number((await providerFor(networkName).getNetwork()).chainId); if (!Number.isSafeInteger(chainId) || chainId <= 0) { throw new Error(`Invalid chainId for ${networkName}: ${chainId}`); } const { networkNames } = await import("@openzeppelin/upgrades-core"); const manifestName = networkNames[chainId] ?? `unknown-${chainId}`; return path.join(root, manifestDir, `${manifestName}.json`); }, getImplementation: async (networkName, address) => ( getERC1967Implementation(providerFor(networkName), address) ), getStorageLayoutHash: async (contractName) => { const artifact = await artifacts.readArtifact(contractName); const validations = await readValidations(hre); const layout = getStorageLayout(validations, getVersion(artifact.bytecode)); return crypto.createHash("sha256") .update(JSON.stringify(normalize(layout))) .digest("hex"); }, }, dispose: () => { for (const provider of rpcProviders.values()) provider.destroy(); }, }; };