Skip to content

Commit

Permalink
Merge pull request #1126 from nomiclabs/hardhat-etherscan-missing-byt…
Browse files Browse the repository at this point in the history
…ecode-message

Add error message for one case in hardhat-etherscan.
  • Loading branch information
fvictorio authored Jan 26, 2021
2 parents 5148cd2 + 5797cf1 commit e6eaaf0
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 58 deletions.
62 changes: 42 additions & 20 deletions packages/hardhat-etherscan/src/etherscan/EtherscanService.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { NomicLabsHardhatPluginError } from "hardhat/plugins";
import type { Response } from "node-fetch";

import { pluginName } from "../constants";

Expand All @@ -24,27 +25,10 @@ export async function verifyContract(
method: "post",
body: parameters,
};
try {
const response = await fetch(url, requestDetails);

if (!response.ok) {
// This could be always interpreted as JSON if there were any such guarantee in the Etherscan API.
const responseText = await response.text();
throw new NomicLabsHardhatPluginError(
pluginName,
`The HTTP server response is not ok. Status code: ${response.status} Response text: ${responseText}`
);
}

const etherscanResponse = new EtherscanResponse(await response.json());
if (!etherscanResponse.isOk()) {
throw new NomicLabsHardhatPluginError(
pluginName,
etherscanResponse.message
);
}

return etherscanResponse;
let response: Response;
try {
response = await fetch(url, requestDetails);
} catch (error) {
throw new NomicLabsHardhatPluginError(
pluginName,
Expand All @@ -54,6 +38,40 @@ Reason: ${error.message}`,
error
);
}

if (!response.ok) {
// This could be always interpreted as JSON if there were any such guarantee in the Etherscan API.
const responseText = await response.text();
throw new NomicLabsHardhatPluginError(
pluginName,
`Failed to send contract verification request.
Endpoint URL: ${url}
The HTTP server response is not ok. Status code: ${response.status} Response text: ${responseText}`
);
}

const etherscanResponse = new EtherscanResponse(await response.json());

if (etherscanResponse.isBytecodeMissingInNetworkError()) {
throw new NomicLabsHardhatPluginError(
pluginName,
`Failed to send contract verification request.
Endpoint URL: ${url}
Reason: The Etherscan API responded that the address ${req.contractaddress} does not have bytecode.
This can happen if the contract was recently deployed and this fact hasn't propagated to the backend yet.
Try waiting for a minute before verifying your contract. If you are invoking this from a script,
try to wait for five confirmations of your contract deployment transaction before running the verification subtask.`
);
}

if (!etherscanResponse.isOk()) {
throw new NomicLabsHardhatPluginError(
pluginName,
etherscanResponse.message
);
}

return etherscanResponse;
}

export async function getVerificationStatus(
Expand Down Expand Up @@ -133,6 +151,10 @@ export default class EtherscanResponse {
return this.message === "Pass - Verified";
}

public isBytecodeMissingInNetworkError() {
return this.message.startsWith("Unable to locate ContractCode at");
}

public isOk() {
return this.status === 1;
}
Expand Down
66 changes: 40 additions & 26 deletions packages/hardhat-etherscan/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ import {
toVerifyRequest,
} from "./etherscan/EtherscanVerifyContractRequest";
import {
getEtherscanEndpoint,
EtherscanURLs,
getEtherscanEndpoints,
retrieveContractBytecode,
} from "./network/prober";
import {
Expand Down Expand Up @@ -104,7 +105,7 @@ interface GetContractInformationArgs {
interface VerifyMinimumBuildArgs {
minimumBuild: Build;
contractInformation: ContractInformation;
etherscanAPIEndpoint: string;
etherscanAPIEndpoints: EtherscanURLs;
address: string;
etherscanAPIKey: string;
solcFullVersion: string;
Expand Down Expand Up @@ -178,18 +179,19 @@ See https://etherscan.io/apis`
pluginName,
`The constructorArguments parameter should be an array.
If your constructor has no arguments pass an empty array. E.g:
await run("${TASK_VERIFY_VERIFY}", {
<other args>,
constructorArguments: []
};`
await run("${TASK_VERIFY_VERIFY}", {
<other args>,
constructorArguments: []
};`
);
}

const compilerVersions: string[] = await run(
TASK_VERIFY_GET_COMPILER_VERSIONS
);

const etherscanAPIEndpoint: string = await run(
const etherscanAPIEndpoints: EtherscanURLs = await run(
TASK_VERIFY_GET_ETHERSCAN_ENDPOINT
);

Expand Down Expand Up @@ -263,7 +265,7 @@ Possible causes are:
const success: boolean = await run(TASK_VERIFY_VERIFY_MINIMUM_BUILD, {
minimumBuild,
contractInformation,
etherscanAPIEndpoint,
etherscanAPIEndpoints,
address,
etherscanAPIKey: etherscan.apiKey,
solcFullVersion,
Expand All @@ -275,7 +277,7 @@ Possible causes are:

// Fallback verification
const verificationStatus = await attemptVerification(
etherscanAPIEndpoint,
etherscanAPIEndpoints,
contractInformation,
address,
etherscan.apiKey,
Expand All @@ -285,15 +287,17 @@ Possible causes are:
);

if (verificationStatus.isVerificationSuccess()) {
const contractURL = new URL(
`/address/${address}#code`,
etherscanAPIEndpoints.browserURL
);
console.log(
`Successfully verified full build of contract ${contractInformation.contractName} on Etherscan`
`Successfully verified full build of contract ${contractInformation.contractName} on Etherscan.
${contractURL}`
);
return;
}

// TODO: Add known edge cases here.
// E.g:
// - "Unable to locate ContractCode at <address>"
let errorMessage = `The contract verification failed.
Reason: ${verificationStatus.message}`;
if (contractInformation.undetectableLibraries.length > 0) {
Expand Down Expand Up @@ -343,7 +347,8 @@ subtask(TASK_VERIFY_GET_CONSTRUCTOR_ARGUMENTS)
throw new NomicLabsHardhatPluginError(
pluginName,
`The module ${constructorArgsModulePath} doesn't export a list. The module should look like this:
module.exports = [ arg1, arg2, ... ];`
module.exports = [ arg1, arg2, ... ];`
);
}

Expand Down Expand Up @@ -380,7 +385,8 @@ subtask(TASK_VERIFY_GET_LIBRARIES)
throw new NomicLabsHardhatPluginError(
pluginName,
`The module ${librariesModulePath} doesn't export a dictionary. The module should look like this:
module.exports = { lib1: "0x...", lib2: "0x...", ... };`
module.exports = { lib1: "0x...", lib2: "0x...", ... };`
);
}

Expand All @@ -397,7 +403,7 @@ Reason: ${error.message}`,
);

async function attemptVerification(
etherscanAPIEndpoint: string,
etherscanAPIEndpoints: EtherscanURLs,
contractInformation: ContractInformation,
contractAddress: string,
etherscanAPIKey: string,
Expand All @@ -416,12 +422,13 @@ async function attemptVerification(
compilerVersion: solcFullVersion,
constructorArguments: deployArgumentsEncoded,
});
const response = await verifyContract(etherscanAPIEndpoint, request);
const response = await verifyContract(etherscanAPIEndpoints.apiURL, request);

console.log(
`Successfully submitted source code for contract
${contractInformation.sourceName}:${contractInformation.contractName} at ${contractAddress}
for verification on Etherscan. Waiting for verification result...`
for verification on Etherscan. Waiting for verification result...
`
);

const pollRequest = toCheckStatusRequest({
Expand All @@ -432,7 +439,7 @@ for verification on Etherscan. Waiting for verification result...`
// Compilation is bound to take some time so there's no sense in requesting status immediately.
await delay(700);
const verificationStatus = await getVerificationStatus(
etherscanAPIEndpoint,
etherscanAPIEndpoints.apiURL,
pollRequest
);

Expand Down Expand Up @@ -569,7 +576,7 @@ See https://etherscan.io/solcversions for more information.`
);

subtask(TASK_VERIFY_GET_ETHERSCAN_ENDPOINT).setAction(async (_, { network }) =>
getEtherscanEndpoint(network.provider, network.name)
getEtherscanEndpoints(network.provider, network.name)
);

subtask(TASK_VERIFY_GET_CONTRACT_INFORMATION)
Expand Down Expand Up @@ -685,7 +692,7 @@ Possible causes are:
subtask(TASK_VERIFY_VERIFY_MINIMUM_BUILD)
.addParam("minimumBuild", undefined, undefined, types.any)
.addParam("contractInformation", undefined, undefined, types.any)
.addParam("etherscanAPIEndpoint", undefined, undefined, types.string)
.addParam("etherscanAPIEndpoints", undefined, undefined, types.any)
.addParam("address", undefined, undefined, types.string)
.addParam("etherscanAPIKey", undefined, undefined, types.string)
.addParam("solcFullVersion", undefined, undefined, types.string)
Expand All @@ -694,7 +701,7 @@ subtask(TASK_VERIFY_VERIFY_MINIMUM_BUILD)
async ({
minimumBuild,
contractInformation,
etherscanAPIEndpoint,
etherscanAPIEndpoints,
address,
etherscanAPIKey,
solcFullVersion,
Expand All @@ -711,7 +718,7 @@ subtask(TASK_VERIFY_VERIFY_MINIMUM_BUILD)

if (minimumBuildContractBytecode === matchedBytecode) {
const minimumBuildVerificationStatus = await attemptVerification(
etherscanAPIEndpoint,
etherscanAPIEndpoints,
contractInformation,
address,
etherscanAPIKey,
Expand All @@ -721,22 +728,29 @@ subtask(TASK_VERIFY_VERIFY_MINIMUM_BUILD)
);

if (minimumBuildVerificationStatus.isVerificationSuccess()) {
const contractURL = new URL(
`/address/${address}#code`,
etherscanAPIEndpoints.browserURL
);
console.log(
`Successfully verified contract ${contractInformation.contractName} on Etherscan`
`Successfully verified contract ${contractInformation.contractName} on Etherscan.
${contractURL}`
);
return true;
}

console.log(
`We tried verifying your contract ${contractInformation.contractName} without including any unrelated one, but it failed.
Trying again with the full solc input used to compile and deploy it.
This means that unrelated contracts may be displayed on Etherscan...`
This means that unrelated contracts may be displayed on Etherscan...
`
);
} else {
console.log(
`Compiling your contract excluding unrelated contracts did not produce identical bytecode.
Trying again with the full solc input used to compile and deploy it.
This means that unrelated contracts may be displayed on Etherscan...`
This means that unrelated contracts may be displayed on Etherscan...
`
);
}

Expand Down
44 changes: 32 additions & 12 deletions packages/hardhat-etherscan/src/network/prober.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,13 @@ import { EthereumProvider } from "hardhat/types";

import { pluginName } from "../constants";

export interface EtherscanURLs {
apiURL: string;
browserURL: string;
}

type NetworkMap = {
[networkID in NetworkID]: string;
[networkID in NetworkID]: EtherscanURLs;
};

// See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md#list-of-chain-ids
Expand All @@ -19,18 +24,33 @@ enum NetworkID {
KOVAN = 42,
}

const networkIDtoEndpoint: NetworkMap = {
[NetworkID.MAINNET]: "https://api.etherscan.io/api",
[NetworkID.ROPSTEN]: "https://api-ropsten.etherscan.io/api",
[NetworkID.RINKEBY]: "https://api-rinkeby.etherscan.io/api",
[NetworkID.GOERLI]: "https://api-goerli.etherscan.io/api",
[NetworkID.KOVAN]: "https://api-kovan.etherscan.io/api",
const networkIDtoEndpoints: NetworkMap = {
[NetworkID.MAINNET]: {
apiURL: "https://api.etherscan.io/api",
browserURL: "https://etherscan.io/",
},
[NetworkID.ROPSTEN]: {
apiURL: "https://api-ropsten.etherscan.io/api",
browserURL: "https://ropsten.etherscan.io",
},
[NetworkID.RINKEBY]: {
apiURL: "https://api-rinkeby.etherscan.io/api",
browserURL: "https://rinkeby.etherscan.io",
},
[NetworkID.GOERLI]: {
apiURL: "https://api-goerli.etherscan.io/api",
browserURL: "https://goerli.etherscan.io",
},
[NetworkID.KOVAN]: {
apiURL: "https://api-kovan.etherscan.io/api",
browserURL: "https://kovan.etherscan.io",
},
};

export async function getEtherscanEndpoint(
export async function getEtherscanEndpoints(
provider: EthereumProvider,
networkName: string
): Promise<string> {
): Promise<EtherscanURLs> {
if (networkName === HARDHAT_NETWORK_NAME) {
throw new NomicLabsHardhatPluginError(
pluginName,
Expand All @@ -40,9 +60,9 @@ export async function getEtherscanEndpoint(

const chainID = parseInt(await provider.send("eth_chainId"), 16) as NetworkID;

const endpoint = networkIDtoEndpoint[chainID];
const endpoints = networkIDtoEndpoints[chainID];

if (endpoint === undefined) {
if (endpoints === undefined) {
throw new NomicLabsHardhatPluginError(
pluginName,
`An etherscan endpoint could not be found for this network. ChainID: ${chainID}. The selected network is ${networkName}.
Expand All @@ -53,7 +73,7 @@ Possible causes are:
);
}

return endpoint;
return endpoints;
}

export async function retrieveContractBytecode(
Expand Down
Loading

0 comments on commit e6eaaf0

Please sign in to comment.