4545const minFlowBalance = 2
4646const blockGasLimit = 120_000_000
4747
48+ // estimateGasErrorRatio is the amount of overestimation eth_estimateGas
49+ // is allowed to produce in order to speed up calculations.
50+ const estimateGasErrorRatio = 0.015
51+
4852type Requester interface {
4953 // SendRawTransaction will submit signed transaction data to the network.
5054 // The submitted EVM transaction hash is returned.
@@ -58,7 +62,7 @@ type Requester interface {
5862 // Note, this function doesn't make and changes in the state/blockchain and is
5963 // useful to execute and retrieve values.
6064 Call (
61- tx * types.LegacyTx ,
65+ tx * types.DynamicFeeTx ,
6266 from common.Address ,
6367 height uint64 ,
6468 stateOverrides * ethTypes.StateOverride ,
@@ -68,7 +72,7 @@ type Requester interface {
6872 // Note, this function doesn't make any changes in the state/blockchain and is
6973 // useful to executed and retrieve the gas consumption and possible failures.
7074 EstimateGas (
71- tx * types.LegacyTx ,
75+ tx * types.DynamicFeeTx ,
7276 from common.Address ,
7377 height uint64 ,
7478 stateOverrides * ethTypes.StateOverride ,
@@ -324,7 +328,7 @@ func (e *EVM) GetStorageAt(
324328}
325329
326330func (e * EVM ) Call (
327- tx * types.LegacyTx ,
331+ tx * types.DynamicFeeTx ,
328332 from common.Address ,
329333 height uint64 ,
330334 stateOverrides * ethTypes.StateOverride ,
@@ -334,42 +338,113 @@ func (e *EVM) Call(
334338 return nil , err
335339 }
336340
337- return result .ReturnedData , err
341+ resultSummary := result .ResultSummary ()
342+ if resultSummary .ErrorCode != 0 {
343+ if resultSummary .ErrorCode == evmTypes .ExecutionErrCodeExecutionReverted {
344+ return nil , errs .NewRevertError (resultSummary .ReturnedData )
345+ }
346+ return nil , errs .NewFailedTransactionError (resultSummary .ErrorMessage )
347+ }
348+
349+ return result .ReturnedData , nil
338350}
339351
340352func (e * EVM ) EstimateGas (
341- tx * types.LegacyTx ,
353+ tx * types.DynamicFeeTx ,
342354 from common.Address ,
343355 height uint64 ,
344356 stateOverrides * ethTypes.StateOverride ,
345357) (uint64 , error ) {
358+ // Note: The following algorithm, is largely inspired from
359+ // https://github.com/onflow/go-ethereum/blob/master/eth/gasestimator/gasestimator.go#L49-L192,
360+ // and adapted to fit our use-case.
361+ // Binary search the gas limit, as it may need to be higher than the amount used
362+ var (
363+ failingGasLimit uint64 // lowest-known gas limit where tx execution fails
364+ passingGasLimit uint64 // lowest-known gas limit where tx execution succeeds
365+ )
366+ // Determine the highest gas limit that can be used during the estimation.
367+ passingGasLimit = blockGasLimit
368+ if tx .Gas >= gethParams .TxGas {
369+ passingGasLimit = tx .Gas
370+ }
371+ tx .Gas = passingGasLimit
372+ // We first execute the transaction at the highest allowable gas limit,
373+ // since if this fails we can return error immediately.
346374 result , err := e .dryRunTx (tx , from , height , stateOverrides )
347375 if err != nil {
348376 return 0 , err
349377 }
378+ resultSummary := result .ResultSummary ()
379+ if resultSummary .ErrorCode != 0 {
380+ if resultSummary .ErrorCode == evmTypes .ExecutionErrCodeExecutionReverted {
381+ return 0 , errs .NewRevertError (resultSummary .ReturnedData )
382+ }
383+ return 0 , errs .NewFailedTransactionError (resultSummary .ErrorMessage )
384+ }
385+
386+ // For almost any transaction, the gas consumed by the unconstrained execution
387+ // above lower-bounds the gas limit required for it to succeed. One exception
388+ // is those that explicitly check gas remaining in order to execute within a
389+ // given limit, but we probably don't want to return the lowest possible gas
390+ // limit for these cases anyway.
391+ failingGasLimit = result .GasConsumed - 1
392+
393+ // There's a fairly high chance for the transaction to execute successfully
394+ // with gasLimit set to the first execution's GasConsumed + GasRefund.
395+ // Explicitly check that gas amount and use as a limit for the binary search.
396+ optimisticGasLimit := (result .GasConsumed + result .GasRefund + gethParams .CallStipend ) * 64 / 63
397+ if optimisticGasLimit < passingGasLimit {
398+ tx .Gas = optimisticGasLimit
399+ result , err = e .dryRunTx (tx , from , height , stateOverrides )
400+ if err != nil {
401+ // This should not happen under normal conditions since if we make it this far the
402+ // transaction had run without error at least once before.
403+ return 0 , err
404+ }
405+ resultSummary := result .ResultSummary ()
406+ if resultSummary .ErrorCode == evmTypes .ExecutionErrCodeOutOfGas {
407+ failingGasLimit = optimisticGasLimit
408+ } else {
409+ passingGasLimit = optimisticGasLimit
410+ }
411+ }
350412
351- if result .Successful () {
352- // As mentioned in https://github.com/ethereum/EIPs/blob/master/EIPS/eip-150.md#specification
353- // Define "all but one 64th" of N as N - floor(N / 64).
354- // If a call asks for more gas than the maximum allowed amount
355- // (i.e. the total amount of gas remaining in the parent after subtracting
356- // the gas cost of the call and memory expansion), do not return an OOG error;
357- // instead, if a call asks for more gas than all but one 64th of the maximum
358- // allowed amount, call with all but one 64th of the maximum allowed amount of
359- // gas (this is equivalent to a version of EIP-901 plus EIP-1142).
360- // CREATE only provides all but one 64th of the parent gas to the child call.
361- result .GasConsumed = AddOne64th (result .GasConsumed )
362-
363- // Adding `gethParams.SstoreSentryGasEIP2200` is needed for this condition:
364- // https://github.com/onflow/go-ethereum/blob/master/core/vm/operations_acl.go#L29-L32
365- result .GasConsumed += gethParams .SstoreSentryGasEIP2200
413+ // Binary search for the smallest gas limit that allows the tx to execute successfully.
414+ for failingGasLimit + 1 < passingGasLimit {
415+ // It is a bit pointless to return a perfect estimation, as changing
416+ // network conditions require the caller to bump it up anyway. Since
417+ // wallets tend to use 20-25% bump, allowing a small approximation
418+ // error is fine (as long as it's upwards).
419+ if float64 (passingGasLimit - failingGasLimit )/ float64 (passingGasLimit ) < estimateGasErrorRatio {
420+ break
421+ }
422+ mid := (passingGasLimit + failingGasLimit ) / 2
423+ if mid > failingGasLimit * 2 {
424+ // Most txs don't need much higher gas limit than their gas used, and most txs don't
425+ // require near the full block limit of gas, so the selection of where to bisect the
426+ // range here is skewed to favor the low side.
427+ mid = failingGasLimit * 2
428+ }
429+ tx .Gas = mid
430+ result , err = e .dryRunTx (tx , from , height , stateOverrides )
431+ if err != nil {
432+ return 0 , err
433+ }
434+ resultSummary := result .ResultSummary ()
435+ if resultSummary .ErrorCode == evmTypes .ExecutionErrCodeOutOfGas {
436+ failingGasLimit = mid
437+ } else {
438+ passingGasLimit = mid
439+ }
440+ }
366441
367- // Take into account any gas refunds, which are calculated only after
368- // transaction execution.
369- result . GasConsumed += result . GasRefund
442+ if tx . AccessList != nil {
443+ passingGasLimit += uint64 ( len ( tx . AccessList )) * gethParams . TxAccessListAddressGas
444+ passingGasLimit += uint64 ( tx . AccessList . StorageKeys ()) * gethParams . TxAccessListStorageKeyGas
370445 }
371446
372- return result . GasConsumed , err
447+ return passingGasLimit , nil
373448}
374449
375450func (e * EVM ) GetCode (
@@ -461,7 +536,7 @@ func (e *EVM) evmToCadenceHeight(height uint64) (uint64, error) {
461536}
462537
463538func (e * EVM ) dryRunTx (
464- tx * types.LegacyTx ,
539+ tx * types.DynamicFeeTx ,
465540 from common.Address ,
466541 height uint64 ,
467542 stateOverrides * ethTypes.StateOverride ,
@@ -521,14 +596,6 @@ func (e *EVM) dryRunTx(
521596 return nil , err
522597 }
523598
524- resultSummary := result .ResultSummary ()
525- if resultSummary .ErrorCode != 0 {
526- if resultSummary .ErrorCode == evmTypes .ExecutionErrCodeExecutionReverted {
527- return nil , errs .NewRevertError (resultSummary .ReturnedData )
528- }
529- return nil , errs .NewFailedTransactionError (resultSummary .ErrorMessage )
530- }
531-
532599 return result , nil
533600}
534601
0 commit comments