From 208efe06edffb907e94fad99c5695beeb2712fa6 Mon Sep 17 00:00:00 2001 From: matteyu Date: Wed, 24 Jun 2026 22:03:58 -0700 Subject: [PATCH 1/3] fix: cosmwasm evm query path repeatable undercharged evm exec --- x/vm/keeper/call_evm.go | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/x/vm/keeper/call_evm.go b/x/vm/keeper/call_evm.go index 2abe5bb..9d66b28 100644 --- a/x/vm/keeper/call_evm.go +++ b/x/vm/keeper/call_evm.go @@ -45,12 +45,22 @@ func (k Keeper) CallEVMWithData(ctx sdk.Context, stateDB *statedb.StateDB, from return nil, err } + // Bound the EVM gas limit by the caller-provided gasCap when set, falling + // back to DefaultGasCap. This prevents callers (e.g. CosmWasm query bindings) + // from triggering EVM execution larger than their remaining gas budget. + gasLimit := config.DefaultGasCap + if gasCap != nil && gasCap.IsUint64() { + if capped := gasCap.Uint64(); capped < gasLimit { + gasLimit = capped + } + } + msg := core.Message{ From: from, To: contract, Nonce: nonce, Value: big.NewInt(0), - GasLimit: config.DefaultGasCap, + GasLimit: gasLimit, GasPrice: big.NewInt(0), GasTipCap: big.NewInt(0), GasFeeCap: big.NewInt(0), @@ -68,7 +78,17 @@ func (k Keeper) CallEVMWithData(ctx sdk.Context, stateDB *statedb.StateDB, from return res, errorsmod.Wrap(types.ErrVMExecution, res.VmError) } - ctx.GasMeter().ConsumeGas(res.GasUsed, "apply evm message") + // Charge the SDK gas meter for the actual pre-refund EVM work (MaxUsedGas) + // rather than the post-refund GasUsed. Internal calls run with a full refund + // and no minimum-gas floor, so GasUsed can fall far below the real compute + // performed by validators; billing MaxUsedGas removes that undercharge. + // Precompile sub-calls keep their existing accounting. + chargedGas := res.GasUsed + if !callFromPrecompile && res.MaxUsedGas > chargedGas { + chargedGas = res.MaxUsedGas + } + + ctx.GasMeter().ConsumeGas(chargedGas, "apply evm message") return res, nil } From 2a567c7ba61b5bbbe33b501daffe2583e6044e0b Mon Sep 17 00:00:00 2001 From: matteyu Date: Wed, 24 Jun 2026 22:24:31 -0700 Subject: [PATCH 2/3] fix: add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a70e545..723e512 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ Follow the [migration document](docs/migrations/v0.5.x_to_v0.6.0.md) for upgrade ### BUG FIXES +- Fix undercharged internal EVM execution in `CallEVMWithData` by (1) bounding the message `GasLimit` to the caller-provided `gasCap` when set (`min(gasCap, DefaultGasCap)`) instead of always using `DefaultGasCap`, and (2) charging the SDK gas meter for the actual pre-refund work (`MaxUsedGas`) rather than the post-refund `GasUsed` on non-precompile calls, preventing a refund-maximizing contract from forcing repeatable, undercharged internal EVM compute (e.g. via the CosmWasm ERC20 query bindings). - Fix EVM fee-abstraction gas refund to compute the refund against the full transaction gas (`gasUsed + leftoverGas`) instead of `gasUsed`, bounding the refund by the paid fee and preventing the fee collector from being drained by high gas limit transactions. - [\#15](https://github.com/KiiChain/evm/pull/15) Fix distribution precompile 32-byte withdraw address inflating native supply by skipping non-20-byte accounts when mirroring balance changes to the StateDB. From cdc7c179396a9e7f9bba605f9eca2f7d76ace76b Mon Sep 17 00:00:00 2001 From: matteyu Date: Mon, 29 Jun 2026 22:13:57 -0700 Subject: [PATCH 3/3] fix: bound internal EVM call gas limit by caller gasCap --- CHANGELOG.md | 2 +- x/vm/keeper/call_evm.go | 15 +-------------- 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 723e512..6b2a0fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,7 @@ Follow the [migration document](docs/migrations/v0.5.x_to_v0.6.0.md) for upgrade ### BUG FIXES -- Fix undercharged internal EVM execution in `CallEVMWithData` by (1) bounding the message `GasLimit` to the caller-provided `gasCap` when set (`min(gasCap, DefaultGasCap)`) instead of always using `DefaultGasCap`, and (2) charging the SDK gas meter for the actual pre-refund work (`MaxUsedGas`) rather than the post-refund `GasUsed` on non-precompile calls, preventing a refund-maximizing contract from forcing repeatable, undercharged internal EVM compute (e.g. via the CosmWasm ERC20 query bindings). +- Bound the message `GasLimit` in `CallEVMWithData` to the caller-provided `gasCap` when set (`min(gasCap, DefaultGasCap)`) instead of always using `DefaultGasCap`, so callers (e.g. the CosmWasm ERC20 query bindings) can constrain internal EVM execution to their remaining gas budget. - Fix EVM fee-abstraction gas refund to compute the refund against the full transaction gas (`gasUsed + leftoverGas`) instead of `gasUsed`, bounding the refund by the paid fee and preventing the fee collector from being drained by high gas limit transactions. - [\#15](https://github.com/KiiChain/evm/pull/15) Fix distribution precompile 32-byte withdraw address inflating native supply by skipping non-20-byte accounts when mirroring balance changes to the StateDB. diff --git a/x/vm/keeper/call_evm.go b/x/vm/keeper/call_evm.go index 9d66b28..18cb7bc 100644 --- a/x/vm/keeper/call_evm.go +++ b/x/vm/keeper/call_evm.go @@ -45,9 +45,6 @@ func (k Keeper) CallEVMWithData(ctx sdk.Context, stateDB *statedb.StateDB, from return nil, err } - // Bound the EVM gas limit by the caller-provided gasCap when set, falling - // back to DefaultGasCap. This prevents callers (e.g. CosmWasm query bindings) - // from triggering EVM execution larger than their remaining gas budget. gasLimit := config.DefaultGasCap if gasCap != nil && gasCap.IsUint64() { if capped := gasCap.Uint64(); capped < gasLimit { @@ -78,17 +75,7 @@ func (k Keeper) CallEVMWithData(ctx sdk.Context, stateDB *statedb.StateDB, from return res, errorsmod.Wrap(types.ErrVMExecution, res.VmError) } - // Charge the SDK gas meter for the actual pre-refund EVM work (MaxUsedGas) - // rather than the post-refund GasUsed. Internal calls run with a full refund - // and no minimum-gas floor, so GasUsed can fall far below the real compute - // performed by validators; billing MaxUsedGas removes that undercharge. - // Precompile sub-calls keep their existing accounting. - chargedGas := res.GasUsed - if !callFromPrecompile && res.MaxUsedGas > chargedGas { - chargedGas = res.MaxUsedGas - } - - ctx.GasMeter().ConsumeGas(chargedGas, "apply evm message") + ctx.GasMeter().ConsumeGas(res.GasUsed, "apply evm message") return res, nil }