From 1ef86906fc7b1e06008f15a3ec8bd1a2acadca74 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Tue, 10 Mar 2026 16:20:36 +0100 Subject: [PATCH] core/vm: fix gas charging for sstore --- core/vm/gas_table.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index 8d720cce72..a04efc6923 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -666,6 +666,9 @@ func gasSelfdestruct8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory } func gasSStore8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { + if evm.readOnly { + return GasCosts{}, ErrWriteProtection + } // If we fail the minimum gas availability invariant, fail (0) if contract.Gas.RegularGas <= params.SstoreSentryGasEIP2200 { return GasCosts{}, errors.New("not enough gas for reentrancy sentry") @@ -692,7 +695,16 @@ func gasSStore8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memo } if original == current { if original == (common.Hash{}) { // create slot (2.1.1) - return GasCosts{RegularGas: cost.RegularGas + params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929, StateGas: params.StorageCreationSize * evm.Context.CostPerGasByte}, nil + // EIP-8037: Charge state gas first (before regular gas), matching the + // spec's charge_state_gas → charge_gas ordering. This ensures that + // state_gas_used is recorded even if the subsequent regular gas charge + // fails with OOG. + stateGas := GasCosts{StateGas: params.StorageCreationSize * evm.Context.CostPerGasByte} + if contract.Gas.Underflow(stateGas) { + return GasCosts{}, errors.New("out of gas for state gas") + } + contract.Gas.Sub(stateGas) + return GasCosts{RegularGas: cost.RegularGas + params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929}, nil } if value == (common.Hash{}) { // delete slot (2.1.2b) evm.StateDB.AddRefund(params.SstoreClearsScheduleRefundEIP3529)