Skip to content

chore: manual action for verifying deployments #758

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions .github/workflows/verifydeployed.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
name: Verify deployed contracts

on:
workflow_dispatch:
inputs:
contracts:
description: 'List of deployed contracts to verify (space delimited)'
required: true
type: string
network:
description: 'Network where the contracts are deployed'
required: true
type: choice
default: mainnet
options:
- mainnet
- arbitrum-one
- goerli
- arbitrum-goerli

jobs:
build:
name: Compile contracts
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '16'
cache: 'yarn'
- run: yarn install --non-interactive --frozen-lockfile

- name: Compile contracts
run: yarn build

- name: Save build artifacts
uses: actions/upload-artifact@v3
with:
name: contract-artifacts
path: |
build
cache/*.json

verify:
name: Verify deployments
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '16'
cache: 'yarn'
- run: yarn install --non-interactive --frozen-lockfile
- name: Get build artifacts
uses: actions/download-artifact@v3
with:
name: contract-artifacts

- name: Verify contracts on Defender
run: yarn hardhat --network ${{ inputs.network }} verify-defender ${{ inputs.contracts }}
env:
DEFENDER_API_KEY: "${{ secrets.DEFENDER_API_KEY }}"
DEFENDER_API_SECRET: "${{ secrets.DEFENDER_API_SECRET }}"
INFURA_KEY: "${{ secrets.INFURA_KEY }}"
WORKFLOW_URL: "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
5 changes: 5 additions & 0 deletions hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import 'hardhat-contract-sizer'
import 'hardhat-tracer'
import '@tenderly/hardhat-tenderly'
import '@openzeppelin/hardhat-upgrades'
import '@openzeppelin/hardhat-defender'
import '@typechain/hardhat'
import 'solidity-coverage'
import 'hardhat-storage-layout'
Expand Down Expand Up @@ -209,6 +210,10 @@ const config: HardhatUserConfig = {
runOnCompile: false,
disambiguatePaths: false,
},
defender: {
apiKey: process.env.DEFENDER_API_KEY!,
apiSecret: process.env.DEFENDER_API_SECRET!,
},
}

setupNetworkProviders(config)
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"@nomiclabs/hardhat-waffle": "^2.0.1",
"@openzeppelin/contracts": "^3.4.1",
"@openzeppelin/contracts-upgradeable": "3.4.2",
"@openzeppelin/hardhat-defender": "^1.8.1",
"@openzeppelin/hardhat-upgrades": "^1.6.0",
"@tenderly/hardhat-tenderly": "^1.0.11",
"@typechain/ethers-v5": "^7.0.0",
Expand Down
102 changes: 102 additions & 0 deletions tasks/verify/defender.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { execSync } from 'child_process'
import { task } from 'hardhat/config'
import { HardhatRuntimeEnvironment as HRE } from 'hardhat/types'
import { constants } from 'ethers'
import { appendFileSync } from 'fs'
import type { VerificationResponse } from '@openzeppelin/hardhat-defender/dist/verify-deployment'

async function main(args: { referenceUrl?: string; contracts: string[] }, hre: HRE) {
const { referenceUrl, contracts } = args
const { defender, network, graph } = hre
const summaryPath = process.env.GITHUB_STEP_SUMMARY
if (summaryPath) appendFileSync(summaryPath, `# Contracts deployment verification\n\n`)

const workflowUrl =
referenceUrl ||
process.env.WORKFLOW_URL ||
execSync(`git config --get remote.origin.url`).toString().trim()
const addressBook = graph().addressBook
const errs = []

for (const contractName of contracts) {
const entry = addressBook.getEntry(contractName)
if (!entry || entry.address === constants.AddressZero) {
errs.push([contractName, { message: `Entry not found on address book.` }])
continue
}

const addressToVerify = entry.implementation?.address ?? entry.address
console.error(`Verifying artifact for ${contractName} at ${addressToVerify}`)

try {
const response = await defender.verifyDeployment(addressToVerify, contractName, workflowUrl)
console.error(`Bytecode match for ${contractName} is ${response.matchType}`)
if (summaryPath) {
appendFileSync(
summaryPath,
`- ${contractName} at ${etherscanLink(network.name, addressToVerify)} is ${
response.matchType
} ${emojiForMatch(response.matchType)}\n`,
)
}
if (response.matchType === 'NO_MATCH') {
errs.push([contractName, { message: `No bytecode match.` }])
}
} catch (err: any) {
if (summaryPath) {
appendFileSync(
summaryPath,
`- ${contractName} at ${etherscanLink(
network.name,
addressToVerify,
)} failed to verify :x:\n`,
)
}
console.error(`Error verifying artifact: ${err.message}`)
errs.push([contractName, err])
}
}

if (errs.length > 0) {
throw new Error(
`Some verifications failed:\n${errs
.map(([name, err]) => ` ${name}: ${err.message}`)
.join('\n')}`,
)
}
}

function etherscanLink(network: string, address: string): string {
switch (network) {
case 'mainnet':
return `[\`${address}\`](https://etherscan.io/address/${address})`
case 'arbitrum-one':
return `[\`${address}\`](https://arbiscan.io/address/${address})`
case 'goerli':
return `[\`${address}\`](https://goerli.etherscan.io/address/${address})`
case 'arbitrum-goerli':
return `[\`${address}\`](https://goerli.arbiscan.io/address/${address})`
default:
return `\`${address}\``
}
}

function emojiForMatch(matchType: VerificationResponse['matchType']): string {
switch (matchType) {
case 'EXACT':
return ':heavy_check_mark:'
case 'PARTIAL':
return ':warning:'
case 'NO_MATCH':
return ':x:'
}
}

task('verify-defender')
.addVariadicPositionalParam('contracts', 'List of contracts to verify')
.addOptionalParam(
'referenceUrl',
'URL to link to for artifact verification (defaults to $WORKFLOW_URL or the remote.origin.url of the repository)',
)
.setDescription('Verifies deployed implementations on Defender')
.setAction(main)
Loading