From 79b548a4ba8c7df6c8501bb21c76f025488c2fbb Mon Sep 17 00:00:00 2001 From: Abmcar Date: Tue, 3 Feb 2026 15:16:26 +0800 Subject: [PATCH 1/2] fix(evm): restore gas refund on JIT revert --- src/compiler/evm_frontend/evm_imported.cpp | 11 +++++------ src/runtime/evm_instance.cpp | 8 ++++++++ src/runtime/evm_instance.h | 6 ++++++ 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/compiler/evm_frontend/evm_imported.cpp b/src/compiler/evm_frontend/evm_imported.cpp index 777055c7..f3d3b2cb 100644 --- a/src/compiler/evm_frontend/evm_imported.cpp +++ b/src/compiler/evm_frontend/evm_imported.cpp @@ -796,9 +796,7 @@ const uint8_t *evmHandleCreateInternal(zen::runtime::EVMInstance *Instance, if (GasUsed != 0) { Instance->chargeGas(GasUsed); } - if (Result.gas_refund > 0) { - Instance->addGasRefund(Result.gas_refund); - } + Instance->addGasRefund(Result.gas_refund); std::vector ReturnData(Result.output_data, Result.output_data + Result.output_size); @@ -963,9 +961,7 @@ static uint64_t evmHandleCallInternal(zen::runtime::EVMInstance *Instance, Instance->chargeGas(GasUsed); } - if (Result.gas_refund > 0) { - Instance->addGasRefund(Result.gas_refund); - } + Instance->addGasRefund(Result.gas_refund); // Copy return data to memory if output area is specified. // Per EVM semantics, bytes beyond returned data length remain unchanged. @@ -1004,6 +1000,7 @@ uint64_t evmHandleCallCode(zen::runtime::EVMInstance *Instance, uint64_t Gas, void evmHandleInvalid(zen::runtime::EVMInstance *Instance) { // Immediately terminate the execution and return the invalid code (4) + Instance->restoreGasRefundSnapshot(); Instance->setReturnData({}); evmc::Result ExeResult( EVMC_INVALID_INSTRUCTION, 0, Instance ? Instance->getGasRefund() : 0, @@ -1015,6 +1012,7 @@ void evmHandleInvalid(zen::runtime::EVMInstance *Instance) { void evmHandleUndefined(zen::runtime::EVMInstance *Instance) { // Immediately terminate the execution and return the undefined code + Instance->restoreGasRefundSnapshot(); Instance->setReturnData({}); evmc::Result ExeResult( EVMC_UNDEFINED_INSTRUCTION, 0, Instance ? Instance->getGasRefund() : 0, @@ -1053,6 +1051,7 @@ void evmSetRevert(zen::runtime::EVMInstance *Instance, uint64_t Offset, ReturnData = std::vector(MemoryBase + Offset, MemoryBase + Offset + Size); } + Instance->restoreGasRefundSnapshot(); Instance->setReturnData(std::move(ReturnData)); const int64_t GasLeft = Instance ? static_cast(Instance->getGas()) : 0; diff --git a/src/runtime/evm_instance.cpp b/src/runtime/evm_instance.cpp index 46ee02a7..b78a35b4 100644 --- a/src/runtime/evm_instance.cpp +++ b/src/runtime/evm_instance.cpp @@ -71,6 +71,7 @@ void EVMInstance::pushMessage(evmc_message *Msg) { initMemoryFrame(Memory, MemoryBase, MemorySize); MessageStack.push_back(Msg); CurrentMessage = Msg; + GasRefundStack.push_back(GasRefund); Gas = Msg ? Msg->gas : 0; } @@ -79,6 +80,9 @@ void EVMInstance::popMessage() { MessageStack.pop_back(); } CurrentMessage = MessageStack.empty() ? nullptr : MessageStack.back(); + if (!GasRefundStack.empty()) { + GasRefundStack.pop_back(); + } if (!MemoryStack.empty()) { Memory = std::move(MemoryStack.back().Data); MemorySize = MemoryStack.back().Size; @@ -111,6 +115,10 @@ void EVMInstance::setExecutionError(const Error &NewErr, uint32_t IgnoredDepth, common::evm_traphandler::EVMTrapState TS) { ZEN_ASSERT(NewErr.getPhase() == common::ErrorPhase::Execution); setError(NewErr); + if (NewErr.getCode() != ErrorCode::NoError && + NewErr.getCode() != ErrorCode::InstanceExit) { + restoreGasRefundSnapshot(); + } if (NewErr.getCode() == ErrorCode::GasLimitExceeded) { setGas(0); // gas left } diff --git a/src/runtime/evm_instance.h b/src/runtime/evm_instance.h index 2b1cdf04..fde721bb 100644 --- a/src/runtime/evm_instance.h +++ b/src/runtime/evm_instance.h @@ -62,6 +62,11 @@ class EVMInstance final : public RuntimeObject { void addGasRefund(uint64_t Amount) { GasRefund += Amount; } void setGasRefund(uint64_t Amount) { GasRefund = Amount; } uint64_t getGasRefund() const { return GasRefund; } + void restoreGasRefundSnapshot() { + if (!GasRefundStack.empty()) { + GasRefund = GasRefundStack.back(); + } + } void setRevision(evmc_revision NewRev) { Rev = NewRev; } // ==================== Memory Methods ==================== @@ -299,6 +304,7 @@ class EVMInstance final : public RuntimeObject { // Message stack for call hierarchy tracking evmc_message *CurrentMessage = nullptr; std::vector MessageStack; + std::vector GasRefundStack; evmc_revision Rev = zen::evm::DEFAULT_REVISION; // Instance-level cache storage (shared across all messages in execution) From a0ec153c8e6bc450965648f0bb74cfc37d193d8f Mon Sep 17 00:00:00 2001 From: Abmcar Date: Tue, 3 Feb 2026 21:06:17 +0800 Subject: [PATCH 2/2] chore(evm): document refund snapshot pop --- src/runtime/evm_instance.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/runtime/evm_instance.cpp b/src/runtime/evm_instance.cpp index b78a35b4..dc8ebbd3 100644 --- a/src/runtime/evm_instance.cpp +++ b/src/runtime/evm_instance.cpp @@ -81,6 +81,9 @@ void EVMInstance::popMessage() { } CurrentMessage = MessageStack.empty() ? nullptr : MessageStack.back(); if (!GasRefundStack.empty()) { + // Only pop the snapshot: successful subcalls keep their accumulated + // refunds. On failures, refund rollback is handled by + // restoreGasRefundSnapshot() using this stack. GasRefundStack.pop_back(); } if (!MemoryStack.empty()) {