github.com/ethereum-optimism/optimism@v1.7.2/packages/sdk/src/l2-provider.ts (about)

     1  import { Provider, TransactionRequest } from '@ethersproject/abstract-provider'
     2  import { serialize } from '@ethersproject/transactions'
     3  import { Contract, BigNumber } from 'ethers'
     4  import { predeploys, getContractInterface } from '@eth-optimism/contracts'
     5  import cloneDeep from 'lodash/cloneDeep'
     6  
     7  import { assert } from './utils/assert'
     8  import { L2Provider, ProviderLike, NumberLike } from './interfaces'
     9  import { toProvider, toNumber, toBigNumber } from './utils'
    10  
    11  type ProviderTypeIsWrong = any
    12  
    13  /**
    14   * Gets a reasonable nonce for the transaction.
    15   *
    16   * @param provider Provider to get the nonce from.
    17   * @param tx Requested transaction.
    18   * @returns A reasonable nonce for the transaction.
    19   */
    20  const getNonceForTx = async (
    21    provider: ProviderLike,
    22    tx: TransactionRequest
    23  ): Promise<number> => {
    24    if (tx.nonce !== undefined) {
    25      return toNumber(tx.nonce as NumberLike)
    26    } else if (tx.from !== undefined) {
    27      return toProvider(provider).getTransactionCount(tx.from)
    28    } else {
    29      // Large nonce with lots of non-zero bytes
    30      return 0xffffffff
    31    }
    32  }
    33  
    34  /**
    35   * Returns a Contract object for the GasPriceOracle.
    36   *
    37   * @param provider Provider to attach the contract to.
    38   * @returns Contract object for the GasPriceOracle.
    39   */
    40  const connectGasPriceOracle = (provider: ProviderLike): Contract => {
    41    return new Contract(
    42      predeploys.OVM_GasPriceOracle,
    43      getContractInterface('OVM_GasPriceOracle'),
    44      toProvider(provider)
    45    )
    46  }
    47  
    48  /**
    49   * Gets the current L1 gas price as seen on L2.
    50   *
    51   * @param l2Provider L2 provider to query the L1 gas price from.
    52   * @returns Current L1 gas price as seen on L2.
    53   */
    54  export const getL1GasPrice = async (
    55    l2Provider: ProviderLike
    56  ): Promise<BigNumber> => {
    57    const gpo = connectGasPriceOracle(l2Provider)
    58    return gpo.l1BaseFee()
    59  }
    60  
    61  /**
    62   * Estimates the amount of L1 gas required for a given L2 transaction.
    63   *
    64   * @param l2Provider L2 provider to query the gas usage from.
    65   * @param tx Transaction to estimate L1 gas for.
    66   * @returns Estimated L1 gas.
    67   */
    68  export const estimateL1Gas = async (
    69    l2Provider: ProviderLike,
    70    tx: TransactionRequest
    71  ): Promise<BigNumber> => {
    72    const gpo = connectGasPriceOracle(l2Provider)
    73    return gpo.getL1GasUsed(
    74      serialize({
    75        to: tx.to,
    76        gasLimit: tx.gasLimit,
    77        gasPrice: tx.gasPrice,
    78        maxFeePerGas: tx.maxFeePerGas,
    79        maxPriorityFeePerGas: tx.maxPriorityFeePerGas,
    80        data: tx.data,
    81        value: tx.value,
    82        chainId: tx.chainId,
    83        type: tx.type,
    84        accessList: tx.accessList,
    85        nonce: tx.nonce
    86          ? BigNumber.from(tx.nonce).toNumber()
    87          : await getNonceForTx(l2Provider, tx),
    88      })
    89    )
    90  }
    91  
    92  /**
    93   * Estimates the amount of L1 gas cost for a given L2 transaction in wei.
    94   *
    95   * @param l2Provider L2 provider to query the gas usage from.
    96   * @param tx Transaction to estimate L1 gas cost for.
    97   * @returns Estimated L1 gas cost.
    98   */
    99  export const estimateL1GasCost = async (
   100    l2Provider: ProviderLike,
   101    tx: TransactionRequest
   102  ): Promise<BigNumber> => {
   103    const gpo = connectGasPriceOracle(l2Provider)
   104    return gpo.getL1Fee(
   105      serialize({
   106        to: tx.to,
   107        gasLimit: tx.gasLimit,
   108        gasPrice: tx.gasPrice,
   109        maxFeePerGas: tx.maxFeePerGas,
   110        maxPriorityFeePerGas: tx.maxPriorityFeePerGas,
   111        data: tx.data,
   112        value: tx.value,
   113        chainId: tx.chainId,
   114        type: tx.type,
   115        accessList: tx.accessList,
   116        nonce: tx.nonce
   117          ? BigNumber.from(tx.nonce).toNumber()
   118          : await getNonceForTx(l2Provider, tx),
   119      })
   120    )
   121  }
   122  
   123  /**
   124   * Estimates the L2 gas cost for a given L2 transaction in wei.
   125   *
   126   * @param l2Provider L2 provider to query the gas usage from.
   127   * @param tx Transaction to estimate L2 gas cost for.
   128   * @returns Estimated L2 gas cost.
   129   */
   130  export const estimateL2GasCost = async (
   131    l2Provider: ProviderLike,
   132    tx: TransactionRequest
   133  ): Promise<BigNumber> => {
   134    const parsed = toProvider(l2Provider)
   135    const l2GasPrice = await parsed.getGasPrice()
   136    const l2GasCost = await parsed.estimateGas(tx)
   137    return l2GasPrice.mul(l2GasCost)
   138  }
   139  
   140  /**
   141   * Estimates the total gas cost for a given L2 transaction in wei.
   142   *
   143   * @param l2Provider L2 provider to query the gas usage from.
   144   * @param tx Transaction to estimate total gas cost for.
   145   * @returns Estimated total gas cost.
   146   */
   147  export const estimateTotalGasCost = async (
   148    l2Provider: ProviderLike,
   149    tx: TransactionRequest
   150  ): Promise<BigNumber> => {
   151    const l1GasCost = await estimateL1GasCost(l2Provider, tx)
   152    const l2GasCost = await estimateL2GasCost(l2Provider, tx)
   153    return l1GasCost.add(l2GasCost)
   154  }
   155  
   156  /**
   157   * Determines if a given Provider is an L2Provider.  Will coerce type
   158   * if true
   159   *
   160   * @param provider The provider to check
   161   * @returns Boolean
   162   * @example
   163   * if (isL2Provider(provider)) {
   164   *   // typescript now knows it is of type L2Provider
   165   *   const gasPrice = await provider.estimateL2GasPrice(tx)
   166   * }
   167   */
   168  export const isL2Provider = <TProvider extends Provider>(
   169    provider: TProvider
   170  ): provider is L2Provider<TProvider> => {
   171    return Boolean((provider as L2Provider<TProvider>)._isL2Provider)
   172  }
   173  
   174  /**
   175   * Returns an provider wrapped as an Optimism L2 provider. Adds a few extra helper functions to
   176   * simplify the process of estimating the gas usage for a transaction on Optimism. Returns a COPY
   177   * of the original provider.
   178   *
   179   * @param provider Provider to wrap into an L2 provider.
   180   * @returns Provider wrapped as an L2 provider.
   181   */
   182  export const asL2Provider = <TProvider extends Provider>(
   183    provider: TProvider
   184  ): L2Provider<TProvider> => {
   185    // Skip if we've already wrapped this provider.
   186    if (isL2Provider(provider)) {
   187      return provider
   188    }
   189  
   190    // Make a copy of the provider since we'll be modifying some internals and don't want to mess
   191    // with the original object.
   192    const l2Provider = cloneDeep(provider) as L2Provider<TProvider>
   193  
   194    // Not exactly sure when the provider wouldn't have a formatter function, but throw an error if
   195    // it doesn't have one. The Provider type doesn't define it but every provider I've dealt with
   196    // seems to have it.
   197    // TODO this may be fixed if library has gotten updated since
   198    const formatter = (l2Provider as ProviderTypeIsWrong).formatter
   199    assert(formatter, `provider.formatter must be defined`)
   200  
   201    // Modify the block formatter to return the state root. Not strictly related to Optimism, just a
   202    // generally useful thing that really should've been on the Ethers block object to begin with.
   203    // TODO: Maybe we should make a PR to add this to the Ethers library?
   204    const ogBlockFormatter = formatter.block.bind(formatter)
   205    formatter.block = (block: any) => {
   206      const parsed = ogBlockFormatter(block)
   207      parsed.stateRoot = block.stateRoot
   208      return parsed
   209    }
   210  
   211    // Modify the block formatter to include all the L2 fields for transactions.
   212    const ogBlockWithTxFormatter = formatter.blockWithTransactions.bind(formatter)
   213    formatter.blockWithTransactions = (block: any) => {
   214      const parsed = ogBlockWithTxFormatter(block)
   215      parsed.stateRoot = block.stateRoot
   216      parsed.transactions = parsed.transactions.map((tx: any, idx: number) => {
   217        const ogTx = block.transactions[idx]
   218        tx.l1BlockNumber = ogTx.l1BlockNumber
   219          ? toNumber(ogTx.l1BlockNumber)
   220          : ogTx.l1BlockNumber
   221        tx.l1Timestamp = ogTx.l1Timestamp
   222          ? toNumber(ogTx.l1Timestamp)
   223          : ogTx.l1Timestamp
   224        tx.l1TxOrigin = ogTx.l1TxOrigin
   225        tx.queueOrigin = ogTx.queueOrigin
   226        tx.rawTransaction = ogTx.rawTransaction
   227        return tx
   228      })
   229      return parsed
   230    }
   231  
   232    // Modify the transaction formatter to include all the L2 fields for transactions.
   233    const ogTxResponseFormatter = formatter.transactionResponse.bind(formatter)
   234    formatter.transactionResponse = (tx: any) => {
   235      const parsed = ogTxResponseFormatter(tx)
   236      parsed.txType = tx.txType
   237      parsed.queueOrigin = tx.queueOrigin
   238      parsed.rawTransaction = tx.rawTransaction
   239      parsed.l1TxOrigin = tx.l1TxOrigin
   240      parsed.l1BlockNumber = tx.l1BlockNumber
   241        ? parseInt(tx.l1BlockNumber, 16)
   242        : tx.l1BlockNumbers
   243      return parsed
   244    }
   245  
   246    // Modify the receipt formatter to include all the L2 fields.
   247    const ogReceiptFormatter = formatter.receipt.bind(formatter)
   248    formatter.receipt = (receipt: any) => {
   249      const parsed = ogReceiptFormatter(receipt)
   250      parsed.l1GasPrice = toBigNumber(receipt.l1GasPrice)
   251      parsed.l1GasUsed = toBigNumber(receipt.l1GasUsed)
   252      parsed.l1Fee = toBigNumber(receipt.l1Fee)
   253      parsed.l1FeeScalar = parseFloat(receipt.l1FeeScalar)
   254      return parsed
   255    }
   256  
   257    // Connect extra functions.
   258    l2Provider.getL1GasPrice = async () => {
   259      return getL1GasPrice(l2Provider)
   260    }
   261    l2Provider.estimateL1Gas = async (tx: TransactionRequest) => {
   262      return estimateL1Gas(l2Provider, tx)
   263    }
   264    l2Provider.estimateL1GasCost = async (tx: TransactionRequest) => {
   265      return estimateL1GasCost(l2Provider, tx)
   266    }
   267    l2Provider.estimateL2GasCost = async (tx: TransactionRequest) => {
   268      return estimateL2GasCost(l2Provider, tx)
   269    }
   270    l2Provider.estimateTotalGasCost = async (tx: TransactionRequest) => {
   271      return estimateTotalGasCost(l2Provider, tx)
   272    }
   273  
   274    l2Provider._isL2Provider = true
   275  
   276    return l2Provider
   277  }