Skip to content

Commit

Permalink
feat: multi verification (#25)
Browse files Browse the repository at this point in the history
* feat: multi verification

* fix: remove some leftovers

* fix: snowscan api url

* fix: improve logging

* fix: readd --force flag for build info

while the issue with compiling --build-info was recently fixed, most ppl are stuck on months old foundry due to foundry-rs/foundry#6516, so reverting the change for now

* fix: lint
  • Loading branch information
sakulstra authored Mar 21, 2024
1 parent 4ca53ba commit 7ea3d13
Show file tree
Hide file tree
Showing 10 changed files with 204 additions and 140 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Below you can see all the CLI arguments and aliases:
-r, --rpcUrl string RPC URL to fetch transaction details, mandatory to support for
debug_traceTransaction. You can use Alchemy or Quicknode providers if
available.
-e, --etherscanUrl string Etherscan API endpoint
-e, --explorerUrl string Etherscan API endpoint
-k, --etherscanApiKey string Etherscan API key
```

Expand Down
Binary file modified bun.lockb
Binary file not shown.
2 changes: 1 addition & 1 deletion src/cli-args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const args = parse<InputParams>(
description:
"RPC URL to fetch transaction details, mandatory to support for debug_traceTransaction. You can use Alchemy or Quicknode providers if available.",
},
etherscanUrl: {
explorerUrl: {
type: String,
optional: true,
alias: "e",
Expand Down
208 changes: 152 additions & 56 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,64 +5,160 @@ export const VERIFY_VERSION = packageJson.version;

export const VERBOSE = !!process.env.VERBOSE;

export const ETHERSCAN_API_KEYS: {
[key: number]: string;
} = {
[Networks.polygon]: process.env.ETHERSCAN_API_KEY_MATIC || "",
[Networks.main]: process.env.ETHERSCAN_API_KEY_MAINNET || "",
[Networks.arbitrum]: process.env.ETHERSCAN_API_KEY_ARBITRUM || "",
[Networks.optimism]: process.env.ETHERSCAN_API_KEY_OPTIMISM || "",
[Networks.goerli]: process.env.ETHERSCAN_API_KEY_GOERLI || "",
[Networks.sepolia]: process.env.ETHERSCAN_API_KEY_SEPOLIA || "",
[Networks.avalanche]: process.env.ETHERSCAN_API_KEY_AVALANCHE || "",
[Networks.avalancheFuji]: process.env.ETHERSCAN_API_KEY_AVALANCHE_FUJI || "",
[Networks.bnb]: process.env.ETHERSCAN_API_KEY_BNB || "",
[Networks.bnbTestnet]: process.env.ETHERSCAN_API_KEY_BNB_TESTNET || "",
[Networks.base]: process.env.ETHERSCAN_API_KEY_BASE || "",
[Networks.metis]: process.env.ETHERSCAN_API_KEY_METIS || "",
[Networks.gnosis]: process.env.ETHERSCAN_API_KEY_GNOSIS || "",
[Networks.scroll]: process.env.ETHERSCAN_API_KEY_SCROLL || "",
[Networks.zkevm]: process.env.ETHERSCAN_API_KEY_ZKEVM || "",
[Networks.celo]: process.env.ETHERSCAN_API_KEY_CELO || "",
export type ExplorerConfig = {
API_URL: string;
SITE_URL?: string;
API_KEY?: string;
};

export const ETHERSCAN_API_URL: {
[key: string]: string;
} = {
[Networks.polygon]: "https://api.polygonscan.com/api",
[Networks.main]: "https://api.etherscan.io/api",
[Networks.arbitrum]: "https://api.arbiscan.io/api",
[Networks.avalanche]: "https://api.routescan.io/v2/network/mainnet/evm/43114/etherscan",
[Networks.avalancheFuji]: "https://api.routescan.io/v2/network/testnet/evm/43113/etherscan",
[Networks.optimism]: "https://api-optimistic.etherscan.io/api",
[Networks.goerli]: "https://api-goerli.etherscan.io/api",
[Networks.sepolia]: "https://api-sepolia.etherscan.io/api",
[Networks.bnb]: "https://api.bscscan.com/api",
[Networks.bnbTestnet]: "https://api-testnet.bscscan.com/api",
[Networks.base]: "https://api.basescan.org/api",
[Networks.metis]: "https://api.routescan.io/v2/network/mainnet/evm/1088/etherscan",
[Networks.gnosis]: "https://api.gnosisscan.io/api",
[Networks.scroll]: "https://api.scrollscan.com/api",
[Networks.zkevm]: "https://api-zkevm.polygonscan.com/api",
[Networks.celo]: "https://api.celoscan.io/api",
export type NetworkConfig = {
RPC: string;
explorers: ExplorerConfig[];
};

export const DEFAULT_RPC_URLS: {
[key: string]: string;
} = {
[Networks.polygon]: process.env.RPC_POLYGON || "",
[Networks.main]: process.env.RPC_MAINNET || "",
[Networks.arbitrum]: process.env.RPC_ARBITRUM || "",
[Networks.avalanche]: process.env.RPC_AVALANCHE || "",
[Networks.optimism]: process.env.RPC_OPTIMISM || "",
[Networks.goerli]: process.env.RPC_GOERLI || "",
[Networks.sepolia]: process.env.RPC_SEPOLIA || "",
[Networks.bnb]: process.env.RPC_BNB || "",
[Networks.bnbTestnet]: process.env.RPC_BNB_TESTNET || "",
[Networks.base]: process.env.RPC_BASE || "",
[Networks.gnosis]: process.env.RPC_GNOSIS || "",
[Networks.scroll]: process.env.RPC_SCROLL || "",
[Networks.zkevm]: process.env.RPC_ZKEVM || "",
[Networks.celo]: process.env.RPC_CELO || "",
[Networks.metis]: process.env.RPC_METIS || "",
export const NETWORK_CONFIGS: Record<string, NetworkConfig> = {
[Networks.polygon]: {
RPC: process.env.RPC_POLYGON || "",
explorers: [
{
API_URL: "https://api.polygonscan.com/api",
API_KEY: process.env.ETHERSCAN_API_KEY_POLYGON || "",
SITE_URL: "https://polygonscan.com",
},
],
},
[Networks.main]: {
RPC: process.env.RPC_MAINNET || "",
explorers: [
{
API_URL: "https://api.etherscan.io/api",
API_KEY: process.env.ETHERSCAN_API_KEY_MAINNET || "",
SITE_URL: "https://etherscan.io",
},
],
},
[Networks.arbitrum]: {
RPC: process.env.RPC_ARBITRUM || "",
explorers: [
{
API_URL: "https://api.arbiscan.io/api",
API_KEY: process.env.ETHERSCAN_API_KEY_ARBITRUM || "",
SITE_URL: "https://arbiscan.io",
},
],
},
[Networks.avalanche]: {
RPC: process.env.RPC_AVALANCHE || "",
explorers: [
{
API_URL: "https://api.snowscan.xyz/api",
API_KEY: process.env.ETHERSCAN_API_KEY_AVALANCHE || "",
SITE_URL: "https://snowscan.xyz",
},
{
API_URL: "https://api.routescan.io/v2/network/mainnet/evm/43114/etherscan",
SITE_URL: "https://snowtrace.io",
},
],
},
[Networks.optimism]: {
RPC: process.env.RPC_OPTIMISM || "",
explorers: [
{
API_URL: "https://api-optimistic.etherscan.io/api",
API_KEY: process.env.ETHERSCAN_API_KEY_OPTIMISM || "",
SITE_URL: "https://optimistic.etherscan.io",
},
],
},
[Networks.sepolia]: {
RPC: process.env.RPC_SEPOLIA || "",
explorers: [
{
API_URL: "https://api-sepolia.etherscan.io/api",
API_KEY: process.env.ETHERSCAN_API_KEY_SEPOLIA || "",
SITE_URL: "https://sepolia.etherscan.io",
},
],
},
[Networks.bnb]: {
RPC: process.env.RPC_BNB || "",
explorers: [
{
API_URL: "https://api.bscscan.com/api",
API_KEY: process.env.ETHERSCAN_API_KEY_BNB || "",
SITE_URL: "https://bscscan.com",
},
],
},
[Networks.bnbTestnet]: {
RPC: process.env.RPC_BNB_TESTNET || "",
explorers: [
{
API_URL: "https://api-testnet.bscscan.com/api",
API_KEY: process.env.ETHERSCAN_API_KEY_BNB_TESTNET || "",
SITE_URL: "https://testnet.bscscan.com",
},
],
},
[Networks.base]: {
RPC: process.env.RPC_BASE || "",
explorers: [
{
API_URL: "https://api.basescan.org/api",
API_KEY: process.env.ETHERSCAN_API_KEY_BASE || "",
SITE_URL: "https://testnet.bscscan.com",
},
],
},
[Networks.gnosis]: {
RPC: process.env.RPC_GNOSIS || "",
explorers: [
{
API_URL: "https://api.gnosisscan.io/api",
API_KEY: process.env.ETHERSCAN_API_KEY_GNOSIS || "",
SITE_URL: "https://gnosisscan.io",
},
],
},
[Networks.scroll]: {
RPC: process.env.RPC_SCROLL || "",
explorers: [
{
API_URL: "https://api.scrollscan.com/api",
API_KEY: process.env.ETHERSCAN_API_KEY_SCROLL || "",
SITE_URL: "https://scrollscan.com",
},
],
},
[Networks.zkevm]: {
RPC: process.env.RPC_ZKEVM || "",
explorers: [
{
API_URL: "https://api-zkevm.polygonscan.com/api",
API_KEY: process.env.ETHERSCAN_API_KEY_ZKEVM || "",
SITE_URL: "https://zkevm.polygonscan.com",
},
],
},
[Networks.celo]: {
RPC: process.env.RPC_CELO || "",
explorers: [
{
API_URL: "https://api.celoscan.io/api",
API_KEY: process.env.ETHERSCAN_API_KEY_CELO || "",
SITE_URL: "https://celoscan.io",
},
],
},
[Networks.metis]: {
RPC: process.env.RPC_METIS || "",
explorers: [
{
API_URL: "https://api.routescan.io/v2/network/mainnet/evm/1088/etherscan",
SITE_URL: "https://explorer.metis.io",
},
{ API_URL: "https://andromeda-explorer.metis.io/api ", SITE_URL: "https://andromeda-explorer.metis.io" },
],
},
};
31 changes: 14 additions & 17 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { exit } from "node:process";
import chalk from "chalk";
import "dotenv/config";
import { args } from "./cli-args";
import { DEFAULT_RPC_URLS, VERIFY_VERSION } from "./config";
import { NETWORK_CONFIGS, VERIFY_VERSION } from "./config";
import { callTraceVerifier } from "./utils/calltrace-verifier";
import { loadArtifacts, loadBuildInfo } from "./utils/foundry-ffi";
import { loadJson } from "./utils/json";
Expand All @@ -32,14 +32,17 @@ const main = async () => {

greets();

const rpc = args.rpcUrl || DEFAULT_RPC_URLS[parsedRun.chain];
const chainId = await getChainId(rpc);
const networkConfig = NETWORK_CONFIGS[parsedRun.chain];
if (args.rpcUrl) networkConfig.RPC = args.rpcUrl;
if (args.explorerUrl) networkConfig.explorers = [{ API_URL: args.explorerUrl, API_KEY: args.etherscanApiKey }];

const chainId = await getChainId(networkConfig.RPC);

try {
console.log("Chain Id:", chainId);
console.log();
} catch (err) {
console.log("Could not connect to RPC endpoint", rpc);
console.log("Could not connect to RPC endpoint", networkConfig.RPC);
process.exit(2);
}

Expand All @@ -48,19 +51,13 @@ const main = async () => {

console.log("\nAnalyzing deployment transactions...\n");
for (const tx of parsedRun.transactions) {
const trace = await getTxInternalCalls(tx.hash, rpc);

try {
await callTraceVerifier(
trace.result,
chainId,
artifacts,
buildInfos,
args.etherscanUrl,
args.etherscanApiKey,
);
} catch (error) {
console.error("[Verification Error]", error);
const trace = await getTxInternalCalls(tx.hash, networkConfig.RPC);
for (const explorer of networkConfig.explorers) {
try {
await callTraceVerifier(trace.result, artifacts, buildInfos, explorer);
} catch (error) {
console.error("[Verification Error]", error);
}
}
}
console.log("\n[catapulta-verify] Verification finished.");
Expand Down
2 changes: 1 addition & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export interface InputParams {
broadcastPath: string;
rpcUrl?: string;
etherscanUrl?: string;
explorerUrl?: string;
etherscanApiKey?: string;
help?: boolean;
}
Expand Down
41 changes: 12 additions & 29 deletions src/utils/calltrace-verifier.ts
Original file line number Diff line number Diff line change
@@ -1,67 +1,50 @@
import type { ExplorerConfig } from "../config";
import { checkIfVerified, checkVerificationStatus, submitVerification, waitTillVisible } from "./explorer-api";
import { getSettingsByArtifact } from "./foundry-ffi";
import { delay } from "./misc";
import { delay, renderExplorerUrl } from "./misc";

export const callTraceVerifier = async (
call: any,
chainId: number,
artifacts: any[],
buildInfos: any[],
etherscanUrl?: string,
etherscanApiKey?: string,
) => {
export const callTraceVerifier = async (call: any, artifacts: any[], buildInfos: any[], explorer: ExplorerConfig) => {
const deployOpcodes = ["CREATE", "CREATE2"];

// Perform nested call tracing verification in each internal call
if (call.calls) {
for (const c of call.calls) {
await callTraceVerifier(c, chainId, artifacts, buildInfos, etherscanUrl, etherscanApiKey);
await callTraceVerifier(c, artifacts, buildInfos, explorer);
}
}

if (!deployOpcodes.includes(call.type)) return;

await waitTillVisible(call.to, Number(chainId), etherscanUrl, etherscanApiKey);
await waitTillVisible(call.to, explorer);

const verified = await checkIfVerified(call.to, Number(chainId), etherscanUrl, etherscanApiKey);
const verified = await checkIfVerified(call.to, explorer);

if (verified) {
console.log(`(${call.to}) is already verified, skipping.`);
console.log(`(${renderExplorerUrl(call.to, explorer)}) is already verified, skipping.`);
return;
}

// if the tx has subdeployments get the deployed bytecode from the last deployment in order to
// compare the bytecode and deployed bytecode strings, resulting in the constructor params

const verificationReq = await getSettingsByArtifact(
Number(chainId),
artifacts,
buildInfos,
call.input,
call.to,
etherscanApiKey,
);
const verificationReq = await getSettingsByArtifact(artifacts, buildInfos, call.input, call.to, explorer.API_KEY);
if (!verificationReq) {
console.log("Couldn't get the params for the verification request");
return;
}

const {
result: guid,
message,
status,
}: any = await submitVerification(verificationReq, Number(chainId), etherscanUrl);
const { result: guid, message, status }: any = await submitVerification(verificationReq, explorer.API_URL);

if (!status || guid.includes("Max rate limit reached")) {
console.log(`Couldn't verify ${call.to} `, guid);
console.log(`Couldn't verify ${renderExplorerUrl(call.to, explorer)} `, guid);
return;
}

console.log(`Verifying contract ${call.to}, with guid: ${guid}`);
console.log(`Verifying contract ${renderExplorerUrl(call.to, explorer)}, with guid: ${guid}`);

for (let i = 0; i < 30; i++) {
await delay(1000);
const { status, message } = await checkVerificationStatus(guid, chainId, etherscanUrl, etherscanApiKey);
const { status, message } = await checkVerificationStatus(guid, explorer);

if (status !== 2) {
console.log(message);
Expand Down
Loading

0 comments on commit 7ea3d13

Please sign in to comment.