From 23fbc5a92303662a81dd075432fdb35afd4b62b1 Mon Sep 17 00:00:00 2001 From: MariusVanDerWijden Date: Thu, 16 Apr 2026 17:48:02 +0200 Subject: [PATCH] core/vm: change state gas calculation --- core/state/journal.go | 106 +++++++++++++++ core/state/statedb.go | 7 + core/state/statedb_hooked.go | 4 + core/state_transition.go | 4 +- core/vm/evm.go | 88 ++++++++++-- core/vm/gas_table.go | 255 ++++++++++++++++------------------- core/vm/interface.go | 4 + core/vm/interpreter.go | 25 +--- core/vm/jump_table.go | 2 +- core/vm/operations_acl.go | 122 ++++++++--------- core/vm/operations_verkle.go | 115 ++++++++-------- 11 files changed, 440 insertions(+), 292 deletions(-) diff --git a/core/state/journal.go b/core/state/journal.go index a79bd7331a..28096dd392 100644 --- a/core/state/journal.go +++ b/core/state/journal.go @@ -135,6 +135,112 @@ func (j *journal) length() int { return len(j.entries) } +// computeStateGrowthCost walks journal entries from the given snapshot revision +// to the current tip and computes the signed net state growth cost. +// It mirrors the EIP-8037 spec's compute_state_growth_cost(snapshot, current). +// +// Positive cost = state grew (accounts created, slots set, code deployed). +// Negative cost = state shrank (accounts removed, slots cleared). +func (j *journal) computeStateGrowthCost(revid int, s *StateDB, costPerStateByte uint64) int64 { + // Find the journal index for this revision. + idx := sort.Search(len(j.validRevisions), func(i int) bool { + return j.validRevisions[i].id >= revid + }) + if idx == len(j.validRevisions) || j.validRevisions[idx].id != revid { + panic(fmt.Errorf("revision id %v not found for state growth cost", revid)) + } + snapshotIdx := j.validRevisions[idx].journalIndex + + // Track which accounts were created and which (addr, key) slots were + // touched since the snapshot. We only care about net effect. + type slotKey struct { + addr common.Address + key common.Hash + } + // For accounts: track the first createObjectChange. We need to know + // if the account existed at snapshot time (it didn't if created after). + accountCreated := make(map[common.Address]bool) // addr → was created since snapshot + // For storage: track the value at snapshot time (first prevvalue seen). + slotSnapshotValue := make(map[slotKey]common.Hash) + slotSeen := make(map[slotKey]bool) + // For code: track the code length at snapshot time (first prevCode seen). + codeSnapshotLen := make(map[common.Address]int) + codeSeen := make(map[common.Address]bool) + + for i := snapshotIdx; i < len(j.entries); i++ { + switch ch := j.entries[i].(type) { + case createObjectChange: + if !accountCreated[ch.account] { + accountCreated[ch.account] = true + } + case storageChange: + sk := slotKey{ch.account, ch.key} + if !slotSeen[sk] { + slotSeen[sk] = true + slotSnapshotValue[sk] = ch.prevvalue + } + case codeChange: + if !codeSeen[ch.account] { + codeSeen[ch.account] = true + codeSnapshotLen[ch.account] = len(ch.prevCode) + } + } + } + + var cost int64 + + // Account creation: +112 bytes for each account that was created + // and still exists now (not deleted by a revert within this scope). + for addr := range accountCreated { + // Check if the account currently exists in stateObjects. + // If it was created and then the snapshot was reverted past it, + // it won't be here. But since we only walk entries that haven't + // been reverted, the account should exist. + if _, exists := s.stateObjects[addr]; exists { + cost += int64(112 * costPerStateByte) + } + } + + // Storage slots: compare snapshot value vs current value. + for sk, snapshotVal := range slotSnapshotValue { + obj := s.getStateObject(sk.addr) + if obj == nil { + continue + } + // Current value is in dirtyStorage (in-tx mutations). + currentVal, dirty := obj.dirtyStorage[sk.key] + if !dirty { + // Not dirty means value didn't change from committed state, + // which means the journal changes were reverted. Net effect = 0. + continue + } + snapshotZero := snapshotVal == (common.Hash{}) + currentZero := currentVal == (common.Hash{}) + if snapshotZero && !currentZero { + cost += int64(32 * costPerStateByte) // new slot + } else if !snapshotZero && currentZero { + cost -= int64(32 * costPerStateByte) // removed slot + } + } + + // Code: compare snapshot code length vs current code length. + for addr, snapshotLen := range codeSnapshotLen { + obj := s.getStateObject(addr) + if obj == nil { + continue + } + currentLen := len(obj.code) + if snapshotLen == 0 && currentLen > 0 { + cost += int64(uint64(currentLen) * costPerStateByte) + } + // Note: code can only be set once per account in practice, + // so we don't handle the removal case (code is never cleared + // to empty during normal execution). + } + + return cost +} + // copy returns a deep-copied journal. func (j *journal) copy() *journal { entries := make([]journalEntry, 0, j.length()) diff --git a/core/state/statedb.go b/core/state/statedb.go index 59ad3cdfef..31aec05dab 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -738,6 +738,13 @@ func (s *StateDB) RevertToSnapshot(revid int) { s.journal.revertToSnapshot(revid, s) } +// ComputeStateGrowthCost returns the signed net state growth cost (in gas) +// between the given snapshot revision and the current state. +// This implements the EIP-8037 diff-at-return mechanism. +func (s *StateDB) ComputeStateGrowthCost(revid int, costPerStateByte uint64) int64 { + return s.journal.computeStateGrowthCost(revid, s, costPerStateByte) +} + // GetRefund returns the current value of the refund counter. func (s *StateDB) GetRefund() uint64 { return s.refund diff --git a/core/state/statedb_hooked.go b/core/state/statedb_hooked.go index 8c217fba48..45e08082e2 100644 --- a/core/state/statedb_hooked.go +++ b/core/state/statedb_hooked.go @@ -150,6 +150,10 @@ func (s *hookedStateDB) Snapshot() int { return s.inner.Snapshot() } +func (s *hookedStateDB) ComputeStateGrowthCost(revid int, costPerStateByte uint64) int64 { + return s.inner.ComputeStateGrowthCost(revid, costPerStateByte) +} + func (s *hookedStateDB) AddPreimage(hash common.Hash, bytes []byte) { s.inner.AddPreimage(hash, bytes) } diff --git a/core/state_transition.go b/core/state_transition.go index d072a9a1b1..e8a1a1fc3c 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -74,9 +74,9 @@ func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.Set var gas vm.GasCosts if isContractCreation && rules.IsHomestead { if rules.IsAmsterdam { - // EIP-8037: account creation is state gas; base tx + CREATE overhead is regular gas. + // EIP-8037 diff-at-return: account creation state gas is computed + // from the state diff at call return, not charged as intrinsic gas. gas.RegularGas = params.TxGas + params.CreateGasAmsterdam - gas.StateGas = params.AccountCreationSize * costPerStateByte } else { gas.RegularGas = params.TxGasContractCreation } diff --git a/core/vm/evm.go b/core/vm/evm.go index c906193e10..e329fec8b5 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -239,6 +239,7 @@ func isSystemCall(caller common.Address) bool { // the necessary steps to create accounts and reverses the state in case of an // execution error or failed value transfer. func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, gas GasBudget, value *uint256.Int) (ret []byte, leftOverGas GasBudget, gasUsed GasUsed, err error) { + initialStateGas := gas.StateGas // EIP-8037: remember for diff-at-return // Capture the tracer start/end events in debug mode if evm.Config.Tracer != nil { evm.captureBegin(evm.depth, CALL, caller, addr, input, gas, value.ToBig()) @@ -305,6 +306,12 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g gasUsed = contract.GasUsed } } + // EIP-8037 diff-at-return: charge state gas based on actual state diff. + if err == nil { + if stateErr := evm.chargeStateGasAtReturn(snapshot, initialStateGas, &gas, &gasUsed); stateErr != nil { + err = stateErr + } + } // When an error was returned by the EVM or when setting the creation code // above we revert to the snapshot and consume any gas remaining. Additionally, // when we're in homestead this also counts for code storage gas errors. @@ -330,6 +337,7 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g // CallCode differs from Call in the sense that it executes the given address' // code with the caller as context. func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byte, gas GasBudget, value *uint256.Int) (ret []byte, leftOverGas GasBudget, gasUsed GasUsed, err error) { + initialStateGas := gas.StateGas // EIP-8037: remember for diff-at-return // Invoke tracer hooks that signal entering/exiting a call frame if evm.Config.Tracer != nil { evm.captureBegin(evm.depth, CALLCODE, caller, addr, input, gas, value.ToBig()) @@ -365,6 +373,12 @@ func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byt gas = contract.Gas gasUsed = contract.GasUsed } + // EIP-8037 diff-at-return: charge state gas based on actual state diff. + if err == nil { + if stateErr := evm.chargeStateGasAtReturn(snapshot, initialStateGas, &gas, &gasUsed); stateErr != nil { + err = stateErr + } + } if err != nil { evm.StateDB.RevertToSnapshot(snapshot) isRevert := err == ErrExecutionReverted @@ -385,6 +399,7 @@ func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byt // DelegateCall differs from CallCode in the sense that it executes the given address' // code with the caller as context and the caller is set to the caller of the caller. func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address, addr common.Address, input []byte, gas GasBudget, value *uint256.Int) (ret []byte, leftOverGas GasBudget, gasUsed GasUsed, err error) { + initialStateGas := gas.StateGas // EIP-8037: remember for diff-at-return // Invoke tracer hooks that signal entering/exiting a call frame if evm.Config.Tracer != nil { // DELEGATECALL inherits value from parent call @@ -415,6 +430,12 @@ func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address, gas = contract.Gas gasUsed = contract.GasUsed } + // EIP-8037 diff-at-return: charge state gas based on actual state diff. + if err == nil { + if stateErr := evm.chargeStateGasAtReturn(snapshot, initialStateGas, &gas, &gasUsed); stateErr != nil { + err = stateErr + } + } if err != nil { evm.StateDB.RevertToSnapshot(snapshot) isRevert := err == ErrExecutionReverted @@ -434,6 +455,7 @@ func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address, // Opcodes that attempt to perform such modifications will result in exceptions // instead of performing the modifications. func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []byte, gas GasBudget) (ret []byte, leftOverGas GasBudget, gasUsed GasUsed, err error) { + initialStateGas := gas.StateGas // EIP-8037: remember for diff-at-return // Invoke tracer hooks that signal entering/exiting a call frame if evm.Config.Tracer != nil { evm.captureBegin(evm.depth, STATICCALL, caller, addr, input, gas, nil) @@ -464,6 +486,13 @@ func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []b gas = contract.Gas gasUsed = contract.GasUsed } + // EIP-8037 diff-at-return: charge state gas based on actual state diff. + // (StaticCall shouldn't create state, but charge anyway for correctness.) + if err == nil { + if stateErr := evm.chargeStateGasAtReturn(snapshot, initialStateGas, &gas, &gasUsed); stateErr != nil { + err = stateErr + } + } if err != nil { evm.StateDB.RevertToSnapshot(snapshot) isRevert := err == ErrExecutionReverted @@ -480,6 +509,7 @@ func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []b // create creates a new contract using code as deployment code. func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value *uint256.Int, address common.Address, typ OpCode) (ret []byte, createAddress common.Address, leftOverGas GasBudget, used GasUsed, err error) { + initialStateGas := gas.StateGas // EIP-8037: remember for diff-at-return // Depth check execution. Fail if we're trying to execute above the // limit. var nonce uint64 @@ -580,6 +610,12 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value contract.IsDeployment = true ret, err = evm.initNewContract(contract, address) + // EIP-8037 diff-at-return: charge state gas based on actual state diff. + if err == nil { + if stateErr := evm.chargeStateGasAtReturn(snapshot, initialStateGas, &contract.Gas, &contract.GasUsed); stateErr != nil { + err = stateErr + } + } if err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas) { evm.StateDB.RevertToSnapshot(snapshot) if err != ErrExecutionReverted { @@ -608,21 +644,16 @@ func (evm *EVM) initNewContract(contract *Contract, address common.Address) ([]b } if evm.chainRules.IsAmsterdam { - // Check max code size BEFORE charging gas so over-max code - // does not consume state gas (which would inflate tx_state). + // Check max code size BEFORE charging gas. if err := CheckMaxCodeSize(&evm.chainRules, uint64(len(ret))); err != nil { return ret, err } - // EIP-8037: Charge regular gas (keccak256 hash) first, then state gas - // (code storage). Regular-before-state prevents reservoir inflation. + // EIP-8037: Charge regular gas (keccak256 hash) only. + // Code deposit state gas is computed from the state diff at call return. regularGas := GasCosts{RegularGas: toWordSize(uint64(len(ret))) * params.Keccak256WordGas} if !contract.UseGas(regularGas, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) { return ret, ErrCodeStoreOutOfGas } - stateGas := GasCosts{StateGas: uint64(len(ret)) * evm.Context.CostPerGasByte} - if !contract.UseGas(stateGas, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) { - return ret, ErrCodeStoreOutOfGas - } } else if evm.chainRules.IsEIP4762 { consumed, wanted := evm.AccessEvents.CodeChunksRangeGas(address, 0, uint64(len(ret)), uint64(len(ret)), true, contract.Gas.RegularGas) contract.UseGas(GasCosts{RegularGas: consumed}, evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk) @@ -693,6 +724,47 @@ func (evm *EVM) resolveCodeHash(addr common.Address) common.Hash { return evm.StateDB.GetCodeHash(addr) } +// chargeStateGasAtReturn implements EIP-8037 diff-at-return: after a successful +// call, compute the net state growth cost from the journal diff and charge it +// from the gas budget (state gas reservoir first, then spillover to regular gas). +// initialStateGas is the reservoir value at call entry, needed to compute how +// much inner calls already consumed. +// Returns an error if the call cannot afford the state gas, in which case the +// caller should revert the state to the snapshot. +func (evm *EVM) chargeStateGasAtReturn(snapshot int, initialStateGas uint64, gas *GasBudget, gasUsed *GasUsed) error { + if !evm.chainRules.IsAmsterdam { + return nil + } + growthCost := evm.StateDB.ComputeStateGrowthCost(snapshot, evm.Context.CostPerGasByte) + + // Inner calls already deducted from reservoir. + alreadyPaid := int64(initialStateGas) - int64(gas.StateGas) + thisCallCost := growthCost - alreadyPaid + + if thisCallCost <= 0 { + // Negative cost = state shrank relative to what was paid, credit the reservoir. + if thisCallCost < 0 { + gas.StateGas += uint64(-thisCallCost) + } + return nil + } + cost := uint64(thisCallCost) + if gas.StateGas >= cost { + gas.StateGas -= cost + gasUsed.StateGas += cost + } else if gas.StateGas+gas.RegularGas >= cost { + // Spillover: reservoir exhausted, take remainder from regular gas. + remainder := cost - gas.StateGas + gasUsed.StateGas += gas.StateGas + gas.StateGas = 0 + gas.RegularGas -= remainder + gasUsed.RegularGas += remainder + } else { + return ErrOutOfGas + } + return nil +} + // ChainConfig returns the environment's chain configuration func (evm *EVM) ChainConfig() *params.ChainConfig { return evm.chainConfig } diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index 8c293a86ec..1201c75740 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -64,26 +64,26 @@ func memoryGasCost(mem *Memory, newMemSize uint64) (uint64, error) { // EXTCODECOPY (stack position 3) // RETURNDATACOPY (stack position 2) func memoryCopierGas(stackpos int) gasFunc { - return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { + return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { // Gas for expanding the memory gas, err := memoryGasCost(mem, memorySize) if err != nil { - return GasCosts{}, err + return 0, err } // And gas for copying data, charged per word at param.CopyGas words, overflow := stack.Back(stackpos).Uint64WithOverflow() if overflow { - return GasCosts{}, ErrGasUintOverflow + return 0, ErrGasUintOverflow } if words, overflow = math.SafeMul(toWordSize(words), params.CopyGas); overflow { - return GasCosts{}, ErrGasUintOverflow + return 0, ErrGasUintOverflow } if gas, overflow = math.SafeAdd(gas, words); overflow { - return GasCosts{}, ErrGasUintOverflow + return 0, ErrGasUintOverflow } - return GasCosts{RegularGas: gas}, nil + return gas, nil } } @@ -95,9 +95,9 @@ var ( gasReturnDataCopy = memoryCopierGas(2) ) -func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { +func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { if evm.readOnly { - return GasCosts{}, ErrWriteProtection + return 0, ErrWriteProtection } var ( y, x = stack.Back(1), stack.Back(0) @@ -114,12 +114,12 @@ func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySi // 3. From a non-zero to a non-zero (CHANGE) switch { case current == (common.Hash{}) && y.Sign() != 0: // 0 => non 0 - return GasCosts{RegularGas: params.SstoreSetGas}, nil + return params.SstoreSetGas, nil case current != (common.Hash{}) && y.Sign() == 0: // non 0 => 0 evm.StateDB.AddRefund(params.SstoreRefundGas) - return GasCosts{RegularGas: params.SstoreClearGas}, nil + return params.SstoreClearGas, nil default: // non 0 => non 0 (or 0 => 0) - return GasCosts{RegularGas: params.SstoreResetGas}, nil + return params.SstoreResetGas, nil } } @@ -139,16 +139,16 @@ func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySi // (2.2.2.2.) Otherwise, add 4800 gas to refund counter. value := common.Hash(y.Bytes32()) if current == value { // noop (1) - return GasCosts{RegularGas: params.NetSstoreNoopGas}, nil + return params.NetSstoreNoopGas, nil } if original == current { if original == (common.Hash{}) { // create slot (2.1.1) - return GasCosts{RegularGas: params.NetSstoreInitGas}, nil + return params.NetSstoreInitGas, nil } if value == (common.Hash{}) { // delete slot (2.1.2b) evm.StateDB.AddRefund(params.NetSstoreClearRefund) } - return GasCosts{RegularGas: params.NetSstoreCleanGas}, nil // write existing slot (2.1.2) + return params.NetSstoreCleanGas, nil // write existing slot (2.1.2) } if original != (common.Hash{}) { if current == (common.Hash{}) { // recreate slot (2.2.1.1) @@ -164,7 +164,7 @@ func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySi evm.StateDB.AddRefund(params.NetSstoreResetRefund) } } - return GasCosts{RegularGas: params.NetSstoreDirtyGas}, nil + return params.NetSstoreDirtyGas, nil } // Here come the EIP2200 rules: @@ -182,13 +182,13 @@ func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySi // (2.2.2.) If original value equals new value (this storage slot is reset): // (2.2.2.1.) If original value is 0, add SSTORE_SET_GAS - SLOAD_GAS to refund counter. // (2.2.2.2.) Otherwise, add SSTORE_RESET_GAS - SLOAD_GAS gas to refund counter. -func gasSStoreEIP2200(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { +func gasSStoreEIP2200(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { if evm.readOnly { - return GasCosts{}, ErrWriteProtection + return 0, 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") + return 0, errors.New("not enough gas for reentrancy sentry") } // Gas sentry honoured, do the actual gas calculation based on the stored value var ( @@ -198,16 +198,16 @@ func gasSStoreEIP2200(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m value := common.Hash(y.Bytes32()) if current == value { // noop (1) - return GasCosts{RegularGas: params.SloadGasEIP2200}, nil + return params.SloadGasEIP2200, nil } if original == current { if original == (common.Hash{}) { // create slot (2.1.1) - return GasCosts{RegularGas: params.SstoreSetGasEIP2200}, nil + return params.SstoreSetGasEIP2200, nil } if value == (common.Hash{}) { // delete slot (2.1.2b) evm.StateDB.AddRefund(params.SstoreClearsScheduleRefundEIP2200) } - return GasCosts{RegularGas: params.SstoreResetGasEIP2200}, nil // write existing slot (2.1.2) + return params.SstoreResetGasEIP2200, nil // write existing slot (2.1.2) } if original != (common.Hash{}) { if current == (common.Hash{}) { // recreate slot (2.2.1.1) @@ -223,66 +223,62 @@ func gasSStoreEIP2200(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m evm.StateDB.AddRefund(params.SstoreResetGasEIP2200 - params.SloadGasEIP2200) } } - return GasCosts{RegularGas: params.SloadGasEIP2200}, nil // dirty update (2.2) + return params.SloadGasEIP2200, nil // dirty update (2.2) } func makeGasLog(n uint64) gasFunc { - return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { + return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { requestedSize, overflow := stack.Back(1).Uint64WithOverflow() if overflow { - return GasCosts{}, ErrGasUintOverflow + return 0, ErrGasUintOverflow } gas, err := memoryGasCost(mem, memorySize) if err != nil { - return GasCosts{}, err + return 0, err } if gas, overflow = math.SafeAdd(gas, params.LogGas); overflow { - return GasCosts{}, ErrGasUintOverflow + return 0, ErrGasUintOverflow } if gas, overflow = math.SafeAdd(gas, n*params.LogTopicGas); overflow { - return GasCosts{}, ErrGasUintOverflow + return 0, ErrGasUintOverflow } var memorySizeGas uint64 if memorySizeGas, overflow = math.SafeMul(requestedSize, params.LogDataGas); overflow { - return GasCosts{}, ErrGasUintOverflow + return 0, ErrGasUintOverflow } if gas, overflow = math.SafeAdd(gas, memorySizeGas); overflow { - return GasCosts{}, ErrGasUintOverflow + return 0, ErrGasUintOverflow } - return GasCosts{RegularGas: gas}, nil + return gas, nil } } -func gasKeccak256(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { +func gasKeccak256(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { gas, err := memoryGasCost(mem, memorySize) if err != nil { - return GasCosts{}, err + return 0, err } wordGas, overflow := stack.Back(1).Uint64WithOverflow() if overflow { - return GasCosts{}, ErrGasUintOverflow + return 0, ErrGasUintOverflow } if wordGas, overflow = math.SafeMul(toWordSize(wordGas), params.Keccak256WordGas); overflow { - return GasCosts{}, ErrGasUintOverflow + return 0, ErrGasUintOverflow } if gas, overflow = math.SafeAdd(gas, wordGas); overflow { - return GasCosts{}, ErrGasUintOverflow + return 0, ErrGasUintOverflow } - return GasCosts{RegularGas: gas}, nil + return gas, nil } // pureMemoryGascost is used by several operations, which aside from their // static cost have a dynamic cost which is solely based on the memory // expansion -func pureMemoryGascost(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { - gas, err := memoryGasCost(mem, memorySize) - if err != nil { - return GasCosts{}, err - } - return GasCosts{RegularGas: gas}, nil +func pureMemoryGascost(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + return memoryGasCost(mem, memorySize) } var ( @@ -293,81 +289,81 @@ var ( gasMStore = pureMemoryGascost ) -func gasCreate(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { +func gasCreate(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { if evm.readOnly { - return GasCosts{}, ErrWriteProtection + return 0, ErrWriteProtection } return pureMemoryGascost(evm, contract, stack, mem, memorySize) } -func gasCreate2(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { +func gasCreate2(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { if evm.readOnly { - return GasCosts{}, ErrWriteProtection + return 0, ErrWriteProtection } gas, err := memoryGasCost(mem, memorySize) if err != nil { - return GasCosts{}, err + return 0, err } wordGas, overflow := stack.Back(2).Uint64WithOverflow() if overflow { - return GasCosts{}, ErrGasUintOverflow + return 0, ErrGasUintOverflow } if wordGas, overflow = math.SafeMul(toWordSize(wordGas), params.Keccak256WordGas); overflow { - return GasCosts{}, ErrGasUintOverflow + return 0, ErrGasUintOverflow } if gas, overflow = math.SafeAdd(gas, wordGas); overflow { - return GasCosts{}, ErrGasUintOverflow + return 0, ErrGasUintOverflow } - return GasCosts{RegularGas: gas}, nil + return gas, nil } -func gasCreateEip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { +func gasCreateEip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { if evm.readOnly { - return GasCosts{}, ErrWriteProtection + return 0, ErrWriteProtection } gas, err := memoryGasCost(mem, memorySize) if err != nil { - return GasCosts{}, err + return 0, err } size, overflow := stack.Back(2).Uint64WithOverflow() if overflow { - return GasCosts{}, ErrGasUintOverflow + return 0, ErrGasUintOverflow } if err := CheckMaxInitCodeSize(&evm.chainRules, size); err != nil { - return GasCosts{}, err + return 0, err } // Since size <= the protocol-defined maximum initcode size limit, these multiplication cannot overflow moreGas := params.InitCodeWordGas * ((size + 31) / 32) if gas, overflow = math.SafeAdd(gas, moreGas); overflow { - return GasCosts{}, ErrGasUintOverflow + return 0, ErrGasUintOverflow } - return GasCosts{RegularGas: gas}, nil + return gas, nil } -func gasCreate2Eip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { +func gasCreate2Eip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { if evm.readOnly { - return GasCosts{}, ErrWriteProtection + return 0, ErrWriteProtection } gas, err := memoryGasCost(mem, memorySize) if err != nil { - return GasCosts{}, err + return 0, err } size, overflow := stack.Back(2).Uint64WithOverflow() if overflow { - return GasCosts{}, ErrGasUintOverflow + return 0, ErrGasUintOverflow } if err := CheckMaxInitCodeSize(&evm.chainRules, size); err != nil { - return GasCosts{}, err + return 0, err } // Since size <= the protocol-defined maximum initcode size limit, these multiplication cannot overflow moreGas := (params.InitCodeWordGas + params.Keccak256WordGas) * ((size + 31) / 32) if gas, overflow = math.SafeAdd(gas, moreGas); overflow { - return GasCosts{}, ErrGasUintOverflow + return 0, ErrGasUintOverflow } - return GasCosts{RegularGas: gas}, nil + return gas, nil } -func gasExpFrontier(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { +func gasExpFrontier(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { expByteLen := uint64((stack.data[stack.len()-2].BitLen() + 7) / 8) var ( @@ -375,12 +371,12 @@ func gasExpFrontier(evm *EVM, contract *Contract, stack *Stack, mem *Memory, mem overflow bool ) if gas, overflow = math.SafeAdd(gas, params.ExpGas); overflow { - return GasCosts{}, ErrGasUintOverflow + return 0, ErrGasUintOverflow } - return GasCosts{RegularGas: gas}, nil + return gas, nil } -func gasExpEIP158(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { +func gasExpEIP158(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { expByteLen := uint64((stack.data[stack.len()-2].BitLen() + 7) / 8) var ( @@ -388,9 +384,9 @@ func gasExpEIP158(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memor overflow bool ) if gas, overflow = math.SafeAdd(gas, params.ExpGas); overflow { - return GasCosts{}, ErrGasUintOverflow + return 0, ErrGasUintOverflow } - return GasCosts{RegularGas: gas}, nil + return gas, nil } var ( @@ -401,20 +397,20 @@ var ( ) func makeCallVariantGasCost(intrinsicFunc intrinsicGasFunc) gasFunc { - return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { + return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { intrinsic, err := intrinsicFunc(evm, contract, stack, mem, memorySize) if err != nil { - return GasCosts{}, err + return 0, err } evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas.RegularGas, intrinsic, stack.Back(0)) if err != nil { - return GasCosts{}, err + return 0, err } gas, overflow := math.SafeAdd(intrinsic, evm.callGasTemp) if overflow { - return GasCosts{}, ErrGasUintOverflow + return 0, ErrGasUintOverflow } - return GasCosts{RegularGas: gas}, nil + return gas, nil } } @@ -515,16 +511,16 @@ func gasStaticCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Mem return memoryGasCost(mem, memorySize) } -func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { +func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { if evm.readOnly { - return GasCosts{}, ErrWriteProtection + return 0, ErrWriteProtection } var gas uint64 // EIP150 homestead gas reprice fork: if evm.chainRules.IsEIP150 { gas = params.SelfdestructGasEIP150 if gas > contract.Gas.RegularGas { - return GasCosts{RegularGas: gas}, nil + return gas, nil } var address = common.Address(stack.Back(0).Bytes20()) @@ -541,116 +537,99 @@ func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me if !evm.StateDB.HasSelfDestructed(contract.Address()) { evm.StateDB.AddRefund(params.SelfdestructRefundGas) } - return GasCosts{RegularGas: gas}, nil + return gas, nil } -func gasCreateEip8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { +func gasCreateEip8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { if evm.readOnly { - return GasCosts{}, ErrWriteProtection + return 0, ErrWriteProtection } gas, err := memoryGasCost(mem, memorySize) if err != nil { - return GasCosts{}, err + return 0, err } size, overflow := stack.Back(2).Uint64WithOverflow() if overflow { - return GasCosts{}, ErrGasUintOverflow + return 0, ErrGasUintOverflow } if err := CheckMaxInitCodeSize(&evm.chainRules, size); err != nil { - return GasCosts{}, err + return 0, err } // Since size <= MaxInitCodeSizeAmsterdam, these multiplications cannot overflow words := (size + 31) / 32 wordGas := params.InitCodeWordGas * words - stateGas := params.AccountCreationSize * evm.Context.CostPerGasByte - return GasCosts{RegularGas: gas + wordGas, StateGas: stateGas}, nil + // EIP-8037 diff-at-return: account creation state gas is computed + // from the state diff at call return, not charged here. + return gas + wordGas, nil } -func gasCreate2Eip8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { +func gasCreate2Eip8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { if evm.readOnly { - return GasCosts{}, ErrWriteProtection + return 0, ErrWriteProtection } gas, err := memoryGasCost(mem, memorySize) if err != nil { - return GasCosts{}, err + return 0, err } size, overflow := stack.Back(2).Uint64WithOverflow() if overflow { - return GasCosts{}, ErrGasUintOverflow + return 0, ErrGasUintOverflow } if err := CheckMaxInitCodeSize(&evm.chainRules, size); err != nil { - return GasCosts{}, err + return 0, err } // Since size <= MaxInitCodeSizeAmsterdam, these multiplications cannot overflow words := (size + 31) / 32 // CREATE2 charges both InitCodeWordGas (EIP-3860) and Keccak256WordGas (for address hashing). wordGas := (params.InitCodeWordGas + params.Keccak256WordGas) * words - stateGas := params.AccountCreationSize * evm.Context.CostPerGasByte - return GasCosts{RegularGas: gas + wordGas, StateGas: stateGas}, nil + // EIP-8037 diff-at-return: account creation state gas is computed + // from the state diff at call return, not charged here. + return gas + wordGas, nil } // gasCall8037 is the stateful gas calculator for CALL in Amsterdam (EIP-8037). -// It only returns the state-dependent gas (account creation as state gas). -// Memory gas, transfer gas, and callGas are handled by gasCallStateless and -// makeCallVariantGasCall. -func gasCall8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { - var ( - gas GasCosts - transfersValue = !stack.Back(2).IsZero() - address = common.Address(stack.Back(1).Bytes20()) - ) - if evm.chainRules.IsEIP158 { - if transfersValue && evm.StateDB.Empty(address) { - gas.StateGas += params.AccountCreationSize * evm.Context.CostPerGasByte - } - } else if !evm.StateDB.Exist(address) { - gas.StateGas += params.AccountCreationSize * evm.Context.CostPerGasByte - } - return gas, nil +// With diff-at-return, account creation state gas is computed from the state +// diff at call return, not charged here. +func gasCall8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + return 0, nil } -func gasSelfdestruct8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { +func gasSelfdestruct8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { if evm.readOnly { - return GasCosts{}, ErrWriteProtection + return 0, ErrWriteProtection } var ( - gas GasCosts + gas uint64 address = common.Address(stack.peek().Bytes20()) ) if !evm.StateDB.AddressInAccessList(address) { // If the caller cannot afford the cost, this change will be rolled back evm.StateDB.AddAddressToAccessList(address) - gas.RegularGas = params.ColdAccountAccessCostEIP2929 - } - // Check we have enough regular gas before we add the address to the BAL - if contract.Gas.RegularGas < gas.RegularGas { - return gas, nil - } - // if empty and transfers value - if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(contract.Address()).Sign() != 0 { - gas.StateGas += params.AccountCreationSize * evm.Context.CostPerGasByte + gas = params.ColdAccountAccessCostEIP2929 } + // EIP-8037 diff-at-return: account creation state gas is computed + // from the state diff at call return, not charged here. return gas, nil } -func gasSStore8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { +func gasSStore8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { if evm.readOnly { - return GasCosts{}, ErrWriteProtection + return 0, 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") + return 0, errors.New("not enough gas for reentrancy sentry") } // Gas sentry honoured, do the actual gas calculation based on the stored value var ( y, x = stack.Back(1), stack.peek() slot = common.Hash(x.Bytes32()) current, original = evm.StateDB.GetStateAndCommittedState(contract.Address(), slot) - cost GasCosts + cost uint64 ) // Check slot presence in the access list if _, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot); !slotPresent { - cost = GasCosts{RegularGas: params.ColdSloadCostEIP2929} + cost = params.ColdSloadCostEIP2929 // If the caller cannot afford the cost, this change will be rolled back evm.StateDB.AddSlotToAccessList(contract.Address(), slot) } @@ -659,24 +638,20 @@ func gasSStore8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memo if current == value { // noop (1) // EIP 2200 original clause: // return params.SloadGasEIP2200, nil - return GasCosts{RegularGas: cost.RegularGas + params.WarmStorageReadCostEIP2929}, nil // SLOAD_GAS + return cost + params.WarmStorageReadCostEIP2929, nil // SLOAD_GAS } if original == current { if original == (common.Hash{}) { // create slot (2.1.1) - // EIP-8037: Return both regular and state gas. The interpreter - // charges regular gas before state gas, preventing reservoir - // inflation when the regular charge OOGs. - return GasCosts{ - RegularGas: cost.RegularGas + params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929, - StateGas: params.StorageCreationSize * evm.Context.CostPerGasByte, - }, nil + // EIP-8037 diff-at-return: storage creation state gas is computed + // from the state diff at call return, not charged here. + return cost + params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929, nil } if value == (common.Hash{}) { // delete slot (2.1.2b) evm.StateDB.AddRefund(params.SstoreClearsScheduleRefundEIP3529) } // EIP-2200 original clause: // return params.SstoreResetGasEIP2200, nil // write existing slot (2.1.2) - return GasCosts{RegularGas: cost.RegularGas + params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929}, nil // write existing slot (2.1.2) + return cost + params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929, nil // write existing slot (2.1.2) } if original != (common.Hash{}) { if current == (common.Hash{}) { // recreate slot (2.2.1.1) @@ -689,7 +664,9 @@ func gasSStore8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memo if original == (common.Hash{}) { // reset to original inexistent slot (2.2.2.1) // EIP 2200 Original clause: //evm.StateDB.AddRefund(params.SstoreSetGasEIP2200 - params.SloadGasEIP2200) - evm.StateDB.AddRefund(params.StorageCreationSize*evm.Context.CostPerGasByte + params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929 - params.WarmStorageReadCostEIP2929) + // EIP-8037 diff-at-return: state gas is handled by the diff, + // so refund only covers the regular gas overpayment. + evm.StateDB.AddRefund(params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929 - params.WarmStorageReadCostEIP2929) } else { // reset to original existing slot (2.2.2.2) // EIP 2200 Original clause: // evm.StateDB.AddRefund(params.SstoreResetGasEIP2200 - params.SloadGasEIP2200) @@ -701,5 +678,5 @@ func gasSStore8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memo } // EIP-2200 original clause: //return params.SloadGasEIP2200, nil // dirty update (2.2) - return GasCosts{RegularGas: cost.RegularGas + params.WarmStorageReadCostEIP2929}, nil // dirty update (2.2) + return cost + params.WarmStorageReadCostEIP2929, nil // dirty update (2.2) } diff --git a/core/vm/interface.go b/core/vm/interface.go index 6a93846ac5..ff4f4142a3 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -86,6 +86,10 @@ type StateDB interface { RevertToSnapshot(int) Snapshot() int + // ComputeStateGrowthCost returns the signed net state growth cost (in gas) + // between the given snapshot revision and the current state (EIP-8037). + ComputeStateGrowthCost(revid int, costPerStateByte uint64) int64 + AddLog(*types.Log) EmitLogsForBurnAccounts() AddPreimage(common.Hash, []byte) diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 23d612f294..16ace007ed 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -219,32 +219,17 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte } // Consume the gas and return an error if not enough gas is available. // cost is explicitly set so that the capture state defer method can get the proper cost - var dynamicCost GasCosts + var dynamicCost uint64 dynamicCost, err = operation.dynamicGas(evm, contract, stack, mem, memorySize) - cost += dynamicCost.RegularGas // for tracing + cost += dynamicCost // for tracing if err != nil { return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err) } - // for tracing: this gas consumption event is emitted below in the debug section. - if evm.chainRules.IsAmsterdam && dynamicCost.StateGas > 0 { - // EIP-8037: charge regular gas before state gas. - if contract.Gas.RegularGas < dynamicCost.RegularGas { - return nil, ErrOutOfGas - } - contract.GasUsed.RegularGas += dynamicCost.RegularGas - contract.Gas.RegularGas -= dynamicCost.RegularGas - stateOnly := GasCosts{StateGas: dynamicCost.StateGas} - if contract.Gas.Underflow(stateOnly) { - return nil, ErrOutOfGas - } - contract.GasUsed.Add(stateOnly) - contract.Gas.Sub(stateOnly) - } else if contract.Gas.Underflow(dynamicCost) { + if contract.Gas.RegularGas < dynamicCost { return nil, ErrOutOfGas - } else { - contract.GasUsed.Add(dynamicCost) - contract.Gas.Sub(dynamicCost) } + contract.Gas.RegularGas -= dynamicCost + contract.GasUsed.RegularGas += dynamicCost } // Do tracing before potential memory expansion diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index 9ea8349e3a..6b11259ecd 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -24,7 +24,7 @@ import ( type ( executionFunc func(pc *uint64, evm *EVM, callContext *ScopeContext) ([]byte, error) - gasFunc func(*EVM, *Contract, *Stack, *Memory, uint64) (GasCosts, error) // last parameter is the requested memory size as a uint64 + gasFunc func(*EVM, *Contract, *Stack, *Memory, uint64) (uint64, error) // last parameter is the requested memory size as a uint64 intrinsicGasFunc func(*EVM, *Contract, *Stack, *Memory, uint64) (uint64, error) // last parameter is the requested memory size as a uint64 // memorySizeFunc returns the required size, and whether the operation overflowed a uint64 memorySizeFunc func(*Stack) (size uint64, overflow bool) diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go index d7fc42005e..a451887d7b 100644 --- a/core/vm/operations_acl.go +++ b/core/vm/operations_acl.go @@ -27,24 +27,24 @@ import ( ) func makeGasSStoreFunc(clearingRefund uint64) gasFunc { - return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { + return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { if evm.readOnly { - return GasCosts{}, ErrWriteProtection + return 0, 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") + return 0, errors.New("not enough gas for reentrancy sentry") } // Gas sentry honoured, do the actual gas calculation based on the stored value var ( y, x = stack.Back(1), stack.peek() slot = common.Hash(x.Bytes32()) current, original = evm.StateDB.GetStateAndCommittedState(contract.Address(), slot) - cost = GasCosts{RegularGas: 0} + cost uint64 ) // Check slot presence in the access list if _, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot); !slotPresent { - cost = GasCosts{RegularGas: params.ColdSloadCostEIP2929} + cost = params.ColdSloadCostEIP2929 // If the caller cannot afford the cost, this change will be rolled back evm.StateDB.AddSlotToAccessList(contract.Address(), slot) } @@ -53,18 +53,18 @@ func makeGasSStoreFunc(clearingRefund uint64) gasFunc { if current == value { // noop (1) // EIP 2200 original clause: // return params.SloadGasEIP2200, nil - return GasCosts{RegularGas: cost.RegularGas + params.WarmStorageReadCostEIP2929}, nil // SLOAD_GAS + return cost + params.WarmStorageReadCostEIP2929, nil // SLOAD_GAS } if original == current { if original == (common.Hash{}) { // create slot (2.1.1) - return GasCosts{RegularGas: cost.RegularGas + params.SstoreSetGasEIP2200}, nil + return cost + params.SstoreSetGasEIP2200, nil } if value == (common.Hash{}) { // delete slot (2.1.2b) evm.StateDB.AddRefund(clearingRefund) } // EIP-2200 original clause: // return params.SstoreResetGasEIP2200, nil // write existing slot (2.1.2) - return GasCosts{RegularGas: cost.RegularGas + (params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929)}, nil // write existing slot (2.1.2) + return cost + (params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929), nil // write existing slot (2.1.2) } if original != (common.Hash{}) { if current == (common.Hash{}) { // recreate slot (2.2.1.1) @@ -89,7 +89,7 @@ func makeGasSStoreFunc(clearingRefund uint64) gasFunc { } // EIP-2200 original clause: //return params.SloadGasEIP2200, nil // dirty update (2.2) - return GasCosts{RegularGas: cost.RegularGas + params.WarmStorageReadCostEIP2929}, nil // dirty update (2.2) + return cost + params.WarmStorageReadCostEIP2929, nil // dirty update (2.2) } } @@ -98,7 +98,7 @@ func makeGasSStoreFunc(clearingRefund uint64) gasFunc { // whose storage is being read) is not yet in accessed_storage_keys, // charge 2100 gas and add the pair to accessed_storage_keys. // If the pair is already in accessed_storage_keys, charge 100 gas. -func gasSLoadEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { +func gasSLoadEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { loc := stack.peek() slot := common.Hash(loc.Bytes32()) // Check slot presence in the access list @@ -106,9 +106,9 @@ func gasSLoadEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me // If the caller cannot afford the cost, this change will be rolled back // If he does afford it, we can skip checking the same thing later on, during execution evm.StateDB.AddSlotToAccessList(contract.Address(), slot) - return GasCosts{RegularGas: params.ColdSloadCostEIP2929}, nil + return params.ColdSloadCostEIP2929, nil } - return GasCosts{RegularGas: params.WarmStorageReadCostEIP2929}, nil + return params.WarmStorageReadCostEIP2929, nil } // gasExtCodeCopyEIP2929 implements extcodecopy according to EIP-2929 @@ -116,11 +116,11 @@ func gasSLoadEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me // > If the target is not in accessed_addresses, // > charge COLD_ACCOUNT_ACCESS_COST gas, and add the address to accessed_addresses. // > Otherwise, charge WARM_STORAGE_READ_COST gas. -func gasExtCodeCopyEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { +func gasExtCodeCopyEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { // memory expansion first (dynamic part of pre-2929 implementation) gas, err := gasExtCodeCopy(evm, contract, stack, mem, memorySize) if err != nil { - return GasCosts{}, err + return 0, err } addr := common.Address(stack.peek().Bytes20()) // Check slot presence in the access list @@ -128,10 +128,9 @@ func gasExtCodeCopyEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memo evm.StateDB.AddAddressToAccessList(addr) var overflow bool // We charge (cold-warm), since 'warm' is already charged as constantGas - if gas.RegularGas, overflow = math.SafeAdd(gas.RegularGas, params.ColdAccountAccessCostEIP2929-params.WarmStorageReadCostEIP2929); overflow { - return GasCosts{}, ErrGasUintOverflow + if gas, overflow = math.SafeAdd(gas, params.ColdAccountAccessCostEIP2929-params.WarmStorageReadCostEIP2929); overflow { + return 0, ErrGasUintOverflow } - return gas, nil } return gas, nil } @@ -143,20 +142,20 @@ func gasExtCodeCopyEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memo // - extcodehash, // - extcodesize, // - (ext) balance -func gasEip2929AccountCheck(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { +func gasEip2929AccountCheck(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { addr := common.Address(stack.peek().Bytes20()) // Check slot presence in the access list if !evm.StateDB.AddressInAccessList(addr) { // If the caller cannot afford the cost, this change will be rolled back evm.StateDB.AddAddressToAccessList(addr) // The warm storage read cost is already charged as constantGas - return GasCosts{RegularGas: params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929}, nil + return params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929, nil } - return GasCosts{RegularGas: 0}, nil + return 0, nil } func makeCallVariantGasCallEIP2929(oldCalculator gasFunc, addressPosition int) gasFunc { - return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { + return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { addr := common.Address(stack.Back(addressPosition).Bytes20()) // Check slot presence in the access list warmAccess := evm.StateDB.AddressInAccessList(addr) @@ -168,7 +167,7 @@ func makeCallVariantGasCallEIP2929(oldCalculator gasFunc, addressPosition int) g // Charge the remaining difference here already, to correctly calculate available // gas for call if !contract.UseGas(GasCosts{RegularGas: coldCost}, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) { - return GasCosts{}, ErrOutOfGas + return 0, ErrOutOfGas } } // Now call the old calculator, which takes into account @@ -187,8 +186,8 @@ func makeCallVariantGasCallEIP2929(oldCalculator gasFunc, addressPosition int) g contract.Gas.RegularGas += coldCost var overflow bool - if gas.RegularGas, overflow = math.SafeAdd(gas.RegularGas, coldCost); overflow { - return GasCosts{}, ErrGasUintOverflow + if gas, overflow = math.SafeAdd(gas, coldCost); overflow { + return 0, ErrGasUintOverflow } return gas, nil } @@ -224,13 +223,13 @@ var ( // 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) { + gasFunc := func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { var ( gas uint64 address = common.Address(stack.peek().Bytes20()) ) if evm.readOnly { - return GasCosts{}, ErrWriteProtection + return 0, ErrWriteProtection } if !evm.StateDB.AddressInAccessList(address) { // If the caller cannot afford the cost, this change will be rolled back @@ -240,11 +239,11 @@ func makeSelfdestructGasFn(refundsEnabled bool) gasFunc { // Terminate the gas measurement if the leftover gas is not sufficient, // it can effectively prevent accessing the states in the following steps if contract.Gas.RegularGas < gas { - return GasCosts{}, ErrOutOfGas + return 0, ErrOutOfGas } } if gas > contract.Gas.RegularGas { - return GasCosts{RegularGas: gas}, nil + return gas, nil } // if empty and transfers value @@ -254,7 +253,7 @@ func makeSelfdestructGasFn(refundsEnabled bool) gasFunc { if refundsEnabled && !evm.StateDB.HasSelfDestructed(contract.Address()) { evm.StateDB.AddRefund(params.SelfdestructRefundGas) } - return GasCosts{RegularGas: gas}, nil + return gas, nil } return gasFunc } @@ -268,30 +267,30 @@ var ( innerGasCallEIP8037 = makeCallVariantGasCallEIP8037(gasCallIntrinsic8037, gasCall8037) ) -func gasCallEIP7702(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { +func gasCallEIP7702(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { // Return early if this call attempts to transfer value in a static context. // Although it's checked in `gasCall`, EIP-7702 loads the target's code before // to determine if it is resolving a delegation. This could incorrectly record // the target in the block access list (BAL) if the call later fails. transfersValue := !stack.Back(2).IsZero() if evm.readOnly && transfersValue { - return GasCosts{}, ErrWriteProtection + return 0, ErrWriteProtection } return innerGasCallEIP7702(evm, contract, stack, mem, memorySize) } -func gasCallEIP8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { +func gasCallEIP8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { // Same write-protection guard as gasCallEIP7702: check before any gas // charging to avoid incorrectly recording in the access list. transfersValue := !stack.Back(2).IsZero() if evm.readOnly && transfersValue { - return GasCosts{}, ErrWriteProtection + return 0, ErrWriteProtection } return innerGasCallEIP8037(evm, contract, stack, mem, memorySize) } func makeCallVariantGasCallEIP7702(intrinsicFunc intrinsicGasFunc) gasFunc { - return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { + return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { var ( eip2929Cost uint64 eip7702Cost uint64 @@ -310,7 +309,7 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc intrinsicGasFunc) gasFunc { // Charge the remaining difference here already, to correctly calculate // available gas for call if !contract.UseGas(GasCosts{RegularGas: eip2929Cost}, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) { - return GasCosts{}, ErrOutOfGas + return 0, ErrOutOfGas } } @@ -321,13 +320,13 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc intrinsicGasFunc) gasFunc { // - create new account intrinsicCost, err := intrinsicFunc(evm, contract, stack, mem, memorySize) if err != nil { - return GasCosts{}, err + return 0, err } // Terminate the gas measurement if the leftover gas is not sufficient, // it can effectively prevent accessing the states in the following steps. // It's an essential safeguard before any stateful check. if contract.Gas.RegularGas < intrinsicCost { - return GasCosts{}, ErrOutOfGas + return 0, ErrOutOfGas } // Check if code is a delegation and if so, charge for resolution. @@ -339,14 +338,14 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc intrinsicGasFunc) gasFunc { eip7702Cost = params.ColdAccountAccessCostEIP2929 } if !contract.UseGas(GasCosts{RegularGas: eip7702Cost}, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) { - return GasCosts{}, ErrOutOfGas + return 0, ErrOutOfGas } } // Calculate the gas budget for the nested call. The costs defined by // EIP-2929 and EIP-7702 have already been applied. evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas.RegularGas, intrinsicCost, stack.Back(0)) if err != nil { - return GasCosts{}, err + return 0, err } // Temporarily add the gas charge back to the contract and return value. By // adding it to the return, it will be charged outside of this function, as @@ -365,15 +364,15 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc intrinsicGasFunc) gasFunc { totalCost uint64 ) if totalCost, overflow = math.SafeAdd(eip2929Cost, eip7702Cost); overflow { - return GasCosts{}, ErrGasUintOverflow + return 0, ErrGasUintOverflow } if totalCost, overflow = math.SafeAdd(totalCost, intrinsicCost); overflow { - return GasCosts{}, ErrGasUintOverflow + return 0, ErrGasUintOverflow } if totalCost, overflow = math.SafeAdd(totalCost, evm.callGasTemp); overflow { - return GasCosts{}, ErrGasUintOverflow + return 0, ErrGasUintOverflow } - return GasCosts{RegularGas: totalCost}, nil + return totalCost, nil } } @@ -382,7 +381,7 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc intrinsicGasFunc) gasFunc { // intrinsicFunc computes the regular gas (memory + transfer, no new account creation). // stateGasFunc computes the state gas (new account creation as state gas). func makeCallVariantGasCallEIP8037(intrinsicFunc intrinsicGasFunc, stateGasFunc gasFunc) gasFunc { - return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { + return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { var ( eip2929Cost uint64 eip7702Cost uint64 @@ -393,21 +392,21 @@ func makeCallVariantGasCallEIP8037(intrinsicFunc intrinsicGasFunc, stateGasFunc evm.StateDB.AddAddressToAccessList(addr) eip2929Cost = params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929 if !contract.UseGas(GasCosts{RegularGas: eip2929Cost}, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) { - return GasCosts{}, ErrOutOfGas + return 0, ErrOutOfGas } } // Compute intrinsic cost (memory + transfer, no new account creation). intrinsicCost, err := intrinsicFunc(evm, contract, stack, mem, memorySize) if err != nil { - return GasCosts{}, err + return 0, err } // Charge intrinsic cost directly (regular gas). This must happen // BEFORE state gas to prevent reservoir inflation, and also serves // as the OOG guard before stateful operations. if !contract.UseGas(GasCosts{RegularGas: intrinsicCost}, evm.Config.Tracer, tracing.GasChangeCallOpCode) { - return GasCosts{}, ErrOutOfGas + return 0, ErrOutOfGas } // EIP-7702 delegation check. @@ -419,28 +418,21 @@ func makeCallVariantGasCallEIP8037(intrinsicFunc intrinsicGasFunc, stateGasFunc eip7702Cost = params.ColdAccountAccessCostEIP2929 } if !contract.UseGas(GasCosts{RegularGas: eip7702Cost}, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) { - return GasCosts{}, ErrOutOfGas + return 0, ErrOutOfGas } } - // Compute and charge state gas (new account creation) AFTER regular gas. - stateGas, err := stateGasFunc(evm, contract, stack, mem, memorySize) - if err != nil { - return GasCosts{}, err - } - if stateGas.StateGas > 0 { - stateGasCost := GasCosts{StateGas: stateGas.StateGas} - if contract.Gas.Underflow(stateGasCost) { - return GasCosts{}, ErrOutOfGas - } - contract.GasUsed.Add(stateGasCost) - contract.Gas.Sub(stateGasCost) + // EIP-8037 diff-at-return: state gas (new account creation) is + // computed from the state diff at call return, not charged here. + // We still call stateGasFunc for any side-effects/error checks. + if _, err = stateGasFunc(evm, contract, stack, mem, memorySize); err != nil { + return 0, err } // Calculate the gas budget for the nested call (63/64 rule). evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas.RegularGas, 0, stack.Back(0)) if err != nil { - return GasCosts{}, err + return 0, err } // Temporarily undo direct regular charges for tracer reporting. @@ -454,14 +446,14 @@ func makeCallVariantGasCallEIP8037(intrinsicFunc intrinsicGasFunc, stateGasFunc totalCost uint64 ) if totalCost, overflow = math.SafeAdd(eip2929Cost, eip7702Cost); overflow { - return GasCosts{}, ErrGasUintOverflow + return 0, ErrGasUintOverflow } if totalCost, overflow = math.SafeAdd(totalCost, intrinsicCost); overflow { - return GasCosts{}, ErrGasUintOverflow + return 0, ErrGasUintOverflow } if totalCost, overflow = math.SafeAdd(totalCost, evm.callGasTemp); overflow { - return GasCosts{}, ErrGasUintOverflow + return 0, ErrGasUintOverflow } - return GasCosts{RegularGas: totalCost}, nil + return totalCost, nil } } diff --git a/core/vm/operations_verkle.go b/core/vm/operations_verkle.go index 4d09f54c1d..9537959514 100644 --- a/core/vm/operations_verkle.go +++ b/core/vm/operations_verkle.go @@ -24,40 +24,40 @@ import ( "github.com/ethereum/go-ethereum/params" ) -func gasSStore4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { - return GasCosts{RegularGas: evm.AccessEvents.SlotGas(contract.Address(), stack.peek().Bytes32(), true, contract.Gas.RegularGas, true)}, nil +func gasSStore4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + return evm.AccessEvents.SlotGas(contract.Address(), stack.peek().Bytes32(), true, contract.Gas.RegularGas, true), nil } -func gasSLoad4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { - return GasCosts{RegularGas: evm.AccessEvents.SlotGas(contract.Address(), stack.peek().Bytes32(), false, contract.Gas.RegularGas, true)}, nil +func gasSLoad4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + return evm.AccessEvents.SlotGas(contract.Address(), stack.peek().Bytes32(), false, contract.Gas.RegularGas, true), nil } -func gasBalance4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { +func gasBalance4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { address := stack.peek().Bytes20() - return GasCosts{RegularGas: evm.AccessEvents.BasicDataGas(address, false, contract.Gas.RegularGas, true)}, nil + return evm.AccessEvents.BasicDataGas(address, false, contract.Gas.RegularGas, true), nil } -func gasExtCodeSize4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { +func gasExtCodeSize4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { address := stack.peek().Bytes20() if _, isPrecompile := evm.precompile(address); isPrecompile { - return GasCosts{}, nil + return 0, nil } - return GasCosts{RegularGas: evm.AccessEvents.BasicDataGas(address, false, contract.Gas.RegularGas, true)}, nil + return evm.AccessEvents.BasicDataGas(address, false, contract.Gas.RegularGas, true), nil } -func gasExtCodeHash4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { +func gasExtCodeHash4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { address := stack.peek().Bytes20() if _, isPrecompile := evm.precompile(address); isPrecompile { - return GasCosts{}, nil + return 0, nil } - return GasCosts{RegularGas: evm.AccessEvents.CodeHashGas(address, false, contract.Gas.RegularGas, true)}, nil + return evm.AccessEvents.CodeHashGas(address, false, contract.Gas.RegularGas, true), nil } func makeCallVariantGasEIP4762(oldCalculator gasFunc, withTransferCosts bool) gasFunc { - return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { + return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { var ( target = common.Address(stack.Back(1).Bytes20()) - witnessGas GasCosts + witnessGas uint64 _, isPrecompile = evm.precompile(target) isSystemContract = target == params.HistoryStorageAddress ) @@ -67,36 +67,37 @@ func makeCallVariantGasEIP4762(oldCalculator gasFunc, withTransferCosts bool) ga if withTransferCosts && !stack.Back(2).IsZero() { wantedValueTransferWitnessGas := evm.AccessEvents.ValueTransferGas(contract.Address(), target, contract.Gas.RegularGas) if wantedValueTransferWitnessGas > contract.Gas.RegularGas { - return GasCosts{RegularGas: wantedValueTransferWitnessGas}, nil + return wantedValueTransferWitnessGas, nil } - witnessGas = GasCosts{RegularGas: wantedValueTransferWitnessGas} + witnessGas = wantedValueTransferWitnessGas } else if isPrecompile || isSystemContract { - witnessGas = GasCosts{RegularGas: params.WarmStorageReadCostEIP2929} + witnessGas = params.WarmStorageReadCostEIP2929 } else { // The charging for the value transfer is done BEFORE subtracting // the 1/64th gas, as this is considered part of the CALL instruction. // (so before we get to this point) // But the message call is part of the subcall, for which only 63/64th // of the gas should be available. - wantedMessageCallWitnessGas := evm.AccessEvents.MessageCallGas(target, contract.Gas.RegularGas-witnessGas.RegularGas) + wantedMessageCallWitnessGas := evm.AccessEvents.MessageCallGas(target, contract.Gas.RegularGas-witnessGas) var overflow bool - if witnessGas.RegularGas, overflow = math.SafeAdd(witnessGas.RegularGas, wantedMessageCallWitnessGas); overflow { - return GasCosts{RegularGas: 0}, ErrGasUintOverflow + if witnessGas, overflow = math.SafeAdd(witnessGas, wantedMessageCallWitnessGas); overflow { + return 0, ErrGasUintOverflow } - if witnessGas.RegularGas > contract.Gas.RegularGas { + if witnessGas > contract.Gas.RegularGas { return witnessGas, nil } } - contract.Gas.Sub(witnessGas) + witnessGasCost := GasCosts{RegularGas: witnessGas} + contract.Gas.Sub(witnessGasCost) // if the operation fails, adds witness gas to the gas before returning the error gas, err := oldCalculator(evm, contract, stack, mem, memorySize) - contract.Gas.Add(witnessGas) // restore witness gas so that it can be charged at the callsite + contract.Gas.Add(witnessGasCost) // restore witness gas so that it can be charged at the callsite var overflow bool - if gas.RegularGas, overflow = math.SafeAdd(gas.RegularGas, witnessGas.RegularGas); overflow { - return GasCosts{}, ErrGasUintOverflow + if gas, overflow = math.SafeAdd(gas, witnessGas); overflow { + return 0, ErrGasUintOverflow } - return GasCosts{RegularGas: gas.RegularGas}, err + return gas, err } } @@ -107,20 +108,20 @@ var ( gasDelegateCallEIP4762 = makeCallVariantGasEIP4762(gasDelegateCall, false) ) -func gasSelfdestructEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { +func gasSelfdestructEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { beneficiaryAddr := common.Address(stack.peek().Bytes20()) if _, isPrecompile := evm.precompile(beneficiaryAddr); isPrecompile { - return GasCosts{}, nil + return 0, nil } if contract.IsSystemCall { - return GasCosts{}, nil + return 0, nil } contractAddr := contract.Address() wanted := evm.AccessEvents.BasicDataGas(contractAddr, false, contract.Gas.RegularGas, false) if wanted > contract.Gas.RegularGas { - return GasCosts{RegularGas: wanted}, nil + return wanted, nil } - statelessGas := GasCosts{RegularGas: wanted} + statelessGas := wanted balanceIsZero := evm.StateDB.GetBalance(contractAddr).Sign() == 0 _, isPrecompile := evm.precompile(beneficiaryAddr) isSystemContract := beneficiaryAddr == params.HistoryStorageAddress @@ -130,39 +131,39 @@ func gasSelfdestructEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Mem } if contractAddr != beneficiaryAddr { - wanted := evm.AccessEvents.BasicDataGas(beneficiaryAddr, false, contract.Gas.RegularGas-statelessGas.RegularGas, false) - if wanted > contract.Gas.RegularGas-statelessGas.RegularGas { - return GasCosts{RegularGas: statelessGas.RegularGas + wanted}, nil + wanted := evm.AccessEvents.BasicDataGas(beneficiaryAddr, false, contract.Gas.RegularGas-statelessGas, false) + if wanted > contract.Gas.RegularGas-statelessGas { + return statelessGas + wanted, nil } - statelessGas.RegularGas += wanted + statelessGas += wanted } // Charge write costs if it transfers value if !balanceIsZero { - wanted := evm.AccessEvents.BasicDataGas(contractAddr, true, contract.Gas.RegularGas-statelessGas.RegularGas, false) - if wanted > contract.Gas.RegularGas-statelessGas.RegularGas { - return GasCosts{RegularGas: statelessGas.RegularGas + wanted}, nil + wanted := evm.AccessEvents.BasicDataGas(contractAddr, true, contract.Gas.RegularGas-statelessGas, false) + if wanted > contract.Gas.RegularGas-statelessGas { + return statelessGas + wanted, nil } - statelessGas.RegularGas += wanted + statelessGas += wanted if contractAddr != beneficiaryAddr { if evm.StateDB.Exist(beneficiaryAddr) { - wanted = evm.AccessEvents.BasicDataGas(beneficiaryAddr, true, contract.Gas.RegularGas-statelessGas.RegularGas, false) + wanted = evm.AccessEvents.BasicDataGas(beneficiaryAddr, true, contract.Gas.RegularGas-statelessGas, false) } else { - wanted = evm.AccessEvents.AddAccount(beneficiaryAddr, true, contract.Gas.RegularGas-statelessGas.RegularGas) + wanted = evm.AccessEvents.AddAccount(beneficiaryAddr, true, contract.Gas.RegularGas-statelessGas) } - if wanted > contract.Gas.RegularGas-statelessGas.RegularGas { - return GasCosts{RegularGas: statelessGas.RegularGas + wanted}, nil + if wanted > contract.Gas.RegularGas-statelessGas { + return statelessGas + wanted, nil } - statelessGas.RegularGas += wanted + statelessGas += wanted } } return statelessGas, nil } -func gasCodeCopyEip4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { +func gasCodeCopyEip4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { gas, err := gasCodeCopy(evm, contract, stack, mem, memorySize) if err != nil { - return GasCosts{}, err + return 0, err } if !contract.IsDeployment && !contract.IsSystemCall { var ( @@ -175,31 +176,31 @@ func gasCodeCopyEip4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, } _, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(contract.Code, uint64CodeOffset, length.Uint64()) - _, wanted := evm.AccessEvents.CodeChunksRangeGas(contract.Address(), copyOffset, nonPaddedCopyLength, uint64(len(contract.Code)), false, contract.Gas.RegularGas-gas.RegularGas) - gas.RegularGas += wanted + _, wanted := evm.AccessEvents.CodeChunksRangeGas(contract.Address(), copyOffset, nonPaddedCopyLength, uint64(len(contract.Code)), false, contract.Gas.RegularGas-gas) + gas += wanted } return gas, nil } -func gasExtCodeCopyEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { +func gasExtCodeCopyEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { // memory expansion first (dynamic part of pre-2929 implementation) gas, err := gasExtCodeCopy(evm, contract, stack, mem, memorySize) if err != nil { - return GasCosts{}, err + return 0, err } addr := common.Address(stack.peek().Bytes20()) _, isPrecompile := evm.precompile(addr) if isPrecompile || addr == params.HistoryStorageAddress { var overflow bool - if gas.RegularGas, overflow = math.SafeAdd(gas.RegularGas, params.WarmStorageReadCostEIP2929); overflow { - return GasCosts{}, ErrGasUintOverflow + if gas, overflow = math.SafeAdd(gas, params.WarmStorageReadCostEIP2929); overflow { + return 0, ErrGasUintOverflow } - return GasCosts{RegularGas: gas.RegularGas}, nil + return gas, nil } - wgas := evm.AccessEvents.BasicDataGas(addr, false, contract.Gas.RegularGas-gas.RegularGas, true) + wgas := evm.AccessEvents.BasicDataGas(addr, false, contract.Gas.RegularGas-gas, true) var overflow bool - if gas.RegularGas, overflow = math.SafeAdd(gas.RegularGas, wgas); overflow { - return GasCosts{}, ErrGasUintOverflow + if gas, overflow = math.SafeAdd(gas, wgas); overflow { + return 0, ErrGasUintOverflow } - return GasCosts{RegularGas: gas.RegularGas}, nil + return gas, nil }