-
Notifications
You must be signed in to change notification settings - Fork 3.5k
/
Copy pathdeposit-erc20.ts
413 lines (369 loc) · 13.5 KB
/
deposit-erc20.ts
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
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
import { promises as fs } from 'fs'
import { task, types } from 'hardhat/config'
import { HardhatRuntimeEnvironment } from 'hardhat/types'
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'
import '@nomiclabs/hardhat-ethers'
import 'hardhat-deploy'
import { Event, Contract, Wallet, providers, utils, ethers } from 'ethers'
import { predeploys, sleep } from '@eth-optimism/core-utils'
import Artifact__WETH9 from '@eth-optimism/sdk/dist/forge-artifacts/WETH9.json'
import Artifact__OptimismMintableERC20TokenFactory from '@eth-optimism/sdk/dist/forge-artifacts/OptimismMintableERC20Factory.json'
import Artifact__OptimismMintableERC20Token from '@eth-optimism/sdk/dist/forge-artifacts/OptimismMintableERC20.json'
import Artifact__L2ToL1MessagePasser from '@eth-optimism/sdk/dist/forge-artifacts/L2ToL1MessagePasser.json'
import Artifact__L2CrossDomainMessenger from '@eth-optimism/sdk/dist/forge-artifacts/L2CrossDomainMessenger.json'
import Artifact__L2StandardBridge from '@eth-optimism/sdk/dist/forge-artifacts/L2StandardBridge.json'
import Artifact__OptimismPortal from '@eth-optimism/sdk/dist/forge-artifacts/OptimismPortal.json'
import Artifact__L1CrossDomainMessenger from '@eth-optimism/sdk/dist/forge-artifacts/L1CrossDomainMessenger.json'
import Artifact__L1StandardBridge from '@eth-optimism/sdk/dist/forge-artifacts/L1StandardBridge.json'
import Artifact__L2OutputOracle from '@eth-optimism/sdk/dist/forge-artifacts/L2OutputOracle.json'
import {
CrossChainMessenger,
MessageStatus,
CONTRACT_ADDRESSES,
OEContractsLike,
DEFAULT_L2_CONTRACT_ADDRESSES,
} from '@eth-optimism/sdk/dist'
const deployWETH9 = async (
hre: HardhatRuntimeEnvironment,
signer: SignerWithAddress,
wrap: boolean
): Promise<Contract> => {
const Factory__WETH9 = new hre.ethers.ContractFactory(
Artifact__WETH9.abi,
Artifact__WETH9.bytecode.object,
signer
)
console.log('Sending deployment transaction')
const WETH9 = await Factory__WETH9.deploy()
const receipt = await WETH9.deployTransaction.wait()
console.log(`WETH9 deployed: ${receipt.transactionHash}`)
if (wrap) {
const deposit = await signer.sendTransaction({
value: utils.parseEther('1'),
to: WETH9.address,
})
await deposit.wait()
}
return WETH9
}
const createOptimismMintableERC20 = async (
hre: HardhatRuntimeEnvironment,
L1ERC20: Contract,
l2Signer: Wallet
): Promise<Contract> => {
const OptimismMintableERC20TokenFactory = new Contract(
predeploys.OptimismMintableERC20Factory,
Artifact__OptimismMintableERC20TokenFactory.abi,
l2Signer
)
const name = await L1ERC20.name()
const symbol = await L1ERC20.symbol()
const tx =
await OptimismMintableERC20TokenFactory.createOptimismMintableERC20(
L1ERC20.address,
`L2 ${name}`,
`L2-${symbol}`
)
const receipt = await tx.wait()
const event = receipt.events.find(
(e: Event) => e.event === 'OptimismMintableERC20Created'
)
if (!event) {
throw new Error('Unable to find OptimismMintableERC20Created event')
}
const l2WethAddress = event.args.localToken
console.log(`Deployed to ${l2WethAddress}`)
return new Contract(
l2WethAddress,
Artifact__OptimismMintableERC20Token.abi,
l2Signer
)
}
// TODO(tynes): this task could be modularized in the future
// so that it can deposit an arbitrary token. Right now it
// deploys a WETH9 contract, mints some WETH9 and then
// deposits that into L2 through the StandardBridge.
task('deposit-erc20', 'Deposits WETH9 onto L2.')
.addParam(
'l2ProviderUrl',
'L2 provider URL.',
'http://localhost:9545',
types.string
)
.addParam(
'opNodeProviderUrl',
'op-node provider URL',
'http://localhost:7545',
types.string
)
.addOptionalParam(
'l1ContractsJsonPath',
'Path to a JSON with L1 contract addresses in it',
'',
types.string
)
.addOptionalParam('signerIndex', 'Index of signer to use', 0, types.int)
.setAction(async (args, hre) => {
const signers = await hre.ethers.getSigners()
if (signers.length === 0) {
throw new Error('No configured signers')
}
if (args.signerIndex < 0 || signers.length <= args.signerIndex) {
throw new Error('Invalid signer index')
}
const signer = signers[args.signerIndex]
const address = await signer.getAddress()
console.log(`Using signer ${address}`)
// Ensure that the signer has a balance before trying to
// do anything
const balance = await signer.getBalance()
if (balance.eq(0)) {
throw new Error('Signer has no balance')
}
const l2Provider = new providers.StaticJsonRpcProvider(args.l2ProviderUrl)
const l2Signer = new hre.ethers.Wallet(
hre.network.config.accounts[args.signerIndex],
l2Provider
)
const l2ChainId = await l2Signer.getChainId()
let contractAddrs = CONTRACT_ADDRESSES[l2ChainId]
if (args.l1ContractsJsonPath) {
const data = await fs.readFile(args.l1ContractsJsonPath)
const json = JSON.parse(data.toString())
contractAddrs = {
l1: {
AddressManager: json.AddressManager,
L1CrossDomainMessenger: json.L1CrossDomainMessengerProxy,
L1StandardBridge: json.L1StandardBridgeProxy,
StateCommitmentChain: ethers.constants.AddressZero,
CanonicalTransactionChain: ethers.constants.AddressZero,
BondManager: ethers.constants.AddressZero,
OptimismPortal: json.OptimismPortalProxy,
L2OutputOracle: json.L2OutputOracleProxy,
OptimismPortal2: json.OptimismPortalProxy,
DisputeGameFactory: json.DisputeGameFactoryProxy,
},
l2: DEFAULT_L2_CONTRACT_ADDRESSES,
} as OEContractsLike
}
console.log(`OptimismPortal: ${contractAddrs.l1.OptimismPortal}`)
const OptimismPortal = new hre.ethers.Contract(
contractAddrs.l1.OptimismPortal,
Artifact__OptimismPortal.abi,
signer
)
console.log(
`L1CrossDomainMessenger: ${contractAddrs.l1.L1CrossDomainMessenger}`
)
const L1CrossDomainMessenger = new hre.ethers.Contract(
contractAddrs.l1.L1CrossDomainMessenger,
Artifact__L1CrossDomainMessenger.abi,
signer
)
console.log(`L1StandardBridge: ${contractAddrs.l1.L1StandardBridge}`)
const L1StandardBridge = new hre.ethers.Contract(
contractAddrs.l1.L1StandardBridge,
Artifact__L1StandardBridge.abi,
signer
)
const L2OutputOracle = new hre.ethers.Contract(
contractAddrs.l1.L2OutputOracle,
Artifact__L2OutputOracle.abi,
signer
)
const L2ToL1MessagePasser = new hre.ethers.Contract(
predeploys.L2ToL1MessagePasser,
Artifact__L2ToL1MessagePasser.abi
)
const L2CrossDomainMessenger = new hre.ethers.Contract(
predeploys.L2CrossDomainMessenger,
Artifact__L2CrossDomainMessenger.abi
)
const L2StandardBridge = new hre.ethers.Contract(
predeploys.L2StandardBridge,
Artifact__L2StandardBridge.abi
)
const messenger = new CrossChainMessenger({
l1SignerOrProvider: signer,
l2SignerOrProvider: l2Signer,
l1ChainId: await signer.getChainId(),
l2ChainId,
bedrock: true,
contracts: contractAddrs,
})
const params = await OptimismPortal.params()
console.log('Intial OptimismPortal.params:')
console.log(params)
console.log('Deploying WETH9 to L1')
const WETH9 = await deployWETH9(hre, signer, true)
console.log(`Deployed to ${WETH9.address}`)
console.log('Creating L2 WETH9')
const OptimismMintableERC20 = await createOptimismMintableERC20(
hre,
WETH9,
l2Signer
)
console.log(`Approving WETH9 for deposit`)
const approvalTx = await messenger.approveERC20(
WETH9.address,
OptimismMintableERC20.address,
hre.ethers.constants.MaxUint256
)
await approvalTx.wait()
console.log('WETH9 approved')
console.log('Depositing WETH9 to L2')
const depositTx = await messenger.depositERC20(
WETH9.address,
OptimismMintableERC20.address,
utils.parseEther('1')
)
await depositTx.wait()
console.log(`ERC20 deposited - ${depositTx.hash}`)
console.log('Checking to make sure deposit was successful')
// Deposit might get reorged, wait and also log for reorgs.
let prevBlockHash: string = ''
for (let i = 0; i < 12; i++) {
const messageReceipt = await signer.provider!.getTransactionReceipt(
depositTx.hash
)
if (messageReceipt.status !== 1) {
console.log(`Deposit failed, retrying...`)
}
// Wait for stability, we want some amount of time after any reorg
if (prevBlockHash !== '' && messageReceipt.blockHash !== prevBlockHash) {
console.log(
`Block hash changed from ${prevBlockHash} to ${messageReceipt.blockHash}`
)
i = 0
} else if (prevBlockHash !== '') {
console.log(`No reorg detected: ${i}`)
}
prevBlockHash = messageReceipt.blockHash
await sleep(1000)
}
console.log(`Deposit confirmed`)
const l2Balance = await OptimismMintableERC20.balanceOf(address)
if (l2Balance.lt(utils.parseEther('1'))) {
throw new Error(
`bad deposit. recipient balance on L2: ${utils.formatEther(l2Balance)}`
)
}
console.log(`Deposit success`)
console.log('Starting withdrawal')
const preBalance = await WETH9.balanceOf(signer.address)
const withdraw = await messenger.withdrawERC20(
WETH9.address,
OptimismMintableERC20.address,
utils.parseEther('1')
)
const withdrawalReceipt = await withdraw.wait()
for (const log of withdrawalReceipt.logs) {
switch (log.address) {
case L2ToL1MessagePasser.address: {
const parsed = L2ToL1MessagePasser.interface.parseLog(log)
console.log(`Log ${parsed.name} from ${log.address}`)
console.log(parsed.args)
console.log()
break
}
case L2StandardBridge.address: {
const parsed = L2StandardBridge.interface.parseLog(log)
console.log(`Log ${parsed.name} from ${log.address}`)
console.log(parsed.args)
console.log()
break
}
case L2CrossDomainMessenger.address: {
const parsed = L2CrossDomainMessenger.interface.parseLog(log)
console.log(`Log ${parsed.name} from ${log.address}`)
console.log(parsed.args)
console.log()
break
}
default: {
console.log(`Unknown log from ${log.address} - ${log.topics[0]}`)
}
}
}
setInterval(async () => {
const currentStatus = await messenger.getMessageStatus(withdraw)
console.log(`Message status: ${MessageStatus[currentStatus]}`)
const latest = await L2OutputOracle.latestBlockNumber()
console.log(
`Latest L2OutputOracle commitment number: ${latest.toString()}`
)
const tip = await signer.provider!.getBlockNumber()
console.log(`L1 chain tip: ${tip.toString()}`)
}, 3000)
const now = Math.floor(Date.now() / 1000)
console.log('Waiting for message to be able to be proved')
await messenger.waitForMessageStatus(withdraw, MessageStatus.READY_TO_PROVE)
console.log('Proving withdrawal...')
const prove = await messenger.proveMessage(withdraw)
const proveReceipt = await prove.wait()
console.log(proveReceipt)
if (proveReceipt.status !== 1) {
throw new Error('Prove withdrawal transaction reverted')
}
console.log('Waiting for message to be able to be relayed')
await messenger.waitForMessageStatus(
withdraw,
MessageStatus.READY_FOR_RELAY
)
console.log('Finalizing withdrawal...')
// TODO: Update SDK to properly estimate gas
const finalize = await messenger.finalizeMessage(withdraw, {
overrides: { gasLimit: 500_000 },
})
const finalizeReceipt = await finalize.wait()
console.log('finalizeReceipt:', finalizeReceipt)
console.log(`Took ${Math.floor(Date.now() / 1000) - now} seconds`)
for (const log of finalizeReceipt.logs) {
switch (log.address) {
case OptimismPortal.address: {
const parsed = OptimismPortal.interface.parseLog(log)
console.log(`Log ${parsed.name} from OptimismPortal (${log.address})`)
console.log(parsed.args)
console.log()
break
}
case L1CrossDomainMessenger.address: {
const parsed = L1CrossDomainMessenger.interface.parseLog(log)
console.log(
`Log ${parsed.name} from L1CrossDomainMessenger (${log.address})`
)
console.log(parsed.args)
console.log()
break
}
case L1StandardBridge.address: {
const parsed = L1StandardBridge.interface.parseLog(log)
console.log(
`Log ${parsed.name} from L1StandardBridge (${log.address})`
)
console.log(parsed.args)
console.log()
break
}
case WETH9.address: {
const parsed = WETH9.interface.parseLog(log)
console.log(`Log ${parsed.name} from WETH9 (${log.address})`)
console.log(parsed.args)
console.log()
break
}
default:
console.log(
`Unknown log emitted from ${log.address} - ${log.topics[0]}`
)
}
}
const postBalance = await WETH9.balanceOf(signer.address)
const expectedBalance = preBalance.add(utils.parseEther('1'))
if (!expectedBalance.eq(postBalance)) {
throw new Error(
`Balance mismatch, expected: ${expectedBalance}, actual: ${postBalance}`
)
}
console.log('Withdrawal success')
})