From 7a241263a6fe94b1a914cc769f9b0f290bfabd39 Mon Sep 17 00:00:00 2001 From: Dimitar Bounov Date: Mon, 7 Oct 2024 22:26:18 -1000 Subject: [PATCH] Try to infer compiler versions from artifact metadata as well --- src/artifacts/helpers.ts | 57 ++++++++++++++----- src/artifacts/solc.ts | 1 + .../artifact_manager/artifact_manager.ts | 24 ++++++-- 3 files changed, 62 insertions(+), 20 deletions(-) diff --git a/src/artifacts/helpers.ts b/src/artifacts/helpers.ts index d4fe747..81d50c5 100644 --- a/src/artifacts/helpers.ts +++ b/src/artifacts/helpers.ts @@ -1,6 +1,6 @@ import { Decoder } from "cbor"; import { bytesToHex, hexToBytes } from "ethereum-cryptography/utils"; -import { assert } from "solc-typed-ast"; +import { assert, isExact } from "solc-typed-ast"; import { readInt16Be, toHexString } from ".."; import { HexString, PartialSolcOutput, UnprefixedHexString } from "./solc"; @@ -149,7 +149,7 @@ function getBytecodeHashHacky(bytecode: string | Uint8Array): ContractMdStruct | function getDeployedBytecodeMdInfo( deployedBytecode: UnprefixedHexString | Uint8Array -): ContractMdStruct { +): ContractMdStruct | undefined { const len = deployedBytecode.length; let rawMd: any = {}; @@ -174,6 +174,7 @@ function getDeployedBytecodeMdInfo( } catch { // The contract bytecode may not have metadata, which would result in random crashes in the decoder. // Catch those so we don't end up crashing in the absence of metadata. + return undefined; } const res: ContractMdStruct = {}; @@ -204,6 +205,10 @@ function getDeployedBytecodeMdInfo( export function getCodeHash(deplBytecode: UnprefixedHexString | Uint8Array): HexString | undefined { const md = getDeployedBytecodeMdInfo(deplBytecode); + if (!md) { + return undefined; + } + // TODO: Should we prefix the hash with the hash type? bzzr0/ipfs if (md.bzzr0 !== undefined) { return md.bzzr0; @@ -241,6 +246,8 @@ export function getCreationCodeHash( return undefined; } +const longVersion = /([0-9]+\.[0-9]+\.[0-9]+)\+.*/; + /** * Given a standard solc JSON output `artifact` find the compiler version used * to compute the contracts. We do this by walking over all of the bytecodes in @@ -248,25 +255,47 @@ export function getCreationCodeHash( * contract. If all contracts in the artifact agree on the version they report, * we return that. */ -export function getArtifactCompilerVersion(artifact: PartialSolcOutput): string | undefined { - let res: string | undefined; - +export function detectArtifactCompilerVersion(artifact: PartialSolcOutput): string | undefined { for (const fileName in artifact.contracts) { for (const contractName in artifact.contracts[fileName]) { - const version = getDeployedBytecodeMdInfo( - artifact.contracts[fileName][contractName].evm.deployedBytecode.object - ).solc; + const contractArtifact = artifact.contracts[fileName][contractName]; + + if (contractArtifact.evm.deployedBytecode.object.length === 0) { + continue; + } + + const md = getDeployedBytecodeMdInfo(contractArtifact.evm.deployedBytecode.object); - assert( - !(version !== undefined && res !== undefined && version !== res), - `Unexpected different compiler versions in the same artifact: ${version} and ${res}` - ); + if (md !== undefined && md.solc !== undefined) { + return md.solc; + } + + if (contractArtifact.metadata === undefined) { + continue; + } + + try { + const mdJson = JSON.parse(contractArtifact.metadata); + + if (mdJson.compiler && mdJson.compiler.version) { + if (isExact(mdJson.compiler.version)) { + return mdJson.compiler.version; + } - res = version; + const m = mdJson.compiler.version.match(longVersion); + + if (m !== null) { + return m[1]; + } + } + } catch (e) { + // Nothing to do; + console.error(e); // @todo remove + } } } - return res; + return undefined; } export function isPartialSolcOutput(arg: any): arg is PartialSolcOutput { diff --git a/src/artifacts/solc.ts b/src/artifacts/solc.ts index 694a256..94e13fb 100644 --- a/src/artifacts/solc.ts +++ b/src/artifacts/solc.ts @@ -49,6 +49,7 @@ export interface PartialCompiledContract { bytecode: PartialBytecodeDescription; deployedBytecode: PartialBytecodeDescription; }; + metadata?: string; } export interface PartialSolcOutput { diff --git a/src/debug/artifact_manager/artifact_manager.ts b/src/debug/artifact_manager/artifact_manager.ts index 71cc03b..8bbbeeb 100644 --- a/src/debug/artifact_manager/artifact_manager.ts +++ b/src/debug/artifact_manager/artifact_manager.ts @@ -22,9 +22,9 @@ import { PartialSolcOutput, RawAST, UnprefixedHexString, + detectArtifactCompilerVersion, fastParseBytecodeSourceMapping, findContractDef, - getArtifactCompilerVersion, getCodeHash, getCreationCodeHash } from "../.."; @@ -158,18 +158,30 @@ export class ArtifactManager implements IArtifactManager { return ABIEncoderVersion.V1; } - constructor(artifacts: PartialSolcOutput[]) { + constructor(artifacts: Array) { this._artifacts = []; this._contracts = []; this._mdHashToContractInfo = new Map(); this._creationBytecodeTemplates = []; this._deployedBytecodeTemplates = []; - for (const artifact of artifacts) { + for (const arg of artifacts) { const reader = new ASTReader(); - const compilerVersion = getArtifactCompilerVersion(artifact); - - assert(compilerVersion !== undefined, `Couldn't find compiler version for artifact`); + let artifact: PartialSolcOutput; + let compilerVersion: string; + + if (arg instanceof Array) { + [artifact, compilerVersion] = arg; + } else { + artifact = arg; + const maybeCompilerVersion = detectArtifactCompilerVersion(artifact); + assert( + maybeCompilerVersion !== undefined, + `Couldn't find compiler version for artifact` + ); + + compilerVersion = maybeCompilerVersion; + } const units = reader.read(artifact); const abiEncoderVersion = this.pickABIEncoderVersion(units, compilerVersion);