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 }