Skip to content

Commit d59341a

Browse files
authored
Merge pull request #1727 from ethereum-optimism/maurelian/eng-1630-post-deployment-verification-script
2 parents 29eedf5 + 5c62a2b commit d59341a

6 files changed

Lines changed: 403 additions & 1 deletion

File tree

.changeset/three-cheetahs-tie.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@eth-optimism/contracts': patch
3+
---
4+
5+
Add AddressDictator validation script

integration-tests/test/fee-payment.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { serialize } from '@ethersproject/transactions'
88
import { predeploys, getContractFactory } from '@eth-optimism/contracts'
99

1010
/* Imports: Internal */
11-
import {gasPriceForL2, isLiveNetwork} from './shared/utils'
11+
import { gasPriceForL2, isLiveNetwork } from './shared/utils'
1212
import { OptimismEnv } from './shared/env'
1313
import { Direction } from './shared/watcher-utils'
1414

packages/contracts/hardhat.config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import '@typechain/hardhat'
1616
import './tasks/deploy'
1717
import './tasks/l2-gasprice'
1818
import './tasks/set-owner'
19+
import './tasks/validate-address-dictator'
20+
import './tasks/validate-chugsplash-dictator'
1921
import './tasks/whitelist'
2022
import './tasks/withdraw-fees'
2123
import 'hardhat-gas-reporter'
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { createInterface } from 'readline'
2+
import { hexStringEquals } from '@eth-optimism/core-utils'
3+
4+
export const getInput = (query) => {
5+
const rl = createInterface({
6+
input: process.stdin,
7+
output: process.stdout,
8+
})
9+
10+
return new Promise((resolve) =>
11+
rl.question(query, (ans) => {
12+
rl.close()
13+
resolve(ans)
14+
})
15+
)
16+
}
17+
18+
const codes = {
19+
reset: '\x1b[0m',
20+
red: '\x1b[0;31m',
21+
green: '\x1b[0;32m',
22+
cyan: '\x1b[0;36m',
23+
yellow: '\x1b[1;33m',
24+
}
25+
26+
export const color = Object.fromEntries(
27+
Object.entries(codes).map(([k]) => [
28+
k,
29+
(msg: string) => `${codes[k]}${msg}${codes.reset}`,
30+
])
31+
)
32+
33+
export const getArtifact = (name: string) => {
34+
// Paths to artifacts relative to artifacts/contracts
35+
const locations = {
36+
'ChainStorageContainer-CTC-batches':
37+
'L1/rollup/ChainStorageContainer.sol/ChainStorageContainer.json',
38+
'ChainStorageContainer-SCC-batches':
39+
'L1/rollup/ChainStorageContainer.sol/ChainStorageContainer.json',
40+
CanonicalTransactionChain:
41+
'L1/rollup/CanonicalTransactionChain.sol/CanonicalTransactionChain.json',
42+
StateCommitmentChain:
43+
'L1/rollup/StateCommitmentChain.sol/StateCommitmentChain.json',
44+
BondManager: 'L1/verification/BondManager.sol/BondManager.json',
45+
OVM_L1CrossDomainMessenger:
46+
'L1/messaging/L1CrossDomainMessenger.sol/L1CrossDomainMessenger.json',
47+
Proxy__OVM_L1CrossDomainMessenger:
48+
'libraries/resolver/Lib_ResolvedDelegateProxy.sol/Lib_ResolvedDelegateProxy.json',
49+
Proxy__OVM_L1StandardBridge:
50+
'chugsplash/L1ChugSplashProxy.sol/L1ChugSplashProxy.json',
51+
}
52+
// eslint-disable-next-line @typescript-eslint/no-var-requires
53+
return require(`../artifacts/contracts/${locations[name]}`)
54+
}
55+
56+
export const getEtherscanUrl = (network, address: string) => {
57+
const escPrefix = network.chainId !== 1 ? `${network.name}.` : ''
58+
return `https://${escPrefix}etherscan.io/address/${address}`
59+
}
60+
61+
// Reduces a byte string to first 32 bytes, with a '...' to indicate when it was shortened
62+
const truncateLongString = (value: string): string => {
63+
return value.length > 66 ? `${value.slice(0, 66)}...` : value
64+
}
65+
66+
export const printComparison = (
67+
action: string,
68+
description: string,
69+
expected: { name: string; value: string },
70+
deployed: { name: string; value: string }
71+
) => {
72+
console.log(action + ':')
73+
if (hexStringEquals(expected.value, deployed.value)) {
74+
console.log(
75+
color.green(
76+
`${expected.name}: ${truncateLongString(expected.value)}
77+
matches
78+
${deployed.name}: ${truncateLongString(deployed.value)}`
79+
)
80+
)
81+
console.log(color.green(`${description} looks good! 😎`))
82+
} else {
83+
throw new Error(`${description} looks wrong.
84+
${expected.value}\ndoes not match\n${deployed.value}.
85+
`)
86+
}
87+
console.log() // Add some whitespace
88+
}
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
'use strict'
2+
3+
import { ethers } from 'ethers'
4+
import { task } from 'hardhat/config'
5+
import * as types from 'hardhat/internal/core/params/argumentTypes'
6+
import { hexStringEquals } from '@eth-optimism/core-utils'
7+
import { getContractFactory } from '../src/contract-defs'
8+
9+
import {
10+
getInput,
11+
color as c,
12+
getArtifact,
13+
getEtherscanUrl,
14+
printComparison,
15+
} from '../src/validation-utils'
16+
17+
task('validate:address-dictator')
18+
.addParam(
19+
'dictator',
20+
'Address of the AddressDictator to validate.',
21+
undefined,
22+
types.string
23+
)
24+
.addParam(
25+
'manager',
26+
'Address of the Address Manager contract which would be updated by the Dictator.',
27+
undefined,
28+
types.string
29+
)
30+
.addParam(
31+
'multisig',
32+
'Address of the multisig contract which should be the final owner',
33+
undefined,
34+
types.string
35+
)
36+
.addOptionalParam(
37+
'contractsRpcUrl',
38+
'RPC Endpoint to query for data',
39+
process.env.CONTRACTS_RPC_URL,
40+
types.string
41+
)
42+
.setAction(async (args) => {
43+
if (!args.contractsRpcUrl) {
44+
throw new Error(
45+
c.red('RPC URL must be set in your env, or passed as an argument.')
46+
)
47+
}
48+
const provider = new ethers.providers.JsonRpcProvider(args.contractsRpcUrl)
49+
50+
const network = await provider.getNetwork()
51+
console.log()
52+
console.log(c.cyan("First make sure you're on the right chain:"))
53+
console.log(
54+
`Reading from the ${c.red(network.name)} network (Chain ID: ${c.red(
55+
'' + network.chainId
56+
)})`
57+
)
58+
await getInput(c.yellow('OK? Hit enter to continue.'))
59+
60+
// eslint-disable-next-line @typescript-eslint/no-var-requires
61+
const dictatorArtifact = require('../artifacts/contracts/L1/deployment/AddressDictator.sol/AddressDictator.json')
62+
const dictatorCode = await provider.getCode(args.dictator)
63+
console.log(
64+
c.cyan(`
65+
Now validating the Address Dictator deployment at\n${getEtherscanUrl(
66+
network,
67+
args.dictator
68+
)}`)
69+
)
70+
printComparison(
71+
'Comparing deployed AddressDictator bytecode against local build artifacts',
72+
'Deployed AddressDictator code',
73+
{ name: 'Compiled bytecode', value: dictatorArtifact.deployedBytecode },
74+
{ name: 'Deployed bytecode', value: dictatorCode }
75+
)
76+
77+
// Connect to the deployed AddressDictator.
78+
const dictatorContract = getContractFactory('AddressDictator')
79+
.attach(args.dictator)
80+
.connect(provider)
81+
82+
const finalOwner = await dictatorContract.finalOwner()
83+
printComparison(
84+
'Comparing the finalOwner address in the AddressDictator to the multisig address',
85+
'finalOwner',
86+
{ name: 'multisig address', value: args.multisig },
87+
{ name: 'finalOwner ', value: finalOwner }
88+
)
89+
90+
const manager = await dictatorContract.manager()
91+
printComparison(
92+
'Validating the AddressManager address in the AddressDictator',
93+
'addressManager',
94+
{ name: 'manager', value: args.manager },
95+
{ name: 'Address Manager', value: manager }
96+
)
97+
await getInput(c.yellow('OK? Hit enter to continue.'))
98+
99+
// Get names and addresses from the Dictator.
100+
const namedAddresses = await dictatorContract.getNamedAddresses()
101+
102+
// In order to reduce noise for the user, we query the AddressManager identify addresses that
103+
// will not be changed, and skip over them in this block.
104+
const managerContract = getContractFactory('Lib_AddressManager')
105+
.attach(args.manager)
106+
.connect(provider)
107+
108+
// Now we loop over those and compare the addresses/deployedBytecode to deployment artifacts.
109+
for (const pair of namedAddresses) {
110+
const currentAddress = await managerContract.getAddress(pair.name)
111+
const artifact = getArtifact(pair.name)
112+
const addressChanged = !hexStringEquals(currentAddress, pair.addr)
113+
if (addressChanged) {
114+
console.log(
115+
c.cyan(`
116+
Now validating the ${pair.name} deployment.
117+
Current address: ${getEtherscanUrl(network, currentAddress)}
118+
Upgraded address ${getEtherscanUrl(network, pair.addr)}`)
119+
)
120+
121+
const code = await provider.getCode(pair.addr)
122+
printComparison(
123+
`Verifying ${pair.name} source code against local deployment artifacts`,
124+
`Deployed ${pair.name} code`,
125+
{
126+
name: 'artifact.deployedBytecode',
127+
value: artifact.deployedBytecode,
128+
},
129+
{ name: 'Deployed bytecode ', value: code }
130+
)
131+
132+
// Identify contracts which inherit from Lib_AddressResolver, and check that they
133+
// have the right manager address.
134+
if (Object.keys(artifact)) {
135+
if (artifact.abi.some((el) => el.name === 'libAddressManager')) {
136+
const libAddressManager = await getContractFactory(
137+
'Lib_AddressResolver'
138+
)
139+
.attach(pair.addr)
140+
.connect(provider)
141+
.libAddressManager()
142+
143+
printComparison(
144+
`Verifying ${pair.name} has the correct AddressManager address`,
145+
`The AddressManager address in ${pair.name}`,
146+
{ name: 'Deployed value', value: libAddressManager },
147+
{ name: 'Expected value', value: manager }
148+
)
149+
}
150+
}
151+
}
152+
await getInput(c.yellow('OK? Hit enter to continue.'))
153+
}
154+
console.log(c.green('AddressManager Validation complete!'))
155+
})

0 commit comments

Comments
 (0)