diff --git a/core/vm/eip8038_test.go b/core/vm/eip8038_test.go index c08c54fb2ce..f20a9d8a0eb 100644 --- a/core/vm/eip8038_test.go +++ b/core/vm/eip8038_test.go @@ -21,6 +21,7 @@ package vm import ( + "strings" "testing" "github.com/ethereum/go-ethereum/common" @@ -235,3 +236,19 @@ func TestEIP8038SelfdestructAccountWrite(t *testing.T) { t.Fatalf("state gas = %d, want %d", res.UsedStateGas, want) } } + +// TestEIP8038SStoreAccessGuard covers the affordability check that bails out +// before the slot is read once the gas left cannot cover the slot's access cost. +// The two PUSH1s cost 6, so a 2506 budget leaves 2500 at the SSTORE: above the +// reentrancy sentry (2300) yet below COLD_STORAGE_ACCESS (3000). The guard must +// fire, distinguishable from the sentry/charge OOG by its "slot access" message. +func TestEIP8038SStoreAccessGuard(t *testing.T) { + budget := NewGasBudget(6+params.SstoreSentryGasEIP2200+200, 0) + _, _, err := run8038(t, sstore(0, 1), budget, new(uint256.Int), nil) + if err == nil { + t.Fatal("expected failure: gas left cannot cover cold-slot access") + } + if !strings.Contains(err.Error(), "not enough gas for slot access") { + t.Fatalf("got %q, want the slot-access guard error", err) + } +} diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index 8cc63cb2369..d8fbf2b461c 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -696,10 +696,10 @@ func gasSStore8037And8038(evm *EVM, contract *Contract, stack *Stack, mem *Memor return GasCosts{}, errors.New("not enough gas for reentrancy sentry") } var ( - y, x = stack.back(1), stack.peek() - slot = common.Hash(x.Bytes32()) - current, original = evm.StateDB.GetStateAndCommittedState(contract.Address(), slot) - value = common.Hash(y.Bytes32()) + y, x = stack.back(1), stack.peek() + slot = common.Hash(x.Bytes32()) + value = common.Hash(y.Bytes32()) + stateSet = params.StorageCreationSize * evm.Context.CostPerStateByte ) // Check slot presence in the access list access := params.WarmStorageReadCostEIP2929 @@ -707,7 +707,12 @@ func gasSStore8037And8038(evm *EVM, contract *Contract, stack *Stack, mem *Memor access = params.ColdStorageAccessAmsterdam evm.StateDB.AddSlotToAccessList(contract.Address(), slot) } - stateSet := params.StorageCreationSize * evm.Context.CostPerStateByte + // Check access cost affordability before reading slot + if contract.Gas.RegularGas < access { + return GasCosts{}, errors.New("not enough gas for slot access") + } + // Read the slot value for gas cost measurement + current, original := evm.StateDB.GetStateAndCommittedState(contract.Address(), slot) if current == value { // noop (1) return GasCosts{RegularGas: access}, nil