Commit d59341a2 authored by Maurelian's avatar Maurelian Committed by GitHub

Merge pull request #1727 from...

Merge pull request #1727 from ethereum-optimism/maurelian/eng-1630-post-deployment-verification-script
parents 29eedf59 5c62a2be
---
'@eth-optimism/contracts': patch
---
Add AddressDictator validation script
......@@ -8,7 +8,7 @@ import { serialize } from '@ethersproject/transactions'
import { predeploys, getContractFactory } from '@eth-optimism/contracts'
/* Imports: Internal */
import {gasPriceForL2, isLiveNetwork} from './shared/utils'
import { gasPriceForL2, isLiveNetwork } from './shared/utils'
import { OptimismEnv } from './shared/env'
import { Direction } from './shared/watcher-utils'
......
......@@ -16,6 +16,8 @@ import '@typechain/hardhat'
import './tasks/deploy'
import './tasks/l2-gasprice'
import './tasks/set-owner'
import './tasks/validate-address-dictator'
import './tasks/validate-chugsplash-dictator'
import './tasks/whitelist'
import './tasks/withdraw-fees'
import 'hardhat-gas-reporter'
......
import { createInterface } from 'readline'
import { hexStringEquals } from '@eth-optimism/core-utils'
export const getInput = (query) => {
const rl = createInterface({
input: process.stdin,
output: process.stdout,
})
return new Promise((resolve) =>
rl.question(query, (ans) => {
rl.close()
resolve(ans)
})
)
}
const codes = {
reset: '\x1b[0m',
red: '\x1b[0;31m',
green: '\x1b[0;32m',
cyan: '\x1b[0;36m',
yellow: '\x1b[1;33m',
}
export const color = Object.fromEntries(
Object.entries(codes).map(([k]) => [
k,
(msg: string) => `${codes[k]}${msg}${codes.reset}`,
])
)
export const getArtifact = (name: string) => {
// Paths to artifacts relative to artifacts/contracts
const locations = {
'ChainStorageContainer-CTC-batches':
'L1/rollup/ChainStorageContainer.sol/ChainStorageContainer.json',
'ChainStorageContainer-SCC-batches':
'L1/rollup/ChainStorageContainer.sol/ChainStorageContainer.json',
CanonicalTransactionChain:
'L1/rollup/CanonicalTransactionChain.sol/CanonicalTransactionChain.json',
StateCommitmentChain:
'L1/rollup/StateCommitmentChain.sol/StateCommitmentChain.json',
BondManager: 'L1/verification/BondManager.sol/BondManager.json',
OVM_L1CrossDomainMessenger:
'L1/messaging/L1CrossDomainMessenger.sol/L1CrossDomainMessenger.json',
Proxy__OVM_L1CrossDomainMessenger:
'libraries/resolver/Lib_ResolvedDelegateProxy.sol/Lib_ResolvedDelegateProxy.json',
Proxy__OVM_L1StandardBridge:
'chugsplash/L1ChugSplashProxy.sol/L1ChugSplashProxy.json',
}
// eslint-disable-next-line @typescript-eslint/no-var-requires
return require(`../artifacts/contracts/${locations[name]}`)
}
export const getEtherscanUrl = (network, address: string) => {
const escPrefix = network.chainId !== 1 ? `${network.name}.` : ''
return `https://${escPrefix}etherscan.io/address/${address}`
}
// Reduces a byte string to first 32 bytes, with a '...' to indicate when it was shortened
const truncateLongString = (value: string): string => {
return value.length > 66 ? `${value.slice(0, 66)}...` : value
}
export const printComparison = (
action: string,
description: string,
expected: { name: string; value: string },
deployed: { name: string; value: string }
) => {
console.log(action + ':')
if (hexStringEquals(expected.value, deployed.value)) {
console.log(
color.green(
`${expected.name}: ${truncateLongString(expected.value)}
matches
${deployed.name}: ${truncateLongString(deployed.value)}`
)
)
console.log(color.green(`${description} looks good! 😎`))
} else {
throw new Error(`${description} looks wrong.
${expected.value}\ndoes not match\n${deployed.value}.
`)
}
console.log() // Add some whitespace
}
'use strict'
import { ethers } from 'ethers'
import { task } from 'hardhat/config'
import * as types from 'hardhat/internal/core/params/argumentTypes'
import { hexStringEquals } from '@eth-optimism/core-utils'
import { getContractFactory } from '../src/contract-defs'
import {
getInput,
color as c,
getArtifact,
getEtherscanUrl,
printComparison,
} from '../src/validation-utils'
task('validate:address-dictator')
.addParam(
'dictator',
'Address of the AddressDictator to validate.',
undefined,
types.string
)
.addParam(
'manager',
'Address of the Address Manager contract which would be updated by the Dictator.',
undefined,
types.string
)
.addParam(
'multisig',
'Address of the multisig contract which should be the final owner',
undefined,
types.string
)
.addOptionalParam(
'contractsRpcUrl',
'RPC Endpoint to query for data',
process.env.CONTRACTS_RPC_URL,
types.string
)
.setAction(async (args) => {
if (!args.contractsRpcUrl) {
throw new Error(
c.red('RPC URL must be set in your env, or passed as an argument.')
)
}
const provider = new ethers.providers.JsonRpcProvider(args.contractsRpcUrl)
const network = await provider.getNetwork()
console.log()
console.log(c.cyan("First make sure you're on the right chain:"))
console.log(
`Reading from the ${c.red(network.name)} network (Chain ID: ${c.red(
'' + network.chainId
)})`
)
await getInput(c.yellow('OK? Hit enter to continue.'))
// eslint-disable-next-line @typescript-eslint/no-var-requires
const dictatorArtifact = require('../artifacts/contracts/L1/deployment/AddressDictator.sol/AddressDictator.json')
const dictatorCode = await provider.getCode(args.dictator)
console.log(
c.cyan(`
Now validating the Address Dictator deployment at\n${getEtherscanUrl(
network,
args.dictator
)}`)
)
printComparison(
'Comparing deployed AddressDictator bytecode against local build artifacts',
'Deployed AddressDictator code',
{ name: 'Compiled bytecode', value: dictatorArtifact.deployedBytecode },
{ name: 'Deployed bytecode', value: dictatorCode }
)
// Connect to the deployed AddressDictator.
const dictatorContract = getContractFactory('AddressDictator')
.attach(args.dictator)
.connect(provider)
const finalOwner = await dictatorContract.finalOwner()
printComparison(
'Comparing the finalOwner address in the AddressDictator to the multisig address',
'finalOwner',
{ name: 'multisig address', value: args.multisig },
{ name: 'finalOwner ', value: finalOwner }
)
const manager = await dictatorContract.manager()
printComparison(
'Validating the AddressManager address in the AddressDictator',
'addressManager',
{ name: 'manager', value: args.manager },
{ name: 'Address Manager', value: manager }
)
await getInput(c.yellow('OK? Hit enter to continue.'))
// Get names and addresses from the Dictator.
const namedAddresses = await dictatorContract.getNamedAddresses()
// In order to reduce noise for the user, we query the AddressManager identify addresses that
// will not be changed, and skip over them in this block.
const managerContract = getContractFactory('Lib_AddressManager')
.attach(args.manager)
.connect(provider)
// Now we loop over those and compare the addresses/deployedBytecode to deployment artifacts.
for (const pair of namedAddresses) {
const currentAddress = await managerContract.getAddress(pair.name)
const artifact = getArtifact(pair.name)
const addressChanged = !hexStringEquals(currentAddress, pair.addr)
if (addressChanged) {
console.log(
c.cyan(`
Now validating the ${pair.name} deployment.
Current address: ${getEtherscanUrl(network, currentAddress)}
Upgraded address ${getEtherscanUrl(network, pair.addr)}`)
)
const code = await provider.getCode(pair.addr)
printComparison(
`Verifying ${pair.name} source code against local deployment artifacts`,
`Deployed ${pair.name} code`,
{
name: 'artifact.deployedBytecode',
value: artifact.deployedBytecode,
},
{ name: 'Deployed bytecode ', value: code }
)
// Identify contracts which inherit from Lib_AddressResolver, and check that they
// have the right manager address.
if (Object.keys(artifact)) {
if (artifact.abi.some((el) => el.name === 'libAddressManager')) {
const libAddressManager = await getContractFactory(
'Lib_AddressResolver'
)
.attach(pair.addr)
.connect(provider)
.libAddressManager()
printComparison(
`Verifying ${pair.name} has the correct AddressManager address`,
`The AddressManager address in ${pair.name}`,
{ name: 'Deployed value', value: libAddressManager },
{ name: 'Expected value', value: manager }
)
}
}
}
await getInput(c.yellow('OK? Hit enter to continue.'))
}
console.log(c.green('AddressManager Validation complete!'))
})
'use strict'
import { ethers } from 'ethers'
import { task } from 'hardhat/config'
import * as types from 'hardhat/internal/core/params/argumentTypes'
import { getContractFactory } from '../src/contract-defs'
import {
getInput,
color as c,
getEtherscanUrl,
printComparison,
} from '../src/validation-utils'
task('validate:chugsplash-dictator')
.addParam(
'dictator',
'Address of the ChugSplashDictator to validate.',
undefined,
types.string
)
.addParam(
'proxy',
'Address of the L1ChugSplashProxy to validate.',
undefined,
types.string
)
.addParam(
'multisig',
'Address of the multisig contract which should be the final owner',
undefined,
types.string
)
.addOptionalParam(
'contractsRpcUrl',
'RPC Endpoint to query for data',
process.env.CONTRACTS_RPC_URL,
types.string
)
.setAction(async (args) => {
if (!args.contractsRpcUrl) {
throw new Error(
c.red('RPC URL must be set in your env, or passed as an argument.')
)
}
const provider = new ethers.providers.JsonRpcProvider(args.contractsRpcUrl)
const network = await provider.getNetwork()
console.log() // the whitespacooooooor
console.log(c.cyan("First make sure you're on the right chain:"))
console.log(
`Reading from the ${c.red(network.name)} network (Chain ID: ${c.red(
'' + network.chainId
)})`
)
await getInput(c.yellow('OK? Hit enter to continue.'))
// eslint-disable-next-line @typescript-eslint/no-var-requires
const dictatorArtifact = require('../artifacts/contracts/L1/deployment/ChugSplashDictator.sol/ChugSplashDictator.json')
const dictatorCode = await provider.getCode(args.dictator)
console.log(
c.cyan(`
Now validating the Chugsplash Dictator deployment at\n${getEtherscanUrl(
network,
args.dictator
)}`)
)
printComparison(
'Comparing deployed ChugSplashDictator bytecode against local build artifacts',
'Deployed ChugSplashDictator code',
{ name: 'Compiled bytecode', value: dictatorArtifact.deployedBytecode },
{ name: 'Deployed bytecode', value: dictatorCode }
)
await getInput(c.yellow('OK? Hit enter to continue.'))
console.log(
c.cyan("The next 4 checks will validate the ChugSplashDictator's config")
)
// Connect to the deployed ChugSplashDictator.
const dictatorContract = getContractFactory('ChugSplashDictator')
.attach(args.dictator)
.connect(provider)
const finalOwner = await dictatorContract.finalOwner()
printComparison(
'1. Comparing the finalOwner address in the ChugSplashDictator to the multisig address',
'finalOwner',
{ name: 'multisig address', value: args.multisig },
{ name: 'finalOwner ', value: finalOwner }
)
await getInput(c.yellow('OK? Hit enter to continue.'))
const dictatorMessengerSlotKey = await dictatorContract.messengerSlotKey()
const dictatorMessengerSlotVal = await dictatorContract.messengerSlotVal()
const proxyMessengerSlotVal = await provider.getStorageAt(
args.proxy,
dictatorMessengerSlotKey
)
printComparison(
'2. Comparing the messenger slot key/value to be set, with the current values in the proxy',
`Storage slot key ${dictatorMessengerSlotKey}`,
{
name: `Value in the proxy at slot key\n${dictatorMessengerSlotKey}`,
value: proxyMessengerSlotVal,
},
{
name: `Dictator will setStorage at slot key\n${dictatorMessengerSlotKey}`,
value: dictatorMessengerSlotVal,
}
)
await getInput(c.yellow('OK? Hit enter to continue.'))
const dictatorBridgeSlotKey = await dictatorContract.bridgeSlotKey()
const dictatorBridgeSlotVal = await dictatorContract.bridgeSlotVal()
const proxyBridgeSlotVal = await provider.getStorageAt(
args.proxy,
dictatorBridgeSlotKey
)
printComparison(
'3. Comparing the _Bridge_ slot key/value to be set, with the current values in the proxy',
`Storage slot key ${dictatorBridgeSlotKey}`,
{
name: `Value currently in the proxy at slot key\n${dictatorBridgeSlotKey}`,
value: proxyBridgeSlotVal,
},
{
name: `Dictator will setStorage in the proxy at slot key\n${dictatorBridgeSlotKey}`,
value: dictatorBridgeSlotVal,
}
)
await getInput(c.yellow('OK? Hit enter to continue.'))
// eslint-disable-next-line @typescript-eslint/no-var-requires
const bridgeArtifact = require('../artifacts/contracts/L1/messaging/L1StandardBridge.sol/L1StandardBridge.json')
const expectedCodeHash = ethers.utils.keccak256(
bridgeArtifact.deployedBytecode
)
const actualCodeHash = await dictatorContract.codeHash()
printComparison(
"4. Comparing the Dictator's codeHash against hash of the local L1StandardBridge build artifacts",
"Dictator's codeHash",
{
name: 'Expected codeHash',
value: expectedCodeHash,
},
{
name: 'Actual codeHash',
value: actualCodeHash,
}
)
await getInput(c.yellow('OK? Hit enter to continue.'))
console.log(c.green('Chugsplash Dictator Validation complete!'))
})
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