From d673d54f7534d311627223cd4960a3954ce4a3ea Mon Sep 17 00:00:00 2001 From: David Racero Date: Thu, 4 Mar 2021 16:10:39 +0100 Subject: [PATCH] First iteration of a Polygon block explorer smart contract verifier --- helpers/contracts-helpers.ts | 18 +++--- helpers/polygon-utils.ts | 103 +++++++++++++++++++++++++++++++++++ helpers/tenderly-utils.ts | 12 ++++ 3 files changed, 123 insertions(+), 10 deletions(-) create mode 100644 helpers/polygon-utils.ts diff --git a/helpers/contracts-helpers.ts b/helpers/contracts-helpers.ts index 455dbb78..0a2eef09 100644 --- a/helpers/contracts-helpers.ts +++ b/helpers/contracts-helpers.ts @@ -24,7 +24,8 @@ import { Artifact } from 'hardhat/types'; import { Artifact as BuidlerArtifact } from '@nomiclabs/buidler/types'; import { verifyContract } from './etherscan-verification'; import { getIErc20Detailed } from './contracts-getters'; -import { usingTenderly } from './tenderly-utils'; +import { usingTenderly, verifyAtTenderly } from './tenderly-utils'; +import { usingPolygon, verifyAtPolygon } from './polygon-utils'; export type MockTokenMap = { [symbol: string]: MintableERC20 }; @@ -99,17 +100,14 @@ export const withSaveAndVerify = async ( await waitForTx(instance.deployTransaction); await registerContractInJsonDb(id, instance); if (usingTenderly()) { - console.log(); - console.log('Doing Tenderly contract verification of', id); - await (DRE as any).tenderlyRPC.verify({ - name: id, - address: instance.address, - }); - console.log(`Verified ${id} at Tenderly!`); - console.log(); + await verifyAtTenderly(id, instance); } if (verify) { - await verifyContract(instance.address, args); + if (usingPolygon()) { + await verifyAtPolygon(id, instance, args); + } else { + await verifyContract(instance.address, args); + } } return instance; }; diff --git a/helpers/polygon-utils.ts b/helpers/polygon-utils.ts new file mode 100644 index 00000000..77e8b56e --- /dev/null +++ b/helpers/polygon-utils.ts @@ -0,0 +1,103 @@ +import axios from 'axios'; +import { Contract } from 'ethers/lib/ethers'; +import { HardhatRuntimeEnvironment } from 'hardhat/types'; +import { source } from 'lowdb/adapters/FileSync'; +import { file } from 'tmp-promise'; +import { DRE } from './misc-utils'; +import { ePolygonNetwork } from './types'; + +const TASK_FLATTEN_GET_FLATTENED_SOURCE = 'flatten:get-flattened-sources'; +const TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS = 'compile:solidity:get-source-paths'; + +/* Polygon Helpers */ + +export const usingPolygon = () => + DRE && Object.keys(ePolygonNetwork).includes((DRE as HardhatRuntimeEnvironment).network.name); + +/* Polygon Verifier */ + +const SOLIDITY_PRAGMA = 'pragma solidity'; +const LICENSE_IDENTIFIER = 'License-Identifier'; +const EXPERIMENTAL_ABIENCODER = 'pragma experimental ABIEncoderV2;'; + +// Remove lines at "text" that includes "matcher" string, but keeping first "keep" lines +const removeLines = (text: string, matcher: string, keep = 0): string => { + let counter = keep; + return text + .split('\n') + .filter((line) => { + const match = !line.includes(matcher); + if (match === false && counter > 0) { + counter--; + return true; + } + return match; + }) + .join('\n'); +}; + +// Try to find the path of a Contract by name of the file without ".sol" +const findPath = async (id: string): Promise => { + const paths = await DRE.run(TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS); + const path = paths.find((x) => { + const t = x.split('/'); + return t[t.length - 1].split('.')[0] == id; + }); + + if (!path) { + throw Error('Missing path for contract name: ${id}'); + } + + return path; +}; + +// Hardhat Flattener, similar to truffle flattener +const hardhatFlattener = async (filePath: string) => + await DRE.run(TASK_FLATTEN_GET_FLATTENED_SOURCE, { files: [filePath] }); + +// Verify a smart contract at Polygon Matic network via a GET request to the block explorer +export const verifyAtPolygon = async ( + id: string, + instance: Contract, + args: (string | string[])[] +) => { + /* + ${net == mumbai or mainnet} + https://explorer-${net}.maticvigil.com/api + ?module=contract + &action=verify + &addressHash={addressHash} + &name={name} + &compilerVersion={compilerVersion} + &optimization={false} + &contractSourceCode={contractSourceCode} + */ + try { + const net = (DRE as HardhatRuntimeEnvironment).network.name; + const filePath = await findPath(id); + const flattenSourceCode = await hardhatFlattener(filePath); + + // Remove pragmas and license identifier after first match, required by block explorers like explorer-mainnet.maticgivil.com or Etherscan + const cleanedSourceCode = removeLines( + removeLines(removeLines(flattenSourceCode, LICENSE_IDENTIFIER, 1), SOLIDITY_PRAGMA, 1), + EXPERIMENTAL_ABIENCODER, + 1 + ); + const response = await axios.get(`https://explorer-${net}.maticvigil.com/api`, { + params: { + module: 'contract', + action: 'verify', + addressHash: instance.address, + name: id, + compilerVersion: 'v0.6.12+commit.27d51765', + optimization: 'true', + contractSourceCode: cleanedSourceCode, + }, + }); + console.log(response); + console.log(JSON.stringify(response, null, 2)); + } catch (error) { + console.error('[Polygon Verify] Error:', error.toString()); + throw error; + } +}; diff --git a/helpers/tenderly-utils.ts b/helpers/tenderly-utils.ts index 27445608..89109599 100644 --- a/helpers/tenderly-utils.ts +++ b/helpers/tenderly-utils.ts @@ -1,3 +1,4 @@ +import { Contract } from 'ethers'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; import { DRE } from './misc-utils'; @@ -5,3 +6,14 @@ export const usingTenderly = () => DRE && ((DRE as HardhatRuntimeEnvironment).network.name.includes('tenderly') || process.env.TENDERLY === 'true'); + +export const verifyAtTenderly = async (id: string, instance: Contract) => { + console.log(); + console.log('Doing Tenderly contract verification of', id); + await (DRE as any).tenderlyRPC.verify({ + name: id, + address: instance.address, + }); + console.log(`Verified ${id} at Tenderly!`); + console.log(); +};