From 6333855163d0ac99240f966df875be64c0885983 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Mon, 13 Apr 2026 08:09:42 +0200 Subject: [PATCH] core: turn gas into a vector (#34691) Pre-refactor PR to get 8037 upstreamed in chunks --------- Co-authored-by: Gary Rong --- core/vm/contract.go | 14 +-- core/vm/eips.go | 6 +- core/vm/evm.go | 14 +-- core/vm/gas_table.go | 190 ++++++++++++++++++---------------- core/vm/gascosts.go | 36 +++++++ core/vm/instructions.go | 6 +- core/vm/instructions_test.go | 2 +- core/vm/interpreter.go | 16 +-- core/vm/jump_table.go | 2 +- core/vm/operations_acl.go | 98 +++++++++--------- core/vm/operations_verkle.go | 115 ++++++++++---------- eth/tracers/js/tracer_test.go | 4 +- 12 files changed, 278 insertions(+), 225 deletions(-) create mode 100644 core/vm/gascosts.go diff --git a/core/vm/contract.go b/core/vm/contract.go index 165ca833f8..3b5695e21a 100644 --- a/core/vm/contract.go +++ b/core/vm/contract.go @@ -42,7 +42,7 @@ type Contract struct { IsDeployment bool IsSystemCall bool - Gas uint64 + Gas GasCosts value *uint256.Int } @@ -56,7 +56,7 @@ func NewContract(caller common.Address, address common.Address, value *uint256.I caller: caller, address: address, jumpDests: jumpDests, - Gas: gas, + Gas: GasCosts{RegularGas: gas}, value: value, } } @@ -127,13 +127,13 @@ func (c *Contract) Caller() common.Address { // UseGas attempts the use gas and subtracts it and returns true on success func (c *Contract) UseGas(gas uint64, logger *tracing.Hooks, reason tracing.GasChangeReason) (ok bool) { - if c.Gas < gas { + if c.Gas.RegularGas < gas { return false } if logger != nil && logger.OnGasChange != nil && reason != tracing.GasChangeIgnored { - logger.OnGasChange(c.Gas, c.Gas-gas, reason) + logger.OnGasChange(c.Gas.RegularGas, c.Gas.RegularGas-gas, reason) } - c.Gas -= gas + c.Gas.RegularGas -= gas return true } @@ -143,9 +143,9 @@ func (c *Contract) RefundGas(gas uint64, logger *tracing.Hooks, reason tracing.G return } if logger != nil && logger.OnGasChange != nil && reason != tracing.GasChangeIgnored { - logger.OnGasChange(c.Gas, c.Gas+gas, reason) + logger.OnGasChange(c.Gas.RegularGas, c.Gas.RegularGas+gas, reason) } - c.Gas += gas + c.Gas.RegularGas += gas } // Address returns the contracts address diff --git a/core/vm/eips.go b/core/vm/eips.go index 3ccd9aaaf0..8f4ca3ae41 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -381,7 +381,7 @@ func opExtCodeCopyEIP4762(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, er addr := common.Address(a.Bytes20()) code := evm.StateDB.GetCode(addr) paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(code, uint64CodeOffset, length.Uint64()) - consumed, wanted := evm.AccessEvents.CodeChunksRangeGas(addr, copyOffset, nonPaddedCopyLength, uint64(len(code)), false, scope.Contract.Gas) + consumed, wanted := evm.AccessEvents.CodeChunksRangeGas(addr, copyOffset, nonPaddedCopyLength, uint64(len(code)), false, scope.Contract.Gas.RegularGas) scope.Contract.UseGas(consumed, evm.Config.Tracer, tracing.GasChangeUnspecified) if consumed < wanted { return nil, ErrOutOfGas @@ -407,7 +407,7 @@ func opPush1EIP4762(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { // touch next chunk if PUSH1 is at the boundary. if so, *pc has // advanced past this boundary. contractAddr := scope.Contract.Address() - consumed, wanted := evm.AccessEvents.CodeChunksRangeGas(contractAddr, *pc+1, uint64(1), uint64(len(scope.Contract.Code)), false, scope.Contract.Gas) + consumed, wanted := evm.AccessEvents.CodeChunksRangeGas(contractAddr, *pc+1, uint64(1), uint64(len(scope.Contract.Code)), false, scope.Contract.Gas.RegularGas) scope.Contract.UseGas(wanted, evm.Config.Tracer, tracing.GasChangeUnspecified) if consumed < wanted { return nil, ErrOutOfGas @@ -435,7 +435,7 @@ func makePushEIP4762(size uint64, pushByteSize int) executionFunc { if !scope.Contract.IsDeployment && !scope.Contract.IsSystemCall { contractAddr := scope.Contract.Address() - consumed, wanted := evm.AccessEvents.CodeChunksRangeGas(contractAddr, uint64(start), uint64(pushByteSize), uint64(len(scope.Contract.Code)), false, scope.Contract.Gas) + consumed, wanted := evm.AccessEvents.CodeChunksRangeGas(contractAddr, uint64(start), uint64(pushByteSize), uint64(len(scope.Contract.Code)), false, scope.Contract.Gas.RegularGas) scope.Contract.UseGas(consumed, evm.Config.Tracer, tracing.GasChangeUnspecified) if consumed < wanted { return nil, ErrOutOfGas diff --git a/core/vm/evm.go b/core/vm/evm.go index 36494de2a8..4df2627486 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -303,7 +303,7 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g contract.IsSystemCall = isSystemCall(caller) contract.SetCallCode(evm.resolveCodeHash(addr), code) ret, err = evm.Run(contract, input, false) - gas = contract.Gas + gas = contract.Gas.RegularGas } } // When an error was returned by the EVM or when setting the creation code @@ -365,7 +365,7 @@ func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byt contract := NewContract(caller, caller, value, gas, evm.jumpDests) contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr)) ret, err = evm.Run(contract, input, false) - gas = contract.Gas + gas = contract.Gas.RegularGas } if err != nil { evm.StateDB.RevertToSnapshot(snapshot) @@ -413,7 +413,7 @@ func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address, contract := NewContract(originCaller, caller, value, gas, evm.jumpDests) contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr)) ret, err = evm.Run(contract, input, false) - gas = contract.Gas + gas = contract.Gas.RegularGas } if err != nil { evm.StateDB.RevertToSnapshot(snapshot) @@ -472,7 +472,7 @@ func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []b // 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. ret, err = evm.Run(contract, input, true) - gas = contract.Gas + gas = contract.Gas.RegularGas } if err != nil { evm.StateDB.RevertToSnapshot(snapshot) @@ -583,10 +583,10 @@ func (evm *EVM) create(caller common.Address, code []byte, gas uint64, value *ui if err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas) { evm.StateDB.RevertToSnapshot(snapshot) if err != ErrExecutionReverted { - contract.UseGas(contract.Gas, evm.Config.Tracer, tracing.GasChangeCallFailedExecution) + contract.UseGas(contract.Gas.RegularGas, evm.Config.Tracer, tracing.GasChangeCallFailedExecution) } } - return ret, address, contract.Gas, err + return ret, address, contract.Gas.RegularGas, err } // initNewContract runs a new contract's creation code, performs checks on the @@ -613,7 +613,7 @@ func (evm *EVM) initNewContract(contract *Contract, address common.Address) ([]b return ret, ErrCodeStoreOutOfGas } } else { - consumed, wanted := evm.AccessEvents.CodeChunksRangeGas(address, 0, uint64(len(ret)), uint64(len(ret)), true, contract.Gas) + consumed, wanted := evm.AccessEvents.CodeChunksRangeGas(address, 0, uint64(len(ret)), uint64(len(ret)), true, contract.Gas.RegularGas) contract.UseGas(consumed, evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk) if len(ret) > 0 && (consumed < wanted) { return ret, ErrCodeStoreOutOfGas diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index f075a99468..b3259b2ec7 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) (uint64, error) { + return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { // Gas for expanding the memory gas, err := memoryGasCost(mem, memorySize) if err != nil { - return 0, err + return GasCosts{}, err } // And gas for copying data, charged per word at param.CopyGas words, overflow := stack.Back(stackpos).Uint64WithOverflow() if overflow { - return 0, ErrGasUintOverflow + return GasCosts{}, ErrGasUintOverflow } if words, overflow = math.SafeMul(toWordSize(words), params.CopyGas); overflow { - return 0, ErrGasUintOverflow + return GasCosts{}, ErrGasUintOverflow } if gas, overflow = math.SafeAdd(gas, words); overflow { - return 0, ErrGasUintOverflow + return GasCosts{}, ErrGasUintOverflow } - return gas, nil + return GasCosts{RegularGas: gas}, nil } } @@ -95,9 +95,9 @@ var ( gasReturnDataCopy = memoryCopierGas(2) ) -func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { +func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { if evm.readOnly { - return 0, ErrWriteProtection + return GasCosts{}, 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 params.SstoreSetGas, nil + return GasCosts{RegularGas: params.SstoreSetGas}, nil case current != (common.Hash{}) && y.Sign() == 0: // non 0 => 0 evm.StateDB.AddRefund(params.SstoreRefundGas) - return params.SstoreClearGas, nil + return GasCosts{RegularGas: params.SstoreClearGas}, nil default: // non 0 => non 0 (or 0 => 0) - return params.SstoreResetGas, nil + return GasCosts{RegularGas: 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 params.NetSstoreNoopGas, nil + return GasCosts{RegularGas: params.NetSstoreNoopGas}, nil } if original == current { if original == (common.Hash{}) { // create slot (2.1.1) - return params.NetSstoreInitGas, nil + return GasCosts{RegularGas: params.NetSstoreInitGas}, nil } if value == (common.Hash{}) { // delete slot (2.1.2b) evm.StateDB.AddRefund(params.NetSstoreClearRefund) } - return params.NetSstoreCleanGas, nil // write existing slot (2.1.2) + return GasCosts{RegularGas: 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 params.NetSstoreDirtyGas, nil + return GasCosts{RegularGas: 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) (uint64, error) { +func gasSStoreEIP2200(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { if evm.readOnly { - return 0, ErrWriteProtection + return GasCosts{}, ErrWriteProtection } // If we fail the minimum gas availability invariant, fail (0) - if contract.Gas <= params.SstoreSentryGasEIP2200 { - return 0, errors.New("not enough gas for reentrancy sentry") + if contract.Gas.RegularGas <= params.SstoreSentryGasEIP2200 { + return GasCosts{}, 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 params.SloadGasEIP2200, nil + return GasCosts{RegularGas: params.SloadGasEIP2200}, nil } if original == current { if original == (common.Hash{}) { // create slot (2.1.1) - return params.SstoreSetGasEIP2200, nil + return GasCosts{RegularGas: params.SstoreSetGasEIP2200}, nil } if value == (common.Hash{}) { // delete slot (2.1.2b) evm.StateDB.AddRefund(params.SstoreClearsScheduleRefundEIP2200) } - return params.SstoreResetGasEIP2200, nil // write existing slot (2.1.2) + return GasCosts{RegularGas: params.SstoreResetGasEIP2200}, nil // write existing slot (2.1.2) } if original != (common.Hash{}) { if current == (common.Hash{}) { // recreate slot (2.2.1.1) @@ -223,62 +223,66 @@ func gasSStoreEIP2200(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m evm.StateDB.AddRefund(params.SstoreResetGasEIP2200 - params.SloadGasEIP2200) } } - return params.SloadGasEIP2200, nil // dirty update (2.2) + return GasCosts{RegularGas: params.SloadGasEIP2200}, nil // dirty update (2.2) } func makeGasLog(n uint64) gasFunc { - return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { requestedSize, overflow := stack.Back(1).Uint64WithOverflow() if overflow { - return 0, ErrGasUintOverflow + return GasCosts{}, ErrGasUintOverflow } gas, err := memoryGasCost(mem, memorySize) if err != nil { - return 0, err + return GasCosts{}, err } if gas, overflow = math.SafeAdd(gas, params.LogGas); overflow { - return 0, ErrGasUintOverflow + return GasCosts{}, ErrGasUintOverflow } if gas, overflow = math.SafeAdd(gas, n*params.LogTopicGas); overflow { - return 0, ErrGasUintOverflow + return GasCosts{}, ErrGasUintOverflow } var memorySizeGas uint64 if memorySizeGas, overflow = math.SafeMul(requestedSize, params.LogDataGas); overflow { - return 0, ErrGasUintOverflow + return GasCosts{}, ErrGasUintOverflow } if gas, overflow = math.SafeAdd(gas, memorySizeGas); overflow { - return 0, ErrGasUintOverflow + return GasCosts{}, ErrGasUintOverflow } - return gas, nil + return GasCosts{RegularGas: gas}, nil } } -func gasKeccak256(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { +func gasKeccak256(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { gas, err := memoryGasCost(mem, memorySize) if err != nil { - return 0, err + return GasCosts{}, err } wordGas, overflow := stack.Back(1).Uint64WithOverflow() if overflow { - return 0, ErrGasUintOverflow + return GasCosts{}, ErrGasUintOverflow } if wordGas, overflow = math.SafeMul(toWordSize(wordGas), params.Keccak256WordGas); overflow { - return 0, ErrGasUintOverflow + return GasCosts{}, ErrGasUintOverflow } if gas, overflow = math.SafeAdd(gas, wordGas); overflow { - return 0, ErrGasUintOverflow + return GasCosts{}, ErrGasUintOverflow } - return gas, nil + return GasCosts{RegularGas: 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) (uint64, error) { - return memoryGasCost(mem, memorySize) +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 } var ( @@ -290,64 +294,64 @@ var ( gasCreate = pureMemoryGascost ) -func gasCreate2(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { +func gasCreate2(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { gas, err := memoryGasCost(mem, memorySize) if err != nil { - return 0, err + return GasCosts{}, err } wordGas, overflow := stack.Back(2).Uint64WithOverflow() if overflow { - return 0, ErrGasUintOverflow + return GasCosts{}, ErrGasUintOverflow } if wordGas, overflow = math.SafeMul(toWordSize(wordGas), params.Keccak256WordGas); overflow { - return 0, ErrGasUintOverflow + return GasCosts{}, ErrGasUintOverflow } if gas, overflow = math.SafeAdd(gas, wordGas); overflow { - return 0, ErrGasUintOverflow + return GasCosts{}, ErrGasUintOverflow } - return gas, nil + return GasCosts{RegularGas: gas}, nil } -func gasCreateEip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { +func gasCreateEip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { gas, err := memoryGasCost(mem, memorySize) if err != nil { - return 0, err + return GasCosts{}, err } size, overflow := stack.Back(2).Uint64WithOverflow() if overflow { - return 0, ErrGasUintOverflow + return GasCosts{}, ErrGasUintOverflow } if err := CheckMaxInitCodeSize(&evm.chainRules, size); err != nil { - return 0, err + return GasCosts{}, 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 0, ErrGasUintOverflow + return GasCosts{}, ErrGasUintOverflow } - return gas, nil + return GasCosts{RegularGas: gas}, nil } -func gasCreate2Eip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { +func gasCreate2Eip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { gas, err := memoryGasCost(mem, memorySize) if err != nil { - return 0, err + return GasCosts{}, err } size, overflow := stack.Back(2).Uint64WithOverflow() if overflow { - return 0, ErrGasUintOverflow + return GasCosts{}, ErrGasUintOverflow } if err := CheckMaxInitCodeSize(&evm.chainRules, size); err != nil { - return 0, err + return GasCosts{}, 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 0, ErrGasUintOverflow + return GasCosts{}, ErrGasUintOverflow } - return gas, nil + return GasCosts{RegularGas: gas}, nil } -func gasExpFrontier(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { +func gasExpFrontier(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { expByteLen := uint64((stack.data[stack.len()-2].BitLen() + 7) / 8) var ( @@ -355,12 +359,12 @@ func gasExpFrontier(evm *EVM, contract *Contract, stack *Stack, mem *Memory, mem overflow bool ) if gas, overflow = math.SafeAdd(gas, params.ExpGas); overflow { - return 0, ErrGasUintOverflow + return GasCosts{}, ErrGasUintOverflow } - return gas, nil + return GasCosts{RegularGas: gas}, nil } -func gasExpEIP158(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { +func gasExpEIP158(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { expByteLen := uint64((stack.data[stack.len()-2].BitLen() + 7) / 8) var ( @@ -368,9 +372,9 @@ func gasExpEIP158(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memor overflow bool ) if gas, overflow = math.SafeAdd(gas, params.ExpGas); overflow { - return 0, ErrGasUintOverflow + return GasCosts{}, ErrGasUintOverflow } - return gas, nil + return GasCosts{RegularGas: gas}, nil } var ( @@ -381,36 +385,36 @@ var ( ) func makeCallVariantGasCost(intrinsicFunc gasFunc) gasFunc { - return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { intrinsic, err := intrinsicFunc(evm, contract, stack, mem, memorySize) if err != nil { - return 0, err + return GasCosts{}, err } - evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, intrinsic, stack.Back(0)) + evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas.RegularGas, intrinsic.RegularGas, stack.Back(0)) if err != nil { - return 0, err + return GasCosts{}, err } - gas, overflow := math.SafeAdd(intrinsic, evm.callGasTemp) + gas, overflow := math.SafeAdd(intrinsic.RegularGas, evm.callGasTemp) if overflow { - return 0, ErrGasUintOverflow + return GasCosts{}, ErrGasUintOverflow } - return gas, nil + return GasCosts{RegularGas: gas}, nil } } -func gasCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { +func gasCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { var ( gas uint64 transfersValue = !stack.Back(2).IsZero() address = common.Address(stack.Back(1).Bytes20()) ) if evm.readOnly && transfersValue { - return 0, ErrWriteProtection + return GasCosts{}, ErrWriteProtection } // Stateless check memoryGas, err := memoryGasCost(mem, memorySize) if err != nil { - return 0, err + return GasCosts{}, err } var transferGas uint64 if transfersValue && !evm.chainRules.IsEIP4762 { @@ -418,12 +422,12 @@ func gasCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m } var overflow bool if gas, overflow = math.SafeAdd(memoryGas, transferGas); overflow { - return 0, ErrGasUintOverflow + return GasCosts{}, ErrGasUintOverflow } // 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 < gas { - return 0, ErrOutOfGas + if contract.Gas.RegularGas < gas { + return GasCosts{}, ErrOutOfGas } // Stateful check var stateGas uint64 @@ -435,15 +439,15 @@ func gasCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m stateGas += params.CallNewAccountGas } if gas, overflow = math.SafeAdd(gas, stateGas); overflow { - return 0, ErrGasUintOverflow + return GasCosts{}, ErrGasUintOverflow } - return gas, nil + return GasCosts{RegularGas: gas}, nil } -func gasCallCodeIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { +func gasCallCodeIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { memoryGas, err := memoryGasCost(mem, memorySize) if err != nil { - return 0, err + return GasCosts{}, err } var ( gas uint64 @@ -453,22 +457,30 @@ func gasCallCodeIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memor gas += params.CallValueTransferGas } if gas, overflow = math.SafeAdd(gas, memoryGas); overflow { - return 0, ErrGasUintOverflow + return GasCosts{}, ErrGasUintOverflow } - return gas, nil + return GasCosts{RegularGas: gas}, nil } -func gasDelegateCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - return memoryGasCost(mem, memorySize) +func gasDelegateCallIntrinsic(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 gasStaticCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - return memoryGasCost(mem, memorySize) +func gasStaticCallIntrinsic(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 gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { +func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { if evm.readOnly { - return 0, ErrWriteProtection + return GasCosts{}, ErrWriteProtection } var gas uint64 @@ -490,5 +502,5 @@ func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me if !evm.StateDB.HasSelfDestructed(contract.Address()) { evm.StateDB.AddRefund(params.SelfdestructRefundGas) } - return gas, nil + return GasCosts{RegularGas: gas}, nil } diff --git a/core/vm/gascosts.go b/core/vm/gascosts.go new file mode 100644 index 0000000000..ba6746758b --- /dev/null +++ b/core/vm/gascosts.go @@ -0,0 +1,36 @@ +// Copyright 2026 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package vm + +import "fmt" + +// GasCosts denotes a vector of gas costs in the +// multidimensional metering paradigm. +type GasCosts struct { + RegularGas uint64 + StateGas uint64 +} + +// Sum returns the total gas (regular + state). +func (g GasCosts) Sum() uint64 { + return g.RegularGas + g.StateGas +} + +// String returns a visual representation of the gas vector. +func (g GasCosts) String() string { + return fmt.Sprintf("<%v,%v>", g.RegularGas, g.StateGas) +} diff --git a/core/vm/instructions.go b/core/vm/instructions.go index a5fa11e307..74400732ac 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -566,7 +566,7 @@ func opMsize(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { } func opGas(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { - scope.Stack.push(new(uint256.Int).SetUint64(scope.Contract.Gas)) + scope.Stack.push(new(uint256.Int).SetUint64(scope.Contract.Gas.RegularGas)) return nil, nil } @@ -658,7 +658,7 @@ func opCreate(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { value = scope.Stack.pop() offset, size = scope.Stack.pop(), scope.Stack.pop() input = scope.Memory.GetCopy(offset.Uint64(), size.Uint64()) - gas = scope.Contract.Gas + gas = scope.Contract.Gas.RegularGas ) if evm.chainRules.IsEIP150 { gas -= gas / 64 @@ -702,7 +702,7 @@ func opCreate2(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { offset, size = scope.Stack.pop(), scope.Stack.pop() salt = scope.Stack.pop() input = scope.Memory.GetCopy(offset.Uint64(), size.Uint64()) - gas = scope.Contract.Gas + gas = scope.Contract.Gas.RegularGas ) // Apply EIP150 diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index 4c6d093d2e..1f69eea3da 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -879,7 +879,7 @@ func TestOpMCopy(t *testing.T) { if dynamicCost, err := gasMcopy(evm, nil, stack, mem, memorySize); err != nil { t.Error(err) } else { - haveGas = GasFastestStep + dynamicCost + haveGas = GasFastestStep + dynamicCost.RegularGas } // Expand mem if memorySize > 0 { diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 620c069fc8..b507595fab 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -166,14 +166,14 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte for { if debug { // Capture pre-execution values for tracing. - logged, pcCopy, gasCopy = false, pc, contract.Gas + logged, pcCopy, gasCopy = false, pc, contract.Gas.RegularGas } if isEIP4762 && !contract.IsDeployment && !contract.IsSystemCall { // if the PC ends up in a new "chunk" of verkleized code, charge the // associated costs. contractAddr := contract.Address() - consumed, wanted := evm.TxContext.AccessEvents.CodeChunksRangeGas(contractAddr, pc, 1, uint64(len(contract.Code)), false, contract.Gas) + consumed, wanted := evm.TxContext.AccessEvents.CodeChunksRangeGas(contractAddr, pc, 1, uint64(len(contract.Code)), false, contract.Gas.RegularGas) contract.UseGas(consumed, evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk) if consumed < wanted { return nil, ErrOutOfGas @@ -192,10 +192,10 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte return nil, &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack} } // for tracing: this gas consumption event is emitted below in the debug section. - if contract.Gas < cost { + if contract.Gas.RegularGas < cost { return nil, ErrOutOfGas } else { - contract.Gas -= cost + contract.Gas.RegularGas -= cost } // All ops with a dynamic memory usage also has a dynamic gas cost. @@ -218,17 +218,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 uint64 + var dynamicCost GasCosts dynamicCost, err = operation.dynamicGas(evm, contract, stack, mem, memorySize) - cost += dynamicCost // for tracing + cost += dynamicCost.RegularGas // 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 contract.Gas < dynamicCost { + if contract.Gas.RegularGas < dynamicCost.RegularGas { return nil, ErrOutOfGas } else { - contract.Gas -= dynamicCost + contract.Gas.RegularGas -= dynamicCost.RegularGas } } diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index a2e2c91194..82fc43ec13 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) (uint64, error) // last parameter is the requested memory size as a uint64 + gasFunc func(*EVM, *Contract, *Stack, *Memory, uint64) (GasCosts, 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 addd2b162f..154c261cae 100644 --- a/core/vm/operations_acl.go +++ b/core/vm/operations_acl.go @@ -27,13 +27,13 @@ import ( ) func makeGasSStoreFunc(clearingRefund uint64) gasFunc { - return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { if evm.readOnly { - return 0, ErrWriteProtection + return GasCosts{}, ErrWriteProtection } // If we fail the minimum gas availability invariant, fail (0) - if contract.Gas <= params.SstoreSentryGasEIP2200 { - return 0, errors.New("not enough gas for reentrancy sentry") + if contract.Gas.RegularGas <= params.SstoreSentryGasEIP2200 { + return GasCosts{}, errors.New("not enough gas for reentrancy sentry") } // Gas sentry honoured, do the actual gas calculation based on the stored value var ( @@ -53,18 +53,18 @@ func makeGasSStoreFunc(clearingRefund uint64) gasFunc { if current == value { // noop (1) // EIP 2200 original clause: // return params.SloadGasEIP2200, nil - return cost + params.WarmStorageReadCostEIP2929, nil // SLOAD_GAS + return GasCosts{RegularGas: cost + params.WarmStorageReadCostEIP2929}, nil // SLOAD_GAS } if original == current { if original == (common.Hash{}) { // create slot (2.1.1) - return cost + params.SstoreSetGasEIP2200, nil + return GasCosts{RegularGas: 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 cost + (params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929), nil // write existing slot (2.1.2) + return GasCosts{RegularGas: 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 cost + params.WarmStorageReadCostEIP2929, nil // dirty update (2.2) + return GasCosts{RegularGas: 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) (uint64, error) { +func gasSLoadEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, 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 params.ColdSloadCostEIP2929, nil + return GasCosts{RegularGas: params.ColdSloadCostEIP2929}, nil } - return params.WarmStorageReadCostEIP2929, nil + return GasCosts{RegularGas: params.WarmStorageReadCostEIP2929}, nil } // gasExtCodeCopyEIP2929 implements extcodecopy according to EIP-2929 @@ -116,12 +116,13 @@ 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) (uint64, error) { +func gasExtCodeCopyEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { // memory expansion first (dynamic part of pre-2929 implementation) - gas, err := gasExtCodeCopy(evm, contract, stack, mem, memorySize) + gasCost, err := gasExtCodeCopy(evm, contract, stack, mem, memorySize) if err != nil { - return 0, err + return GasCosts{}, err } + gas := gasCost.RegularGas addr := common.Address(stack.peek().Bytes20()) // Check slot presence in the access list if !evm.StateDB.AddressInAccessList(addr) { @@ -129,11 +130,11 @@ func gasExtCodeCopyEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memo var overflow bool // We charge (cold-warm), since 'warm' is already charged as constantGas if gas, overflow = math.SafeAdd(gas, params.ColdAccountAccessCostEIP2929-params.WarmStorageReadCostEIP2929); overflow { - return 0, ErrGasUintOverflow + return GasCosts{}, ErrGasUintOverflow } - return gas, nil + return GasCosts{RegularGas: gas}, nil } - return gas, nil + return GasCosts{RegularGas: gas}, nil } // gasEip2929AccountCheck checks whether the first stack item (as address) is present in the access list. @@ -143,20 +144,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) (uint64, error) { +func gasEip2929AccountCheck(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, 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 params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929, nil + return GasCosts{RegularGas: params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929}, nil } - return 0, nil + return GasCosts{}, nil } func makeCallVariantGasCallEIP2929(oldCalculator gasFunc, addressPosition int) gasFunc { - return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { addr := common.Address(stack.Back(addressPosition).Bytes20()) // Check slot presence in the access list warmAccess := evm.StateDB.AddressInAccessList(addr) @@ -168,7 +169,7 @@ func makeCallVariantGasCallEIP2929(oldCalculator gasFunc, addressPosition int) g // Charge the remaining difference here already, to correctly calculate available // gas for call if !contract.UseGas(coldCost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) { - return 0, ErrOutOfGas + return GasCosts{}, ErrOutOfGas } } // Now call the old calculator, which takes into account @@ -176,21 +177,22 @@ func makeCallVariantGasCallEIP2929(oldCalculator gasFunc, addressPosition int) g // - transfer value // - memory expansion // - 63/64ths rule - gas, err := oldCalculator(evm, contract, stack, mem, memorySize) + gasCost, err := oldCalculator(evm, contract, stack, mem, memorySize) if warmAccess || err != nil { - return gas, err + return gasCost, err } // In case of a cold access, we temporarily add the cold charge back, and also // add it to the returned gas. By adding it to the return, it will be charged // outside of this function, as part of the dynamic gas, and that will make it // also become correctly reported to tracers. - contract.Gas += coldCost + contract.Gas.RegularGas += coldCost + gas := gasCost.RegularGas var overflow bool if gas, overflow = math.SafeAdd(gas, coldCost); overflow { - return 0, ErrGasUintOverflow + return GasCosts{}, ErrGasUintOverflow } - return gas, nil + return GasCosts{RegularGas: gas}, nil } } @@ -224,13 +226,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) (uint64, error) { + gasFunc := func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { var ( gas uint64 address = common.Address(stack.peek().Bytes20()) ) if evm.readOnly { - return 0, ErrWriteProtection + return GasCosts{}, ErrWriteProtection } if !evm.StateDB.AddressInAccessList(address) { // If the caller cannot afford the cost, this change will be rolled back @@ -239,8 +241,8 @@ 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 < gas { - return 0, ErrOutOfGas + if contract.Gas.RegularGas < gas { + return GasCosts{}, ErrOutOfGas } } // if empty and transfers value @@ -250,7 +252,7 @@ func makeSelfdestructGasFn(refundsEnabled bool) gasFunc { if refundsEnabled && !evm.StateDB.HasSelfDestructed(contract.Address()) { evm.StateDB.AddRefund(params.SelfdestructRefundGas) } - return gas, nil + return GasCosts{RegularGas: gas}, nil } return gasFunc } @@ -262,20 +264,20 @@ var ( gasCallCodeEIP7702 = makeCallVariantGasCallEIP7702(gasCallCodeIntrinsic) ) -func gasCallEIP7702(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { +func gasCallEIP7702(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, 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 0, ErrWriteProtection + return GasCosts{}, ErrWriteProtection } return innerGasCallEIP7702(evm, contract, stack, mem, memorySize) } func makeCallVariantGasCallEIP7702(intrinsicFunc gasFunc) gasFunc { - return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { var ( eip2929Cost uint64 eip7702Cost uint64 @@ -294,7 +296,7 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc gasFunc) gasFunc { // Charge the remaining difference here already, to correctly calculate // available gas for call if !contract.UseGas(eip2929Cost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) { - return 0, ErrOutOfGas + return GasCosts{}, ErrOutOfGas } } @@ -305,13 +307,13 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc gasFunc) gasFunc { // - create new account intrinsicCost, err := intrinsicFunc(evm, contract, stack, mem, memorySize) if err != nil { - return 0, err + return GasCosts{}, 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 < intrinsicCost { - return 0, ErrOutOfGas + if contract.Gas.RegularGas < intrinsicCost.RegularGas { + return GasCosts{}, ErrOutOfGas } // Check if code is a delegation and if so, charge for resolution. @@ -323,20 +325,20 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc gasFunc) gasFunc { eip7702Cost = params.ColdAccountAccessCostEIP2929 } if !contract.UseGas(eip7702Cost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) { - return 0, ErrOutOfGas + return GasCosts{}, 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, intrinsicCost, stack.Back(0)) + evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas.RegularGas, intrinsicCost.RegularGas, stack.Back(0)) if err != nil { - return 0, err + return GasCosts{}, 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 // part of the dynamic gas. This will ensure it is correctly reported to // tracers. - contract.Gas += eip2929Cost + eip7702Cost + contract.Gas.RegularGas += eip2929Cost + eip7702Cost // Aggregate the gas costs from all components, including EIP-2929, EIP-7702, // the CALL opcode itself, and the cost incurred by nested calls. @@ -345,14 +347,14 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc gasFunc) gasFunc { totalCost uint64 ) if totalCost, overflow = math.SafeAdd(eip2929Cost, eip7702Cost); overflow { - return 0, ErrGasUintOverflow + return GasCosts{}, ErrGasUintOverflow } - if totalCost, overflow = math.SafeAdd(totalCost, intrinsicCost); overflow { - return 0, ErrGasUintOverflow + if totalCost, overflow = math.SafeAdd(totalCost, intrinsicCost.RegularGas); overflow { + return GasCosts{}, ErrGasUintOverflow } if totalCost, overflow = math.SafeAdd(totalCost, evm.callGasTemp); overflow { - return 0, ErrGasUintOverflow + return GasCosts{}, ErrGasUintOverflow } - return totalCost, nil + return GasCosts{RegularGas: totalCost}, nil } } diff --git a/core/vm/operations_verkle.go b/core/vm/operations_verkle.go index 30f9957775..d57f2c4dcf 100644 --- a/core/vm/operations_verkle.go +++ b/core/vm/operations_verkle.go @@ -24,37 +24,37 @@ import ( "github.com/ethereum/go-ethereum/params" ) -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, true), nil +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 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, 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 gasBalance4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { +func gasBalance4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { address := stack.peek().Bytes20() - return evm.AccessEvents.BasicDataGas(address, false, contract.Gas, true), nil + return GasCosts{RegularGas: evm.AccessEvents.BasicDataGas(address, false, contract.Gas.RegularGas, true)}, nil } -func gasExtCodeSize4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { +func gasExtCodeSize4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { address := stack.peek().Bytes20() if _, isPrecompile := evm.precompile(address); isPrecompile { - return 0, nil + return GasCosts{}, nil } - return evm.AccessEvents.BasicDataGas(address, false, contract.Gas, true), nil + return GasCosts{RegularGas: evm.AccessEvents.BasicDataGas(address, false, contract.Gas.RegularGas, true)}, nil } -func gasExtCodeHash4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { +func gasExtCodeHash4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { address := stack.peek().Bytes20() if _, isPrecompile := evm.precompile(address); isPrecompile { - return 0, nil + return GasCosts{}, nil } - return evm.AccessEvents.CodeHashGas(address, false, contract.Gas, true), nil + return GasCosts{RegularGas: 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) (uint64, error) { + return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { var ( target = common.Address(stack.Back(1).Bytes20()) witnessGas uint64 @@ -65,9 +65,9 @@ func makeCallVariantGasEIP4762(oldCalculator gasFunc, withTransferCosts bool) ga // If value is transferred, it is charged before 1/64th // is subtracted from the available gas pool. if withTransferCosts && !stack.Back(2).IsZero() { - wantedValueTransferWitnessGas := evm.AccessEvents.ValueTransferGas(contract.Address(), target, contract.Gas) - if wantedValueTransferWitnessGas > contract.Gas { - return wantedValueTransferWitnessGas, nil + wantedValueTransferWitnessGas := evm.AccessEvents.ValueTransferGas(contract.Address(), target, contract.Gas.RegularGas) + if wantedValueTransferWitnessGas > contract.Gas.RegularGas { + return GasCosts{RegularGas: wantedValueTransferWitnessGas}, nil } witnessGas = wantedValueTransferWitnessGas } else if isPrecompile || isSystemContract { @@ -78,25 +78,26 @@ func makeCallVariantGasEIP4762(oldCalculator gasFunc, withTransferCosts bool) ga // (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-witnessGas) + wantedMessageCallWitnessGas := evm.AccessEvents.MessageCallGas(target, contract.Gas.RegularGas-witnessGas) var overflow bool if witnessGas, overflow = math.SafeAdd(witnessGas, wantedMessageCallWitnessGas); overflow { - return 0, ErrGasUintOverflow + return GasCosts{}, ErrGasUintOverflow } - if witnessGas > contract.Gas { - return witnessGas, nil + if witnessGas > contract.Gas.RegularGas { + return GasCosts{RegularGas: witnessGas}, nil } } - contract.Gas -= witnessGas + contract.Gas.RegularGas -= witnessGas // if the operation fails, adds witness gas to the gas before returning the error - gas, err := oldCalculator(evm, contract, stack, mem, memorySize) - contract.Gas += witnessGas // restore witness gas so that it can be charged at the callsite + gasCost, err := oldCalculator(evm, contract, stack, mem, memorySize) + contract.Gas.RegularGas += witnessGas // restore witness gas so that it can be charged at the callsite + gas := gasCost.RegularGas var overflow bool if gas, overflow = math.SafeAdd(gas, witnessGas); overflow { - return 0, ErrGasUintOverflow + return GasCosts{}, ErrGasUintOverflow } - return gas, err + return GasCosts{RegularGas: gas}, err } } @@ -107,18 +108,18 @@ var ( gasDelegateCallEIP4762 = makeCallVariantGasEIP4762(gasDelegateCall, false) ) -func gasSelfdestructEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { +func gasSelfdestructEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { beneficiaryAddr := common.Address(stack.peek().Bytes20()) if _, isPrecompile := evm.precompile(beneficiaryAddr); isPrecompile { - return 0, nil + return GasCosts{}, nil } if contract.IsSystemCall { - return 0, nil + return GasCosts{}, nil } contractAddr := contract.Address() - wanted := evm.AccessEvents.BasicDataGas(contractAddr, false, contract.Gas, false) - if wanted > contract.Gas { - return wanted, nil + wanted := evm.AccessEvents.BasicDataGas(contractAddr, false, contract.Gas.RegularGas, false) + if wanted > contract.Gas.RegularGas { + return GasCosts{RegularGas: wanted}, nil } statelessGas := wanted balanceIsZero := evm.StateDB.GetBalance(contractAddr).Sign() == 0 @@ -126,44 +127,45 @@ func gasSelfdestructEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Mem isSystemContract := beneficiaryAddr == params.HistoryStorageAddress if (isPrecompile || isSystemContract) && balanceIsZero { - return statelessGas, nil + return GasCosts{RegularGas: statelessGas}, nil } if contractAddr != beneficiaryAddr { - wanted := evm.AccessEvents.BasicDataGas(beneficiaryAddr, false, contract.Gas-statelessGas, false) - if wanted > contract.Gas-statelessGas { - return statelessGas + wanted, nil + wanted := evm.AccessEvents.BasicDataGas(beneficiaryAddr, false, contract.Gas.RegularGas-statelessGas, false) + if wanted > contract.Gas.RegularGas-statelessGas { + return GasCosts{RegularGas: statelessGas + wanted}, nil } statelessGas += wanted } // Charge write costs if it transfers value if !balanceIsZero { - wanted := evm.AccessEvents.BasicDataGas(contractAddr, true, contract.Gas-statelessGas, false) - if wanted > contract.Gas-statelessGas { - return statelessGas + wanted, nil + wanted := evm.AccessEvents.BasicDataGas(contractAddr, true, contract.Gas.RegularGas-statelessGas, false) + if wanted > contract.Gas.RegularGas-statelessGas { + return GasCosts{RegularGas: statelessGas + wanted}, nil } statelessGas += wanted if contractAddr != beneficiaryAddr { if evm.StateDB.Exist(beneficiaryAddr) { - wanted = evm.AccessEvents.BasicDataGas(beneficiaryAddr, true, contract.Gas-statelessGas, false) + wanted = evm.AccessEvents.BasicDataGas(beneficiaryAddr, true, contract.Gas.RegularGas-statelessGas, false) } else { - wanted = evm.AccessEvents.AddAccount(beneficiaryAddr, true, contract.Gas-statelessGas) + wanted = evm.AccessEvents.AddAccount(beneficiaryAddr, true, contract.Gas.RegularGas-statelessGas) } - if wanted > contract.Gas-statelessGas { - return statelessGas + wanted, nil + if wanted > contract.Gas.RegularGas-statelessGas { + return GasCosts{RegularGas: statelessGas + wanted}, nil } statelessGas += wanted } } - return statelessGas, nil + return GasCosts{RegularGas: statelessGas}, nil } -func gasCodeCopyEip4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - gas, err := gasCodeCopy(evm, contract, stack, mem, memorySize) +func gasCodeCopyEip4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { + gasCost, err := gasCodeCopy(evm, contract, stack, mem, memorySize) if err != nil { - return 0, err + return GasCosts{}, err } + gas := gasCost.RegularGas if !contract.IsDeployment && !contract.IsSystemCall { var ( codeOffset = stack.Back(1) @@ -175,31 +177,32 @@ 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-gas) + _, wanted := evm.AccessEvents.CodeChunksRangeGas(contract.Address(), copyOffset, nonPaddedCopyLength, uint64(len(contract.Code)), false, contract.Gas.RegularGas-gas) gas += wanted } - return gas, nil + return GasCosts{RegularGas: gas}, nil } -func gasExtCodeCopyEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { +func gasExtCodeCopyEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { // memory expansion first (dynamic part of pre-2929 implementation) - gas, err := gasExtCodeCopy(evm, contract, stack, mem, memorySize) + gasCost, err := gasExtCodeCopy(evm, contract, stack, mem, memorySize) if err != nil { - return 0, err + return GasCosts{}, err } + gas := gasCost.RegularGas addr := common.Address(stack.peek().Bytes20()) _, isPrecompile := evm.precompile(addr) if isPrecompile || addr == params.HistoryStorageAddress { var overflow bool if gas, overflow = math.SafeAdd(gas, params.WarmStorageReadCostEIP2929); overflow { - return 0, ErrGasUintOverflow + return GasCosts{}, ErrGasUintOverflow } - return gas, nil + return GasCosts{RegularGas: gas}, nil } - wgas := evm.AccessEvents.BasicDataGas(addr, false, contract.Gas-gas, true) + wgas := evm.AccessEvents.BasicDataGas(addr, false, contract.Gas.RegularGas-gas, true) var overflow bool if gas, overflow = math.SafeAdd(gas, wgas); overflow { - return 0, ErrGasUintOverflow + return GasCosts{}, ErrGasUintOverflow } - return gas, nil + return GasCosts{RegularGas: gas}, nil } diff --git a/eth/tracers/js/tracer_test.go b/eth/tracers/js/tracer_test.go index b21e104abc..694debcf98 100644 --- a/eth/tracers/js/tracer_test.go +++ b/eth/tracers/js/tracer_test.go @@ -66,9 +66,9 @@ func runTrace(tracer *tracers.Tracer, vmctx *vmContext, chaincfg *params.ChainCo tracer.OnTxStart(evm.GetVMContext(), types.NewTx(&types.LegacyTx{Gas: gasLimit, GasPrice: vmctx.txCtx.GasPrice.ToBig()}), contract.Caller()) tracer.OnEnter(0, byte(vm.CALL), contract.Caller(), contract.Address(), []byte{}, startGas, value.ToBig()) ret, err := evm.Run(contract, []byte{}, false) - tracer.OnExit(0, ret, startGas-contract.Gas, err, true) + tracer.OnExit(0, ret, startGas-contract.Gas.RegularGas, err, true) // Rest gas assumes no refund - tracer.OnTxEnd(&types.Receipt{GasUsed: gasLimit - contract.Gas}, nil) + tracer.OnTxEnd(&types.Receipt{GasUsed: gasLimit - contract.Gas.RegularGas}, nil) if err != nil { return nil, err }