1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
import assert from 'assert'
import { ethers, Contract } from 'ethers'
import { Provider } from '@ethersproject/abstract-provider'
import { Signer } from '@ethersproject/abstract-signer'
import { sleep, getChainId } from '@eth-optimism/core-utils'
import { HardhatRuntimeEnvironment } from 'hardhat/types'
import 'hardhat-deploy'
import '@eth-optimism/hardhat-deploy-config'
import '@nomiclabs/hardhat-ethers'
export interface DictatorConfig {
globalConfig: {
proxyAdmin: string
controller: string
finalOwner: string
addressManager: string
}
proxyAddressConfig: {
l2OutputOracleProxy: string
optimismPortalProxy: string
l1CrossDomainMessengerProxy: string
l1StandardBridgeProxy: string
optimismMintableERC20FactoryProxy: string
l1ERC721BridgeProxy: string
systemConfigProxy: string
}
implementationAddressConfig: {
l2OutputOracleImpl: string
optimismPortalImpl: string
l1CrossDomainMessengerImpl: string
l1StandardBridgeImpl: string
optimismMintableERC20FactoryImpl: string
l1ERC721BridgeImpl: string
portalSenderImpl: string
systemConfigImpl: string
}
systemConfigConfig: {
owner: string
overhead: number
scalar: number
batcherHash: string
gasLimit: number
}
}
export const deployAndVerifyAndThen = async ({
hre,
name,
args,
contract,
iface,
postDeployAction,
}: {
hre: HardhatRuntimeEnvironment
name: string
args: any[]
contract?: string
iface?: string
postDeployAction?: (contract: Contract) => Promise<void>
}) => {
const { deploy } = hre.deployments
const { deployer } = await hre.getNamedAccounts()
// Hardhat deploy will usually do this check for us, but currently doesn't also consider
// external deployments when doing this check. By doing the check ourselves, we also get to
// consider external deployments. If we already have the deployment, return early.
const existing = await hre.deployments.getOrNull(name)
if (existing) {
console.log(
`skipping ${name} deployment, using existing at ${existing.address}`
)
return
}
const result = await deploy(name, {
contract,
from: deployer,
args,
log: true,
waitConfirmations: hre.deployConfig.numDeployConfirmations,
})
await hre.ethers.provider.waitForTransaction(result.transactionHash)
if (result.newlyDeployed) {
if (postDeployAction) {
const signer = hre.ethers.provider.getSigner(deployer)
let abi = result.abi
if (iface !== undefined) {
const factory = await hre.ethers.getContractFactory(iface)
abi = factory.interface as any
}
await postDeployAction(
getAdvancedContract({
hre,
contract: new Contract(result.address, abi, signer),
})
)
}
}
}
// Returns a version of the contract object which modifies all of the input contract's methods to:
// 1. Waits for a confirmed receipt with more than deployConfig.numDeployConfirmations confirmations.
// 2. Include simple resubmission logic, ONLY for Kovan, which appears to drop transactions.
export const getAdvancedContract = (opts: {
hre: any
contract: Contract
}): Contract => {
// Temporarily override Object.defineProperty to bypass ether's object protection.
const def = Object.defineProperty
Object.defineProperty = (obj, propName, prop) => {
prop.writable = true
return def(obj, propName, prop)
}
const contract = new Contract(
opts.contract.address,
opts.contract.interface,
opts.contract.signer || opts.contract.provider
)
// Now reset Object.defineProperty
Object.defineProperty = def
// Override each function call to also `.wait()` so as to simplify the deploy scripts' syntax.
for (const fnName of Object.keys(contract.functions)) {
const fn = contract[fnName].bind(contract)
;(contract as any)[fnName] = async (...args: any) => {
// We want to use the gas price that has been configured at the beginning of the deployment.
// However, if the function being triggered is a "constant" (static) function, then we don't
// want to provide a gas price because we're prone to getting insufficient balance errors.
let gasPrice: number | undefined
try {
gasPrice = opts.hre.deployConfig.gasPrice
} catch (err) {
// Fine, no gas price
}
if (contract.interface.getFunction(fnName).constant) {
gasPrice = 0
}
const tx = await fn(...args, {
gasPrice,
})
if (typeof tx !== 'object' || typeof tx.wait !== 'function') {
return tx
}
// Special logic for:
// (1) handling confirmations
// (2) handling an issue on Kovan specifically where transactions get dropped for no
// apparent reason.
const maxTimeout = 120
let timeout = 0
while (true) {
await sleep(1000)
const receipt = await contract.provider.getTransactionReceipt(tx.hash)
if (receipt === null) {
timeout++
if (timeout > maxTimeout && opts.hre.network.name === 'kovan') {
// Special resubmission logic ONLY required on Kovan.
console.log(
`WARNING: Exceeded max timeout on transaction. Attempting to submit transaction again...`
)
return contract[fnName](...args)
}
} else if (
receipt.confirmations >= opts.hre.deployConfig.numDeployConfirmations
) {
return tx
}
}
}
}
return contract
}
export const getContractFromArtifact = async (
hre: any,
name: string,
options: {
iface?: string
signerOrProvider?: Signer | Provider | string
} = {}
): Promise<ethers.Contract> => {
const artifact = await hre.deployments.get(name)
await hre.ethers.provider.waitForTransaction(artifact.receipt.transactionHash)
// Get the deployed contract's interface.
let iface = new hre.ethers.utils.Interface(artifact.abi)
// Override with optional iface name if requested.
if (options.iface) {
const factory = await hre.ethers.getContractFactory(options.iface)
iface = factory.interface
}
let signerOrProvider: Signer | Provider = hre.ethers.provider
if (options.signerOrProvider) {
if (typeof options.signerOrProvider === 'string') {
signerOrProvider = hre.ethers.provider.getSigner(options.signerOrProvider)
} else {
signerOrProvider = options.signerOrProvider
}
}
return getAdvancedContract({
hre,
contract: new hre.ethers.Contract(
artifact.address,
iface,
signerOrProvider
),
})
}
export const getContractsFromArtifacts = async (
hre: any,
configs: Array<{
name: string
iface?: string
signerOrProvider?: Signer | Provider | string
}>
): Promise<ethers.Contract[]> => {
const contracts = []
for (const config of configs) {
contracts.push(await getContractFromArtifact(hre, config.name, config))
}
return contracts
}
export const isHardhatNode = async (hre) => {
return (await getChainId(hre.ethers.provider)) === 31337
}
export const assertContractVariable = async (
contract: ethers.Contract,
variable: string,
expected: any
) => {
// Need to make a copy that doesn't have a signer or we get the error that contracts with
// signers cannot override the from address.
const temp = new ethers.Contract(
contract.address,
contract.interface,
contract.provider
)
const actual = await temp.callStatic[variable]({
from: ethers.constants.AddressZero,
})
if (ethers.utils.isAddress(expected)) {
assert(
actual.toLowerCase() === expected.toLowerCase(),
`[FATAL] ${variable} is ${actual} but should be ${expected}`
)
return
}
assert(
actual === expected || (actual.eq && actual.eq(expected)),
`[FATAL] ${variable} is ${actual} but should be ${expected}`
)
}
export const getDeploymentAddress = async (
hre: any,
name: string
): Promise<string> => {
const deployment = await hre.deployments.get(name)
return deployment.address
}
export const makeDictatorConfig = async (
hre: any,
controller: string,
finalOwner: string,
fresh: boolean
): Promise<DictatorConfig> => {
return {
globalConfig: {
proxyAdmin: await getDeploymentAddress(hre, 'ProxyAdmin'),
controller,
finalOwner,
addressManager: fresh
? ethers.constants.AddressZero
: await getDeploymentAddress(hre, 'Lib_AddressManager'),
},
proxyAddressConfig: {
l2OutputOracleProxy: await getDeploymentAddress(
hre,
'L2OutputOracleProxy'
),
optimismPortalProxy: await getDeploymentAddress(
hre,
'OptimismPortalProxy'
),
l1CrossDomainMessengerProxy: await getDeploymentAddress(
hre,
fresh
? 'L1CrossDomainMessengerProxy'
: 'Proxy__OVM_L1CrossDomainMessenger'
),
l1StandardBridgeProxy: await getDeploymentAddress(
hre,
fresh ? 'L1StandardBridgeProxy' : 'Proxy__OVM_L1StandardBridge'
),
optimismMintableERC20FactoryProxy: await getDeploymentAddress(
hre,
'OptimismMintableERC20FactoryProxy'
),
l1ERC721BridgeProxy: await getDeploymentAddress(
hre,
'L1ERC721BridgeProxy'
),
systemConfigProxy: await getDeploymentAddress(hre, 'SystemConfigProxy'),
},
implementationAddressConfig: {
l2OutputOracleImpl: await getDeploymentAddress(hre, 'L2OutputOracle'),
optimismPortalImpl: await getDeploymentAddress(hre, 'OptimismPortal'),
l1CrossDomainMessengerImpl: await getDeploymentAddress(
hre,
'L1CrossDomainMessenger'
),
l1StandardBridgeImpl: await getDeploymentAddress(hre, 'L1StandardBridge'),
optimismMintableERC20FactoryImpl: await getDeploymentAddress(
hre,
'OptimismMintableERC20Factory'
),
l1ERC721BridgeImpl: await getDeploymentAddress(hre, 'L1ERC721Bridge'),
portalSenderImpl: await getDeploymentAddress(hre, 'PortalSender'),
systemConfigImpl: await getDeploymentAddress(hre, 'SystemConfig'),
},
systemConfigConfig: {
owner: hre.deployConfig.systemConfigOwner,
overhead: hre.deployConfig.gasPriceOracleOverhead,
scalar: hre.deployConfig.gasPriceOracleDecimals,
batcherHash: hre.ethers.utils.hexZeroPad(
hre.deployConfig.batchSenderAddress,
32
),
gasLimit: hre.deployConfig.l2GenesisBlockGasLimit,
},
}
}
export const assertDictatorConfig = async (
dictator: Contract,
config: DictatorConfig
) => {
const dictatorConfig = await dictator.config()
for (const [outerConfigKey, outerConfigValue] of Object.entries(config)) {
for (const [innerConfigKey, innerConfigValue] of Object.entries(
outerConfigValue
)) {
let have = dictatorConfig[outerConfigKey][innerConfigKey]
let want = innerConfigValue as any
if (ethers.utils.isAddress(want)) {
want = want.toLowerCase()
have = have.toLowerCase()
} else if (typeof want === 'number') {
want = ethers.BigNumber.from(want)
have = ethers.BigNumber.from(have)
assert(
want.eq(have),
`incorrect config for ${outerConfigKey}.${innerConfigKey}. Want: ${want}, have: ${have}`
)
return
}
assert(
want === have,
`incorrect config for ${outerConfigKey}.${innerConfigKey}. Want: ${want}, have: ${have}`
)
}
}
}
// Large balance to fund accounts with.
export const BIG_BALANCE = ethers.BigNumber.from(`0xFFFFFFFFFFFFFFFFFFFF`)