eth-bridge.ts 5.57 KB
Newer Older
1
/* eslint-disable @typescript-eslint/no-unused-vars */
2
import { ethers, Overrides, BigNumber } from 'ethers'
3
import { TransactionRequest, BlockTag } from '@ethersproject/abstract-provider'
4
import { predeploys } from '@eth-optimism/contracts'
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
import { hexStringEquals } from '@eth-optimism/core-utils'

import {
  NumberLike,
  AddressLike,
  TokenBridgeMessage,
  MessageDirection,
} from '../interfaces'
import { toAddress, omit } from '../utils'
import { StandardBridgeAdapter } from './standard-bridge'

/**
 * Bridge adapter for the ETH bridge.
 */
export class ETHBridgeAdapter extends StandardBridgeAdapter {
20 21 22 23 24 25 26 27
  public async approval(
    l1Token: AddressLike,
    l2Token: AddressLike,
    signer: ethers.Signer
  ): Promise<BigNumber> {
    throw new Error(`approval not necessary for ETH bridge`)
  }

28 29 30 31 32 33 34 35 36 37 38 39 40
  public async getDepositsByAddress(
    address: AddressLike,
    opts?: {
      fromBlock?: BlockTag
      toBlock?: BlockTag
    }
  ): Promise<TokenBridgeMessage[]> {
    const events = await this.l1Bridge.queryFilter(
      this.l1Bridge.filters.ETHDepositInitiated(address),
      opts?.fromBlock,
      opts?.toBlock
    )

41 42 43 44
    return events
      .map((event) => {
        return {
          direction: MessageDirection.L1_TO_L2,
45 46
          from: event.args.from,
          to: event.args.to,
47 48
          l1Token: ethers.constants.AddressZero,
          l2Token: predeploys.OVM_ETH,
49 50
          amount: event.args.amount,
          data: event.args.extraData,
51 52 53 54 55 56 57 58 59
          logIndex: event.logIndex,
          blockNumber: event.blockNumber,
          transactionHash: event.transactionHash,
        }
      })
      .sort((a, b) => {
        // Sort descending by block number
        return b.blockNumber - a.blockNumber
      })
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
  }

  public async getWithdrawalsByAddress(
    address: AddressLike,
    opts?: {
      fromBlock?: BlockTag
      toBlock?: BlockTag
    }
  ): Promise<TokenBridgeMessage[]> {
    const events = await this.l2Bridge.queryFilter(
      this.l2Bridge.filters.WithdrawalInitiated(undefined, undefined, address),
      opts?.fromBlock,
      opts?.toBlock
    )

    return events
      .filter((event) => {
        // Only find ETH withdrawals.
        return (
79 80
          hexStringEquals(event.args.l1Token, ethers.constants.AddressZero) &&
          hexStringEquals(event.args.l2Token, predeploys.OVM_ETH)
81 82 83 84 85
        )
      })
      .map((event) => {
        return {
          direction: MessageDirection.L2_TO_L1,
86 87 88 89 90 91
          from: event.args.from,
          to: event.args.to,
          l1Token: event.args.l1Token,
          l2Token: event.args.l2Token,
          amount: event.args.amount,
          data: event.args.extraData,
92 93 94 95 96
          logIndex: event.logIndex,
          blockNumber: event.blockNumber,
          transactionHash: event.transactionHash,
        }
      })
97 98 99 100
      .sort((a, b) => {
        // Sort descending by block number
        return b.blockNumber - a.blockNumber
      })
101 102 103 104 105 106 107 108 109 110 111 112 113 114
  }

  public async supportsTokenPair(
    l1Token: AddressLike,
    l2Token: AddressLike
  ): Promise<boolean> {
    // Only support ETH deposits and withdrawals.
    return (
      hexStringEquals(toAddress(l1Token), ethers.constants.AddressZero) &&
      hexStringEquals(toAddress(l2Token), predeploys.OVM_ETH)
    )
  }

  populateTransaction = {
115 116 117 118 119 120 121
    approve: async (
      l1Token: AddressLike,
      l2Token: AddressLike,
      amount: NumberLike,
      opts?: {
        overrides?: Overrides
      }
122
    ): Promise<never> => {
123 124 125
      throw new Error(`approvals not necessary for ETH bridge`)
    },

126 127 128 129 130
    deposit: async (
      l1Token: AddressLike,
      l2Token: AddressLike,
      amount: NumberLike,
      opts?: {
131
        recipient?: AddressLike
132 133 134 135 136 137 138 139
        l2GasLimit?: NumberLike
        overrides?: Overrides
      }
    ): Promise<TransactionRequest> => {
      if (!(await this.supportsTokenPair(l1Token, l2Token))) {
        throw new Error(`token pair not supported by bridge`)
      }

140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
      if (opts?.recipient === undefined) {
        return this.l1Bridge.populateTransaction.depositETH(
          opts?.l2GasLimit || 200_000, // Default to 200k gas limit.
          '0x', // No data.
          {
            ...omit(opts?.overrides || {}, 'value'),
            value: amount,
          }
        )
      } else {
        return this.l1Bridge.populateTransaction.depositETHTo(
          toAddress(opts.recipient),
          opts?.l2GasLimit || 200_000, // Default to 200k gas limit.
          '0x', // No data.
          {
            ...omit(opts?.overrides || {}, 'value'),
            value: amount,
          }
        )
      }
160 161 162 163 164 165 166
    },

    withdraw: async (
      l1Token: AddressLike,
      l2Token: AddressLike,
      amount: NumberLike,
      opts?: {
167
        recipient?: AddressLike
168 169 170 171 172 173 174
        overrides?: Overrides
      }
    ): Promise<TransactionRequest> => {
      if (!(await this.supportsTokenPair(l1Token, l2Token))) {
        throw new Error(`token pair not supported by bridge`)
      }

175 176 177 178 179 180
      if (opts?.recipient === undefined) {
        return this.l2Bridge.populateTransaction.withdraw(
          toAddress(l2Token),
          amount,
          0, // L1 gas not required.
          '0x', // No data.
181 182 183 184
          {
            ...omit(opts?.overrides || {}, 'value'),
            value: this.messenger.bedrock ? amount : 0,
          }
185 186 187 188 189 190 191 192
        )
      } else {
        return this.l2Bridge.populateTransaction.withdrawTo(
          toAddress(l2Token),
          toAddress(opts.recipient),
          amount,
          0, // L1 gas not required.
          '0x', // No data.
193 194 195 196
          {
            ...omit(opts?.overrides || {}, 'value'),
            value: this.messenger.bedrock ? amount : 0,
          }
197 198
        )
      }
199 200 201
    },
  }
}