From f71f4da77a01e44fbbca60010f7a24fe58a44434 Mon Sep 17 00:00:00 2001 From: MariusVanDerWijden Date: Tue, 28 Apr 2026 22:33:01 +0200 Subject: [PATCH] core: last fixes --- core/state_processor.go | 19 +++++++++-- core/state_transition.go | 6 ++-- core/vm/eips.go | 8 +++++ core/vm/evm.go | 10 +++++- core/vm/jump_table.go | 1 + core/vm/operations_acl.go | 72 +++++++++++++++++++++++++++++++++++++++ params/protocol_params.go | 1 + 7 files changed, 110 insertions(+), 7 deletions(-) diff --git a/core/state_processor.go b/core/state_processor.go index ac233cc8d3..a00989d3fc 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -241,6 +241,19 @@ func ApplyTransaction(evm *vm.EVM, gp *GasPool, statedb *state.StateDB, header * return ApplyTransactionWithEVM(msg, gp, statedb, header.Number, header.Hash(), header.Time, tx, evm) } +// systemCallGasBudget returns the gas budget for system calls. Pre-Amsterdam +// the budget is 30M regular gas. Post-Amsterdam (EIP-8037), an additional +// state-gas reservoir of `STATE_BYTES_PER_STORAGE_SET × CPSB × SYSTEM_MAX_SSTORES_PER_CALL` +// is provided to cover the expected new SSTOREs in system contracts. +func systemCallGasBudget(evm *vm.EVM) vm.GasBudget { + const regular = 30_000_000 + if evm.ChainConfig().IsAmsterdam(evm.Context.BlockNumber, evm.Context.Time) { + stateGas := params.StorageCreationSize * evm.Context.CostPerStateByte * params.SystemMaxSstoresPerCall + return vm.NewGasBudget(regular, stateGas) + } + return vm.NewGasBudgetReg(regular) +} + // ProcessBeaconBlockRoot applies the EIP-4788 system call to the beacon block root // contract. This method is exported to be used in tests. func ProcessBeaconBlockRoot(beaconRoot common.Hash, evm *vm.EVM) { @@ -261,7 +274,7 @@ func ProcessBeaconBlockRoot(beaconRoot common.Hash, evm *vm.EVM) { } evm.SetTxContext(NewEVMTxContext(msg)) evm.StateDB.AddAddressToAccessList(params.BeaconRootsAddress) - _, _, _ = evm.Call(msg.From, *msg.To, msg.Data, vm.NewGasBudgetReg(30_000_000), common.U2560) + _, _, _ = evm.Call(msg.From, *msg.To, msg.Data, systemCallGasBudget(evm), common.U2560) if evm.StateDB.AccessEvents() != nil { evm.StateDB.AccessEvents().Merge(evm.AccessEvents) } @@ -288,7 +301,7 @@ func ProcessParentBlockHash(prevHash common.Hash, evm *vm.EVM) { } evm.SetTxContext(NewEVMTxContext(msg)) evm.StateDB.AddAddressToAccessList(params.HistoryStorageAddress) - _, _, err := evm.Call(msg.From, *msg.To, msg.Data, vm.NewGasBudgetReg(30_000_000), common.U2560) + _, _, err := evm.Call(msg.From, *msg.To, msg.Data, systemCallGasBudget(evm), common.U2560) if err != nil { panic(err) } @@ -327,7 +340,7 @@ func processRequestsSystemCall(requests *[][]byte, evm *vm.EVM, requestType byte } evm.SetTxContext(NewEVMTxContext(msg)) evm.StateDB.AddAddressToAccessList(addr) - ret, _, err := evm.Call(msg.From, *msg.To, msg.Data, vm.NewGasBudgetReg(30_000_000), common.U2560) + ret, _, err := evm.Call(msg.From, *msg.To, msg.Data, systemCallGasBudget(evm), common.U2560) if evm.StateDB.AccessEvents() != nil { evm.StateDB.AccessEvents().Merge(evm.AccessEvents) } diff --git a/core/state_transition.go b/core/state_transition.go index 0662e8c16b..e0fd882dcf 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -769,10 +769,10 @@ func (st *stateTransition) applyAuthorization(rules params.Rules, auth *types.Se return 0, err } - // If the account already exists in state, refund the new account cost - // charged in the intrinsic calculation. + // If the account is not empty (per EIP-161: non-zero nonce, balance, or + // code) refund the new account cost charged in the intrinsic calculation. var refund uint64 - if st.state.Exist(authority) { + if !st.state.Empty(authority) { if rules.IsAmsterdam { // EIP-8037: refund account creation state gas to the reservoir refund = params.AccountCreationSize * st.evm.Context.CostPerStateByte diff --git a/core/vm/eips.go b/core/vm/eips.go index 54e5cb0c60..aa03453cd8 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -44,6 +44,7 @@ var activators = map[int]func(*JumpTable){ 7939: enable7939, 8024: enable8024, 7843: enable7843, + 8037: enable8037, } // EnableEIP enables the given EIP on the config. @@ -169,6 +170,13 @@ func enable3529(jt *JumpTable) { jt[SELFDESTRUCT].dynamicGas = gasSelfdestructEIP3529 } +// enable8037 enables EIP-8037 SSTORE repricing: the regular-gas portion of +// new slot creation and same-tx 0→X→0 reset is reduced; the state-gas +// portion is charged/refunded at frame-end via the journal. +func enable8037(jt *JumpTable) { + jt[SSTORE].dynamicGas = gasSStoreEIP8037 +} + // enable3198 applies EIP-3198 (BASEFEE Opcode) // - Adds an opcode that returns the current block's base fee. func enable3198(jt *JumpTable) { diff --git a/core/vm/evm.go b/core/vm/evm.go index 8a01205ba6..a0250bfdc0 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -660,7 +660,15 @@ func (evm *EVM) initNewContract(contract *Contract, address common.Address) ([]b } if !evm.chainRules.IsEIP4762 { - createDataGas := uint64(len(ret)) * params.CreateDataGas + var createDataGas uint64 + if evm.chainRules.IsAmsterdam { + // EIP-8037: regular gas portion is the keccak hashing cost + // (6 × ⌈L/32⌉). The state-gas portion (L × CPSB) is charged + // at frame end via the journal's codeChange walker. + createDataGas = ((uint64(len(ret)) + 31) / 32) * params.Keccak256WordGas + } else { + createDataGas = uint64(len(ret)) * params.CreateDataGas + } if !contract.UseGas(GasCosts{RegularGas: createDataGas}, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) { return ret, ErrCodeStoreOutOfGas } diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index 82fc43ec13..62ee31e3a1 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -97,6 +97,7 @@ func newAmsterdamInstructionSet() JumpTable { instructionSet := newOsakaInstructionSet() enable7843(&instructionSet) // EIP-7843 (SLOTNUM opcode) enable8024(&instructionSet) // EIP-8024 (Backward compatible SWAPN, DUPN, EXCHANGE) + enable8037(&instructionSet) // EIP-8037 SSTORE repricing return validate(instructionSet) } diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go index 313d03819e..87941a5ae1 100644 --- a/core/vm/operations_acl.go +++ b/core/vm/operations_acl.go @@ -222,8 +222,80 @@ var ( // gasSStoreEIP3529 implements gas cost for SSTORE according to EIP-3529 // Replace `SSTORE_CLEARS_SCHEDULE` with `SSTORE_RESET_GAS + ACCESS_LIST_STORAGE_KEY_COST` (4,800) gasSStoreEIP3529 = makeGasSStoreFunc(params.SstoreClearsScheduleRefundEIP3529) + + // gasSStoreEIP8037 implements gas cost for SSTORE under EIP-8037. + // New slot creation (orig=0, current=0, value!=0) is repriced from + // SstoreSetGas (20,000) to SstoreUpdateGas - ColdSloadCost (2,900); the + // state-gas portion (32 × CPSB) is charged at frame-end via the journal. + // Likewise the same-tx 0→X→0 reset refund is reduced from 19,900 to + // SstoreUpdateGas - ColdSloadCost - WarmStorageReadCost (2,800); the + // state-gas refund is also handled at frame-end. + gasSStoreEIP8037 = makeGasSStoreFuncAmsterdam(params.SstoreClearsScheduleRefundEIP3529) ) +// makeGasSStoreFuncAmsterdam returns the EIP-8037 SSTORE gas function. It is +// identical to makeGasSStoreFunc except that the regular-gas portion of new +// slot creation and same-tx 0→X→0 reset is reduced (the state-gas portion is +// charged/refunded at frame-end via the journal). +func makeGasSStoreFuncAmsterdam(clearingRefund uint64) gasFunc { + return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { + if evm.readOnly { + return GasCosts{}, ErrWriteProtection + } + if contract.Gas.RegularGas <= params.SstoreSentryGasEIP2200 { + 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) + cost = uint64(0) + ) + if _, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot); !slotPresent { + cost = params.ColdSloadCostEIP2929 + evm.StateDB.AddSlotToAccessList(contract.Address(), slot) + } + value := common.Hash(y.Bytes32()) + + // EIP-8037: regular-gas portion of new slot creation is the storage + // update cost minus cold sload (2,900). State-gas portion is at + // frame-end. + sstoreNewSlotRegularGas := params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929 + + if current == value { // noop + return GasCosts{RegularGas: cost + params.WarmStorageReadCostEIP2929}, nil + } + if original == current { + if original == (common.Hash{}) { // create slot (2.1.1) + return GasCosts{RegularGas: cost + sstoreNewSlotRegularGas}, nil + } + if value == (common.Hash{}) { // delete pre-existing slot + evm.StateDB.AddRefund(clearingRefund) + } + return GasCosts{RegularGas: cost + (params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929)}, nil + } + if original != (common.Hash{}) { + if current == (common.Hash{}) { // recreate slot (2.2.1.1) + evm.StateDB.SubRefund(clearingRefund) + } else if value == (common.Hash{}) { // delete dirty (2.2.1.2) + evm.StateDB.AddRefund(clearingRefund) + } + } + if original == value { + if original == (common.Hash{}) { // 0→X→0: reset to original-zero + // EIP-8037: regular-gas refund is reduced because the + // original SET cost was already reduced to + // sstoreNewSlotRegularGas. State-gas refund (32 × CPSB) + // is applied at frame-end. + evm.StateDB.AddRefund(sstoreNewSlotRegularGas - params.WarmStorageReadCostEIP2929) + } else { // reset to original existing slot + evm.StateDB.AddRefund((params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929) - params.WarmStorageReadCostEIP2929) + } + } + return GasCosts{RegularGas: cost + params.WarmStorageReadCostEIP2929}, nil + } +} + // makeSelfdestructGasFn can create the selfdestruct dynamic gas function for EIP-2929 and EIP-3529 func makeSelfdestructGasFn(refundsEnabled bool) gasFunc { gasFunc := func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { diff --git a/params/protocol_params.go b/params/protocol_params.go index e631776a51..d76ed62b15 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -193,6 +193,7 @@ const ( AccountCreationSize = 112 StorageCreationSize = 32 AuthorizationCreationSize = 23 + SystemMaxSstoresPerCall = 16 // EIP-8037: upper bound on new SSTOREs per system call GasBlockAccessListItem = 2000 // EIP-7928: gas cost per BAL item for gas limit check )