UNPKG

@nomicfoundation/hardhat-verify

Version:
157 lines 8.14 kB
import { assertHardhatInvariant, HardhatError, } from "@nomicfoundation/hardhat-errors"; import { getFullyQualifiedName, parseFullyQualifiedName, } from "hardhat/utils/contract-names"; import { getBuildInfoAndOutput } from "./artifacts.js"; import { formatInferredSolcVersion } from "./metadata.js"; /** * Resolves on-chain bytecode back to a locally compiled contract, * either by explicit FQN or by scanning all artifacts. * * Throws if: * - no build info is found; * - the compiler versions are incompatible; * - the deployed bytecode doesn’t match; * - zero or multiple matches in inference mode. */ // TODO: add tests once the todos in getBuildInfoAndOutput are resolved. export class ContractInformationResolver { #artifacts; #compatibleSolcVersions; #networkName; constructor(artifacts, compatibleSolcVersions, networkName) { this.#artifacts = artifacts; this.#compatibleSolcVersions = compatibleSolcVersions; this.#networkName = networkName; } async resolve(contract, deployedBytecode) { if (contract !== undefined) { return await this.#resolveByFqn(contract, deployedBytecode); } else { return await this.#resolveByBytecodeLookup(deployedBytecode); } } /** * Resolves a contract by its fully qualified name by comparing its compiled * build info against the on-chain bytecode. * * @param contract The fully qualified contract name (e.g. "contracts/Token.sol:Token"). * @param deployedBytecode The on-chain bytecode wrapped in a Bytecode instance. * @returns The matching ContractInformation. * @throws {HardhatError} with the descriptor: * - CONTRACT_NOT_FOUND if the artifact for the contract does not exist. * - BUILD_INFO_NOT_FOUND if no build info is found for the contract. * - BUILD_INFO_SOLC_VERSION_MISMATCH if the build info’s solc version * is incompatible with the deployed bytecode. * - DEPLOYED_BYTECODE_MISMATCH if the compiled and deployed bytecodes * do not match. */ async #resolveByFqn(contract, deployedBytecode) { const artifactExists = await this.#artifacts.artifactExists(contract); if (!artifactExists) { // TODO: we could use HardhatError.ERRORS.CORE.ARTIFACTS.NOT_FOUND // but we need to build the "suggestion" string, like in #throwNotFoundError // within the artifacts manager throw new HardhatError(HardhatError.ERRORS.HARDHAT_VERIFY.GENERAL.CONTRACT_NOT_FOUND, { contract, }); } const buildInfoAndOutput = await getBuildInfoAndOutput(this.#artifacts, contract); if (buildInfoAndOutput === undefined) { throw new HardhatError(HardhatError.ERRORS.HARDHAT_VERIFY.GENERAL.BUILD_INFO_NOT_FOUND, { contract, }); } const isSolcVersionCompatible = this.#compatibleSolcVersions.includes(buildInfoAndOutput.buildInfo.solcVersion); if (!isSolcVersionCompatible) { const formattedSolcVersion = formatInferredSolcVersion(deployedBytecode.solcVersion); const versionDetails = deployedBytecode.hasVersionRange() ? `a Solidity version in the range ${formattedSolcVersion}` : `the Solidity version ${formattedSolcVersion}`; throw new HardhatError(HardhatError.ERRORS.HARDHAT_VERIFY.GENERAL.BUILD_INFO_SOLC_VERSION_MISMATCH, { contract, buildInfoSolcVersion: buildInfoAndOutput.buildInfo.solcVersion, networkName: this.#networkName, versionDetails, }); } const contractInformation = this.#matchAndBuild(contract, buildInfoAndOutput, deployedBytecode); if (contractInformation === null) { throw new HardhatError(HardhatError.ERRORS.HARDHAT_VERIFY.GENERAL.DEPLOYED_BYTECODE_MISMATCH, { contractDescription: `the contract "${contract}"` }); } return contractInformation; } /** * Infers a contract by scanning all artifacts and matching their compiled * bytecode against the on-chain bytecode. * * @param deployedBytecode The on-chain bytecode wrapped in a Bytecode instance. * @returns The matching ContractInformation. * @throws {HardhatError} with the descriptor: * - DEPLOYED_BYTECODE_MISMATCH if no matching contracts are found. * - DEPLOYED_BYTECODE_MULTIPLE_MATCHES if more than one matching contract * is found. */ async #resolveByBytecodeLookup(deployedBytecode) { const candidates = await this.#artifacts.getAllFullyQualifiedNames(); const matches = []; for (const contract of candidates) { const buildInfoAndOutput = await getBuildInfoAndOutput(this.#artifacts, contract); if (buildInfoAndOutput === undefined) { // TODO: can this happen? should we throw an error? continue; } const isSolcVersionCompatible = this.#compatibleSolcVersions.includes(buildInfoAndOutput.buildInfo.solcVersion); if (!isSolcVersionCompatible) { continue; } const contractInformation = this.#matchAndBuild(contract, buildInfoAndOutput, deployedBytecode); if (contractInformation !== null) { matches.push(contractInformation); } } if (matches.length === 0) { throw new HardhatError(HardhatError.ERRORS.HARDHAT_VERIFY.GENERAL.DEPLOYED_BYTECODE_MISMATCH, { contractDescription: "any of your local contracts" }); } if (matches.length > 1) { const fqnList = matches.map((c) => ` * ${c.userFqn}`).join("\n"); throw new HardhatError(HardhatError.ERRORS.HARDHAT_VERIFY.GENERAL.DEPLOYED_BYTECODE_MULTIPLE_MATCHES, { fqnList }); } return matches[0]; } /** * Compares on-chain bytecode against the compiled deployedBytecode in the * build output, and assembles a ContractInformation object if they match. * * @param contract The fully qualified contract name (e.g. "src/A.sol:MyA"). * @param buildInfoAndOutput An object containing the compiler’s BuildInfo * and its Output. * @param deployedBytecode The on-chain bytecode wrapped in a Bytecode instance. * @returns A ContractInformation object when the compiled and deployed bytecodes * match, or `null` otherwise. * @throws {HardhatError} If the compiled contract output or its deployedBytecode * is missing in the build output. */ #matchAndBuild(contract, { buildInfo, buildInfoOutput }, deployedBytecode) { const { sourceName, contractName } = parseFullyQualifiedName(contract); const inputSourceName = buildInfo.userSourceNameMap[sourceName]; const compilerOutputContract = buildInfoOutput.output.contracts?.[inputSourceName]?.[contractName]; // TODO: can this happen after validating the artifact and build info? should we throw an error? assertHardhatInvariant(compilerOutputContract !== undefined, "The compiled contract output was not found in the build info."); const compilerOutputBytecode = compilerOutputContract?.evm?.deployedBytecode; // TODO: can this happen after validating the artifact and build info? should we throw an error? assertHardhatInvariant(compilerOutputBytecode !== undefined, "The deployed bytecode of the compiled contract was not found in the build info."); if (deployedBytecode.compare(compilerOutputBytecode)) { return { compilerInput: buildInfo.input, solcLongVersion: buildInfo.solcLongVersion, sourceName, userFqn: contract, inputFqn: getFullyQualifiedName(inputSourceName, contractName), compilerOutputContract, deployedBytecode: deployedBytecode.bytecode, }; } return null; } } //# sourceMappingURL=contract.js.map