github.com/ethereum-optimism/optimism@v1.7.2/packages/sdk/tasks/deposit-eth.ts (about)

     1  import { promises as fs } from 'fs'
     2  
     3  import { task, types } from 'hardhat/config'
     4  import '@nomiclabs/hardhat-ethers'
     5  import 'hardhat-deploy'
     6  import { Deployment } from 'hardhat-deploy/types'
     7  import { predeploys } from '@eth-optimism/core-utils'
     8  import { providers, utils, ethers } from 'ethers'
     9  import Artifact__L2ToL1MessagePasser from '@eth-optimism/contracts-bedrock/forge-artifacts/L2ToL1MessagePasser.sol/L2ToL1MessagePasser.json'
    10  import Artifact__L2CrossDomainMessenger from '@eth-optimism/contracts-bedrock/forge-artifacts/L2CrossDomainMessenger.sol/L2CrossDomainMessenger.json'
    11  import Artifact__L2StandardBridge from '@eth-optimism/contracts-bedrock/forge-artifacts/L2StandardBridge.sol/L2StandardBridge.json'
    12  import Artifact__OptimismPortal from '@eth-optimism/contracts-bedrock/forge-artifacts/OptimismPortal.sol/OptimismPortal.json'
    13  import Artifact__L1CrossDomainMessenger from '@eth-optimism/contracts-bedrock/forge-artifacts/L1CrossDomainMessenger.sol/L1CrossDomainMessenger.json'
    14  import Artifact__L1StandardBridge from '@eth-optimism/contracts-bedrock/forge-artifacts/L1StandardBridge.sol/L1StandardBridge.json'
    15  import Artifact__L2OutputOracle from '@eth-optimism/contracts-bedrock/forge-artifacts/L2OutputOracle.sol/L2OutputOracle.json'
    16  
    17  import {
    18    CrossChainMessenger,
    19    MessageStatus,
    20    CONTRACT_ADDRESSES,
    21    OEContractsLike,
    22    DEFAULT_L2_CONTRACT_ADDRESSES,
    23  } from '../src'
    24  
    25  const { formatEther } = utils
    26  
    27  task('deposit-eth', 'Deposits ether to L2.')
    28    .addParam(
    29      'l2ProviderUrl',
    30      'L2 provider URL.',
    31      'http://localhost:9545',
    32      types.string
    33    )
    34    .addOptionalParam('to', 'Recipient of the ether', '', types.string)
    35    .addOptionalParam(
    36      'amount',
    37      'Amount of ether to send (in ETH)',
    38      '',
    39      types.string
    40    )
    41    .addOptionalParam(
    42      'withdraw',
    43      'Follow up with a withdrawal',
    44      true,
    45      types.boolean
    46    )
    47    .addOptionalParam(
    48      'l1ContractsJsonPath',
    49      'Path to a JSON with L1 contract addresses in it',
    50      '',
    51      types.string
    52    )
    53    .addOptionalParam('signerIndex', 'Index of signer to use', 0, types.int)
    54    .addOptionalParam('withdrawAmount', 'Amount to withdraw', '', types.string)
    55    .setAction(async (args, hre) => {
    56      const signers = await hre.ethers.getSigners()
    57      if (signers.length === 0) {
    58        throw new Error('No configured signers')
    59      }
    60      if (args.signerIndex < 0 || signers.length <= args.signerIndex) {
    61        throw new Error('Invalid signer index')
    62      }
    63      const signer = signers[args.signerIndex]
    64      const address = await signer.getAddress()
    65      console.log(`Using signer ${address}`)
    66  
    67      // Ensure that the signer has a balance before trying to
    68      // do anything
    69      const balance = await signer.getBalance()
    70      if (balance.eq(0)) {
    71        throw new Error('Signer has no balance')
    72      }
    73      console.log(`Signer balance: ${formatEther(balance.toString())}`)
    74  
    75      const l2Provider = new providers.StaticJsonRpcProvider(args.l2ProviderUrl)
    76  
    77      // send to self if not specified
    78      const to = args.to ? args.to : address
    79      const amount = args.amount
    80        ? utils.parseEther(args.amount)
    81        : utils.parseEther('1')
    82      const withdrawAmount = args.withdrawAmount
    83        ? utils.parseEther(args.withdrawAmount)
    84        : amount.div(2)
    85  
    86      const l2Signer = new hre.ethers.Wallet(
    87        hre.network.config.accounts[args.signerIndex],
    88        l2Provider
    89      )
    90  
    91      const l2ChainId = await l2Signer.getChainId()
    92      let contractAddrs = CONTRACT_ADDRESSES[l2ChainId]
    93      if (args.l1ContractsJsonPath) {
    94        const data = await fs.readFile(args.l1ContractsJsonPath)
    95        const json = JSON.parse(data.toString())
    96        contractAddrs = {
    97          l1: {
    98            AddressManager: json.AddressManager,
    99            L1CrossDomainMessenger: json.L1CrossDomainMessengerProxy,
   100            L1StandardBridge: json.L1StandardBridgeProxy,
   101            StateCommitmentChain: ethers.constants.AddressZero,
   102            CanonicalTransactionChain: ethers.constants.AddressZero,
   103            BondManager: ethers.constants.AddressZero,
   104            OptimismPortal: json.OptimismPortalProxy,
   105            L2OutputOracle: json.L2OutputOracleProxy,
   106            OptimismPortal2: json.OptimismPortalProxy,
   107            DisputeGameFactory: json.DisputeGameFactoryProxy,
   108          },
   109          l2: DEFAULT_L2_CONTRACT_ADDRESSES,
   110        } as OEContractsLike
   111      } else if (!contractAddrs) {
   112        // If the contract addresses have not been hardcoded,
   113        // attempt to read them from deployment artifacts
   114        let Deployment__AddressManager: Deployment
   115        try {
   116          Deployment__AddressManager = await hre.deployments.get('AddressManager')
   117        } catch (e) {
   118          Deployment__AddressManager = await hre.deployments.get(
   119            'Lib_AddressManager'
   120          )
   121        }
   122        let Deployment__L1CrossDomainMessenger: Deployment
   123        try {
   124          Deployment__L1CrossDomainMessenger = await hre.deployments.get(
   125            'L1CrossDomainMessengerProxy'
   126          )
   127        } catch (e) {
   128          Deployment__L1CrossDomainMessenger = await hre.deployments.get(
   129            'Proxy__OVM_L1CrossDomainMessenger'
   130          )
   131        }
   132        let Deployment__L1StandardBridge: Deployment
   133        try {
   134          Deployment__L1StandardBridge = await hre.deployments.get(
   135            'L1StandardBridgeProxy'
   136          )
   137        } catch (e) {
   138          Deployment__L1StandardBridge = await hre.deployments.get(
   139            'Proxy__OVM_L1StandardBridge'
   140          )
   141        }
   142  
   143        const Deployment__OptimismPortal = await hre.deployments.get(
   144          'OptimismPortalProxy'
   145        )
   146        const Deployment__L2OutputOracle = await hre.deployments.get(
   147          'L2OutputOracleProxy'
   148        )
   149        contractAddrs = {
   150          l1: {
   151            AddressManager: Deployment__AddressManager.address,
   152            L1CrossDomainMessenger: Deployment__L1CrossDomainMessenger.address,
   153            L1StandardBridge: Deployment__L1StandardBridge.address,
   154            StateCommitmentChain: ethers.constants.AddressZero,
   155            CanonicalTransactionChain: ethers.constants.AddressZero,
   156            BondManager: ethers.constants.AddressZero,
   157            OptimismPortal: Deployment__OptimismPortal.address,
   158            L2OutputOracle: Deployment__L2OutputOracle.address,
   159          },
   160          l2: DEFAULT_L2_CONTRACT_ADDRESSES,
   161        }
   162      }
   163  
   164      console.log(`OptimismPortal: ${contractAddrs.l1.OptimismPortal}`)
   165      const OptimismPortal = new hre.ethers.Contract(
   166        contractAddrs.l1.OptimismPortal,
   167        Artifact__OptimismPortal.abi,
   168        signer
   169      )
   170  
   171      console.log(
   172        `L1CrossDomainMessenger: ${contractAddrs.l1.L1CrossDomainMessenger}`
   173      )
   174      const L1CrossDomainMessenger = new hre.ethers.Contract(
   175        contractAddrs.l1.L1CrossDomainMessenger,
   176        Artifact__L1CrossDomainMessenger.abi,
   177        signer
   178      )
   179  
   180      console.log(`L1StandardBridge: ${contractAddrs.l1.L1StandardBridge}`)
   181      const L1StandardBridge = new hre.ethers.Contract(
   182        contractAddrs.l1.L1StandardBridge,
   183        Artifact__L1StandardBridge.abi,
   184        signer
   185      )
   186  
   187      console.log(`L2OutputOracle: ${contractAddrs.l1.L2OutputOracle}`)
   188      const L2OutputOracle = new hre.ethers.Contract(
   189        contractAddrs.l1.L2OutputOracle,
   190        Artifact__L2OutputOracle.abi,
   191        signer
   192      )
   193  
   194      const L2ToL1MessagePasser = new hre.ethers.Contract(
   195        predeploys.L2ToL1MessagePasser,
   196        Artifact__L2ToL1MessagePasser.abi
   197      )
   198  
   199      const L2CrossDomainMessenger = new hre.ethers.Contract(
   200        predeploys.L2CrossDomainMessenger,
   201        Artifact__L2CrossDomainMessenger.abi
   202      )
   203  
   204      const L2StandardBridge = new hre.ethers.Contract(
   205        predeploys.L2StandardBridge,
   206        Artifact__L2StandardBridge.abi
   207      )
   208  
   209      const messenger = new CrossChainMessenger({
   210        l1SignerOrProvider: signer,
   211        l2SignerOrProvider: l2Signer,
   212        l1ChainId: await signer.getChainId(),
   213        l2ChainId,
   214        bedrock: true,
   215        contracts: contractAddrs,
   216      })
   217  
   218      const opBalanceBefore = await signer!.provider!.getBalance(
   219        OptimismPortal.address
   220      )
   221  
   222      const l1BridgeBalanceBefore = await signer!.provider!.getBalance(
   223        L1StandardBridge.address
   224      )
   225  
   226      // Deposit ETH
   227      console.log('Depositing ETH through StandardBridge')
   228      console.log(`Sending ${formatEther(amount)} ether`)
   229      const ethDeposit = await messenger.depositETH(amount, { recipient: to })
   230      console.log(`Transaction hash: ${ethDeposit.hash}`)
   231      const depositMessageReceipt = await messenger.waitForMessageReceipt(
   232        ethDeposit
   233      )
   234      if (depositMessageReceipt.receiptStatus !== 1) {
   235        throw new Error('deposit failed')
   236      }
   237      console.log(
   238        `Deposit complete - included in block ${depositMessageReceipt.transactionReceipt.blockNumber}`
   239      )
   240  
   241      const opBalanceAfter = await signer!.provider!.getBalance(
   242        OptimismPortal.address
   243      )
   244  
   245      const l1BridgeBalanceAfter = await signer!.provider!.getBalance(
   246        L1StandardBridge.address
   247      )
   248  
   249      console.log(
   250        `L1StandardBridge balance before: ${formatEther(l1BridgeBalanceBefore)}`
   251      )
   252  
   253      console.log(
   254        `L1StandardBridge balance after: ${formatEther(l1BridgeBalanceAfter)}`
   255      )
   256  
   257      console.log(
   258        `OptimismPortal balance before: ${formatEther(opBalanceBefore)}`
   259      )
   260      console.log(`OptimismPortal balance after: ${formatEther(opBalanceAfter)}`)
   261  
   262      if (!opBalanceBefore.add(amount).eq(opBalanceAfter)) {
   263        throw new Error(`OptimismPortal balance mismatch`)
   264      }
   265  
   266      const l2Balance = await l2Provider.getBalance(to)
   267      console.log(
   268        `L2 balance of deposit recipient: ${utils.formatEther(
   269          l2Balance.toString()
   270        )}`
   271      )
   272  
   273      if (!args.withdraw) {
   274        return
   275      }
   276  
   277      console.log('Withdrawing ETH')
   278      const ethWithdraw = await messenger.withdrawETH(withdrawAmount)
   279      console.log(`Transaction hash: ${ethWithdraw.hash}`)
   280      const ethWithdrawReceipt = await ethWithdraw.wait()
   281      console.log(
   282        `ETH withdrawn on L2 - included in block ${ethWithdrawReceipt.blockNumber}`
   283      )
   284  
   285      {
   286        // check the logs
   287        for (const log of ethWithdrawReceipt.logs) {
   288          switch (log.address) {
   289            case L2ToL1MessagePasser.address: {
   290              const parsed = L2ToL1MessagePasser.interface.parseLog(log)
   291              console.log(parsed.name)
   292              console.log(parsed.args)
   293              console.log()
   294              break
   295            }
   296            case L2StandardBridge.address: {
   297              const parsed = L2StandardBridge.interface.parseLog(log)
   298              console.log(parsed.name)
   299              console.log(parsed.args)
   300              console.log()
   301              break
   302            }
   303            case L2CrossDomainMessenger.address: {
   304              const parsed = L2CrossDomainMessenger.interface.parseLog(log)
   305              console.log(parsed.name)
   306              console.log(parsed.args)
   307              console.log()
   308              break
   309            }
   310            default: {
   311              console.log(`Unknown log from ${log.address} - ${log.topics[0]}`)
   312            }
   313          }
   314        }
   315      }
   316  
   317      console.log('Waiting to be able to prove withdrawal')
   318  
   319      const proveInterval = setInterval(async () => {
   320        const currentStatus = await messenger.getMessageStatus(ethWithdrawReceipt)
   321        console.log(`Message status: ${MessageStatus[currentStatus]}`)
   322        const latest = await L2OutputOracle.latestBlockNumber()
   323        console.log(
   324          `Latest L2OutputOracle commitment number: ${latest.toString()}`
   325        )
   326        const tip = await signer.provider!.getBlockNumber()
   327        console.log(`L1 chain tip: ${tip.toString()}`)
   328      }, 3000)
   329  
   330      try {
   331        await messenger.waitForMessageStatus(
   332          ethWithdrawReceipt,
   333          MessageStatus.READY_TO_PROVE
   334        )
   335      } finally {
   336        clearInterval(proveInterval)
   337      }
   338  
   339      console.log('Proving eth withdrawal...')
   340      const ethProve = await messenger.proveMessage(ethWithdrawReceipt)
   341      console.log(`Transaction hash: ${ethProve.hash}`)
   342      const ethProveReceipt = await ethProve.wait()
   343      if (ethProveReceipt.status !== 1) {
   344        throw new Error('Prove withdrawal transaction reverted')
   345      }
   346      console.log('Successfully proved withdrawal')
   347  
   348      console.log('Waiting to be able to finalize withdrawal')
   349  
   350      const finalizeInterval = setInterval(async () => {
   351        const currentStatus = await messenger.getMessageStatus(ethWithdrawReceipt)
   352        console.log(`Message status: ${MessageStatus[currentStatus]}`)
   353      }, 3000)
   354  
   355      try {
   356        await messenger.waitForMessageStatus(
   357          ethWithdrawReceipt,
   358          MessageStatus.READY_FOR_RELAY
   359        )
   360      } finally {
   361        clearInterval(finalizeInterval)
   362      }
   363  
   364      console.log('Finalizing eth withdrawal...')
   365      const ethFinalize = await messenger.finalizeMessage(ethWithdrawReceipt)
   366      console.log(`Transaction hash: ${ethFinalize.hash}`)
   367      const ethFinalizeReceipt = await ethFinalize.wait()
   368      if (ethFinalizeReceipt.status !== 1) {
   369        throw new Error('Finalize withdrawal reverted')
   370      }
   371  
   372      console.log(
   373        `ETH withdrawal complete - included in block ${ethFinalizeReceipt.blockNumber}`
   374      )
   375      {
   376        // Check that the logs are correct
   377        for (const log of ethFinalizeReceipt.logs) {
   378          switch (log.address) {
   379            case L1StandardBridge.address: {
   380              const parsed = L1StandardBridge.interface.parseLog(log)
   381              console.log(parsed.name)
   382              console.log(parsed.args)
   383              console.log()
   384              if (
   385                parsed.name !== 'ETHBridgeFinalized' &&
   386                parsed.name !== 'ETHWithdrawalFinalized'
   387              ) {
   388                throw new Error('Wrong event name from L1StandardBridge')
   389              }
   390              if (!parsed.args.amount.eq(withdrawAmount)) {
   391                throw new Error('Wrong amount in event')
   392              }
   393              if (parsed.args.from !== address) {
   394                throw new Error('Wrong to in event')
   395              }
   396              if (parsed.args.to !== address) {
   397                throw new Error('Wrong from in event')
   398              }
   399              break
   400            }
   401            case L1CrossDomainMessenger.address: {
   402              const parsed = L1CrossDomainMessenger.interface.parseLog(log)
   403              console.log(parsed.name)
   404              console.log(parsed.args)
   405              console.log()
   406              if (parsed.name !== 'RelayedMessage') {
   407                throw new Error('Wrong event from L1CrossDomainMessenger')
   408              }
   409              break
   410            }
   411            case OptimismPortal.address: {
   412              const parsed = OptimismPortal.interface.parseLog(log)
   413              console.log(parsed.name)
   414              console.log(parsed.args)
   415              console.log()
   416              // TODO: remove this if check
   417              if (parsed.name === 'WithdrawalFinalized') {
   418                if (parsed.args.success !== true) {
   419                  throw new Error('Unsuccessful withdrawal call')
   420                }
   421              }
   422              break
   423            }
   424            default: {
   425              console.log(`Unknown log from ${log.address} - ${log.topics[0]}`)
   426            }
   427          }
   428        }
   429      }
   430  
   431      const opBalanceFinally = await signer!.provider!.getBalance(
   432        OptimismPortal.address
   433      )
   434  
   435      if (!opBalanceFinally.add(withdrawAmount).eq(opBalanceAfter)) {
   436        throw new Error('OptimismPortal balance mismatch')
   437      }
   438      console.log('Withdraw success')
   439    })