From 9fd8d8f1cbba42b698e9f2f907a44f8d6a958447 Mon Sep 17 00:00:00 2001 From: Luke Date: Sat, 25 Apr 2026 11:43:21 +0800 Subject: [PATCH 1/3] Use latest block for fill transaction gas estimation --- rpc/backend/call_tx.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpc/backend/call_tx.go b/rpc/backend/call_tx.go index a1c59cf85f..9ed2585eff 100644 --- a/rpc/backend/call_tx.go +++ b/rpc/backend/call_tx.go @@ -279,7 +279,7 @@ func (b *Backend) SetTxDefaults(ctx context.Context, args evmtypes.TransactionAr Nonce: args.Nonce, } - blockNr := rpctypes.NewBlockNumber(big.NewInt(0)) + blockNr := rpctypes.EthLatestBlockNumber blockNrOrHash := rpctypes.BlockNumberOrHash{BlockNumber: &blockNr} estimated, err := b.EstimateGas(ctx, callArgs, &blockNrOrHash, nil) if err != nil { From 828456a77dbcc878b03cde512ee6e6716b220d82 Mon Sep 17 00:00:00 2001 From: Luke Date: Sat, 25 Apr 2026 11:48:04 +0800 Subject: [PATCH 2/3] Update changelog for fill transaction fix --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e95f31755..f905717c2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,7 @@ - [\#993](https://github.com/cosmos/evm/pull/993) Enforce `src_callback` contract address to match the packet sender for IBC acknowledgement and timeout callbacks to prevent arbitrary contract execution. - [\#1050](https://github.com/cosmos/evm/pull/1050) Align precompile gas calculation with expected EVM gas semantics. - [\#1107](https://github.com/cosmos/evm/pull/1107) Skip StateDB commit error transactions during receipt conversion to prevent `invalid message index` errors in block RPCs. +- [\#1138](https://github.com/cosmos/evm/pull/1138) Use the latest block instead of numeric `0` when `eth_fillTransaction` estimates gas so the RPC does not resolve block 1 by mistake. ## v0.6.0 From ca41a6e5bf549244aeb25334d42fc2ba72f90e9b Mon Sep 17 00:00:00 2001 From: Luke Date: Tue, 28 Apr 2026 09:39:26 +0800 Subject: [PATCH 3/3] test: cover SetTxDefaults gas defaults --- rpc/backend/call_tx_test.go | 155 ++++++++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 rpc/backend/call_tx_test.go diff --git a/rpc/backend/call_tx_test.go b/rpc/backend/call_tx_test.go new file mode 100644 index 0000000000..3e8ae996d6 --- /dev/null +++ b/rpc/backend/call_tx_test.go @@ -0,0 +1,155 @@ +package backend + +import ( + "context" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" + + tmbytes "github.com/cometbft/cometbft/libs/bytes" + tmrpctypes "github.com/cometbft/cometbft/rpc/core/types" + tmtypes "github.com/cometbft/cometbft/types" + + "github.com/cosmos/evm/rpc/backend/mocks" + "github.com/cosmos/evm/testutil/constants" + feemarkettypes "github.com/cosmos/evm/x/feemarket/types" + evmtypes "github.com/cosmos/evm/x/vm/types" + + grpctypes "github.com/cosmos/cosmos-sdk/types/grpc" +) + +func TestSetTxDefaults(t *testing.T) { + from := common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678") + to := common.HexToAddress("0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef") + latestHeight := int64(10) + estimatedGas := hexutil.Uint64(21000) + providedGas := hexutil.Uint64(50000) + nonce := hexutil.Uint64(0) + gasPrice := (*hexutil.Big)(common.Big1) + maxFeePerGas := (*hexutil.Big)(common.Big2) + + configurator := evmtypes.NewEVMConfigurator() + configurator.ResetTestConfig() + require.NoError(t, evmtypes.SetChainConfig(evmtypes.DefaultChainConfig(constants.ExampleChainID.EVMChainID))) + require.NoError(t, configurator.WithEVMCoinInfo(constants.ExampleChainCoinInfo[constants.ExampleChainID]).Configure()) + + testCases := []struct { + name string + malleate func() evmtypes.TransactionArgs + expectError bool + errorContains string + expectGas *hexutil.Uint64 + expectEstimateGas bool + }{ + { + name: "estimate gas with latest block", + malleate: func() evmtypes.TransactionArgs { + return evmtypes.TransactionArgs{ + From: &from, + To: &to, + Nonce: &nonce, + } + }, + expectGas: &estimatedGas, + expectEstimateGas: true, + }, + { + name: "preserve provided gas", + malleate: func() evmtypes.TransactionArgs { + return evmtypes.TransactionArgs{ + From: &from, + To: &to, + Gas: &providedGas, + Nonce: &nonce, + } + }, + expectGas: &providedGas, + }, + { + name: "error when gas price and dynamic fee are both set", + malleate: func() evmtypes.TransactionArgs { + return evmtypes.TransactionArgs{ + From: &from, + To: &to, + GasPrice: gasPrice, + MaxFeePerGas: maxFeePerGas, + } + }, + expectError: true, + errorContains: "both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + backend := setupMockBackend(t) + mockClient := backend.ClientCtx.Client.(*mocks.Client) + mockEVMQueryClient := backend.QueryClient.QueryClient.(*mocks.EVMQueryClient) + mockFeeMarketQueryClient := backend.QueryClient.FeeMarket.(*mocks.FeeMarketQueryClient) + mockEVMQueryClient.ExpectedCalls = nil + + mockEVMQueryClient.On("Params", mock.Anything, mock.Anything, mock.Anything). + Run(func(args mock.Arguments) { + header := args.Get(2).(grpc.HeaderCallOption).HeaderAddr + *header = metadata.MD{} + header.Set(grpctypes.GRPCBlockHeightHeader, "10") + }). + Return(&evmtypes.QueryParamsResponse{Params: evmtypes.DefaultParams()}, nil). + Maybe() + + mockClient.On("Block", mock.Anything, &latestHeight).Return(&tmrpctypes.ResultBlock{ + BlockID: tmtypes.BlockID{Hash: tmbytes.HexBytes{0x01}}, + Block: &tmtypes.Block{ + Header: tmtypes.Header{ + Height: latestHeight, + Time: time.Now(), + ChainID: constants.ExampleChainID.ChainID, + }, + }, + }, nil).Maybe() + mockClient.On("BlockResults", mock.Anything, &latestHeight).Return(&tmrpctypes.ResultBlockResults{ + Height: latestHeight, + }, nil).Maybe() + mockClient.On("ConsensusParams", mock.Anything, &latestHeight).Return(&tmrpctypes.ResultConsensusParams{ + ConsensusParams: tmtypes.ConsensusParams{Block: tmtypes.BlockParams{MaxGas: 10000000}}, + }, nil).Maybe() + mockClient.On("Header", mock.Anything, &latestHeight).Return(&tmrpctypes.ResultHeader{ + Header: &tmtypes.Header{ + Height: latestHeight, + Time: time.Now(), + ChainID: constants.ExampleChainID.ChainID, + ProposerAddress: []byte{0x01}, + }, + }, nil).Maybe() + mockEVMQueryClient.On("BaseFee", mock.Anything, mock.Anything). + Return(&evmtypes.QueryBaseFeeResponse{}, nil).Maybe() + mockEVMQueryClient.On("ValidatorAccount", mock.Anything, mock.Anything). + Return(&evmtypes.QueryValidatorAccountResponse{}, assert.AnError).Maybe() + mockFeeMarketQueryClient.On("Params", mock.Anything, mock.Anything). + Return(&feemarkettypes.QueryParamsResponse{Params: feemarkettypes.DefaultParams()}, nil).Maybe() + + if tc.expectEstimateGas { + mockEVMQueryClient.On("EstimateGas", mock.Anything, mock.AnythingOfType("*types.EthCallRequest")). + Return(&evmtypes.EstimateGasResponse{Gas: uint64(estimatedGas)}, nil).Once() + } + + result, err := backend.SetTxDefaults(context.Background(), tc.malleate()) + if tc.expectError { + require.Error(t, err) + require.Contains(t, err.Error(), tc.errorContains) + return + } + + require.NoError(t, err) + require.NotNil(t, result.Gas) + require.Equal(t, *tc.expectGas, *result.Gas) + }) + } +}