Skip to content

Commit 2825b58

Browse files
authored
Merge pull request #687 from onflow/revert-671-mpeter/improve-gas-estimation-logic
Revert "Improve gas estimation logic for `eth_estimateGas`"
2 parents 6d11b34 + 15cbe70 commit 2825b58

File tree

5 files changed

+55
-129
lines changed

5 files changed

+55
-129
lines changed

api/utils.go

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ func handleError[T any](err error, log zerolog.Logger, collector metrics.Collect
136136
// `EVM.dryRun` inside Cadence scripts, meaning that no state change
137137
// will occur.
138138
// This is only useful for `eth_estimateGas` and `eth_call` endpoints.
139-
func encodeTxFromArgs(args ethTypes.TransactionArgs) (*types.DynamicFeeTx, error) {
139+
func encodeTxFromArgs(args ethTypes.TransactionArgs) (*types.LegacyTx, error) {
140140
var data []byte
141141
if args.Data != nil {
142142
data = *args.Data
@@ -156,19 +156,12 @@ func encodeTxFromArgs(args ethTypes.TransactionArgs) (*types.DynamicFeeTx, error
156156
value = args.Value.ToInt()
157157
}
158158

159-
accessList := types.AccessList{}
160-
if args.AccessList != nil {
161-
accessList = *args.AccessList
162-
}
163-
164-
return &types.DynamicFeeTx{
165-
Nonce: 0,
166-
To: args.To,
167-
Value: value,
168-
Gas: gasLimit,
169-
Data: data,
170-
GasTipCap: (*big.Int)(args.MaxPriorityFeePerGas),
171-
GasFeeCap: (*big.Int)(args.MaxFeePerGas),
172-
AccessList: accessList,
159+
return &types.LegacyTx{
160+
Nonce: 0,
161+
To: args.To,
162+
Value: value,
163+
Gas: gasLimit,
164+
GasPrice: big.NewInt(0),
165+
Data: data,
173166
}, nil
174167
}

services/requester/requester.go

Lines changed: 33 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,6 @@ var (
4545
const minFlowBalance = 2
4646
const 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-
5248
type Requester interface {
5349
// SendRawTransaction will submit signed transaction data to the network.
5450
// The submitted EVM transaction hash is returned.
@@ -62,7 +58,7 @@ type Requester interface {
6258
// Note, this function doesn't make and changes in the state/blockchain and is
6359
// useful to execute and retrieve values.
6460
Call(
65-
tx *types.DynamicFeeTx,
61+
tx *types.LegacyTx,
6662
from common.Address,
6763
height uint64,
6864
stateOverrides *ethTypes.StateOverride,
@@ -72,7 +68,7 @@ type Requester interface {
7268
// Note, this function doesn't make any changes in the state/blockchain and is
7369
// useful to executed and retrieve the gas consumption and possible failures.
7470
EstimateGas(
75-
tx *types.DynamicFeeTx,
71+
tx *types.LegacyTx,
7672
from common.Address,
7773
height uint64,
7874
stateOverrides *ethTypes.StateOverride,
@@ -328,7 +324,7 @@ func (e *EVM) GetStorageAt(
328324
}
329325

330326
func (e *EVM) Call(
331-
tx *types.DynamicFeeTx,
327+
tx *types.LegacyTx,
332328
from common.Address,
333329
height uint64,
334330
stateOverrides *ethTypes.StateOverride,
@@ -338,113 +334,42 @@ func (e *EVM) Call(
338334
return nil, err
339335
}
340336

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
337+
return result.ReturnedData, err
350338
}
351339

352340
func (e *EVM) EstimateGas(
353-
tx *types.DynamicFeeTx,
341+
tx *types.LegacyTx,
354342
from common.Address,
355343
height uint64,
356344
stateOverrides *ethTypes.StateOverride,
357345
) (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.
374346
result, err := e.dryRunTx(tx, from, height, stateOverrides)
375347
if err != nil {
376348
return 0, err
377349
}
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-
}
412350

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-
}
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
441366

442-
if tx.AccessList != nil {
443-
passingGasLimit += uint64(len(tx.AccessList)) * gethParams.TxAccessListAddressGas
444-
passingGasLimit += uint64(tx.AccessList.StorageKeys()) * gethParams.TxAccessListStorageKeyGas
367+
// Take into account any gas refunds, which are calculated only after
368+
// transaction execution.
369+
result.GasConsumed += result.GasRefund
445370
}
446371

447-
return passingGasLimit, nil
372+
return result.GasConsumed, err
448373
}
449374

450375
func (e *EVM) GetCode(
@@ -536,7 +461,7 @@ func (e *EVM) evmToCadenceHeight(height uint64) (uint64, error) {
536461
}
537462

538463
func (e *EVM) dryRunTx(
539-
tx *types.DynamicFeeTx,
464+
tx *types.LegacyTx,
540465
from common.Address,
541466
height uint64,
542467
stateOverrides *ethTypes.StateOverride,
@@ -596,6 +521,14 @@ func (e *EVM) dryRunTx(
596521
return nil, err
597522
}
598523

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+
599532
return result, nil
600533
}
601534

tests/web3js/build_evm_state_test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ it('should handle a large number of EVM interactions', async () => {
156156
gas: 55_000,
157157
gasPrice: conf.minGasPrice
158158
}, 82n)
159-
assert.equal(estimatedGas, 21358n)
159+
assert.equal(estimatedGas, 23823n)
160160

161161
estimatedGas = await web3.eth.estimateGas({
162162
from: conf.eoa.address,
@@ -165,7 +165,7 @@ it('should handle a large number of EVM interactions', async () => {
165165
gas: 55_000,
166166
gasPrice: conf.minGasPrice
167167
}, latest)
168-
assert.equal(estimatedGas, 26811n)
168+
assert.equal(estimatedGas, 29292n)
169169

170170
// Add calls to verify correctness of eth_getCode on historical heights
171171
let code = await web3.eth.getCode(contractAddress, 82n)

tests/web3js/debug_traces_test.js

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ it('should retrieve transaction traces', async () => {
3535
// Assert proper response for `callTracer`
3636
let txTrace = response.body.result
3737
assert.equal(txTrace.from, '0xfacf71692421039876a5bb4f10ef7a439d8ef61e')
38-
assert.equal(txTrace.gas, '0x1167ac')
38+
assert.equal(txTrace.gas, '0x118e0c')
3939
assert.equal(txTrace.gasUsed, '0x114010')
4040
assert.equal(txTrace.to, '0x99a64c993965f8d69f985b5171bc20065cc32fab')
4141
assert.lengthOf(txTrace.input, 9856n)
@@ -92,7 +92,7 @@ it('should retrieve transaction traces', async () => {
9292
// Assert proper response for `callTracer`
9393
txTrace = response.body.result
9494
assert.equal(txTrace.from, '0xfacf71692421039876a5bb4f10ef7a439d8ef61e')
95-
assert.equal(txTrace.gas, '0x697f')
95+
assert.equal(txTrace.gas, '0x72c3')
9696
assert.equal(txTrace.gasUsed, '0x6827')
9797
assert.equal(txTrace.to, '0x99a64c993965f8d69f985b5171bc20065cc32fab')
9898
assert.equal(
@@ -161,10 +161,10 @@ it('should retrieve transaction traces', async () => {
161161
txTraces,
162162
[
163163
{
164-
txHash: '0xc34f49f9c6b56ebd88095054e2ad42d6854ba818a9657caf3f8500161a5e4ef7',
164+
txHash: '0x87449feedc004c75c0e8b12d01656f2e28366c7d73b1b5336beae20aaa5033dd',
165165
result: {
166166
from: '0xfacf71692421039876a5bb4f10ef7a439d8ef61e',
167-
gas: '0x697f',
167+
gas: '0x72c3',
168168
gasUsed: '0x6827',
169169
to: '0x99a64c993965f8d69f985b5171bc20065cc32fab',
170170
input: '0x6057361d0000000000000000000000000000000000000000000000000000000000000064',
@@ -200,10 +200,10 @@ it('should retrieve transaction traces', async () => {
200200
txTraces,
201201
[
202202
{
203-
txHash: '0xc34f49f9c6b56ebd88095054e2ad42d6854ba818a9657caf3f8500161a5e4ef7',
203+
txHash: '0x87449feedc004c75c0e8b12d01656f2e28366c7d73b1b5336beae20aaa5033dd',
204204
result: {
205205
from: '0xfacf71692421039876a5bb4f10ef7a439d8ef61e',
206-
gas: '0x697f',
206+
gas: '0x72c3',
207207
gasUsed: '0x6827',
208208
to: '0x99a64c993965f8d69f985b5171bc20065cc32fab',
209209
input: '0x6057361d0000000000000000000000000000000000000000000000000000000000000064',
@@ -257,15 +257,15 @@ it('should retrieve transaction traces', async () => {
257257
txTrace,
258258
{
259259
from: conf.eoa.address.toLowerCase(),
260-
gas: '0xbf57',
260+
gas: '0xc9c7',
261261
gasUsed: '0x6147',
262262
to: contractAddress.toLowerCase(),
263263
input: '0xc550f90f',
264264
output: '0x0000000000000000000000000000000000000000000000000000000000000006',
265265
calls: [
266266
{
267267
from: contractAddress.toLowerCase(),
268-
gas: '0x5f01',
268+
gas: '0x6948',
269269
gasUsed: '0x2',
270270
to: '0x0000000000000000000000010000000000000001',
271271
input: '0x53e87d66',

tests/web3js/eth_deploy_contract_and_interact_test.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ it('deploy contract and interact', async () => {
221221
},
222222
'0x1'
223223
)
224-
assert.equal(gasEstimate, 21510n)
224+
assert.equal(gasEstimate, 23977n)
225225

226226
gasEstimate = await web3.eth.estimateGas(
227227
{
@@ -233,7 +233,7 @@ it('deploy contract and interact', async () => {
233233
},
234234
'latest'
235235
)
236-
assert.equal(gasEstimate, 25052n)
236+
assert.equal(gasEstimate, 27398n)
237237

238238
// check that `eth_call` can handle state overrides
239239
let stateOverrides = {
@@ -274,7 +274,7 @@ it('deploy contract and interact', async () => {
274274
assert.isDefined(response.body)
275275

276276
result = response.body.result
277-
assert.equal(result, '0x697f')
277+
assert.equal(result, '0x72c3')
278278

279279
stateOverrides = {
280280
[contractAddress]: {
@@ -295,5 +295,5 @@ it('deploy contract and interact', async () => {
295295
// setting a storage slot from a zero-value, to a non-zero value has an
296296
// increase of about 20,000 gas. Which is quite different to `0x72c3`.
297297
result = response.body.result
298-
assert.equal(result, '0xac6d')
298+
assert.equal(result, '0xb69a')
299299
})

0 commit comments

Comments
 (0)