github.com/ethereum-optimism/optimism@v1.7.2/packages/sdk/src/adapters/standard-bridge.ts (about)

     1  /* eslint-disable @typescript-eslint/no-unused-vars */
     2  import {
     3    ethers,
     4    Contract,
     5    Overrides,
     6    Signer,
     7    BigNumber,
     8    CallOverrides,
     9  } from 'ethers'
    10  import {
    11    TransactionRequest,
    12    TransactionResponse,
    13    BlockTag,
    14  } from '@ethersproject/abstract-provider'
    15  import { predeploys } from '@eth-optimism/contracts'
    16  import { hexStringEquals } from '@eth-optimism/core-utils'
    17  import l1StandardBridgeArtifact from '@eth-optimism/contracts-bedrock/forge-artifacts/L1StandardBridge.sol/L1StandardBridge.json'
    18  import l2StandardBridgeArtifact from '@eth-optimism/contracts-bedrock/forge-artifacts/L2StandardBridge.sol/L2StandardBridge.json'
    19  import optimismMintableERC20 from '@eth-optimism/contracts-bedrock/forge-artifacts/OptimismMintableERC20.sol/OptimismMintableERC20.json'
    20  
    21  import { CrossChainMessenger } from '../cross-chain-messenger'
    22  import {
    23    IBridgeAdapter,
    24    NumberLike,
    25    AddressLike,
    26    TokenBridgeMessage,
    27    MessageDirection,
    28  } from '../interfaces'
    29  import { toAddress } from '../utils'
    30  
    31  /**
    32   * Bridge adapter for any token bridge that uses the standard token bridge interface.
    33   */
    34  export class StandardBridgeAdapter implements IBridgeAdapter {
    35    public messenger: CrossChainMessenger
    36    public l1Bridge: Contract
    37    public l2Bridge: Contract
    38  
    39    /**
    40     * Creates a StandardBridgeAdapter instance.
    41     *
    42     * @param opts Options for the adapter.
    43     * @param opts.messenger Provider used to make queries related to cross-chain interactions.
    44     * @param opts.l1Bridge L1 bridge contract.
    45     * @param opts.l2Bridge L2 bridge contract.
    46     */
    47    constructor(opts: {
    48      messenger: CrossChainMessenger
    49      l1Bridge: AddressLike
    50      l2Bridge: AddressLike
    51    }) {
    52      this.messenger = opts.messenger
    53      this.l1Bridge = new Contract(
    54        toAddress(opts.l1Bridge),
    55        l1StandardBridgeArtifact.abi,
    56        this.messenger.l1Provider
    57      )
    58      this.l2Bridge = new Contract(
    59        toAddress(opts.l2Bridge),
    60        l2StandardBridgeArtifact.abi,
    61        this.messenger.l2Provider
    62      )
    63    }
    64  
    65    public async getDepositsByAddress(
    66      address: AddressLike,
    67      opts?: {
    68        fromBlock?: BlockTag
    69        toBlock?: BlockTag
    70      }
    71    ): Promise<TokenBridgeMessage[]> {
    72      const events = await this.l1Bridge.queryFilter(
    73        this.l1Bridge.filters.ERC20DepositInitiated(
    74          undefined,
    75          undefined,
    76          address
    77        ),
    78        opts?.fromBlock,
    79        opts?.toBlock
    80      )
    81  
    82      return events
    83        .filter((event) => {
    84          // Specifically filter out ETH. ETH deposits and withdrawals are handled by the ETH bridge
    85          // adapter. Bridges that are not the ETH bridge should not be able to handle or even
    86          // present ETH deposits or withdrawals.
    87          return (
    88            !hexStringEquals(event.args.l1Token, ethers.constants.AddressZero) &&
    89            !hexStringEquals(event.args.l2Token, predeploys.OVM_ETH)
    90          )
    91        })
    92        .map((event) => {
    93          return {
    94            direction: MessageDirection.L1_TO_L2,
    95            from: event.args.from,
    96            to: event.args.to,
    97            l1Token: event.args.l1Token,
    98            l2Token: event.args.l2Token,
    99            amount: event.args.amount,
   100            data: event.args.extraData,
   101            logIndex: event.logIndex,
   102            blockNumber: event.blockNumber,
   103            transactionHash: event.transactionHash,
   104          }
   105        })
   106        .sort((a, b) => {
   107          // Sort descending by block number
   108          return b.blockNumber - a.blockNumber
   109        })
   110    }
   111  
   112    public async getWithdrawalsByAddress(
   113      address: AddressLike,
   114      opts?: {
   115        fromBlock?: BlockTag
   116        toBlock?: BlockTag
   117      }
   118    ): Promise<TokenBridgeMessage[]> {
   119      const events = await this.l2Bridge.queryFilter(
   120        this.l2Bridge.filters.WithdrawalInitiated(undefined, undefined, address),
   121        opts?.fromBlock,
   122        opts?.toBlock
   123      )
   124  
   125      return events
   126        .filter((event) => {
   127          // Specifically filter out ETH. ETH deposits and withdrawals are handled by the ETH bridge
   128          // adapter. Bridges that are not the ETH bridge should not be able to handle or even
   129          // present ETH deposits or withdrawals.
   130          return (
   131            !hexStringEquals(event.args.l1Token, ethers.constants.AddressZero) &&
   132            !hexStringEquals(event.args.l2Token, predeploys.OVM_ETH)
   133          )
   134        })
   135        .map((event) => {
   136          return {
   137            direction: MessageDirection.L2_TO_L1,
   138            from: event.args.from,
   139            to: event.args.to,
   140            l1Token: event.args.l1Token,
   141            l2Token: event.args.l2Token,
   142            amount: event.args.amount,
   143            data: event.args.extraData,
   144            logIndex: event.logIndex,
   145            blockNumber: event.blockNumber,
   146            transactionHash: event.transactionHash,
   147          }
   148        })
   149        .sort((a, b) => {
   150          // Sort descending by block number
   151          return b.blockNumber - a.blockNumber
   152        })
   153    }
   154  
   155    public async supportsTokenPair(
   156      l1Token: AddressLike,
   157      l2Token: AddressLike
   158    ): Promise<boolean> {
   159      const contract = new Contract(
   160        toAddress(l2Token),
   161        optimismMintableERC20.abi,
   162        this.messenger.l2Provider
   163      )
   164      // Don't support ETH deposits or withdrawals via this bridge.
   165      if (
   166        hexStringEquals(toAddress(l1Token), ethers.constants.AddressZero) ||
   167        hexStringEquals(toAddress(l2Token), predeploys.OVM_ETH)
   168      ) {
   169        return false
   170      }
   171  
   172      // Make sure the L1 token matches.
   173      const remoteL1Token = await contract.l1Token()
   174  
   175      if (!hexStringEquals(remoteL1Token, toAddress(l1Token))) {
   176        return false
   177      }
   178  
   179      // Make sure the L2 bridge matches.
   180      const remoteL2Bridge = await contract.l2Bridge()
   181      if (!hexStringEquals(remoteL2Bridge, this.l2Bridge.address)) {
   182        return false
   183      }
   184  
   185      return true
   186    }
   187  
   188    public async approval(
   189      l1Token: AddressLike,
   190      l2Token: AddressLike,
   191      signer: ethers.Signer
   192    ): Promise<BigNumber> {
   193      if (!(await this.supportsTokenPair(l1Token, l2Token))) {
   194        throw new Error(`token pair not supported by bridge`)
   195      }
   196  
   197      const token = new Contract(
   198        toAddress(l1Token),
   199        optimismMintableERC20.abi,
   200        this.messenger.l1Provider
   201      )
   202  
   203      return token.allowance(await signer.getAddress(), this.l1Bridge.address)
   204    }
   205  
   206    public async approve(
   207      l1Token: AddressLike,
   208      l2Token: AddressLike,
   209      amount: NumberLike,
   210      signer: Signer,
   211      opts?: {
   212        overrides?: Overrides
   213      }
   214    ): Promise<TransactionResponse> {
   215      return signer.sendTransaction(
   216        await this.populateTransaction.approve(l1Token, l2Token, amount, opts)
   217      )
   218    }
   219  
   220    public async deposit(
   221      l1Token: AddressLike,
   222      l2Token: AddressLike,
   223      amount: NumberLike,
   224      signer: Signer,
   225      opts?: {
   226        recipient?: AddressLike
   227        l2GasLimit?: NumberLike
   228        overrides?: Overrides
   229      }
   230    ): Promise<TransactionResponse> {
   231      return signer.sendTransaction(
   232        await this.populateTransaction.deposit(l1Token, l2Token, amount, opts)
   233      )
   234    }
   235  
   236    public async withdraw(
   237      l1Token: AddressLike,
   238      l2Token: AddressLike,
   239      amount: NumberLike,
   240      signer: Signer,
   241      opts?: {
   242        recipient?: AddressLike
   243        overrides?: Overrides
   244      }
   245    ): Promise<TransactionResponse> {
   246      return signer.sendTransaction(
   247        await this.populateTransaction.withdraw(l1Token, l2Token, amount, opts)
   248      )
   249    }
   250  
   251    populateTransaction = {
   252      approve: async (
   253        l1Token: AddressLike,
   254        l2Token: AddressLike,
   255        amount: NumberLike,
   256        opts?: {
   257          overrides?: Overrides
   258        }
   259      ): Promise<TransactionRequest> => {
   260        if (!(await this.supportsTokenPair(l1Token, l2Token))) {
   261          throw new Error(`token pair not supported by bridge`)
   262        }
   263  
   264        const token = new Contract(
   265          toAddress(l1Token),
   266          optimismMintableERC20.abi,
   267          this.messenger.l1Provider
   268        )
   269  
   270        return token.populateTransaction.approve(
   271          this.l1Bridge.address,
   272          amount,
   273          opts?.overrides || {}
   274        )
   275      },
   276  
   277      deposit: async (
   278        l1Token: AddressLike,
   279        l2Token: AddressLike,
   280        amount: NumberLike,
   281        opts?: {
   282          recipient?: AddressLike
   283          l2GasLimit?: NumberLike
   284          overrides?: Overrides
   285        }
   286      ): Promise<TransactionRequest> => {
   287        if (!(await this.supportsTokenPair(l1Token, l2Token))) {
   288          throw new Error(`token pair not supported by bridge`)
   289        }
   290  
   291        if (opts?.recipient === undefined) {
   292          return this.l1Bridge.populateTransaction.depositERC20(
   293            toAddress(l1Token),
   294            toAddress(l2Token),
   295            amount,
   296            opts?.l2GasLimit || 200_000, // Default to 200k gas limit.
   297            '0x', // No data.
   298            opts?.overrides || {}
   299          )
   300        } else {
   301          return this.l1Bridge.populateTransaction.depositERC20To(
   302            toAddress(l1Token),
   303            toAddress(l2Token),
   304            toAddress(opts.recipient),
   305            amount,
   306            opts?.l2GasLimit || 200_000, // Default to 200k gas limit.
   307            '0x', // No data.
   308            opts?.overrides || {}
   309          )
   310        }
   311      },
   312  
   313      withdraw: async (
   314        l1Token: AddressLike,
   315        l2Token: AddressLike,
   316        amount: NumberLike,
   317        opts?: {
   318          recipient?: AddressLike
   319          overrides?: Overrides
   320        }
   321      ): Promise<TransactionRequest> => {
   322        if (!(await this.supportsTokenPair(l1Token, l2Token))) {
   323          throw new Error(`token pair not supported by bridge`)
   324        }
   325  
   326        if (opts?.recipient === undefined) {
   327          return this.l2Bridge.populateTransaction.withdraw(
   328            toAddress(l2Token),
   329            amount,
   330            0, // L1 gas not required.
   331            '0x', // No data.
   332            opts?.overrides || {}
   333          )
   334        } else {
   335          return this.l2Bridge.populateTransaction.withdrawTo(
   336            toAddress(l2Token),
   337            toAddress(opts.recipient),
   338            amount,
   339            0, // L1 gas not required.
   340            '0x', // No data.
   341            opts?.overrides || {}
   342          )
   343        }
   344      },
   345    }
   346  
   347    estimateGas = {
   348      approve: async (
   349        l1Token: AddressLike,
   350        l2Token: AddressLike,
   351        amount: NumberLike,
   352        opts?: {
   353          overrides?: CallOverrides
   354        }
   355      ): Promise<BigNumber> => {
   356        return this.messenger.l1Provider.estimateGas(
   357          await this.populateTransaction.approve(l1Token, l2Token, amount, opts)
   358        )
   359      },
   360  
   361      deposit: async (
   362        l1Token: AddressLike,
   363        l2Token: AddressLike,
   364        amount: NumberLike,
   365        opts?: {
   366          recipient?: AddressLike
   367          l2GasLimit?: NumberLike
   368          overrides?: CallOverrides
   369        }
   370      ): Promise<BigNumber> => {
   371        return this.messenger.l1Provider.estimateGas(
   372          await this.populateTransaction.deposit(l1Token, l2Token, amount, opts)
   373        )
   374      },
   375  
   376      withdraw: async (
   377        l1Token: AddressLike,
   378        l2Token: AddressLike,
   379        amount: NumberLike,
   380        opts?: {
   381          recipient?: AddressLike
   382          overrides?: CallOverrides
   383        }
   384      ): Promise<BigNumber> => {
   385        return this.messenger.l2Provider.estimateGas(
   386          await this.populateTransaction.withdraw(l1Token, l2Token, amount, opts)
   387        )
   388      },
   389    }
   390  }