From 5ea531157ab8632e29d63912cfc2f87ebab6eb46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Claudio=20Nale?= Date: Wed, 23 Sep 2020 14:53:44 -0300 Subject: [PATCH 1/5] Update buidler-etherscan to support multi-solc pipeline. --- packages/buidler-etherscan/package.json | 3 +- packages/buidler-etherscan/src/index.ts | 48 ++- .../buidler-etherscan/src/solc/bytecode.ts | 279 +++++++++++------- .../hardhat.config.js | 2 +- .../hardhat.config.js | 2 +- .../test/hardhat-project/hardhat.config.js | 2 +- .../test/integration/PluginTests.ts | 2 +- 7 files changed, 201 insertions(+), 137 deletions(-) diff --git a/packages/buidler-etherscan/package.json b/packages/buidler-etherscan/package.json index 2ae29e2765..a10cb00d06 100644 --- a/packages/buidler-etherscan/package.json +++ b/packages/buidler-etherscan/package.json @@ -39,7 +39,7 @@ "@ethersproject/abi": "^5.0.2", "@ethersproject/address": "^5.0.2", "cbor": "^5.0.2", - "ethereumjs-abi": "^0.6.8", + "fs-extra": "^7.0.1", "node-fetch": "^2.6.0", "semver": "^6.3.0" }, @@ -47,6 +47,7 @@ "@nomiclabs/hardhat-ethers": "^2.0.0", "@types/cbor": "^5.0.1", "@types/chai": "^4.2.0", + "@types/fs-extra": "^5.1.0", "@types/nock": "^9.3.1", "@types/node-fetch": "^2.3.7", "@types/semver": "^6.0.2", diff --git a/packages/buidler-etherscan/src/index.ts b/packages/buidler-etherscan/src/index.ts index 4b19cd2643..9e51cd4a08 100644 --- a/packages/buidler-etherscan/src/index.ts +++ b/packages/buidler-etherscan/src/index.ts @@ -60,12 +60,18 @@ See https://etherscan.io/apis` // TODO: perhaps querying and scraping this list would be a better approach? // This list should be validated - it links to https://github.com/ethereum/solc-bin/blob/gh-pages/bin/list.txt // which has many old compilers included in the list too. - // TODO-HH: handle multiple compilers + const configuredVersions = config.solidity.compilers.map(({ version }) => { + return version; + }); + if (config.solidity.overrides !== undefined) { + for (const { version } of Object.values(config.solidity.overrides)) { + configuredVersions.push(version); + } + } if ( - !semver.satisfies( - config.solidity.compilers[0].version, - supportedSolcVersionRange - ) + configuredVersions.some((version) => { + return !semver.satisfies(version, supportedSolcVersionRange); + }) ) { throw new NomicLabsHardhatPluginError( pluginName, @@ -143,23 +149,19 @@ The selected network is ${network.name}.` const bytecodeBuffer = Buffer.from(deployedContractBytecode, "hex"); const inferredSolcVersion = await inferSolcVersion(bytecodeBuffer); - // TODO-HH: handle multiple compilers - if ( - !semver.satisfies( - config.solidity.compilers[0].version, - inferredSolcVersion.range - ) - ) { + const matchingVersions = configuredVersions.filter((version) => { + return semver.satisfies(version, inferredSolcVersion.range); + }); + if (matchingVersions.length === 0) { let detailedContext; if (inferredSolcVersion.inferralType === InferralType.EXACT) { detailedContext = `The expected version is ${inferredSolcVersion.range}.`; } else { detailedContext = `The expected version range is ${inferredSolcVersion.range}.`; } - // TODO-HH: handle multiple compilers - const message = `The bytecode retrieved could not have been generated by the selected compiler. -The selected compiler version is v${config.solidity.compilers[0].version}. + const message = `The bytecode retrieved could not have been generated by any of the selected compilers. ${detailedContext} +The selected compiler versions are: ${configuredVersions.join(", ")}. Possible causes are: - Wrong compiler version in hardhat config. @@ -169,12 +171,10 @@ Possible causes are: } const { lookupMatchingBytecode, compile } = await import("./solc/bytecode"); - // TODO: this gives us the input for all contracts. - // This could be restricted to relevant contracts in a future iteration of the compiler tasks. - const { compilerInput, compilerOutput } = await compile(run); + const builds = await compile(run, matchingVersions, config.paths.artifacts); const contractMatches = await lookupMatchingBytecode( - compilerOutput.contracts, + builds, deployedContractBytecode, inferredSolcVersion.inferralType ); @@ -211,13 +211,11 @@ ${nameList}`; ); // Ensure the linking information is present in the compiler input; - compilerInput.settings.libraries = contractInformation.libraryLinks; - const compilerInputJSON = JSON.stringify(compilerInput); + contractInformation.compilerInput.settings.libraries = + contractInformation.libraryLinks; + const compilerInputJSON = JSON.stringify(contractInformation.compilerInput); - // TODO-HH: handle multiple compilers - const solcFullVersion = await getLongVersion( - config.solidity.compilers[0].version - ); + const solcFullVersion = await getLongVersion(contractInformation.solcVersion); const { toVerifyRequest, toCheckStatusRequest } = await import( "./etherscan/EtherscanVerifyContractRequest" diff --git a/packages/buidler-etherscan/src/solc/bytecode.ts b/packages/buidler-etherscan/src/solc/bytecode.ts index 95f6bf7575..b3978df8a7 100644 --- a/packages/buidler-etherscan/src/solc/bytecode.ts +++ b/packages/buidler-etherscan/src/solc/bytecode.ts @@ -1,59 +1,155 @@ +import fs from "fs-extra"; +import { Artifacts } from "hardhat/plugins"; import { RunTaskFunction } from "hardhat/types"; +import path from "path"; import { METADATA_LENGTH_SIZE, readSolcMetadataLength } from "./metadata"; import { InferralType } from "./version"; +type BytecodeComparison = + | { match: false } + | { match: true; contractInformation: BytecodeExtractedData }; + +interface BytecodeExtractedData { + immutableValues: ImmutableValues; + libraryLinks: ResolvedLinks; + normalizedBytecode: string; +} + +interface ResolvedLinks { + [sourceName: string]: { + [libraryName: string]: string; + }; +} + +interface ImmutableValues { + [key: string]: string; +} + +interface BytecodeSlice { + start: number; + length: number; +} + +type LinkReferences = CompilerOutputBytecode["linkReferences"][string][string]; +type NestedSliceReferences = BytecodeSlice[][]; + +/* Taken from stack trace hardhat network internals + * This is not an exhaustive interface for compiler input nor output. + */ + +interface CompilerInput { + language: "Solidity"; + sources: { [sourceName: string]: { content: string } }; + settings: { + optimizer: { runs: number; enabled: boolean }; + evmVersion?: string; + libraries?: ResolvedLinks; + }; +} + +interface CompilerOutput { + sources: CompilerOutputSources; + contracts: { + [sourceName: string]: { + [contractName: string]: { + abi: any; + evm: { + bytecode: CompilerOutputBytecode; + deployedBytecode: CompilerOutputBytecode; + methodIdentifiers: { + [methodSignature: string]: string; + }; + }; + }; + }; + }; +} + +interface CompilerOutputSource { + id: number; + ast: any; +} + +interface CompilerOutputSources { + [sourceName: string]: CompilerOutputSource; +} + +interface CompilerOutputBytecode { + object: string; + opcodes: string; + sourceMap: string; + linkReferences: { + [sourceName: string]: { + [libraryName: string]: Array<{ start: 0; length: 20 }>; + }; + }; + immutableReferences?: { + [key: string]: Array<{ start: number; length: number }>; + }; +} + +interface BuildInfo { + input: CompilerInput; + output: CompilerOutput; + solcVersion: string; +} + +interface ContractBuildInfo extends BuildInfo { + contractName: string; + contractFilename: string; +} + +/**/ + export async function lookupMatchingBytecode( - contractFiles: CompilerOutput["contracts"], + contractBuilds: ContractBuildInfo[], deployedBytecode: string, inferralType: InferralType ) { const contractMatches = []; - for (const [contractFilename, contracts] of Object.entries(contractFiles)) { - for (const [contractName, contract] of Object.entries(contracts)) { - // Normalize deployed bytecode according to this contract. - const { deployedBytecode: runtimeBytecodeSymbols } = contract.evm; - - const comparison = await compareBytecode( - deployedBytecode, - runtimeBytecodeSymbols, - inferralType - ); - - if (comparison.match) { - const { - contractInformation: { - immutableValues, - libraryLinks, - normalizedBytecode, - }, - } = comparison; - // The bytecode matches - contractMatches.push({ + for (const { + contractName, + contractFilename, + input, + output, + solcVersion, + } of contractBuilds) { + const contract = output.contracts[contractFilename][contractName]; + // Normalize deployed bytecode according to this contract. + const { deployedBytecode: runtimeBytecodeSymbols } = contract.evm; + + const comparison = await compareBytecode( + deployedBytecode, + runtimeBytecodeSymbols, + inferralType + ); + + if (comparison.match) { + const { + contractInformation: { immutableValues, libraryLinks, normalizedBytecode, - contractFilename, - contractName, - contract, - }); - } + }, + } = comparison; + // The bytecode matches + contractMatches.push({ + compilerInput: input, + solcVersion, + immutableValues, + libraryLinks, + normalizedBytecode, + contractFilename, + contractName, + contract, + }); } } return contractMatches; } -type BytecodeComparison = - | { match: false } - | { match: true; contractInformation: BytecodeExtractedData }; - -interface BytecodeExtractedData { - immutableValues: ImmutableValues; - libraryLinks: ResolvedLinks; - normalizedBytecode: string; -} - export async function compareBytecode( deployedBytecode: string, runtimeBytecodeSymbols: CompilerOutputBytecode, @@ -125,24 +221,6 @@ export async function compareBytecode( return { match: false }; } -interface ResolvedLinks { - [sourceName: string]: { - [libraryName: string]: string; - }; -} - -interface ImmutableValues { - [key: string]: string; -} - -interface BytecodeSlice { - start: number; - length: number; -} - -type LinkReferences = CompilerOutputBytecode["linkReferences"][string][string]; -type NestedSliceReferences = BytecodeSlice[][]; - export async function normalizeBytecode( bytecode: string, symbols: CompilerOutputBytecode @@ -224,61 +302,48 @@ export function zeroOutSlices( return code; } -/* Taken from stack trace hardhat internals - * This is not an exhaustive interface for compiler input nor output. - */ +export async function compile( + taskRun: RunTaskFunction, + matchingVersions: string[], + artifactsPath: string +) { + const { TASK_COMPILE } = await import("hardhat/builtin-tasks/task-names"); -export interface CompilerInput { - language: "Solidity"; - sources: { [sourceName: string]: { content: string } }; - settings: { - optimizer: { runs: number; enabled: boolean }; - evmVersion?: string; - libraries?: ResolvedLinks; - }; -} + await taskRun(TASK_COMPILE); -export interface CompilerOutput { - contracts: { - [sourceName: string]: { - [contractName: string]: { - abi: any; - evm: { - bytecode: CompilerOutputBytecode; - deployedBytecode: CompilerOutputBytecode; - }; - }; - }; - }; -} + const artifacts = new Artifacts(artifactsPath); + const buildInfoFiles = await artifacts.getBuildInfoFiles(); -export interface CompilerOutputBytecode { - object: string; - linkReferences: { - [sourceName: string]: { - [libraryName: string]: Array<{ start: number; length: 20 }>; - }; - }; - immutableReferences?: { - [key: string]: Array<{ start: number; length: number }>; - }; -} + // TODO: Here would be an ideal place to separate builds into several compilation jobs + // to address https://github.com/nomiclabs/buidler/issues/804 if possible. + const builds: { [buildHash: string]: BuildInfo } = {}; + for (const buildInfoFile of buildInfoFiles) { + const buildInfo: BuildInfo = await fs.readJSON(buildInfoFile); + if (matchingVersions.includes(buildInfo.solcVersion)) { + builds[path.basename(buildInfoFile)] = buildInfo; + } + } -/**/ + const contracts = []; + const artifactFiles = await artifacts.getArtifacts(); + for (const artifactFile of artifactFiles) { + const contractName = path.basename(artifactFile.replace(".json", "")); + const contractFilename = path.relative( + artifactsPath, + path.dirname(artifactFile) + ); + const dbgFile = path.join( + artifactsPath, + contractFilename, + `${contractName}.dbg.json` + ); + const dbgInfo: { buildInfo: string } = await fs.readJSON(dbgFile); + contracts.push({ + contractName, + contractFilename, + ...builds[path.basename(dbgInfo.buildInfo)], + }); + } -// TODO: This is extremely ugly and should be replaced with better build primitives when possible. -// Ideally, we would access the input and output through some sort of artifact. -export async function compile(taskRun: RunTaskFunction) { - const { - TASK_COMPILE_SOLIDITY_COMPILE, - TASK_COMPILE_SOLIDITY_GET_COMPILER_INPUT, - } = await import("hardhat/builtin-tasks/task-names"); - - const compilerInput = (await taskRun( - TASK_COMPILE_SOLIDITY_GET_COMPILER_INPUT - )) as CompilerInput; - const compilerOutput = (await taskRun( - TASK_COMPILE_SOLIDITY_COMPILE - )) as CompilerOutput; - return { compilerInput, compilerOutput }; + return contracts; } diff --git a/packages/buidler-etherscan/test/hardhat-project-defined-config/hardhat.config.js b/packages/buidler-etherscan/test/hardhat-project-defined-config/hardhat.config.js index a35c698209..64b11905b6 100644 --- a/packages/buidler-etherscan/test/hardhat-project-defined-config/hardhat.config.js +++ b/packages/buidler-etherscan/test/hardhat-project-defined-config/hardhat.config.js @@ -6,7 +6,7 @@ module.exports = { etherscan: { apiKey: "testtoken", }, - solc: { + solidity: { version: "0.5.15", }, }; diff --git a/packages/buidler-etherscan/test/hardhat-project-undefined-config/hardhat.config.js b/packages/buidler-etherscan/test/hardhat-project-undefined-config/hardhat.config.js index 0b9c1c1a06..e1160d8e39 100644 --- a/packages/buidler-etherscan/test/hardhat-project-undefined-config/hardhat.config.js +++ b/packages/buidler-etherscan/test/hardhat-project-undefined-config/hardhat.config.js @@ -3,7 +3,7 @@ const { loadPluginFile } = require("hardhat/plugins-testing"); loadPluginFile(__dirname + "/../../src/index"); module.exports = { - solc: { + solidity: { version: "0.5.15", }, }; diff --git a/packages/buidler-etherscan/test/hardhat-project/hardhat.config.js b/packages/buidler-etherscan/test/hardhat-project/hardhat.config.js index 12a3df5644..5247c680cc 100644 --- a/packages/buidler-etherscan/test/hardhat-project/hardhat.config.js +++ b/packages/buidler-etherscan/test/hardhat-project/hardhat.config.js @@ -8,7 +8,7 @@ module.exports = { etherscan: { apiKey: process.env.ETHERSCAN_API_KEY, }, - solc: { + solidity: { version: "0.5.15", }, networks: { diff --git a/packages/buidler-etherscan/test/integration/PluginTests.ts b/packages/buidler-etherscan/test/integration/PluginTests.ts index 36e414b505..9b7d92b04a 100644 --- a/packages/buidler-etherscan/test/integration/PluginTests.ts +++ b/packages/buidler-etherscan/test/integration/PluginTests.ts @@ -165,6 +165,6 @@ async function deployContract( const factory = await ethers.getContractFactory(contractName, wallet); const contract = await factory.deploy(...constructorArguments); - await contract.deployTransaction.wait(3); + await contract.deployTransaction.wait(4); return contract.address; } From b3add10023d5beccdb7ab19298bb7bee2f03aede Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Claudio=20Nale?= Date: Wed, 23 Sep 2020 16:07:35 -0300 Subject: [PATCH 2/5] Update bytecode lookup unit tests. --- .../test/unit/solc/bytecode.ts | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/packages/buidler-etherscan/test/unit/solc/bytecode.ts b/packages/buidler-etherscan/test/unit/solc/bytecode.ts index cc9b2f1da4..6cab2f6586 100644 --- a/packages/buidler-etherscan/test/unit/solc/bytecode.ts +++ b/packages/buidler-etherscan/test/unit/solc/bytecode.ts @@ -24,6 +24,8 @@ describe("Compiler bytecode and deployed bytecode matching", () => { const contractSymbols = { object: contract.runtimeBytecode.slice(2), linkReferences: contract.linkReferences, + opcodes: "", + sourceMap: "", // immutableReferences: contract.immutableReferences, }; const deployedBytecode = contract.deployedBytecode.slice(2); @@ -50,6 +52,8 @@ describe("Compiler bytecode and deployed bytecode matching", () => { const contractSymbols = { object: contract.runtimeBytecode.slice(2), linkReferences: contract.linkReferences, + opcodes: "", + sourceMap: "", immutableReferences: contract.immutableReferences, }; const deployedBytecode = contract.deployedBytecode.slice(2); @@ -101,6 +105,8 @@ describe("Compiler bytecode and deployed bytecode matching", () => { const contractSymbols = { object: bytecodeWithNewMetadata, linkReferences: contract.linkReferences, + opcodes: "", + sourceMap: "", immutableReferences: contract.immutableReferences, }; @@ -128,6 +134,8 @@ describe("Compiler bytecode and deployed bytecode matching", () => { const contractSymbols = { object: contract.runtimeBytecode.slice(2), linkReferences: contract.linkReferences, + opcodes: "", + sourceMap: "", immutableReferences: contract.immutableReferences, }; const deployedBytecode = contract.deployedBytecode.slice(2); @@ -159,6 +167,8 @@ describe("Compiler bytecode and deployed bytecode matching", () => { const contractSymbols = { object: contract.runtimeBytecode.slice(2), linkReferences: contract.linkReferences, + opcodes: "", + sourceMap: "", // immutableReferences: contract.immutableReferences, }; const deployedBytecode = contract.deployedBytecode.slice(2); @@ -188,6 +198,8 @@ describe("Compiler bytecode and deployed bytecode matching", () => { const contractSymbols = { object: contract.runtimeBytecode.slice(2), linkReferences: contract.linkReferences, + opcodes: "", + sourceMap: "", // immutableReferences: contract.immutableReferences, }; const deployedBytecode = contract.deployedBytecode.slice(2); @@ -218,6 +230,8 @@ describe("Compiler bytecode and deployed bytecode matching", () => { const contractSymbols = { object: contract.runtimeBytecode.slice(2), linkReferences: contract.linkReferences, + opcodes: "", + sourceMap: "", immutableReferences: contract.immutableReferences, }; const deployedBytecode = contract.deployedBytecode.slice(2); @@ -246,6 +260,8 @@ describe("Compiler bytecode and deployed bytecode matching", () => { const contractSymbols = { object: contract.runtimeBytecode.slice(2), linkReferences: contract.linkReferences, + opcodes: "", + sourceMap: "", immutableReferences: contract.immutableReferences, }; const deployedBytecode = contract.deployedBytecode.slice(2); @@ -272,6 +288,8 @@ describe("Compiler bytecode and deployed bytecode matching", () => { const contractSymbols = { object: contract.runtimeBytecode.slice(2), linkReferences: contract.linkReferences, + opcodes: "", + sourceMap: "", immutableReferences: contract.immutableReferences, }; const deployedBytecode = contract.deployedBytecode.slice(2); @@ -300,6 +318,8 @@ describe("Compiler bytecode and deployed bytecode matching", () => { const contractSymbols = { object: contract.runtimeBytecode.slice(2), linkReferences: contract.linkReferences, + opcodes: "", + sourceMap: "", immutableReferences: contract.immutableReferences, }; const deployedBytecode = contract.deployedBytecode.slice(2); From a14c018fa3126ae4f4558f91ec74a2e3a0fa6151 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Claudio=20Nale?= Date: Wed, 23 Sep 2020 17:36:43 -0300 Subject: [PATCH 3/5] Rename a few variables. Adjust some types too. --- packages/buidler-etherscan/src/ABIEncoder.ts | 4 +-- .../EtherscanVerifyContractRequest.ts | 4 +-- packages/buidler-etherscan/src/index.ts | 12 +++---- .../buidler-etherscan/src/solc/bytecode.ts | 34 +++++++------------ .../buidler-etherscan/test/unit/ABIEncoder.ts | 18 +++++----- 5 files changed, 31 insertions(+), 41 deletions(-) diff --git a/packages/buidler-etherscan/src/ABIEncoder.ts b/packages/buidler-etherscan/src/ABIEncoder.ts index b189fbd722..3e91e7e15a 100644 --- a/packages/buidler-etherscan/src/ABIEncoder.ts +++ b/packages/buidler-etherscan/src/ABIEncoder.ts @@ -4,7 +4,7 @@ import { pluginName } from "./pluginContext"; export async function encodeArguments( abi: any, - contractFilename: string, + sourceName: string, contractName: string, constructorArguments: any[] ) { @@ -24,7 +24,7 @@ export async function encodeArguments( } = await import("./ABITypes"); if (isABIArgumentLengthError(error)) { // TODO: add a list of types and constructor arguments to the error message? - const message = `The constructor for ${contractFilename}:${contractName} has ${error.count.types} parameters + const message = `The constructor for ${sourceName}:${contractName} has ${error.count.types} parameters but ${error.count.values} arguments were provided instead.`; throw new NomicLabsHardhatPluginError(pluginName, message, error); } diff --git a/packages/buidler-etherscan/src/etherscan/EtherscanVerifyContractRequest.ts b/packages/buidler-etherscan/src/etherscan/EtherscanVerifyContractRequest.ts index 44ed43c9f1..7bbf1560d1 100644 --- a/packages/buidler-etherscan/src/etherscan/EtherscanVerifyContractRequest.ts +++ b/packages/buidler-etherscan/src/etherscan/EtherscanVerifyContractRequest.ts @@ -25,7 +25,7 @@ export function toVerifyRequest(params: { apiKey: string; contractAddress: string; sourceCode: string; - contractFilename: string; + sourceName: string; contractName: string; compilerVersion: string; constructorArguments: string; @@ -37,7 +37,7 @@ export function toVerifyRequest(params: { contractaddress: params.contractAddress, sourceCode: params.sourceCode, codeformat: "solidity-standard-json-input", - contractname: `${params.contractFilename}:${params.contractName}`, + contractname: `${params.sourceName}:${params.contractName}`, compilerversion: params.compilerVersion, constructorArguements: params.constructorArguments, }; diff --git a/packages/buidler-etherscan/src/index.ts b/packages/buidler-etherscan/src/index.ts index 9e51cd4a08..3ab48beddb 100644 --- a/packages/buidler-etherscan/src/index.ts +++ b/packages/buidler-etherscan/src/index.ts @@ -60,9 +60,7 @@ See https://etherscan.io/apis` // TODO: perhaps querying and scraping this list would be a better approach? // This list should be validated - it links to https://github.com/ethereum/solc-bin/blob/gh-pages/bin/list.txt // which has many old compilers included in the list too. - const configuredVersions = config.solidity.compilers.map(({ version }) => { - return version; - }); + const configuredVersions = config.solidity.compilers.map((c) => c.version); if (config.solidity.overrides !== undefined) { for (const { version } of Object.values(config.solidity.overrides)) { configuredVersions.push(version); @@ -192,7 +190,7 @@ Possible causes are: if (contractMatches.length > 1) { const nameList = contractMatches .map((contract) => { - return `${contract.contractFilename}:${contract.contractName}`; + return `${contract.sourceName}:${contract.contractName}`; }) .join(", "); const message = `More than one contract was found to match the deployed bytecode. @@ -205,7 +203,7 @@ ${nameList}`; const { encodeArguments } = await import("./ABIEncoder"); const deployArgumentsEncoded = await encodeArguments( contractInformation.contract.abi, - contractInformation.contractFilename, + contractInformation.sourceName, contractInformation.contractName, constructorArguments ); @@ -224,7 +222,7 @@ ${nameList}`; apiKey: etherscan.apiKey, contractAddress: address, sourceCode: compilerInputJSON, - contractFilename: contractInformation.contractFilename, + sourceName: contractInformation.sourceName, contractName: contractInformation.contractName, compilerVersion: solcFullVersion, constructorArguments: deployArgumentsEncoded, @@ -237,7 +235,7 @@ ${nameList}`; console.log( `Successfully submitted source code for contract -${contractInformation.contractFilename}:${contractInformation.contractName} at ${address} +${contractInformation.sourceName}:${contractInformation.contractName} at ${address} for verification on etherscan. Waiting for verification result...` ); diff --git a/packages/buidler-etherscan/src/solc/bytecode.ts b/packages/buidler-etherscan/src/solc/bytecode.ts index b3978df8a7..444335674d 100644 --- a/packages/buidler-etherscan/src/solc/bytecode.ts +++ b/packages/buidler-etherscan/src/solc/bytecode.ts @@ -95,9 +95,10 @@ interface BuildInfo { solcVersion: string; } -interface ContractBuildInfo extends BuildInfo { +interface ContractBuildInfo { contractName: string; - contractFilename: string; + sourceName: string; + buildInfo: BuildInfo; } /**/ @@ -108,14 +109,8 @@ export async function lookupMatchingBytecode( inferralType: InferralType ) { const contractMatches = []; - for (const { - contractName, - contractFilename, - input, - output, - solcVersion, - } of contractBuilds) { - const contract = output.contracts[contractFilename][contractName]; + for (const { contractName, sourceName, buildInfo } of contractBuilds) { + const contract = buildInfo.output.contracts[sourceName][contractName]; // Normalize deployed bytecode according to this contract. const { deployedBytecode: runtimeBytecodeSymbols } = contract.evm; @@ -135,12 +130,12 @@ export async function lookupMatchingBytecode( } = comparison; // The bytecode matches contractMatches.push({ - compilerInput: input, - solcVersion, + compilerInput: buildInfo.input, + solcVersion: buildInfo.solcVersion, immutableValues, libraryLinks, normalizedBytecode, - contractFilename, + sourceName, contractName, contract, }); @@ -306,7 +301,7 @@ export async function compile( taskRun: RunTaskFunction, matchingVersions: string[], artifactsPath: string -) { +): Promise { const { TASK_COMPILE } = await import("hardhat/builtin-tasks/task-names"); await taskRun(TASK_COMPILE); @@ -328,20 +323,17 @@ export async function compile( const artifactFiles = await artifacts.getArtifacts(); for (const artifactFile of artifactFiles) { const contractName = path.basename(artifactFile.replace(".json", "")); - const contractFilename = path.relative( - artifactsPath, - path.dirname(artifactFile) - ); + const sourceName = path.relative(artifactsPath, path.dirname(artifactFile)); const dbgFile = path.join( artifactsPath, - contractFilename, + sourceName, `${contractName}.dbg.json` ); const dbgInfo: { buildInfo: string } = await fs.readJSON(dbgFile); contracts.push({ contractName, - contractFilename, - ...builds[path.basename(dbgInfo.buildInfo)], + sourceName, + buildInfo: builds[path.basename(dbgInfo.buildInfo)], }); } diff --git a/packages/buidler-etherscan/test/unit/ABIEncoder.ts b/packages/buidler-etherscan/test/unit/ABIEncoder.ts index b85576d4a8..9aa17b24ea 100644 --- a/packages/buidler-etherscan/test/unit/ABIEncoder.ts +++ b/packages/buidler-etherscan/test/unit/ABIEncoder.ts @@ -4,7 +4,7 @@ import { NomicLabsHardhatPluginError } from "hardhat/plugins"; import { encodeArguments } from "../../src/ABIEncoder"; describe("constructor argument validation tests", () => { - const contractFilename = "TheContract.sol"; + const sourceName = "TheContract.sol"; const contractName = "TheContract"; it("should validate empty argument list", async () => { @@ -18,7 +18,7 @@ describe("constructor argument validation tests", () => { const constructorArguments: any[] = []; const encodedArguments = await encodeArguments( abi, - contractFilename, + sourceName, contractName, constructorArguments ); @@ -41,7 +41,7 @@ describe("constructor argument validation tests", () => { const constructorArguments: any[] = [50]; const encodedArguments = await encodeArguments( abi, - contractFilename, + sourceName, contractName, constructorArguments ); @@ -71,7 +71,7 @@ describe("constructor argument validation tests", () => { const encodedArguments = await encodeArguments( abi, - contractFilename, + sourceName, contractName, constructorArguments ).catch((reason) => { @@ -99,7 +99,7 @@ describe("constructor argument validation tests", () => { const constructorArguments: any[] = [50]; return encodeArguments( abi, - contractFilename, + sourceName, contractName, constructorArguments ).catch((reason) => { @@ -123,7 +123,7 @@ describe("constructor argument validation tests", () => { const constructorArguments: any[] = [50, 100]; return encodeArguments( abi, - contractFilename, + sourceName, contractName, constructorArguments ).catch((reason) => { @@ -159,7 +159,7 @@ describe("constructor argument validation tests", () => { ]; const encodedArguments = await encodeArguments( abi, - contractFilename, + sourceName, contractName, constructorArguments ); @@ -201,7 +201,7 @@ describe("constructor argument validation tests", () => { ]; return encodeArguments( abi, - contractFilename, + sourceName, contractName, constructorArguments ).catch((reason) => { @@ -258,7 +258,7 @@ describe("constructor argument validation tests", () => { ]; const encodedArguments = await encodeArguments( abi, - contractFilename, + sourceName, contractName, constructorArguments ); From d183caebd38e404608b0cbc75eaf14eceece408e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Claudio=20Nale?= Date: Wed, 23 Sep 2020 17:50:08 -0300 Subject: [PATCH 4/5] Add special case for multiple compilers in error message. --- packages/buidler-etherscan/src/index.ts | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/packages/buidler-etherscan/src/index.ts b/packages/buidler-etherscan/src/index.ts index 3ab48beddb..6caef9a8f6 100644 --- a/packages/buidler-etherscan/src/index.ts +++ b/packages/buidler-etherscan/src/index.ts @@ -151,18 +151,31 @@ The selected network is ${network.name}.` return semver.satisfies(version, inferredSolcVersion.range); }); if (matchingVersions.length === 0) { - let detailedContext; + const detailedContext = []; if (inferredSolcVersion.inferralType === InferralType.EXACT) { - detailedContext = `The expected version is ${inferredSolcVersion.range}.`; + detailedContext.push( + `The expected version is ${inferredSolcVersion.range}.` + ); + } else { + detailedContext.push( + `The expected version range is ${inferredSolcVersion.range}.` + ); + } + // There is always at least one configured version. + if (configuredVersions.length > 1) { + detailedContext.push( + `The selected compiler versions are: ${configuredVersions.join(", ")}` + ); } else { - detailedContext = `The expected version range is ${inferredSolcVersion.range}.`; + detailedContext.push( + `The selected compiler version is: ${configuredVersions[0]}` + ); } const message = `The bytecode retrieved could not have been generated by any of the selected compilers. -${detailedContext} -The selected compiler versions are: ${configuredVersions.join(", ")}. +${detailedContext.join("\n")} Possible causes are: - - Wrong compiler version in hardhat config. + - Wrong compiler version selected in hardhat config. - The given address is wrong. - The selected network (${network.name}) is wrong.`; throw new NomicLabsHardhatPluginError(pluginName, message); From 090f8cbf1c2501dd85a2f557328029657e1c7e02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Claudio=20Nale?= Date: Thu, 24 Sep 2020 14:26:25 -0300 Subject: [PATCH 5/5] Add an error message for constructor only libraries. These don't leave an address in deployed bytecode and thus cannot be detected. --- packages/buidler-etherscan/src/index.ts | 14 ++++++++++++++ packages/buidler-etherscan/src/solc/bytecode.ts | 10 ++++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/packages/buidler-etherscan/src/index.ts b/packages/buidler-etherscan/src/index.ts index 6caef9a8f6..c726a59ff8 100644 --- a/packages/buidler-etherscan/src/index.ts +++ b/packages/buidler-etherscan/src/index.ts @@ -213,6 +213,20 @@ ${nameList}`; } const [contractInformation] = contractMatches; + const libraryLinks = contractInformation.libraryLinks; + const deployLibraryReferences = + contractInformation.contract.evm.bytecode.linkReferences; + if ( + Object.keys(libraryLinks).length < + Object.keys(deployLibraryReferences).length + ) { + throw new NomicLabsHardhatPluginError( + pluginName, + `The contract ${contractInformation.sourceName}:${contractInformation.contractName} has one or more library references that cannot be detected from deployed bytecode. +This can occur if the library is only called in the contract constructor.` + ); + } + const { encodeArguments } = await import("./ABIEncoder"); const deployArgumentsEncoded = await encodeArguments( contractInformation.contract.abi, diff --git a/packages/buidler-etherscan/src/solc/bytecode.ts b/packages/buidler-etherscan/src/solc/bytecode.ts index 444335674d..7288162662 100644 --- a/packages/buidler-etherscan/src/solc/bytecode.ts +++ b/packages/buidler-etherscan/src/solc/bytecode.ts @@ -222,7 +222,9 @@ export async function normalizeBytecode( ) { const nestedSliceReferences: NestedSliceReferences = []; const libraryLinks: ResolvedLinks = {}; - for (const [filename, libraries] of Object.entries(symbols.linkReferences)) { + for (const [sourceName, libraries] of Object.entries( + symbols.linkReferences + )) { for (const [libraryName, linkReferences] of Object.entries(libraries)) { // Is this even a possibility? if (linkReferences.length === 0) { @@ -230,11 +232,11 @@ export async function normalizeBytecode( } const { start, length } = linkReferences[0]; - if (libraryLinks[filename] === undefined) { - libraryLinks[filename] = {}; + if (libraryLinks[sourceName] === undefined) { + libraryLinks[sourceName] = {}; } // We have the bytecode encoded as a hex string - libraryLinks[filename][libraryName] = `0x${bytecode.slice( + libraryLinks[sourceName][libraryName] = `0x${bytecode.slice( start * 2, (start + length) * 2 )}`;