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

     1  import { promises as fs } from 'fs'
     2  
     3  import { task, types } from 'hardhat/config'
     4  import { HardhatRuntimeEnvironment } from 'hardhat/types'
     5  import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'
     6  import '@nomiclabs/hardhat-ethers'
     7  import 'hardhat-deploy'
     8  import { Event, Contract, Wallet, providers, utils, ethers } from 'ethers'
     9  import { predeploys, sleep } from '@eth-optimism/core-utils'
    10  import Artifact__WETH9 from '@eth-optimism/contracts-bedrock/forge-artifacts/WETH9.sol/WETH9.json'
    11  import Artifact__OptimismMintableERC20TokenFactory from '@eth-optimism/contracts-bedrock/forge-artifacts/OptimismMintableERC20Factory.sol/OptimismMintableERC20Factory.json'
    12  import Artifact__OptimismMintableERC20Token from '@eth-optimism/contracts-bedrock/forge-artifacts/OptimismMintableERC20.sol/OptimismMintableERC20.json'
    13  import Artifact__L2ToL1MessagePasser from '@eth-optimism/contracts-bedrock/forge-artifacts/L2ToL1MessagePasser.sol/L2ToL1MessagePasser.json'
    14  import Artifact__L2CrossDomainMessenger from '@eth-optimism/contracts-bedrock/forge-artifacts/L2CrossDomainMessenger.sol/L2CrossDomainMessenger.json'
    15  import Artifact__L2StandardBridge from '@eth-optimism/contracts-bedrock/forge-artifacts/L2StandardBridge.sol/L2StandardBridge.json'
    16  import Artifact__OptimismPortal from '@eth-optimism/contracts-bedrock/forge-artifacts/OptimismPortal.sol/OptimismPortal.json'
    17  import Artifact__L1CrossDomainMessenger from '@eth-optimism/contracts-bedrock/forge-artifacts/L1CrossDomainMessenger.sol/L1CrossDomainMessenger.json'
    18  import Artifact__L1StandardBridge from '@eth-optimism/contracts-bedrock/forge-artifacts/L1StandardBridge.sol/L1StandardBridge.json'
    19  import Artifact__L2OutputOracle from '@eth-optimism/contracts-bedrock/forge-artifacts/L2OutputOracle.sol/L2OutputOracle.json'
    20  
    21  import {
    22    CrossChainMessenger,
    23    MessageStatus,
    24    CONTRACT_ADDRESSES,
    25    OEContractsLike,
    26    DEFAULT_L2_CONTRACT_ADDRESSES,
    27  } from '../src'
    28  
    29  const deployWETH9 = async (
    30    hre: HardhatRuntimeEnvironment,
    31    signer: SignerWithAddress,
    32    wrap: boolean
    33  ): Promise<Contract> => {
    34    const Factory__WETH9 = new hre.ethers.ContractFactory(
    35      Artifact__WETH9.abi,
    36      Artifact__WETH9.bytecode.object,
    37      signer
    38    )
    39  
    40    console.log('Sending deployment transaction')
    41    const WETH9 = await Factory__WETH9.deploy()
    42    const receipt = await WETH9.deployTransaction.wait()
    43    console.log(`WETH9 deployed: ${receipt.transactionHash}`)
    44  
    45    if (wrap) {
    46      const deposit = await signer.sendTransaction({
    47        value: utils.parseEther('1'),
    48        to: WETH9.address,
    49      })
    50      await deposit.wait()
    51    }
    52  
    53    return WETH9
    54  }
    55  
    56  const createOptimismMintableERC20 = async (
    57    hre: HardhatRuntimeEnvironment,
    58    L1ERC20: Contract,
    59    l2Signer: Wallet
    60  ): Promise<Contract> => {
    61    const OptimismMintableERC20TokenFactory = new Contract(
    62      predeploys.OptimismMintableERC20Factory,
    63      Artifact__OptimismMintableERC20TokenFactory.abi,
    64      l2Signer
    65    )
    66  
    67    const name = await L1ERC20.name()
    68    const symbol = await L1ERC20.symbol()
    69  
    70    const tx =
    71      await OptimismMintableERC20TokenFactory.createOptimismMintableERC20(
    72        L1ERC20.address,
    73        `L2 ${name}`,
    74        `L2-${symbol}`
    75      )
    76  
    77    const receipt = await tx.wait()
    78    const event = receipt.events.find(
    79      (e: Event) => e.event === 'OptimismMintableERC20Created'
    80    )
    81  
    82    if (!event) {
    83      throw new Error('Unable to find OptimismMintableERC20Created event')
    84    }
    85  
    86    const l2WethAddress = event.args.localToken
    87    console.log(`Deployed to ${l2WethAddress}`)
    88  
    89    return new Contract(
    90      l2WethAddress,
    91      Artifact__OptimismMintableERC20Token.abi,
    92      l2Signer
    93    )
    94  }
    95  
    96  // TODO(tynes): this task could be modularized in the future
    97  // so that it can deposit an arbitrary token. Right now it
    98  // deploys a WETH9 contract, mints some WETH9 and then
    99  // deposits that into L2 through the StandardBridge.
   100  task('deposit-erc20', 'Deposits WETH9 onto L2.')
   101    .addParam(
   102      'l2ProviderUrl',
   103      'L2 provider URL.',
   104      'http://localhost:9545',
   105      types.string
   106    )
   107    .addParam(
   108      'opNodeProviderUrl',
   109      'op-node provider URL',
   110      'http://localhost:7545',
   111      types.string
   112    )
   113    .addOptionalParam(
   114      'l1ContractsJsonPath',
   115      'Path to a JSON with L1 contract addresses in it',
   116      '',
   117      types.string
   118    )
   119    .addOptionalParam('signerIndex', 'Index of signer to use', 0, types.int)
   120    .setAction(async (args, hre) => {
   121      const signers = await hre.ethers.getSigners()
   122      if (signers.length === 0) {
   123        throw new Error('No configured signers')
   124      }
   125      if (args.signerIndex < 0 || signers.length <= args.signerIndex) {
   126        throw new Error('Invalid signer index')
   127      }
   128      const signer = signers[args.signerIndex]
   129      const address = await signer.getAddress()
   130      console.log(`Using signer ${address}`)
   131  
   132      // Ensure that the signer has a balance before trying to
   133      // do anything
   134      const balance = await signer.getBalance()
   135      if (balance.eq(0)) {
   136        throw new Error('Signer has no balance')
   137      }
   138  
   139      const l2Provider = new providers.StaticJsonRpcProvider(args.l2ProviderUrl)
   140  
   141      const l2Signer = new hre.ethers.Wallet(
   142        hre.network.config.accounts[args.signerIndex],
   143        l2Provider
   144      )
   145  
   146      const l2ChainId = await l2Signer.getChainId()
   147      let contractAddrs = CONTRACT_ADDRESSES[l2ChainId]
   148      if (args.l1ContractsJsonPath) {
   149        const data = await fs.readFile(args.l1ContractsJsonPath)
   150        const json = JSON.parse(data.toString())
   151        contractAddrs = {
   152          l1: {
   153            AddressManager: json.AddressManager,
   154            L1CrossDomainMessenger: json.L1CrossDomainMessengerProxy,
   155            L1StandardBridge: json.L1StandardBridgeProxy,
   156            StateCommitmentChain: ethers.constants.AddressZero,
   157            CanonicalTransactionChain: ethers.constants.AddressZero,
   158            BondManager: ethers.constants.AddressZero,
   159            OptimismPortal: json.OptimismPortalProxy,
   160            L2OutputOracle: json.L2OutputOracleProxy,
   161            OptimismPortal2: json.OptimismPortalProxy,
   162            DisputeGameFactory: json.DisputeGameFactoryProxy,
   163          },
   164          l2: DEFAULT_L2_CONTRACT_ADDRESSES,
   165        } as OEContractsLike
   166      }
   167  
   168      console.log(`OptimismPortal: ${contractAddrs.l1.OptimismPortal}`)
   169      const OptimismPortal = new hre.ethers.Contract(
   170        contractAddrs.l1.OptimismPortal,
   171        Artifact__OptimismPortal.abi,
   172        signer
   173      )
   174  
   175      console.log(
   176        `L1CrossDomainMessenger: ${contractAddrs.l1.L1CrossDomainMessenger}`
   177      )
   178      const L1CrossDomainMessenger = new hre.ethers.Contract(
   179        contractAddrs.l1.L1CrossDomainMessenger,
   180        Artifact__L1CrossDomainMessenger.abi,
   181        signer
   182      )
   183  
   184      console.log(`L1StandardBridge: ${contractAddrs.l1.L1StandardBridge}`)
   185      const L1StandardBridge = new hre.ethers.Contract(
   186        contractAddrs.l1.L1StandardBridge,
   187        Artifact__L1StandardBridge.abi,
   188        signer
   189      )
   190  
   191      const L2OutputOracle = new hre.ethers.Contract(
   192        contractAddrs.l1.L2OutputOracle,
   193        Artifact__L2OutputOracle.abi,
   194        signer
   195      )
   196  
   197      const L2ToL1MessagePasser = new hre.ethers.Contract(
   198        predeploys.L2ToL1MessagePasser,
   199        Artifact__L2ToL1MessagePasser.abi
   200      )
   201  
   202      const L2CrossDomainMessenger = new hre.ethers.Contract(
   203        predeploys.L2CrossDomainMessenger,
   204        Artifact__L2CrossDomainMessenger.abi
   205      )
   206  
   207      const L2StandardBridge = new hre.ethers.Contract(
   208        predeploys.L2StandardBridge,
   209        Artifact__L2StandardBridge.abi
   210      )
   211  
   212      const messenger = new CrossChainMessenger({
   213        l1SignerOrProvider: signer,
   214        l2SignerOrProvider: l2Signer,
   215        l1ChainId: await signer.getChainId(),
   216        l2ChainId,
   217        bedrock: true,
   218        contracts: contractAddrs,
   219      })
   220  
   221      const params = await OptimismPortal.params()
   222      console.log('Intial OptimismPortal.params:')
   223      console.log(params)
   224  
   225      console.log('Deploying WETH9 to L1')
   226      const WETH9 = await deployWETH9(hre, signer, true)
   227      console.log(`Deployed to ${WETH9.address}`)
   228  
   229      console.log('Creating L2 WETH9')
   230      const OptimismMintableERC20 = await createOptimismMintableERC20(
   231        hre,
   232        WETH9,
   233        l2Signer
   234      )
   235  
   236      console.log(`Approving WETH9 for deposit`)
   237      const approvalTx = await messenger.approveERC20(
   238        WETH9.address,
   239        OptimismMintableERC20.address,
   240        hre.ethers.constants.MaxUint256
   241      )
   242      await approvalTx.wait()
   243      console.log('WETH9 approved')
   244  
   245      console.log('Depositing WETH9 to L2')
   246      const depositTx = await messenger.depositERC20(
   247        WETH9.address,
   248        OptimismMintableERC20.address,
   249        utils.parseEther('1')
   250      )
   251      await depositTx.wait()
   252      console.log(`ERC20 deposited - ${depositTx.hash}`)
   253  
   254      console.log('Checking to make sure deposit was successful')
   255      // Deposit might get reorged, wait and also log for reorgs.
   256      let prevBlockHash: string = ''
   257      for (let i = 0; i < 12; i++) {
   258        const messageReceipt = await signer.provider!.getTransactionReceipt(
   259          depositTx.hash
   260        )
   261        if (messageReceipt.status !== 1) {
   262          console.log(`Deposit failed, retrying...`)
   263        }
   264  
   265        // Wait for stability, we want some amount of time after any reorg
   266        if (prevBlockHash !== '' && messageReceipt.blockHash !== prevBlockHash) {
   267          console.log(
   268            `Block hash changed from ${prevBlockHash} to ${messageReceipt.blockHash}`
   269          )
   270          i = 0
   271        } else if (prevBlockHash !== '') {
   272          console.log(`No reorg detected: ${i}`)
   273        }
   274  
   275        prevBlockHash = messageReceipt.blockHash
   276        await sleep(1000)
   277      }
   278      console.log(`Deposit confirmed`)
   279  
   280      const l2Balance = await OptimismMintableERC20.balanceOf(address)
   281      if (l2Balance.lt(utils.parseEther('1'))) {
   282        throw new Error(
   283          `bad deposit. recipient balance on L2: ${utils.formatEther(l2Balance)}`
   284        )
   285      }
   286      console.log(`Deposit success`)
   287  
   288      console.log('Starting withdrawal')
   289      const preBalance = await WETH9.balanceOf(signer.address)
   290      const withdraw = await messenger.withdrawERC20(
   291        WETH9.address,
   292        OptimismMintableERC20.address,
   293        utils.parseEther('1')
   294      )
   295      const withdrawalReceipt = await withdraw.wait()
   296      for (const log of withdrawalReceipt.logs) {
   297        switch (log.address) {
   298          case L2ToL1MessagePasser.address: {
   299            const parsed = L2ToL1MessagePasser.interface.parseLog(log)
   300            console.log(`Log ${parsed.name} from ${log.address}`)
   301            console.log(parsed.args)
   302            console.log()
   303            break
   304          }
   305          case L2StandardBridge.address: {
   306            const parsed = L2StandardBridge.interface.parseLog(log)
   307            console.log(`Log ${parsed.name} from ${log.address}`)
   308            console.log(parsed.args)
   309            console.log()
   310            break
   311          }
   312          case L2CrossDomainMessenger.address: {
   313            const parsed = L2CrossDomainMessenger.interface.parseLog(log)
   314            console.log(`Log ${parsed.name} from ${log.address}`)
   315            console.log(parsed.args)
   316            console.log()
   317            break
   318          }
   319          default: {
   320            console.log(`Unknown log from ${log.address} - ${log.topics[0]}`)
   321          }
   322        }
   323      }
   324  
   325      setInterval(async () => {
   326        const currentStatus = await messenger.getMessageStatus(withdraw)
   327        console.log(`Message status: ${MessageStatus[currentStatus]}`)
   328        const latest = await L2OutputOracle.latestBlockNumber()
   329        console.log(
   330          `Latest L2OutputOracle commitment number: ${latest.toString()}`
   331        )
   332        const tip = await signer.provider!.getBlockNumber()
   333        console.log(`L1 chain tip: ${tip.toString()}`)
   334      }, 3000)
   335  
   336      const now = Math.floor(Date.now() / 1000)
   337  
   338      console.log('Waiting for message to be able to be proved')
   339      await messenger.waitForMessageStatus(withdraw, MessageStatus.READY_TO_PROVE)
   340  
   341      console.log('Proving withdrawal...')
   342      const prove = await messenger.proveMessage(withdraw)
   343      const proveReceipt = await prove.wait()
   344      console.log(proveReceipt)
   345      if (proveReceipt.status !== 1) {
   346        throw new Error('Prove withdrawal transaction reverted')
   347      }
   348  
   349      console.log('Waiting for message to be able to be relayed')
   350      await messenger.waitForMessageStatus(
   351        withdraw,
   352        MessageStatus.READY_FOR_RELAY
   353      )
   354  
   355      console.log('Finalizing withdrawal...')
   356      // TODO: Update SDK to properly estimate gas
   357      const finalize = await messenger.finalizeMessage(withdraw, {
   358        overrides: { gasLimit: 500_000 },
   359      })
   360      const finalizeReceipt = await finalize.wait()
   361      console.log('finalizeReceipt:', finalizeReceipt)
   362      console.log(`Took ${Math.floor(Date.now() / 1000) - now} seconds`)
   363  
   364      for (const log of finalizeReceipt.logs) {
   365        switch (log.address) {
   366          case OptimismPortal.address: {
   367            const parsed = OptimismPortal.interface.parseLog(log)
   368            console.log(`Log ${parsed.name} from OptimismPortal (${log.address})`)
   369            console.log(parsed.args)
   370            console.log()
   371            break
   372          }
   373          case L1CrossDomainMessenger.address: {
   374            const parsed = L1CrossDomainMessenger.interface.parseLog(log)
   375            console.log(
   376              `Log ${parsed.name} from L1CrossDomainMessenger (${log.address})`
   377            )
   378            console.log(parsed.args)
   379            console.log()
   380            break
   381          }
   382          case L1StandardBridge.address: {
   383            const parsed = L1StandardBridge.interface.parseLog(log)
   384            console.log(
   385              `Log ${parsed.name} from L1StandardBridge (${log.address})`
   386            )
   387            console.log(parsed.args)
   388            console.log()
   389            break
   390          }
   391          case WETH9.address: {
   392            const parsed = WETH9.interface.parseLog(log)
   393            console.log(`Log ${parsed.name} from WETH9 (${log.address})`)
   394            console.log(parsed.args)
   395            console.log()
   396            break
   397          }
   398          default:
   399            console.log(
   400              `Unknown log emitted from ${log.address} - ${log.topics[0]}`
   401            )
   402        }
   403      }
   404  
   405      const postBalance = await WETH9.balanceOf(signer.address)
   406  
   407      const expectedBalance = preBalance.add(utils.parseEther('1'))
   408      if (!expectedBalance.eq(postBalance)) {
   409        throw new Error(
   410          `Balance mismatch, expected: ${expectedBalance}, actual: ${postBalance}`
   411        )
   412      }
   413      console.log('Withdrawal success')
   414    })