Skip to content

Commit bc181d0

Browse files
authored
Merge pull request #758 from spalladino/chore/verify-deployments-on-defender
chore: manual action for verifying deployments
2 parents fd7032b + 483eb40 commit bc181d0

File tree

5 files changed

+297
-4
lines changed

5 files changed

+297
-4
lines changed

.github/workflows/verifydeployed.yml

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
name: Verify deployed contracts
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
contracts:
7+
description: 'List of deployed contracts to verify (space delimited)'
8+
required: true
9+
type: string
10+
network:
11+
description: 'Network where the contracts are deployed'
12+
required: true
13+
type: choice
14+
default: mainnet
15+
options:
16+
- mainnet
17+
- arbitrum-one
18+
- goerli
19+
- arbitrum-goerli
20+
21+
jobs:
22+
build:
23+
name: Compile contracts
24+
runs-on: ubuntu-latest
25+
steps:
26+
- uses: actions/checkout@v3
27+
- uses: actions/setup-node@v3
28+
with:
29+
node-version: '16'
30+
cache: 'yarn'
31+
- run: yarn install --non-interactive --frozen-lockfile
32+
33+
- name: Compile contracts
34+
run: yarn build
35+
36+
- name: Save build artifacts
37+
uses: actions/upload-artifact@v3
38+
with:
39+
name: contract-artifacts
40+
path: |
41+
build
42+
cache/*.json
43+
44+
verify:
45+
name: Verify deployments
46+
runs-on: ubuntu-latest
47+
needs: build
48+
steps:
49+
- uses: actions/checkout@v3
50+
- uses: actions/setup-node@v3
51+
with:
52+
node-version: '16'
53+
cache: 'yarn'
54+
- run: yarn install --non-interactive --frozen-lockfile
55+
- name: Get build artifacts
56+
uses: actions/download-artifact@v3
57+
with:
58+
name: contract-artifacts
59+
60+
- name: Verify contracts on Defender
61+
run: yarn hardhat --network ${{ inputs.network }} verify-defender ${{ inputs.contracts }}
62+
env:
63+
DEFENDER_API_KEY: "${{ secrets.DEFENDER_API_KEY }}"
64+
DEFENDER_API_SECRET: "${{ secrets.DEFENDER_API_SECRET }}"
65+
INFURA_KEY: "${{ secrets.INFURA_KEY }}"
66+
WORKFLOW_URL: "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"

hardhat.config.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import 'hardhat-contract-sizer'
1919
import 'hardhat-tracer'
2020
import '@tenderly/hardhat-tenderly'
2121
import '@openzeppelin/hardhat-upgrades'
22+
import '@openzeppelin/hardhat-defender'
2223
import '@typechain/hardhat'
2324
import 'solidity-coverage'
2425
import 'hardhat-storage-layout'
@@ -209,6 +210,10 @@ const config: HardhatUserConfig = {
209210
runOnCompile: false,
210211
disambiguatePaths: false,
211212
},
213+
defender: {
214+
apiKey: process.env.DEFENDER_API_KEY!,
215+
apiSecret: process.env.DEFENDER_API_SECRET!,
216+
},
212217
}
213218

214219
setupNetworkProviders(config)

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"@nomiclabs/hardhat-waffle": "^2.0.1",
2828
"@openzeppelin/contracts": "^3.4.1",
2929
"@openzeppelin/contracts-upgradeable": "3.4.2",
30+
"@openzeppelin/hardhat-defender": "^1.8.1",
3031
"@openzeppelin/hardhat-upgrades": "^1.6.0",
3132
"@tenderly/hardhat-tenderly": "^1.0.11",
3233
"@typechain/ethers-v5": "^7.0.0",

tasks/verify/defender.ts

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import { execSync } from 'child_process'
2+
import { task } from 'hardhat/config'
3+
import { HardhatRuntimeEnvironment as HRE } from 'hardhat/types'
4+
import { constants } from 'ethers'
5+
import { appendFileSync } from 'fs'
6+
import type { VerificationResponse } from '@openzeppelin/hardhat-defender/dist/verify-deployment'
7+
8+
async function main(args: { referenceUrl?: string; contracts: string[] }, hre: HRE) {
9+
const { referenceUrl, contracts } = args
10+
const { defender, network, graph } = hre
11+
const summaryPath = process.env.GITHUB_STEP_SUMMARY
12+
if (summaryPath) appendFileSync(summaryPath, `# Contracts deployment verification\n\n`)
13+
14+
const workflowUrl =
15+
referenceUrl ||
16+
process.env.WORKFLOW_URL ||
17+
execSync(`git config --get remote.origin.url`).toString().trim()
18+
const addressBook = graph().addressBook
19+
const errs = []
20+
21+
for (const contractName of contracts) {
22+
const entry = addressBook.getEntry(contractName)
23+
if (!entry || entry.address === constants.AddressZero) {
24+
errs.push([contractName, { message: `Entry not found on address book.` }])
25+
continue
26+
}
27+
28+
const addressToVerify = entry.implementation?.address ?? entry.address
29+
console.error(`Verifying artifact for ${contractName} at ${addressToVerify}`)
30+
31+
try {
32+
const response = await defender.verifyDeployment(addressToVerify, contractName, workflowUrl)
33+
console.error(`Bytecode match for ${contractName} is ${response.matchType}`)
34+
if (summaryPath) {
35+
appendFileSync(
36+
summaryPath,
37+
`- ${contractName} at ${etherscanLink(network.name, addressToVerify)} is ${
38+
response.matchType
39+
} ${emojiForMatch(response.matchType)}\n`,
40+
)
41+
}
42+
if (response.matchType === 'NO_MATCH') {
43+
errs.push([contractName, { message: `No bytecode match.` }])
44+
}
45+
} catch (err: any) {
46+
if (summaryPath) {
47+
appendFileSync(
48+
summaryPath,
49+
`- ${contractName} at ${etherscanLink(
50+
network.name,
51+
addressToVerify,
52+
)} failed to verify :x:\n`,
53+
)
54+
}
55+
console.error(`Error verifying artifact: ${err.message}`)
56+
errs.push([contractName, err])
57+
}
58+
}
59+
60+
if (errs.length > 0) {
61+
throw new Error(
62+
`Some verifications failed:\n${errs
63+
.map(([name, err]) => ` ${name}: ${err.message}`)
64+
.join('\n')}`,
65+
)
66+
}
67+
}
68+
69+
function etherscanLink(network: string, address: string): string {
70+
switch (network) {
71+
case 'mainnet':
72+
return `[\`${address}\`](https://etherscan.io/address/${address})`
73+
case 'arbitrum-one':
74+
return `[\`${address}\`](https://arbiscan.io/address/${address})`
75+
case 'goerli':
76+
return `[\`${address}\`](https://goerli.etherscan.io/address/${address})`
77+
case 'arbitrum-goerli':
78+
return `[\`${address}\`](https://goerli.arbiscan.io/address/${address})`
79+
default:
80+
return `\`${address}\``
81+
}
82+
}
83+
84+
function emojiForMatch(matchType: VerificationResponse['matchType']): string {
85+
switch (matchType) {
86+
case 'EXACT':
87+
return ':heavy_check_mark:'
88+
case 'PARTIAL':
89+
return ':warning:'
90+
case 'NO_MATCH':
91+
return ':x:'
92+
}
93+
}
94+
95+
task('verify-defender')
96+
.addVariadicPositionalParam('contracts', 'List of contracts to verify')
97+
.addOptionalParam(
98+
'referenceUrl',
99+
'URL to link to for artifact verification (defaults to $WORKFLOW_URL or the remote.origin.url of the repository)',
100+
)
101+
.setDescription('Verifies deployed implementations on Defender')
102+
.setAction(main)

0 commit comments

Comments
 (0)