diff --git a/CHANGELOG.md b/CHANGELOG.md index b837f2dbd..6cc5260a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,6 +61,7 @@ - [\#1061](https://github.com/cosmos/evm/pull/1061) Block nested ICS20 forwarding in source callbacks. - [\#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. +- [\#1183](https://github.com/cosmos/evm/pull/1183) Align eth_call's KV gas accounting with deliverTx so it stops rejecting valid estimateGas results for native precompile txs. ## v0.6.0 diff --git a/tests/integration/x/vm/test_grpc_query.go b/tests/integration/x/vm/test_grpc_query.go index 7867bd31b..e16b04295 100644 --- a/tests/integration/x/vm/test_grpc_query.go +++ b/tests/integration/x/vm/test_grpc_query.go @@ -18,6 +18,7 @@ import ( "github.com/holiman/uint256" "github.com/stretchr/testify/require" + bankprecompile "github.com/cosmos/evm/precompiles/bank" "github.com/cosmos/evm/server/config" testconstants "github.com/cosmos/evm/testutil/constants" "github.com/cosmos/evm/testutil/integration/evm/factory" @@ -2211,6 +2212,40 @@ func (s *KeeperTestSuite) TestEthCall() { } } +// TestEthCallGasMatchesDeliverTxForNativePrecompile asserts that EthCall and +// a broadcasted tx report the same GasUsed when targeting a native precompile, +// covering the BuildEvmExecutionCtx call added to EthCall. +func (s *KeeperTestSuite) TestEthCallGasMatchesDeliverTxForNativePrecompile() { + s.SetupTest() + + sender := s.Keyring.GetKey(0) + to := common.HexToAddress(types.BankPrecompileAddress) + data, err := bankprecompile.ABI.Pack(bankprecompile.BalancesMethod, sender.Addr) + s.Require().NoError(err) + + gas := hexutil.Uint64(500_000) + args, err := json.Marshal(&types.TransactionArgs{ + From: &sender.Addr, To: &to, Data: (*hexutil.Bytes)(&data), Gas: &gas, + }) + s.Require().NoError(err) + + callRes, err := s.Network.GetEvmClient().EthCall(s.Network.GetContext(), &types.EthCallRequest{ + Args: args, GasCap: config.DefaultGasCap, + }) + s.Require().NoError(err) + s.Require().Empty(callRes.VmError) + + txRes, err := s.Factory.ExecuteEthTx(sender.Priv, types.EvmTxArgs{ + To: &to, GasLimit: uint64(gas), Input: data, + }) + s.Require().NoError(err) + deliverRes, err := types.DecodeTxResponse(txRes.Data) + s.Require().NoError(err) + s.Require().Empty(deliverRes.VmError) + + s.Require().Equal(deliverRes.GasUsed, callRes.GasUsed) +} + func (s *KeeperTestSuite) TestBalance() { testCases := []struct { name string diff --git a/x/vm/keeper/grpc_query.go b/x/vm/keeper/grpc_query.go index 8f8ff567f..29cbee776 100644 --- a/x/vm/keeper/grpc_query.go +++ b/x/vm/keeper/grpc_query.go @@ -268,6 +268,12 @@ func (k Keeper) EthCall(c context.Context, req *types.EthCallRequest) (_ *types. } } + // Align eth_call's KV gas accounting with deliverTx: clear KV/transient gas + // configs so EVM-internal cosmos-sdk store ops (e.g. precompile.FlushToCacheCtx) + // don't charge gas. Otherwise eth_call would report a stricter floor than + // real broadcast for txs reaching native precompiles. + ctx = evmante.BuildEvmExecutionCtx(ctx) + var args types.TransactionArgs err = json.Unmarshal(req.Args, &args) if err != nil { diff --git a/x/vm/keeper/grpc_query_test.go b/x/vm/keeper/grpc_query_test.go new file mode 100644 index 000000000..5a1d05676 --- /dev/null +++ b/x/vm/keeper/grpc_query_test.go @@ -0,0 +1,24 @@ +package keeper + +import ( + "testing" + + "github.com/stretchr/testify/require" + + storetypes "github.com/cosmos/cosmos-sdk/store/v2/types" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// buildTraceCtx must zero KV gas configs so simulation matches deliverTx +// (whose ante also zeroes KV via BuildEvmExecutionCtx). SLOAD/SSTORE gas +// accounting then remains opcode-driven and does not consume KV gas, and +// pre-precompile cosmos-sdk store ops (e.g. stateDB.FlushToCacheCtx) are +// free — same as on real chain. +func TestBuildTraceCtxZeroesKVGasConfig(t *testing.T) { + parent := sdk.Context{}. + WithKVGasConfig(storetypes.KVGasConfig()). + WithTransientKVGasConfig(storetypes.TransientGasConfig()) + traceCtx := buildTraceCtx(parent, 1_000_000) + require.Equal(t, storetypes.GasConfig{}, traceCtx.KVGasConfig()) + require.Equal(t, storetypes.GasConfig{}, traceCtx.TransientKVGasConfig()) +}