diff --git a/jsonrpc/eth_endpoint.go b/jsonrpc/eth_endpoint.go index ece4f03c38..79002409b9 100644 --- a/jsonrpc/eth_endpoint.go +++ b/jsonrpc/eth_endpoint.go @@ -551,23 +551,28 @@ func (e *Eth) EstimateGas(arg *txnArgs, rawNum *BlockNumber) (interface{}, error return nil, err } + forksInTime := e.store.GetForksInTime(header.Number) + + if transaction.IsValueTransfer() || transaction.IsContractCreation() { + // if it is a simple value transfer or a contract creation, + // we already know what is the transaction gas cost, no need to apply transaction + gasCost, err := state.TransactionGasCost(transaction, forksInTime.Homestead, forksInTime.Istanbul) + if err != nil { + return nil, err + } + + return argUint64(gasCost), nil + } + // Force transaction gas price if empty if err = e.fillTransactionGasPrice(transaction); err != nil { return nil, err } - forksInTime := e.store.GetForksInTime(header.Number) - - var standardGas uint64 - if transaction.IsContractCreation() && forksInTime.Homestead { - standardGas = state.TxGasContractCreation - } else { - standardGas = state.TxGas - } - var ( - lowEnd = standardGas - highEnd uint64 + standardGas = state.TxGas + lowEnd = standardGas + highEnd uint64 ) // If the gas limit was passed in, use it as a ceiling @@ -579,7 +584,6 @@ func (e *Eth) EstimateGas(arg *txnArgs, rawNum *BlockNumber) (interface{}, error } gasPriceInt := new(big.Int).Set(transaction.GasPrice) - valueInt := new(big.Int).Set(transaction.Value) var availableBalance *big.Int @@ -602,14 +606,6 @@ func (e *Eth) EstimateGas(arg *txnArgs, rawNum *BlockNumber) (interface{}, error } availableBalance = new(big.Int).Set(accountBalance) - - if transaction.Value != nil { - if valueInt.Cmp(availableBalance) > 0 { - return 0, ErrInsufficientFunds - } - - availableBalance.Sub(availableBalance, valueInt) - } } // Recalculate the gas ceiling based on the available funds (if any) @@ -663,7 +659,7 @@ func (e *Eth) EstimateGas(arg *txnArgs, rawNum *BlockNumber) (interface{}, error transaction.Gas = gas - result, applyErr := e.store.ApplyTxn(header, transaction, nil, false) + result, applyErr := e.store.ApplyTxn(header, transaction, nil, true) if result != nil { data = []byte(hex.EncodeToString(result.ReturnValue)) diff --git a/jsonrpc/eth_state_test.go b/jsonrpc/eth_state_test.go index a027ad8507..7c6f81aab6 100644 --- a/jsonrpc/eth_state_test.go +++ b/jsonrpc/eth_state_test.go @@ -772,7 +772,7 @@ func TestEth_EstimateGas_Reverts(t *testing.T) { } } -func TestEth_EstimateGas_Errors(t *testing.T) { +func TestEth_EstimateGas_ValueTransfer(t *testing.T) { store := getExampleStore() ethEndpoint := newTestEthEndpoint(store) @@ -780,19 +780,51 @@ func TestEth_EstimateGas_Errors(t *testing.T) { store.account.account.Balance = big.NewInt(0) // The transaction has a value > 0 + from := types.StringToAddress("0xSenderAddress") + to := types.StringToAddress("0xReceiverAddress") mockTx := constructMockTx(nil, nil) mockTx.Value = argBytesPtr([]byte{0x1}) + mockTx.From = &from + mockTx.To = &to // Run the estimation - estimate, estimateErr := ethEndpoint.EstimateGas( + estimate, err := ethEndpoint.EstimateGas( mockTx, nil, ) - assert.Equal(t, 0, estimate) + assert.NotNil(t, estimate) + estimateUint64, ok := estimate.(argUint64) + assert.True(t, ok) + assert.NoError(t, err) + assert.Equal(t, state.TxGas, uint64(estimateUint64)) // simple value transfers are 21000wei always +} + +func TestEth_EstimateGas_ContractCreation(t *testing.T) { + store := getExampleStore() + ethEndpoint := newTestEthEndpoint(store) + + // Account doesn't have any balance + store.account.account.Balance = big.NewInt(0) + + // The transaction has a value > 0 + from := types.StringToAddress("0xSenderAddress") + mockTx := constructMockTx(nil, nil) + mockTx.From = &from + mockTx.Input = argBytesPtr([]byte{}) + mockTx.To = nil + + // Run the estimation + estimate, err := ethEndpoint.EstimateGas( + mockTx, + nil, + ) - // Make sure the insufficient funds error message is contained - assert.ErrorIs(t, estimateErr, ErrInsufficientFunds) + assert.NotNil(t, estimate) + estimateUint64, ok := estimate.(argUint64) + assert.True(t, ok) + assert.NoError(t, err) + assert.Equal(t, state.TxGasContractCreation, uint64(estimateUint64)) } type mockSpecialStore struct { @@ -867,7 +899,7 @@ func (m *mockSpecialStore) GetCode(root types.Hash, addr types.Address) ([]byte, } func (m *mockSpecialStore) GetForksInTime(blockNumber uint64) chain.ForksInTime { - return chain.ForksInTime{} + return chain.AllForksEnabled.At(0) } func (m *mockSpecialStore) ApplyTxn(header *types.Header, txn *types.Transaction, _ types.StateOverride, _ bool) (*runtime.ExecutionResult, error) { diff --git a/types/transaction.go b/types/transaction.go index f19a79b9ee..53f8380ac8 100644 --- a/types/transaction.go +++ b/types/transaction.go @@ -74,6 +74,14 @@ func (t *Transaction) IsContractCreation() bool { return t.To == nil } +// IsValueTransfer checks if tx is a value transfer +func (t *Transaction) IsValueTransfer() bool { + return t.Value != nil && + t.Value.Sign() != 0 && + len(t.Input) == 0 && + !t.IsContractCreation() +} + // ComputeHash computes the hash of the transaction func (t *Transaction) ComputeHash(blockNumber uint64) *Transaction { GetTransactionHashHandler(blockNumber).ComputeHash(t)