Commit 28649d64 authored by Mark Tyneway's avatar Mark Tyneway Committed by GitHub

contracts-bedrock: add forge contract verification support (#3141)

* contracts-bedrock: add forge contract verification support

Add etherscan contract verification for the hh/foundry
compiler toolchain. This will eventually be upstreamed,
but is implemented here to ensure that it works thoroughly
first.

To run the task:

```
export ETHERSCAN_API_KEY=$(cat api-key.txt)
$ npx hardhat forge-verify --network goerli
```

In the future, other contract verification backends
will be added to forge and this task will be updated
to support them.

Example contract that was verified: https://goerli.etherscan.io/address/0x1234662682c85fa6fb375416d14db965eba222ba#code

An individual contract can be targeted instead of all of them
with the `--contract` flag. If hardhat deploy has configured
external deployments, forge will not be able to verify them.
It may be possible to implement that in the future.

* contracts-bedrock: modularize tasks
parent 357c7707
---
'@eth-optimism/contracts-bedrock': patch
---
Add harhdat forge contract verification support
......@@ -9,14 +9,7 @@ import '@nomiclabs/hardhat-ethers'
import 'hardhat-deploy'
// Hardhat tasks
import './tasks/genesis-l1'
import './tasks/genesis-l2'
import './tasks/deposits'
import './tasks/rekey'
import './tasks/rollup-config'
import './tasks/check-op-node'
import './tasks/check-l2-config'
import './tasks/watch'
import './tasks'
subtask(TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS).setAction(
async (_, __, runSuper) => {
......
......@@ -53,6 +53,7 @@
"ethereumjs-wallet": "^1.0.2",
"@defi-wonderland/smock": "^2.0.2",
"@foundry-rs/hardhat-forge": "^0.1.16",
"@foundry-rs/easy-foundryup": "^0.1.3",
"@nomiclabs/hardhat-ethers": "^2.0.0",
"@nomiclabs/hardhat-waffle": "^2.0.0",
"@typechain/ethers-v5": "^10.1.0",
......
import { spawn as spawn } from 'child_process'
import { task, types } from 'hardhat/config'
import * as foundryup from '@foundry-rs/easy-foundryup'
import 'hardhat-deploy'
import { ethers } from 'ethers'
interface ForgeVerifyArgs {
chainId: string
compilerVersion: string
constructorArgs: string
optimizerRuns: number
contractAddress: string
contractName: string
etherscanApiKey: string
}
const verifyArgs = (opts: ForgeVerifyArgs): string[] => {
const allArgs: string[] = []
if (!opts.chainId) {
throw new Error(`No chain-id provided`)
}
allArgs.push(`--chain`, opts.chainId)
if (opts.compilerVersion) {
allArgs.push('--compiler-version', opts.compilerVersion)
}
if (opts.constructorArgs) {
allArgs.push('--constructor-args', opts.constructorArgs)
}
if (typeof opts.optimizerRuns === 'number') {
allArgs.push('--num-of-optimizations', opts.optimizerRuns.toString())
}
allArgs.push('--watch')
if (!opts.contractAddress) {
throw new Error('No contract address provided')
}
allArgs.push(opts.contractAddress)
if (!opts.contractName) {
throw new Error('No contract name provided')
}
allArgs.push(opts.contractName)
if (!opts.etherscanApiKey) {
throw new Error('No Etherscan API key provided')
}
allArgs.push(opts.etherscanApiKey)
return allArgs
}
const spawnVerify = async (opts: ForgeVerifyArgs): Promise<boolean> => {
const args = ['verify-contract', ...verifyArgs(opts)]
const forgeCmd = await foundryup.getForgeCommand()
return new Promise((resolve) => {
const process = spawn(forgeCmd, args, {
stdio: 'inherit',
})
process.on('exit', (code) => {
resolve(code === 0)
})
})
}
task('forge-contract-verify', 'Verify contracts using forge')
.addOptionalParam(
'contract',
'Name of the contract to verify',
'',
types.string
)
.addOptionalParam(
'etherscanApiKey',
'Etherscan API key',
process.env.ETHERSCAN_API_KEY,
types.string
)
.setAction(async (args, hre) => {
const deployments = await hre.deployments.all()
if (args.contract !== '') {
if (!deployments[args.contract]) {
throw new Error(
`Contract ${args.contract} not found in ${hre.network} deployments`
)
}
}
for (const [contract, deployment] of Object.entries(deployments)) {
if (args.contract !== '' && args.contract !== contract) {
continue
}
const chainId = await hre.getChainId()
const contractAddress = deployment.address
const etherscanApiKey = args.etherscanApiKey
let metadata = deployment.metadata as any
// Handle double nested JSON stringify
while (typeof metadata === 'string') {
metadata = JSON.parse(metadata) as any
}
const contractName = Object.values(
metadata.settings.compilationTarget
)[0].toString()
const compilerVersion = metadata.compiler.version
const iface = new ethers.utils.Interface(deployment.abi)
const constructorArgs = iface.encodeDeploy(deployment.args)
const optimizerRuns = metadata.settings.optimizer
const success = await spawnVerify({
chainId,
compilerVersion,
constructorArgs,
optimizerRuns,
contractAddress,
contractName,
etherscanApiKey,
})
if (success) {
console.log(`Contract verification successful for ${contractName}`)
} else {
console.log(`Contract verification unsuccesful for ${contractName}`)
}
}
})
import './genesis-l1'
import './genesis-l2'
import './deposits'
import './rekey'
import './rollup-config'
import './check-op-node'
import './check-l2-config'
import './watch'
import './forge-verify'
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment