diff --git a/api/api.go b/api/api.go index 0a7474de..c447937e 100644 --- a/api/api.go +++ b/api/api.go @@ -576,7 +576,7 @@ func (b *BlockChainAPI) Call( res, err := b.evm.Call(tx, from, height, stateOverrides, blockOverrides) if err != nil { - return handleError[hexutil.Bytes](err, l, b.collector) + return nil, err } return res, nil @@ -725,7 +725,8 @@ func (b *BlockChainAPI) EstimateGas( tx, err := encodeTxFromArgs(args) if err != nil { - return hexutil.Uint64(BlockGasLimit), nil // return block gas limit + // return max tx gas limit + return hexutil.Uint64(models.TxMaxGasLimit), nil } // Default address in case user does not provide one @@ -745,7 +746,7 @@ func (b *BlockChainAPI) EstimateGas( estimatedGas, err := b.evm.EstimateGas(tx, from, height, stateOverrides) if err != nil { - return handleError[hexutil.Uint64](err, l, b.collector) + return 0, err } return hexutil.Uint64(estimatedGas), nil diff --git a/api/debug.go b/api/debug.go index 330c7bc3..128ff548 100644 --- a/api/debug.go +++ b/api/debug.go @@ -194,7 +194,7 @@ func (d *DebugAPI) TraceCall( flowEVM.StorageAccountAddress(d.config.FlowNetworkID), d.registerStore, blocksProvider, - BlockGasLimit, + models.TxMaxGasLimit, ) view, err := viewProvider.GetBlockView(block.Height) diff --git a/api/utils.go b/api/utils.go index 42650f69..1dece5af 100644 --- a/api/utils.go +++ b/api/utils.go @@ -9,6 +9,7 @@ import ( ethTypes "github.com/onflow/flow-evm-gateway/eth/types" "github.com/onflow/flow-evm-gateway/metrics" + "github.com/onflow/flow-evm-gateway/models" errs "github.com/onflow/flow-evm-gateway/models/errors" "github.com/onflow/flow-evm-gateway/storage" "github.com/onflow/go-ethereum/common" @@ -153,7 +154,7 @@ func encodeTxFromArgs(args ethTypes.TransactionArgs) (*types.DynamicFeeTx, error // provide a high enough gas for the tx to be able to execute, // capped by the gas set in transaction args. - gasLimit := BlockGasLimit + gasLimit := models.TxMaxGasLimit if args.Gas != nil { gasLimit = uint64(*args.Gas) } diff --git a/models/transaction.go b/models/transaction.go index 2cbfc306..849e51b5 100644 --- a/models/transaction.go +++ b/models/transaction.go @@ -7,6 +7,7 @@ import ( "math/big" "github.com/onflow/cadence" + errs "github.com/onflow/flow-evm-gateway/models/errors" "github.com/onflow/flow-go/fvm/evm/events" "github.com/onflow/flow-go/fvm/evm/types" "github.com/onflow/go-ethereum/common" @@ -27,6 +28,11 @@ const ( // more expensive to propagate; larger transactions also take more resources // to validate whether they fit into the pool or not. TxMaxSize = 4 * TxSlotSize // 128KB + + // TxMaxSize is the maximum valid gas limit for EVM transactions that are + // wrapped within a Cadence transaction, configured with 9999 as the + // computation limit. + TxMaxGasLimit = uint64(50_000_000) ) var BaseFeePerGas = big.NewInt(1) @@ -270,6 +276,10 @@ func ValidateTransaction( signer gethTypes.Signer, opts *txpool.ValidationOptions, ) error { + if tx.Gas() > TxMaxGasLimit { + return errs.NewTxGasLimitTooHighError(TxMaxGasLimit) + } + txDataLen := len(tx.Data()) // Contract creation doesn't validate call data, handle first diff --git a/models/transaction_test.go b/models/transaction_test.go index 74324891..f3e3c012 100644 --- a/models/transaction_test.go +++ b/models/transaction_test.go @@ -303,6 +303,20 @@ func TestValidateTransaction(t *testing.T) { valid: true, errMsg: "", }, + "gas limit exceeds max allowed value": { + tx: gethTypes.NewTx( + &gethTypes.LegacyTx{ + Nonce: 1, + To: &validToAddress, + Value: big.NewInt(0), + Gas: TxMaxGasLimit + 25_000, + GasPrice: big.NewInt(0), + Data: []byte{}, + }, + ), + valid: false, + errMsg: "invalid: failed transaction: tx gas limit exceeds the max value of 50000000", + }, "send to 0 address": { tx: gethTypes.NewTx( &gethTypes.LegacyTx{ diff --git a/services/requester/requester.go b/services/requester/requester.go index e17b622c..866d68e2 100644 --- a/services/requester/requester.go +++ b/services/requester/requester.go @@ -44,7 +44,6 @@ var ( const minFlowBalance = 2 const blockGasLimit = 120_000_000 -const txMaxGasLimit = 50_000_000 // estimateGasErrorRatio is the amount of overestimation eth_estimateGas // is allowed to produce in order to speed up calculations. @@ -189,10 +188,6 @@ func (e *EVM) SendRawTransaction(ctx context.Context, data []byte) (common.Hash, return common.Hash{}, err } - if tx.Gas() > txMaxGasLimit { - return common.Hash{}, errs.NewTxGasLimitTooHighError(txMaxGasLimit) - } - if err := models.ValidateTransaction(tx, e.head, e.evmSigner, e.validationOptions); err != nil { return common.Hash{}, err } @@ -333,7 +328,7 @@ func (e *EVM) EstimateGas( passingGasLimit uint64 // lowest-known gas limit where tx execution succeeds ) // Determine the highest gas limit that can be used during the estimation. - passingGasLimit = blockGasLimit + passingGasLimit = models.TxMaxGasLimit if tx.Gas >= gethParams.TxGas { passingGasLimit = tx.Gas } @@ -473,7 +468,7 @@ func (e *EVM) getBlockView( evm.StorageAccountAddress(e.config.FlowNetworkID), e.registerStore, blocksProvider, - blockGasLimit, + models.TxMaxGasLimit, ) return viewProvider.GetBlockView(height) diff --git a/tests/web3js/debug_traces_test.js b/tests/web3js/debug_traces_test.js index 820fd8e9..7e20a0f5 100644 --- a/tests/web3js/debug_traces_test.js +++ b/tests/web3js/debug_traces_test.js @@ -656,4 +656,25 @@ it('should retrieve call traces', async () => { ) assert.equal(updateTrace.value, '0x0') assert.equal(updateTrace.type, 'CALL') + + // test that `gas` limit does not exceed the `50M` value + traceCall = { + from: conf.eoa.address, + to: contractAddress, + data: callData, + value: '0x0', + gasPrice: web3.utils.toHex(conf.minGasPrice), + gas: '0x6CB8080' + } + response = await helpers.callRPCMethod( + 'debug_traceCall', + [traceCall, 'latest', callTracer] + ) + assert.equal(response.status, 200) + assert.isDefined(response.body) + + assert.equal( + response.body.error.message, + 'gas limit is bigger than max gas limit allowed 114000000 > 50000000' + ) }) diff --git a/tests/web3js/eth_failure_handling_test.js b/tests/web3js/eth_failure_handling_test.js index 6185296a..c3ea3210 100644 --- a/tests/web3js/eth_failure_handling_test.js +++ b/tests/web3js/eth_failure_handling_test.js @@ -6,8 +6,31 @@ const web3 = conf.web3 it('should fail when tx gas limit higher than the max value', async () => { let receiver = web3.eth.accounts.create() + let signedTx = await receiver.signTransaction({ + from: receiver.address, + to: conf.eoa.address, + value: 10, + gasPrice: conf.minGasPrice, + gasLimit: 51_000_000, // max tx gas limit is 50_000_000 + }) + let response = await helpers.callRPCMethod( + 'eth_sendRawTransaction', + [signedTx.rawTransaction] + ) + assert.equal(200, response.status) + assert.isDefined(response.body) + + assert.include( + response.body.error.message, + 'tx gas limit exceeds the max value of 50000000' + ) +}) + +it('should fail when eth_call gas limit higher than the max value', async () => { + let receiver = web3.eth.accounts.create() + try { - await helpers.signAndSend({ + await web3.eth.call({ from: conf.eoa.address, to: receiver.address, value: 10, @@ -15,7 +38,32 @@ it('should fail when tx gas limit higher than the max value', async () => { gasLimit: 51_000_000, // max tx gas limit is 50_000_000 }) } catch (e) { - assert.include(e.message, 'tx gas limit exceeds the max value of 50000000') + assert.include( + e.message, + 'gas limit is bigger than max gas limit allowed 51000000 > 50000000' + ) + return + } + + assert.fail('should not reach') +}) + +it('should fail when eth_estimateGas gas limit higher than the max value', async () => { + let receiver = web3.eth.accounts.create() + + try { + await web3.eth.estimateGas({ + from: conf.eoa.address, + to: receiver.address, + value: 10, + gasPrice: conf.minGasPrice, + gasLimit: 51_000_000, // max tx gas limit is 50_000_000 + }) + } catch (e) { + assert.include( + e.message, + 'gas limit is bigger than max gas limit allowed 51000000 > 50000000' + ) return }