deposit-erc20.ts 13.7 KB
Newer Older
1 2
import { promises as fs } from 'fs'

3 4
import { task, types } from 'hardhat/config'
import { HardhatRuntimeEnvironment } from 'hardhat/types'
5
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'
6 7
import '@nomiclabs/hardhat-ethers'
import 'hardhat-deploy'
Mark Tyneway's avatar
Mark Tyneway committed
8
import { Event, Contract, Wallet, providers, utils, ethers } from 'ethers'
Mark Tyneway's avatar
Mark Tyneway committed
9 10 11 12 13 14 15 16 17 18
import { predeploys, sleep } from '@eth-optimism/core-utils'
import Artifact__WETH9 from '@eth-optimism/contracts-bedrock/forge-artifacts/WETH9.sol/WETH9.json'
import Artifact__OptimismMintableERC20TokenFactory from '@eth-optimism/contracts-bedrock/forge-artifacts/OptimismMintableERC20Factory.sol/OptimismMintableERC20Factory.json'
import Artifact__OptimismMintableERC20Token from '@eth-optimism/contracts-bedrock/forge-artifacts/OptimismMintableERC20.sol/OptimismMintableERC20.json'
import Artifact__L2ToL1MessagePasser from '@eth-optimism/contracts-bedrock/forge-artifacts/L2ToL1MessagePasser.sol/L2ToL1MessagePasser.json'
import Artifact__L2CrossDomainMessenger from '@eth-optimism/contracts-bedrock/forge-artifacts/L2CrossDomainMessenger.sol/L2CrossDomainMessenger.json'
import Artifact__L2StandardBridge from '@eth-optimism/contracts-bedrock/forge-artifacts/L2StandardBridge.sol/L2StandardBridge.json'
import Artifact__OptimismPortal from '@eth-optimism/contracts-bedrock/forge-artifacts/OptimismPortal.sol/OptimismPortal.json'
import Artifact__L1CrossDomainMessenger from '@eth-optimism/contracts-bedrock/forge-artifacts/L1CrossDomainMessenger.sol/L1CrossDomainMessenger.json'
import Artifact__L1StandardBridge from '@eth-optimism/contracts-bedrock/forge-artifacts/L1StandardBridge.sol/L1StandardBridge.json'
Mark Tyneway's avatar
Mark Tyneway committed
19
import Artifact__L2OutputOracle from '@eth-optimism/contracts-bedrock/forge-artifacts/L2OutputOracle.sol/L2OutputOracle.json'
20

21 22 23 24 25 26 27
import {
  CrossChainMessenger,
  MessageStatus,
  CONTRACT_ADDRESSES,
  OEContractsLike,
  DEFAULT_L2_CONTRACT_ADDRESSES,
} from '../src'
28 29 30

const deployWETH9 = async (
  hre: HardhatRuntimeEnvironment,
31
  signer: SignerWithAddress,
32 33 34 35
  wrap: boolean
): Promise<Contract> => {
  const Factory__WETH9 = new hre.ethers.ContractFactory(
    Artifact__WETH9.abi,
Mark Tyneway's avatar
Mark Tyneway committed
36
    Artifact__WETH9.bytecode.object,
37 38 39
    signer
  )

40
  console.log('Sending deployment transaction')
41
  const WETH9 = await Factory__WETH9.deploy()
42 43
  const receipt = await WETH9.deployTransaction.wait()
  console.log(`WETH9 deployed: ${receipt.transactionHash}`)
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60

  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> => {
61
  const OptimismMintableERC20TokenFactory = new Contract(
62
    predeploys.OptimismMintableERC20Factory,
63
    Artifact__OptimismMintableERC20TokenFactory.abi,
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
    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')
  }

86
  const l2WethAddress = event.args.localToken
87 88
  console.log(`Deployed to ${l2WethAddress}`)

89
  return new Contract(
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
    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
  )
113 114 115 116 117 118
  .addOptionalParam(
    'l1ContractsJsonPath',
    'Path to a JSON with L1 contract addresses in it',
    '',
    types.string
  )
119
  .addOptionalParam('signerIndex', 'Index of signer to use', 0, types.int)
120 121 122 123 124
  .setAction(async (args, hre) => {
    const signers = await hre.ethers.getSigners()
    if (signers.length === 0) {
      throw new Error('No configured signers')
    }
125 126 127 128
    if (args.signerIndex < 0 || signers.length <= args.signerIndex) {
      throw new Error('Invalid signer index')
    }
    const signer = signers[args.signerIndex]
129 130 131 132 133 134 135 136 137 138 139 140 141
    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(
142
      hre.network.config.accounts[args.signerIndex],
143 144 145
      l2Provider
    )

146
    const l2ChainId = await l2Signer.getChainId()
147 148 149
    let contractAddrs = CONTRACT_ADDRESSES[l2ChainId]
    if (args.l1ContractsJsonPath) {
      const data = await fs.readFile(args.l1ContractsJsonPath)
Mark Tyneway's avatar
Mark Tyneway committed
150
      const json = JSON.parse(data.toString())
151
      contractAddrs = {
Mark Tyneway's avatar
Mark Tyneway committed
152 153 154 155 156 157 158 159 160 161
        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,
        },
162 163 164
        l2: DEFAULT_L2_CONTRACT_ADDRESSES,
      } as OEContractsLike
    }
165

Mark Tyneway's avatar
Mark Tyneway committed
166
    console.log(`OptimismPortal: ${contractAddrs.l1.OptimismPortal}`)
167
    const OptimismPortal = new hre.ethers.Contract(
168 169
      contractAddrs.l1.OptimismPortal,
      Artifact__OptimismPortal.abi,
170 171 172
      signer
    )

Mark Tyneway's avatar
Mark Tyneway committed
173 174 175
    console.log(
      `L1CrossDomainMessenger: ${contractAddrs.l1.L1CrossDomainMessenger}`
    )
176
    const L1CrossDomainMessenger = new hre.ethers.Contract(
177 178
      contractAddrs.l1.L1CrossDomainMessenger,
      Artifact__L1CrossDomainMessenger.abi,
179 180 181
      signer
    )

Mark Tyneway's avatar
Mark Tyneway committed
182
    console.log(`L1StandardBridge: ${contractAddrs.l1.L1StandardBridge}`)
183
    const L1StandardBridge = new hre.ethers.Contract(
184 185
      contractAddrs.l1.L1StandardBridge,
      Artifact__L1StandardBridge.abi,
186 187 188
      signer
    )

Mark Tyneway's avatar
Mark Tyneway committed
189 190 191 192 193 194
    const L2OutputOracle = new hre.ethers.Contract(
      contractAddrs.l1.L2OutputOracle,
      Artifact__L2OutputOracle.abi,
      signer
    )

195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213
    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(),
214
      l2ChainId,
215
      bedrock: true,
216
      contracts: contractAddrs,
217 218
    })

Mark Tyneway's avatar
Mark Tyneway committed
219 220 221 222
    const params = await OptimismPortal.params()
    console.log('Intial OptimismPortal.params:')
    console.log(params)

223
    console.log('Deploying WETH9 to L1')
224
    const WETH9 = await deployWETH9(hre, signer, true)
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
    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}`)

Mark Tyneway's avatar
Mark Tyneway committed
252 253
    console.log('Checking to make sure deposit was successful')
    // Deposit might get reorged, wait and also log for reorgs.
254
    let prevBlockHash: string = ''
Mark Tyneway's avatar
Mark Tyneway committed
255 256 257 258 259
    for (let i = 0; i < 12; i++) {
      const messageReceipt = await signer.provider!.getTransactionReceipt(
        depositTx.hash
      )
      if (messageReceipt.status !== 1) {
260
        console.log(`Deposit failed, retrying...`)
Kelvin Fichter's avatar
Kelvin Fichter committed
261 262
      }

Mark Tyneway's avatar
Mark Tyneway committed
263 264
      // Wait for stability, we want some amount of time after any reorg
      if (prevBlockHash !== '' && messageReceipt.blockHash !== prevBlockHash) {
Kelvin Fichter's avatar
Kelvin Fichter committed
265
        console.log(
Mark Tyneway's avatar
Mark Tyneway committed
266
          `Block hash changed from ${prevBlockHash} to ${messageReceipt.blockHash}`
Kelvin Fichter's avatar
Kelvin Fichter committed
267
        )
268
        i = 0
Mark Tyneway's avatar
Mark Tyneway committed
269 270
      } else if (prevBlockHash !== '') {
        console.log(`No reorg detected: ${i}`)
Kelvin Fichter's avatar
Kelvin Fichter committed
271 272
      }

Mark Tyneway's avatar
Mark Tyneway committed
273
      prevBlockHash = messageReceipt.blockHash
Kelvin Fichter's avatar
Kelvin Fichter committed
274
      await sleep(1000)
275
    }
Mark Tyneway's avatar
Mark Tyneway committed
276
    console.log(`Deposit confirmed`)
Kelvin Fichter's avatar
Kelvin Fichter committed
277

278 279
    const l2Balance = await OptimismMintableERC20.balanceOf(address)
    if (l2Balance.lt(utils.parseEther('1'))) {
Mark Tyneway's avatar
Mark Tyneway committed
280 281 282
      throw new Error(
        `bad deposit. recipient balance on L2: ${utils.formatEther(l2Balance)}`
      )
283
    }
284
    console.log(`Deposit success`)
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

    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]}`)
Mark Tyneway's avatar
Mark Tyneway committed
326
      const latest = await L2OutputOracle.latestBlockNumber()
Mark Tyneway's avatar
Mark Tyneway committed
327 328 329
      console.log(
        `Latest L2OutputOracle commitment number: ${latest.toString()}`
      )
330 331
      const tip = await signer.provider!.getBlockNumber()
      console.log(`L1 chain tip: ${tip.toString()}`)
332 333 334 335
    }, 3000)

    const now = Math.floor(Date.now() / 1000)

336 337 338 339 340 341 342 343 344 345 346
    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')
    }

347 348 349 350 351 352
    console.log('Waiting for message to be able to be relayed')
    await messenger.waitForMessageStatus(
      withdraw,
      MessageStatus.READY_FOR_RELAY
    )

353
    console.log('Finalizing withdrawal...')
354 355 356 357
    // TODO: Update SDK to properly estimate gas
    const finalize = await messenger.finalizeMessage(withdraw, {
      overrides: { gasLimit: 500_000 },
    })
358
    const finalizeReceipt = await finalize.wait()
359
    console.log('finalizeReceipt:', finalizeReceipt)
360 361
    console.log(`Took ${Math.floor(Date.now() / 1000) - now} seconds`)

362
    for (const log of finalizeReceipt.logs) {
363 364 365
      switch (log.address) {
        case OptimismPortal.address: {
          const parsed = OptimismPortal.interface.parseLog(log)
366
          console.log(`Log ${parsed.name} from OptimismPortal (${log.address})`)
367 368 369 370 371 372
          console.log(parsed.args)
          console.log()
          break
        }
        case L1CrossDomainMessenger.address: {
          const parsed = L1CrossDomainMessenger.interface.parseLog(log)
373 374 375
          console.log(
            `Log ${parsed.name} from L1CrossDomainMessenger (${log.address})`
          )
376 377 378 379 380 381
          console.log(parsed.args)
          console.log()
          break
        }
        case L1StandardBridge.address: {
          const parsed = L1StandardBridge.interface.parseLog(log)
382 383 384 385 386 387 388 389 390 391
          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})`)
392 393 394 395 396 397 398 399 400 401 402 403 404 405 406
          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)) {
407 408 409
      throw new Error(
        `Balance mismatch, expected: ${expectedBalance}, actual: ${postBalance}`
      )
410 411 412
    }
    console.log('Withdrawal success')
  })