Skip to content

Commit

Permalink
Merge pull request #5507 from rimrakhimov/rimrakhimov/blockscout-plugin
Browse files Browse the repository at this point in the history
Feature: blockscout plugin support
  • Loading branch information
schaable authored Sep 11, 2024
2 parents ec5d587 + 510c80a commit 963f91f
Show file tree
Hide file tree
Showing 14 changed files with 491 additions and 7 deletions.
5 changes: 5 additions & 0 deletions .changeset/khaki-drinks-work.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nomicfoundation/hardhat-verify": patch
---

Added Blockscout as a verification provider
1 change: 1 addition & 0 deletions packages/hardhat-verify/src/blockscout.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { Blockscout } from "./internal/blockscout";
17 changes: 16 additions & 1 deletion packages/hardhat-verify/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ import {
TASK_VERIFY_SOURCIFY,
TASK_VERIFY_SOURCIFY_DISABLED_WARNING,
TASK_VERIFY_GET_CONTRACT_INFORMATION,
TASK_VERIFY_BLOCKSCOUT,
} from "./internal/task-names";
import {
etherscanConfigExtender,
sourcifyConfigExtender,
blockscoutConfigExtender,
} from "./internal/config";
import {
InvalidConstructorArgumentsError,
Expand All @@ -44,6 +46,7 @@ import {
import "./internal/type-extensions";
import "./internal/tasks/etherscan";
import "./internal/tasks/sourcify";
import "./internal/tasks/blockscout";

// Main task args
export interface VerifyTaskArgs {
Expand Down Expand Up @@ -84,6 +87,7 @@ export interface VerificationSubtask {

extendConfig(etherscanConfigExtender);
extendConfig(sourcifyConfigExtender);
extendConfig(blockscoutConfigExtender);

/**
* Main verification task.
Expand Down Expand Up @@ -180,7 +184,18 @@ subtask(
});
}

if (!config.etherscan.enabled && !config.sourcify.enabled) {
if (config.blockscout.enabled) {
verificationSubtasks.push({
label: "Blockscout",
subtaskName: TASK_VERIFY_BLOCKSCOUT,
});
}

if (
!config.etherscan.enabled &&
!config.sourcify.enabled &&
!config.blockscout.enabled
) {
console.warn(
chalk.yellow(
`[WARNING] No verification services are enabled. Please enable at least one verification service in your configuration.`
Expand Down
21 changes: 21 additions & 0 deletions packages/hardhat-verify/src/internal/blockscout.chain-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { ChainConfig } from "../types";

// See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md#list-of-chain-ids
export const builtinChains: ChainConfig[] = [
{
network: "mainnet",
chainId: 1,
urls: {
apiURL: "https://eth.blockscout.com/api",
browserURL: "https://eth.blockscout.com/",
},
},
{
network: "sepolia",
chainId: 11155111,
urls: {
apiURL: "https://eth-sepolia.blockscout.com/api",
browserURL: "https://eth-sepolia.blockscout.com/",
},
},
];
175 changes: 175 additions & 0 deletions packages/hardhat-verify/src/internal/blockscout.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import type { EthereumProvider } from "hardhat/types";
import type { ChainConfig } from "../types";

import { HARDHAT_NETWORK_NAME } from "hardhat/plugins";

import {
ChainConfigNotFoundError,
HardhatNetworkNotSupportedError,
} from "./errors";
import { ValidationResponse } from "./utilities";
import { builtinChains } from "./blockscout.chain-config";

import { Etherscan } from "./etherscan";

/**
* Blockscout verification provider for verifying smart contracts.
*/
export class Blockscout {
private _etherscan: Etherscan;

/**
* Create a new instance of the Blockscout verification provider.
* @param apiUrl - The Blockscout API URL, e.g. https://eth.blockscout.com/api.
* @param browserUrl - The Blockscout browser URL, e.g. https://eth.blockscout.com.
*/
constructor(public apiUrl: string, public browserUrl: string) {
this._etherscan = new Etherscan("api_key", apiUrl, browserUrl);
}

public static async getCurrentChainConfig(
networkName: string,
ethereumProvider: EthereumProvider,
customChains: ChainConfig[]
): Promise<ChainConfig> {
const currentChainId = parseInt(
await ethereumProvider.send("eth_chainId"),
16
);

const currentChainConfig = [
// custom chains has higher precedence than builtin chains
...[...customChains].reverse(), // the last entry has higher precedence
...builtinChains,
].find(({ chainId }) => chainId === currentChainId);

if (currentChainConfig === undefined) {
if (networkName === HARDHAT_NETWORK_NAME) {
throw new HardhatNetworkNotSupportedError();
}

throw new ChainConfigNotFoundError(currentChainId);
}

return currentChainConfig;
}

public static fromChainConfig(chainConfig: ChainConfig): Blockscout {
const apiUrl = chainConfig.urls.apiURL;
const browserUrl = chainConfig.urls.browserURL.trim().replace(/\/$/, "");

return new Blockscout(apiUrl, browserUrl);
}

/**
* Check if a smart contract is verified on Blockscout.
* @link https://docs.blockscout.com/for-users/api/rpc-endpoints/contract#get-contract-source-code-for-a-verified-contract
* @param address - The address of the smart contract.
* @returns True if the contract is verified, false otherwise.
* @throws {NetworkRequestError} if there is an error on the request.
* @throws {ContractVerificationInvalidStatusCodeError} if the API returns an invalid status code.
*/
public async isVerified(address: string): Promise<boolean> {
return this._etherscan.isVerified(address);
}

/**
* Verify a smart contract on Blockscout.
* @link https://docs.blockscout.com/for-users/api/rpc-endpoints/contract#verify-a-contract-with-standard-input-json-file
* @param contractAddress - The address of the smart contract to verify.
* @param sourceCode - The source code of the smart contract.
* @param contractName - The name of the smart contract, e.g. "contracts/Sample.sol:MyContract"
* @param compilerVersion - The version of the Solidity compiler used, e.g. `v0.8.19+commit.7dd6d404`
* @returns A promise that resolves to an `BlockscoutResponse` object.
* @throws {NetworkRequestError} if there is an error on the request.
* @throws {ContractVerificationInvalidStatusCodeError} if the API returns an invalid status code.
* @throws {ContractVerificationMissingBytecodeError} if the bytecode is not found on the block explorer.
* @throws {ContractAlreadyVerifiedError} if the contract is already verified.
* @throws {HardhatVerifyError} if the response status is not OK.
*/
public async verify(
contractAddress: string,
sourceCode: string,
contractName: string,
compilerVersion: string
): Promise<BlockscoutResponse> {
const etherscanResponse = await this._etherscan.verify(
contractAddress,
sourceCode,
contractName,
compilerVersion,
""
);

return new BlockscoutResponse(
etherscanResponse.status,
etherscanResponse.message
);
}

/**
* Get the verification status of a smart contract from Blockscout.
* This method performs polling of the verification status if it's pending.
* @link https://docs.blockscout.com/for-users/api/rpc-endpoints/contract#return-status-of-a-verification-attempt
* @param guid - The verification GUID to check.
* @returns A promise that resolves to an `BlockscoutResponse` object.
* @throws {NetworkRequestError} if there is an error on the request.
* @throws {ContractStatusPollingInvalidStatusCodeError} if the API returns an invalid status code.
* @throws {ContractStatusPollingResponseNotOkError} if the response status is not OK.
*/
public async getVerificationStatus(
guid: string
): Promise<BlockscoutResponse> {
const etherscanResponse = await this._etherscan.getVerificationStatus(guid);

return new BlockscoutResponse(
etherscanResponse.status,
etherscanResponse.message
);
}

/**
* Get the Blockscout URL for viewing a contract's details.
* @param address - The address of the smart contract.
* @returns The URL to view the contract on Blockscout's website.
*/
public getContractUrl(address: string): string {
return `${this.browserUrl}/address/${address}#code`;
}
}

class BlockscoutResponse implements ValidationResponse {
public readonly status: number;
public readonly message: string;

constructor(status: number, message: string) {
this.status = status;
this.message = message;
}

public isPending() {
return this.message === "Pending in queue";
}

public isFailure() {
return this.message === "Fail - Unable to verify";
}

public isSuccess() {
return this.message === "Pass - Verified";
}

public isAlreadyVerified() {
return (
// returned by blockscout
this.message.startsWith("Smart-contract already verified") ||
// returned by etherscan
this.message.startsWith("Contract source code already verified") ||
this.message.startsWith("Already Verified")
);
}

public isOk() {
return this.status === 1;
}
}
19 changes: 18 additions & 1 deletion packages/hardhat-verify/src/internal/config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import type LodashCloneDeepT from "lodash.clonedeep";
import type { HardhatConfig, HardhatUserConfig } from "hardhat/types";
import type { EtherscanConfig, SourcifyConfig } from "../types";
import type {
EtherscanConfig,
SourcifyConfig,
BlockscoutConfig,
} from "../types";

import chalk from "chalk";

Expand Down Expand Up @@ -44,3 +48,16 @@ export function sourcifyConfigExtender(
const userSourcifyConfig = cloneDeep(userConfig.sourcify);
config.sourcify = { ...defaultSourcifyConfig, ...userSourcifyConfig };
}

export function blockscoutConfigExtender(
config: HardhatConfig,
userConfig: Readonly<HardhatUserConfig>
): void {
const defaultBlockscoutConfig: BlockscoutConfig = {
enabled: false,
customChains: [],
};
const cloneDeep = require("lodash.clonedeep") as typeof LodashCloneDeepT;
const userBlockscoutConfig = cloneDeep(userConfig.blockscout);
config.blockscout = { ...defaultBlockscoutConfig, ...userBlockscoutConfig };
}
7 changes: 7 additions & 0 deletions packages/hardhat-verify/src/internal/task-names.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,10 @@ export const TASK_VERIFY_SOURCIFY_ATTEMPT_VERIFICATION =
"verify:sourcify-attempt-verification";
export const TASK_VERIFY_SOURCIFY_DISABLED_WARNING =
"verify:sourcify-disabled-warning";

// Blockscout
export const TASK_VERIFY_BLOCKSCOUT = "verify:blockscout";
export const TASK_VERIFY_BLOCKSCOUT_RESOLVE_ARGUMENTS =
"verify:blockscout-resolve-arguments";
export const TASK_VERIFY_BLOCKSCOUT_ATTEMPT_VERIFICATION =
"verify:blockscout-attempt-verification";
Loading

0 comments on commit 963f91f

Please sign in to comment.