From ceebf0b6c5c4ded5d079c93683c56b4987a23523 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 28 Aug 2026 09:23:06 +0000 Subject: [PATCH] fix(evm): consume all gas when a frame halts exceptionally The three places that finalise an evmc_result all ended with Result.gas_left = .getGas(); which overwrites whatever the halt paths had already decided. The instance's gas counter is not authoritative after a halt: the interpreter zeroes the *frame's* gas (interpreter.cpp) and the JIT trap path never touches gas at all, so an exceptional halt handed the caller back gas the EVM had already burned. evmone applies the opposite rule in make_execution_result - "an exceptional halt consumes all gas; only a success or revert keeps gas_left" - and that is the consensus behaviour. Route the three sites through one helper that applies that rule. Observed on mainnet blocks 25818502 and 25818530, where a nested frame halted with EVMC_INVALID_MEMORY_ACCESS and returned 339011 of its 440977 gas limit. This is necessary but not sufficient for those two blocks: the halt itself is a separate JIT defect (a null-pointer dereference inside JIT'd code that the trap handler reports as out-of-bounds memory). Both blocks replay correctly under mode=interpreter, so the remaining defect is JIT-only. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_0187PTseLY2NbzxKrnstDWwS --- src/evm/evm.h | 17 +++++++++++++++++ src/runtime/runtime.cpp | 2 +- src/vm/dt_evmc_vm.cpp | 5 +++-- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/evm/evm.h b/src/evm/evm.h index aa16d43be..9c93168b6 100644 --- a/src/evm/evm.h +++ b/src/evm/evm.h @@ -49,6 +49,23 @@ constexpr auto MAX_SIZE_OF_INITCODE = 0xC000; /// code. static constexpr auto EMPTY_CODE_HASH = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470_bytes32; + +/// Store the gas a finished frame hands back to its caller, applying the EVM's +/// terminal gas rule: an exceptional halt consumes all of the frame's gas, and +/// only a success or a revert returns any. +/// +/// This must be the last write to `Result.gas_left`. The instance's own gas +/// counter is not authoritative once a frame has halted - the interpreter +/// zeroes the frame's gas rather than the instance's, and the JIT trap path +/// never touches either - so assigning `Instance::getGas()` unconditionally +/// hands the caller gas the EVM has already burned. evmone applies the same +/// rule in `make_execution_result`. +inline void setFrameGasLeft(evmc::Result &Result, uint64_t InstanceGas) { + const bool KeepsGas = + Result.status_code == EVMC_SUCCESS || Result.status_code == EVMC_REVERT; + Result.gas_left = KeepsGas ? static_cast(InstanceGas) : 0; +} + } // namespace zen::evm #endif // ZEN_EVM_GAS_EVM_H diff --git a/src/runtime/runtime.cpp b/src/runtime/runtime.cpp index c3c94a165..663c0f626 100644 --- a/src/runtime/runtime.cpp +++ b/src/runtime/runtime.cpp @@ -785,7 +785,7 @@ void Runtime::callEVMMainOnPhysStack(EVMInstance &Inst, evmc_message &Msg, } else { callEVMInInterpMode(Inst, MsgWithCode, Result); } - Result.gas_left = Inst.getGas(); + zen::evm::setFrameGasLeft(Result, Inst.getGas()); } void Runtime::callEVMMain(EVMInstance &Inst, evmc_message &Msg, diff --git a/src/vm/dt_evmc_vm.cpp b/src/vm/dt_evmc_vm.cpp index 62d1ff936..7bdb45fee 100644 --- a/src/vm/dt_evmc_vm.cpp +++ b/src/vm/dt_evmc_vm.cpp @@ -6,6 +6,7 @@ #include "common/enums.h" #include "common/errors.h" #include "compiler/evm_frontend/evm_analyzer.h" +#include "evm/evm.h" #include "evm/interpreter.h" #include "evm/opcode_handlers.h" #include "jit_profile.h" @@ -691,7 +692,7 @@ evmc_result runInterpreterOnResolvedInstance(DTVM *VM, EVMModule *Mod, evmc::Result Result = std::move(const_cast(Ctx.getExeResult())); - Result.gas_left = TheInst->getGas(); + zen::evm::setFrameGasLeft(Result, TheInst->getGas()); return Result.release_raw(); } @@ -985,7 +986,7 @@ evmc_result execute(evmc_vm *EVMInstance, const evmc_host_interface *Host, VM->RT->callEVMInJITMode(*TheInst, MsgWithCode, Result); #endif // ZEN_ENABLE_VIRTUAL_STACK - Result.gas_left = TheInst->getGas(); + zen::evm::setFrameGasLeft(Result, TheInst->getGas()); return Result.release_raw(); } #else