From 29e0a6f4042d19f507c6a11c8a468bda3c332f04 Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Mon, 20 Apr 2026 15:33:29 +0800 Subject: [PATCH] core/vm, eth, tests: introduce gas budget (#34712) This PR introduces a gasBudget struct to track the available gas for EVM execution. With the upcoming EIP-8037, multi-dimensional gas accounting will be introduced, requiring multiple gas budget counters to be tracked simultaneously. To support this, the counters are grouped into a gasBudget structure. This change is a prerequisite for internal refactoring in preparation for EIP-8037. --------- Co-authored-by: MariusVanDerWijden --- cmd/evm/internal/t8ntool/transaction.go | 8 +- core/bench_test.go | 4 +- core/bintrie_witness_test.go | 4 +- core/state_processor.go | 6 +- core/state_transition.go | 69 ++++++++------- core/tracing/hooks.go | 2 + core/txpool/validation.go | 4 +- core/vm/contract.go | 24 +++--- core/vm/contracts.go | 16 ++-- core/vm/contracts_fuzz_test.go | 2 +- core/vm/contracts_test.go | 8 +- core/vm/eips.go | 6 +- core/vm/evm.go | 110 ++++++++++++------------ core/vm/gas_table_test.go | 12 +-- core/vm/gascosts.go | 63 +++++++++++++- core/vm/instructions.go | 16 ++-- core/vm/instructions_test.go | 2 +- core/vm/interpreter.go | 2 +- core/vm/interpreter_test.go | 4 +- core/vm/operations_acl.go | 6 +- core/vm/runtime/runtime.go | 16 ++-- eth/tracers/js/tracer_test.go | 6 +- eth/tracers/logger/logger_test.go | 2 +- tests/state_test.go | 8 +- tests/transaction_test_util.go | 3 +- 25 files changed, 235 insertions(+), 168 deletions(-) diff --git a/cmd/evm/internal/t8ntool/transaction.go b/cmd/evm/internal/t8ntool/transaction.go index 3a457eeaec..82c0f30b42 100644 --- a/cmd/evm/internal/t8ntool/transaction.go +++ b/cmd/evm/internal/t8ntool/transaction.go @@ -133,15 +133,15 @@ func Transaction(ctx *cli.Context) error { } // Check intrinsic gas rules := chainConfig.Rules(common.Big0, true, 0) - gas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai) + cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai) if err != nil { r.Error = err results = append(results, r) continue } - r.IntrinsicGas = gas - if tx.Gas() < gas { - r.Error = fmt.Errorf("%w: have %d, want %d", core.ErrIntrinsicGas, tx.Gas(), gas) + r.IntrinsicGas = cost.RegularGas + if tx.Gas() < cost.RegularGas { + r.Error = fmt.Errorf("%w: have %d, want %d", core.ErrIntrinsicGas, tx.Gas(), cost.RegularGas) results = append(results, r) continue } diff --git a/core/bench_test.go b/core/bench_test.go index 932188f8e2..20d1a7794b 100644 --- a/core/bench_test.go +++ b/core/bench_test.go @@ -89,7 +89,7 @@ func genValueTx(nbytes int) func(int, *BlockGen) { data := make([]byte, nbytes) return func(i int, gen *BlockGen) { toaddr := common.Address{} - gas, _ := IntrinsicGas(data, nil, nil, false, false, false, false) + cost, _ := IntrinsicGas(data, nil, nil, false, false, false, false) signer := gen.Signer() gasPrice := big.NewInt(0) if gen.header.BaseFee != nil { @@ -99,7 +99,7 @@ func genValueTx(nbytes int) func(int, *BlockGen) { Nonce: gen.TxNonce(benchRootAddr), To: &toaddr, Value: big.NewInt(1), - Gas: gas, + Gas: cost.RegularGas, Data: data, GasPrice: gasPrice, }) diff --git a/core/bintrie_witness_test.go b/core/bintrie_witness_test.go index e4cb34cb56..1b033151d3 100644 --- a/core/bintrie_witness_test.go +++ b/core/bintrie_witness_test.go @@ -97,11 +97,11 @@ func TestProcessUBT(t *testing.T) { txCost1 := params.TxGas txCost2 := params.TxGas - contractCreationCost := intrinsicContractCreationGas + + contractCreationCost := intrinsicContractCreationGas.RegularGas + params.WitnessChunkReadCost + params.WitnessChunkWriteCost + params.WitnessBranchReadCost + params.WitnessBranchWriteCost + /* creation */ params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* creation with value */ 739 /* execution costs */ - codeWithExtCodeCopyGas := intrinsicCodeWithExtCodeCopyGas + + codeWithExtCodeCopyGas := intrinsicCodeWithExtCodeCopyGas.RegularGas + params.WitnessChunkReadCost + params.WitnessChunkWriteCost + params.WitnessBranchReadCost + params.WitnessBranchWriteCost + /* creation (tx) */ params.WitnessChunkReadCost + params.WitnessChunkWriteCost + params.WitnessBranchReadCost + params.WitnessBranchWriteCost + /* creation (CREATE at pc=0x20) */ params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* write code hash */ diff --git a/core/state_processor.go b/core/state_processor.go index 0a324379f9..9cb7cc73bc 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -259,7 +259,7 @@ func ProcessBeaconBlockRoot(beaconRoot common.Hash, evm *vm.EVM) { } evm.SetTxContext(NewEVMTxContext(msg)) evm.StateDB.AddAddressToAccessList(params.BeaconRootsAddress) - _, _, _ = evm.Call(msg.From, *msg.To, msg.Data, 30_000_000, common.U2560) + _, _, _ = evm.Call(msg.From, *msg.To, msg.Data, vm.NewGasBudget(30_000_000), common.U2560) if evm.StateDB.AccessEvents() != nil { evm.StateDB.AccessEvents().Merge(evm.AccessEvents) } @@ -286,7 +286,7 @@ func ProcessParentBlockHash(prevHash common.Hash, evm *vm.EVM) { } evm.SetTxContext(NewEVMTxContext(msg)) evm.StateDB.AddAddressToAccessList(params.HistoryStorageAddress) - _, _, err := evm.Call(msg.From, *msg.To, msg.Data, 30_000_000, common.U2560) + _, _, err := evm.Call(msg.From, *msg.To, msg.Data, vm.NewGasBudget(30_000_000), common.U2560) if err != nil { panic(err) } @@ -325,7 +325,7 @@ func processRequestsSystemCall(requests *[][]byte, evm *vm.EVM, requestType byte } evm.SetTxContext(NewEVMTxContext(msg)) evm.StateDB.AddAddressToAccessList(addr) - ret, _, err := evm.Call(msg.From, *msg.To, msg.Data, 30_000_000, common.U2560) + ret, _, err := evm.Call(msg.From, *msg.To, msg.Data, vm.NewGasBudget(30_000_000), common.U2560) if evm.StateDB.AccessEvents() != nil { evm.StateDB.AccessEvents().Merge(evm.AccessEvents) } diff --git a/core/state_transition.go b/core/state_transition.go index bd7e5daeff..297e8580ee 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -68,7 +68,7 @@ func (result *ExecutionResult) Revert() []byte { } // IntrinsicGas computes the 'intrinsic gas' for a message with the given data. -func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.SetCodeAuthorization, isContractCreation, isHomestead, isEIP2028, isEIP3860 bool) (uint64, error) { +func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.SetCodeAuthorization, isContractCreation, isHomestead, isEIP2028, isEIP3860 bool) (vm.GasCosts, error) { // Set the starting gas for the raw transaction var gas uint64 if isContractCreation && isHomestead { @@ -89,19 +89,19 @@ func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.Set nonZeroGas = params.TxDataNonZeroGasEIP2028 } if (math.MaxUint64-gas)/nonZeroGas < nz { - return 0, ErrGasUintOverflow + return vm.GasCosts{}, ErrGasUintOverflow } gas += nz * nonZeroGas if (math.MaxUint64-gas)/params.TxDataZeroGas < z { - return 0, ErrGasUintOverflow + return vm.GasCosts{}, ErrGasUintOverflow } gas += z * params.TxDataZeroGas if isContractCreation && isEIP3860 { lenWords := toWordSize(dataLen) if (math.MaxUint64-gas)/params.InitCodeWordGas < lenWords { - return 0, ErrGasUintOverflow + return vm.GasCosts{}, ErrGasUintOverflow } gas += lenWords * params.InitCodeWordGas } @@ -113,7 +113,7 @@ func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.Set if authList != nil { gas += uint64(len(authList)) * params.CallNewAccountGas } - return gas, nil + return vm.GasCosts{RegularGas: gas}, nil } // FloorDataGas computes the minimum gas required for a transaction based on its data tokens (EIP-7623). @@ -242,12 +242,12 @@ func ApplyMessage(evm *vm.EVM, msg *Message, gp *GasPool) (*ExecutionResult, err // 5. Run Script section // 6. Derive new state root type stateTransition struct { - gp *GasPool - msg *Message - gasRemaining uint64 - initialGas uint64 - state vm.StateDB - evm *vm.EVM + gp *GasPool + msg *Message + initialBudget vm.GasBudget + gasRemaining vm.GasBudget + state vm.StateDB + evm *vm.EVM } // newStateTransition initialises and returns a new state transition object. @@ -304,8 +304,8 @@ func (st *stateTransition) buyGas() error { if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil { st.evm.Config.Tracer.OnGasChange(0, st.msg.GasLimit, tracing.GasChangeTxInitialBalance) } - st.gasRemaining = st.msg.GasLimit - st.initialGas = st.msg.GasLimit + st.gasRemaining = vm.NewGasBudget(st.msg.GasLimit) + st.initialBudget = st.gasRemaining.Copy() mgvalU256, _ := uint256.FromBig(mgval) st.state.SubBalance(st.msg.From, mgvalU256, tracing.BalanceDecreaseGasBuy) @@ -446,14 +446,17 @@ func (st *stateTransition) execute() (*ExecutionResult, error) { contractCreation = msg.To == nil floorDataGas uint64 ) - // Check clauses 4-5, subtract intrinsic gas if everything is correct - gas, err := IntrinsicGas(msg.Data, msg.AccessList, msg.SetCodeAuthorizations, contractCreation, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai) + cost, err := IntrinsicGas(msg.Data, msg.AccessList, msg.SetCodeAuthorizations, contractCreation, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai) if err != nil { return nil, err } - if st.gasRemaining < gas { - return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gasRemaining, gas) + prior, sufficient := st.gasRemaining.Charge(cost) + if !sufficient { + return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gasRemaining.RegularGas, cost.RegularGas) + } + if t := st.evm.Config.Tracer; t != nil && t.OnGasChange != nil { + t.OnGasChange(prior, st.gasRemaining.RegularGas, tracing.GasChangeTxIntrinsicGas) } // Gas limit suffices for the floor data cost (EIP-7623) if rules.IsPrague { @@ -465,10 +468,6 @@ func (st *stateTransition) execute() (*ExecutionResult, error) { return nil, fmt.Errorf("%w: have %d, want %d", ErrFloorDataGas, msg.GasLimit, floorDataGas) } } - if t := st.evm.Config.Tracer; t != nil && t.OnGasChange != nil { - t.OnGasChange(st.gasRemaining, st.gasRemaining-gas, tracing.GasChangeTxIntrinsicGas) - } - st.gasRemaining -= gas if rules.IsEIP4762 { st.evm.AccessEvents.AddTxOrigin(msg.From) @@ -535,14 +534,14 @@ func (st *stateTransition) execute() (*ExecutionResult, error) { peakGasUsed := st.gasUsed() // Compute refund counter, capped to a refund quotient. - st.gasRemaining += st.calcRefund() + st.gasRemaining.Refund(st.calcRefund()) + if rules.IsPrague { // After EIP-7623: Data-heavy transactions pay the floor gas. - if st.gasUsed() < floorDataGas { - prev := st.gasRemaining - st.gasRemaining = st.initialGas - floorDataGas + if used := st.gasUsed(); used < floorDataGas { + prior, _ := st.gasRemaining.Charge(vm.GasCosts{RegularGas: floorDataGas - used}) if t := st.evm.Config.Tracer; t != nil && t.OnGasChange != nil { - t.OnGasChange(prev, st.gasRemaining, tracing.GasChangeTxDataFloor) + t.OnGasChange(prior, st.gasRemaining.RegularGas, tracing.GasChangeTxDataFloor) } } if peakGasUsed < floorDataGas { @@ -555,10 +554,10 @@ func (st *stateTransition) execute() (*ExecutionResult, error) { // Return gas to the gas pool if rules.IsAmsterdam { // Refund is excluded for returning - err = st.gp.ReturnGas(st.initialGas-peakGasUsed, st.gasUsed()) + err = st.gp.ReturnGas(st.initialBudget.RegularGas-peakGasUsed, st.gasUsed()) } else { // Refund is included for returning - err = st.gp.ReturnGas(st.gasRemaining, st.gasUsed()) + err = st.gp.ReturnGas(st.gasRemaining.RegularGas, st.gasUsed()) } if err != nil { return nil, err @@ -655,7 +654,7 @@ func (st *stateTransition) applyAuthorization(auth *types.SetCodeAuthorization) } // calcRefund computes refund counter, capped to a refund quotient. -func (st *stateTransition) calcRefund() uint64 { +func (st *stateTransition) calcRefund() vm.GasBudget { var refund uint64 if !st.evm.ChainConfig().IsLondon(st.evm.Context.BlockNumber) { // Before EIP-3529: refunds were capped to gasUsed / 2 @@ -668,26 +667,26 @@ func (st *stateTransition) calcRefund() uint64 { refund = st.state.GetRefund() } if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil && refund > 0 { - st.evm.Config.Tracer.OnGasChange(st.gasRemaining, st.gasRemaining+refund, tracing.GasChangeTxRefunds) + st.evm.Config.Tracer.OnGasChange(st.gasRemaining.RegularGas, st.gasRemaining.RegularGas+refund, tracing.GasChangeTxRefunds) } - return refund + return vm.NewGasBudget(refund) } // returnGas returns ETH for remaining gas, // exchanged at the original rate. func (st *stateTransition) returnGas() { - remaining := uint256.NewInt(st.gasRemaining) + remaining := uint256.NewInt(st.gasRemaining.RegularGas) remaining.Mul(remaining, uint256.MustFromBig(st.msg.GasPrice)) st.state.AddBalance(st.msg.From, remaining, tracing.BalanceIncreaseGasReturn) - if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil && st.gasRemaining > 0 { - st.evm.Config.Tracer.OnGasChange(st.gasRemaining, 0, tracing.GasChangeTxLeftOverReturned) + if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil && st.gasRemaining.RegularGas > 0 { + st.evm.Config.Tracer.OnGasChange(st.gasRemaining.RegularGas, 0, tracing.GasChangeTxLeftOverReturned) } } // gasUsed returns the amount of gas used up by the state transition. func (st *stateTransition) gasUsed() uint64 { - return st.initialGas - st.gasRemaining + return st.gasRemaining.Used(st.initialBudget) } // blobGasUsed returns the amount of blob gas used by the message. diff --git a/core/tracing/hooks.go b/core/tracing/hooks.go index de63689bc5..62c62ac3b3 100644 --- a/core/tracing/hooks.go +++ b/core/tracing/hooks.go @@ -167,6 +167,8 @@ type ( // GasChangeHook is invoked when the gas changes. GasChangeHook = func(old, new uint64, reason GasChangeReason) + // TODO(sina, rjl), please add GasChangeV2Hook by landing the multi-dimensional gas + /* - Chain events - */ diff --git a/core/txpool/validation.go b/core/txpool/validation.go index 13b1bfa312..3569bba08d 100644 --- a/core/txpool/validation.go +++ b/core/txpool/validation.go @@ -129,8 +129,8 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types if err != nil { return err } - if tx.Gas() < intrGas { - return fmt.Errorf("%w: gas %v, minimum needed %v", core.ErrIntrinsicGas, tx.Gas(), intrGas) + if tx.Gas() < intrGas.RegularGas { + return fmt.Errorf("%w: gas %v, minimum needed %v", core.ErrIntrinsicGas, tx.Gas(), intrGas.RegularGas) } // Ensure the transaction can cover floor data gas. if rules.IsPrague { diff --git a/core/vm/contract.go b/core/vm/contract.go index 3b5695e21a..a55a5dde8b 100644 --- a/core/vm/contract.go +++ b/core/vm/contract.go @@ -42,12 +42,12 @@ type Contract struct { IsDeployment bool IsSystemCall bool - Gas GasCosts + Gas GasBudget value *uint256.Int } // NewContract returns a new contract environment for the execution of EVM. -func NewContract(caller common.Address, address common.Address, value *uint256.Int, gas uint64, jumpDests JumpDestCache) *Contract { +func NewContract(caller common.Address, address common.Address, value *uint256.Int, gas GasBudget, jumpDests JumpDestCache) *Contract { // Initialize the jump analysis cache if it's nil, mostly for tests if jumpDests == nil { jumpDests = newMapJumpDests() @@ -56,7 +56,7 @@ func NewContract(caller common.Address, address common.Address, value *uint256.I caller: caller, address: address, jumpDests: jumpDests, - Gas: GasCosts{RegularGas: gas}, + Gas: gas, value: value, } } @@ -126,26 +126,26 @@ 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.RegularGas < gas { +func (c *Contract) UseGas(cost GasCosts, logger *tracing.Hooks, reason tracing.GasChangeReason) (ok bool) { + prior, ok := c.Gas.Charge(cost) + if !ok { return false } if logger != nil && logger.OnGasChange != nil && reason != tracing.GasChangeIgnored { - logger.OnGasChange(c.Gas.RegularGas, c.Gas.RegularGas-gas, reason) + logger.OnGasChange(prior, c.Gas.RegularGas, reason) } - c.Gas.RegularGas -= gas return true } -// RefundGas refunds gas to the contract -func (c *Contract) RefundGas(gas uint64, logger *tracing.Hooks, reason tracing.GasChangeReason) { - if gas == 0 { +// RefundGas refunds the leftover gas budget back to the contract. +func (c *Contract) RefundGas(refund GasBudget, logger *tracing.Hooks, reason tracing.GasChangeReason) { + prior, changed := c.Gas.Refund(refund) + if !changed { return } if logger != nil && logger.OnGasChange != nil && reason != tracing.GasChangeIgnored { - logger.OnGasChange(c.Gas.RegularGas, c.Gas.RegularGas+gas, reason) + logger.OnGasChange(prior, c.Gas.RegularGas, reason) } - c.Gas.RegularGas += gas } // Address returns the contracts address diff --git a/core/vm/contracts.go b/core/vm/contracts.go index b4cddf0bca..b1eed79282 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -260,25 +260,25 @@ func ActivePrecompiles(rules params.Rules) []common.Address { // RunPrecompiledContract runs and evaluates the output of a precompiled contract. // It returns // - the returned bytes, -// - the _remaining_ gas, +// - the remaining gas budget, // - any error that occurred -func RunPrecompiledContract(stateDB StateDB, p PrecompiledContract, address common.Address, input []byte, suppliedGas uint64, logger *tracing.Hooks) (ret []byte, remainingGas uint64, err error) { +func RunPrecompiledContract(stateDB StateDB, p PrecompiledContract, address common.Address, input []byte, gas GasBudget, logger *tracing.Hooks) (ret []byte, remaining GasBudget, err error) { gasCost := p.RequiredGas(input) - if suppliedGas < gasCost { - return nil, 0, ErrOutOfGas + prior, ok := gas.Charge(GasCosts{RegularGas: gasCost}) + if !ok { + gas.Exhaust() + return nil, gas, ErrOutOfGas } if logger != nil && logger.OnGasChange != nil { - logger.OnGasChange(suppliedGas, suppliedGas-gasCost, tracing.GasChangeCallPrecompiledContract) + logger.OnGasChange(prior, gas.RegularGas, tracing.GasChangeCallPrecompiledContract) } - suppliedGas -= gasCost - // Touch the precompile for block-level accessList recording once Amsterdam // fork is activated. if stateDB != nil { stateDB.Exist(address) } output, err := p.Run(input) - return output, suppliedGas, err + return output, gas, err } // ecrecover implemented as a native contract. diff --git a/core/vm/contracts_fuzz_test.go b/core/vm/contracts_fuzz_test.go index 34ed1d87fe..35a9bd0257 100644 --- a/core/vm/contracts_fuzz_test.go +++ b/core/vm/contracts_fuzz_test.go @@ -36,7 +36,7 @@ func FuzzPrecompiledContracts(f *testing.F) { return } inWant := string(input) - RunPrecompiledContract(nil, p, a, input, gas, nil) + RunPrecompiledContract(nil, p, a, input, NewGasBudget(gas), nil) if inHave := string(input); inWant != inHave { t.Errorf("Precompiled %v modified input data", a) } diff --git a/core/vm/contracts_test.go b/core/vm/contracts_test.go index b647dcc62b..dda753f504 100644 --- a/core/vm/contracts_test.go +++ b/core/vm/contracts_test.go @@ -99,7 +99,7 @@ func testPrecompiled(addr string, test precompiledTest, t *testing.T) { in := common.Hex2Bytes(test.Input) gas := p.RequiredGas(in) t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) { - if res, _, err := RunPrecompiledContract(nil, p, common.HexToAddress(addr), in, gas, nil); err != nil { + if res, _, err := RunPrecompiledContract(nil, p, common.HexToAddress(addr), in, NewGasBudget(gas), nil); err != nil { t.Error(err) } else if common.Bytes2Hex(res) != test.Expected { t.Errorf("Expected %v, got %v", test.Expected, common.Bytes2Hex(res)) @@ -121,7 +121,7 @@ func testPrecompiledOOG(addr string, test precompiledTest, t *testing.T) { gas := test.Gas - 1 t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) { - _, _, err := RunPrecompiledContract(nil, p, common.HexToAddress(addr), in, gas, nil) + _, _, err := RunPrecompiledContract(nil, p, common.HexToAddress(addr), in, NewGasBudget(gas), nil) if err.Error() != "out of gas" { t.Errorf("Expected error [out of gas], got [%v]", err) } @@ -138,7 +138,7 @@ func testPrecompiledFailure(addr string, test precompiledFailureTest, t *testing in := common.Hex2Bytes(test.Input) gas := p.RequiredGas(in) t.Run(test.Name, func(t *testing.T) { - _, _, err := RunPrecompiledContract(nil, p, common.HexToAddress(addr), in, gas, nil) + _, _, err := RunPrecompiledContract(nil, p, common.HexToAddress(addr), in, NewGasBudget(gas), nil) if err.Error() != test.ExpectedError { t.Errorf("Expected error [%v], got [%v]", test.ExpectedError, err) } @@ -169,7 +169,7 @@ func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) { start := time.Now() for bench.Loop() { copy(data, in) - res, _, err = RunPrecompiledContract(nil, p, common.HexToAddress(addr), data, reqGas, nil) + res, _, err = RunPrecompiledContract(nil, p, common.HexToAddress(addr), data, NewGasBudget(reqGas), nil) } elapsed := uint64(time.Since(start)) if elapsed < 1 { diff --git a/core/vm/eips.go b/core/vm/eips.go index 8f4ca3ae41..54e5cb0c60 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -382,7 +382,7 @@ func opExtCodeCopyEIP4762(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, er 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.RegularGas) - scope.Contract.UseGas(consumed, evm.Config.Tracer, tracing.GasChangeUnspecified) + scope.Contract.UseGas(GasCosts{RegularGas: consumed}, evm.Config.Tracer, tracing.GasChangeUnspecified) if consumed < wanted { return nil, ErrOutOfGas } @@ -408,7 +408,7 @@ func opPush1EIP4762(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { // 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.RegularGas) - scope.Contract.UseGas(wanted, evm.Config.Tracer, tracing.GasChangeUnspecified) + scope.Contract.UseGas(GasCosts{RegularGas: wanted}, evm.Config.Tracer, tracing.GasChangeUnspecified) if consumed < wanted { return nil, ErrOutOfGas } @@ -436,7 +436,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.RegularGas) - scope.Contract.UseGas(consumed, evm.Config.Tracer, tracing.GasChangeUnspecified) + scope.Contract.UseGas(GasCosts{RegularGas: 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 8f68478842..ca8d8967ec 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -236,13 +236,13 @@ func isSystemCall(caller common.Address) bool { // parameters. It also handles any necessary value transfer required and takse // 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 uint64, value *uint256.Int) (ret []byte, leftOverGas uint64, err error) { +func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, gas GasBudget, value *uint256.Int) (ret []byte, leftOverGas GasBudget, err error) { // 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()) + evm.captureBegin(evm.depth, CALL, caller, addr, input, gas.RegularGas, value.ToBig()) defer func(startGas uint64) { - evm.captureEnd(evm.depth, startGas, leftOverGas, ret, err) - }(gas) + evm.captureEnd(evm.depth, startGas, leftOverGas.RegularGas, ret, err) + }(gas.RegularGas) } // Fail if we're trying to execute above the call depth limit if evm.depth > int(params.CallCreateDepth) { @@ -265,12 +265,12 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g // list in write mode. If there is enough gas paying for the addition of the code // hash leaf to the access list, then account creation will proceed unimpaired. // Thus, only pay for the creation of the code hash leaf here. - wgas := evm.AccessEvents.CodeHashGas(addr, true, gas, false) - if gas < wgas { + wgas := evm.AccessEvents.CodeHashGas(addr, true, gas.RegularGas, false) + if _, ok := gas.Charge(GasCosts{RegularGas: wgas}); !ok { evm.StateDB.RevertToSnapshot(snapshot) - return nil, 0, ErrOutOfGas + gas.Exhaust() + return nil, gas, ErrOutOfGas } - gas -= wgas } if !isPrecompile && evm.chainRules.IsEIP158 && value.IsZero() { @@ -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.RegularGas + gas = contract.Gas } } // When an error was returned by the EVM or when setting the creation code @@ -313,9 +313,9 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g evm.StateDB.RevertToSnapshot(snapshot) if err != ErrExecutionReverted { if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil { - evm.Config.Tracer.OnGasChange(gas, 0, tracing.GasChangeCallFailedExecution) + evm.Config.Tracer.OnGasChange(gas.RegularGas, 0, tracing.GasChangeCallFailedExecution) } - gas = 0 + gas.Exhaust() } // TODO: consider clearing up unused snapshots: //} else { @@ -331,13 +331,13 @@ 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 uint64, value *uint256.Int) (ret []byte, leftOverGas uint64, err error) { +func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byte, gas GasBudget, value *uint256.Int) (ret []byte, leftOverGas GasBudget, err error) { // 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()) + evm.captureBegin(evm.depth, CALLCODE, caller, addr, input, gas.RegularGas, value.ToBig()) defer func(startGas uint64) { - evm.captureEnd(evm.depth, startGas, leftOverGas, ret, err) - }(gas) + evm.captureEnd(evm.depth, startGas, leftOverGas.RegularGas, ret, err) + }(gas.RegularGas) } // Fail if we're trying to execute above the call depth limit if evm.depth > int(params.CallCreateDepth) { @@ -365,15 +365,15 @@ 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.RegularGas + gas = contract.Gas } if err != nil { evm.StateDB.RevertToSnapshot(snapshot) if err != ErrExecutionReverted { if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil { - evm.Config.Tracer.OnGasChange(gas, 0, tracing.GasChangeCallFailedExecution) + evm.Config.Tracer.OnGasChange(gas.RegularGas, 0, tracing.GasChangeCallFailedExecution) } - gas = 0 + gas.Exhaust() } } return ret, gas, err @@ -384,14 +384,14 @@ 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 uint64, value *uint256.Int) (ret []byte, leftOverGas uint64, err error) { +func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address, addr common.Address, input []byte, gas GasBudget, value *uint256.Int) (ret []byte, leftOverGas GasBudget, err error) { // Invoke tracer hooks that signal entering/exiting a call frame if evm.Config.Tracer != nil { // DELEGATECALL inherits value from parent call - evm.captureBegin(evm.depth, DELEGATECALL, caller, addr, input, gas, value.ToBig()) + evm.captureBegin(evm.depth, DELEGATECALL, caller, addr, input, gas.RegularGas, value.ToBig()) defer func(startGas uint64) { - evm.captureEnd(evm.depth, startGas, leftOverGas, ret, err) - }(gas) + evm.captureEnd(evm.depth, startGas, leftOverGas.RegularGas, ret, err) + }(gas.RegularGas) } // Fail if we're trying to execute above the call depth limit if evm.depth > int(params.CallCreateDepth) { @@ -413,15 +413,15 @@ 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.RegularGas + gas = contract.Gas } if err != nil { evm.StateDB.RevertToSnapshot(snapshot) if err != ErrExecutionReverted { if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil { - evm.Config.Tracer.OnGasChange(gas, 0, tracing.GasChangeCallFailedExecution) + evm.Config.Tracer.OnGasChange(gas.RegularGas, 0, tracing.GasChangeCallFailedExecution) } - gas = 0 + gas.Exhaust() } } return ret, gas, err @@ -431,13 +431,13 @@ func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address, // as parameters while disallowing any modifications to the state during the call. // 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 uint64) (ret []byte, leftOverGas uint64, err error) { +func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []byte, gas GasBudget) (ret []byte, leftOverGas GasBudget, err error) { // 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) + evm.captureBegin(evm.depth, STATICCALL, caller, addr, input, gas.RegularGas, nil) defer func(startGas uint64) { - evm.captureEnd(evm.depth, startGas, leftOverGas, ret, err) - }(gas) + evm.captureEnd(evm.depth, startGas, leftOverGas.RegularGas, ret, err) + }(gas.RegularGas) } // Fail if we're trying to execute above the call depth limit if evm.depth > int(params.CallCreateDepth) { @@ -472,28 +472,27 @@ 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.RegularGas + gas = contract.Gas } if err != nil { evm.StateDB.RevertToSnapshot(snapshot) if err != ErrExecutionReverted { if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil { - evm.Config.Tracer.OnGasChange(gas, 0, tracing.GasChangeCallFailedExecution) + evm.Config.Tracer.OnGasChange(gas.RegularGas, 0, tracing.GasChangeCallFailedExecution) } - - gas = 0 + gas.Exhaust() } } return ret, gas, err } // create creates a new contract using code as deployment code. -func (evm *EVM) create(caller common.Address, code []byte, gas uint64, value *uint256.Int, address common.Address, typ OpCode) (ret []byte, createAddress common.Address, leftOverGas uint64, err error) { +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, err error) { if evm.Config.Tracer != nil { - evm.captureBegin(evm.depth, typ, caller, address, code, gas, value.ToBig()) + evm.captureBegin(evm.depth, typ, caller, address, code, gas.RegularGas, value.ToBig()) defer func(startGas uint64) { - evm.captureEnd(evm.depth, startGas, leftOverGas, ret, err) - }(gas) + evm.captureEnd(evm.depth, startGas, leftOverGas.RegularGas, ret, err) + }(gas.RegularGas) } // Depth check execution. Fail if we're trying to execute above the // limit. @@ -511,14 +510,15 @@ func (evm *EVM) create(caller common.Address, code []byte, gas uint64, value *ui // Charge the contract creation init gas in verkle mode if evm.chainRules.IsEIP4762 { - statelessGas := evm.AccessEvents.ContractCreatePreCheckGas(address, gas) - if statelessGas > gas { - return nil, common.Address{}, 0, ErrOutOfGas + statelessGas := evm.AccessEvents.ContractCreatePreCheckGas(address, gas.RegularGas) + prior, ok := gas.Charge(GasCosts{RegularGas: statelessGas}) + if !ok { + gas.Exhaust() + return nil, common.Address{}, gas, ErrOutOfGas } if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil { - evm.Config.Tracer.OnGasChange(gas, gas-statelessGas, tracing.GasChangeWitnessContractCollisionCheck) + evm.Config.Tracer.OnGasChange(prior, gas.RegularGas, tracing.GasChangeWitnessContractCollisionCheck) } - gas = gas - statelessGas } // We add this to the access list _before_ taking a snapshot. Even if the @@ -536,9 +536,10 @@ func (evm *EVM) create(caller common.Address, code []byte, gas uint64, value *ui (contractHash != (common.Hash{}) && contractHash != types.EmptyCodeHash) || // non-empty code isEIP7610RejectedAccount(evm.ChainConfig().ChainID, address, evm.chainRules.IsEIP158) { if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil { - evm.Config.Tracer.OnGasChange(gas, 0, tracing.GasChangeCallFailedExecution) + evm.Config.Tracer.OnGasChange(gas.RegularGas, 0, tracing.GasChangeCallFailedExecution) } - return nil, common.Address{}, 0, ErrContractAddressCollision + gas.Exhaust() + return nil, common.Address{}, gas, ErrContractAddressCollision } // Create a new account on the state only if the object was not present. // It might be possible the contract code is deployed to a pre-existent @@ -558,14 +559,15 @@ func (evm *EVM) create(caller common.Address, code []byte, gas uint64, value *ui } // Charge the contract creation init gas in verkle mode if evm.chainRules.IsEIP4762 { - consumed, wanted := evm.AccessEvents.ContractCreateInitGas(address, gas) + consumed, wanted := evm.AccessEvents.ContractCreateInitGas(address, gas.RegularGas) if consumed < wanted { - return nil, common.Address{}, 0, ErrOutOfGas + gas.Exhaust() + return nil, common.Address{}, gas, ErrOutOfGas } + prior, _ := gas.Charge(GasCosts{RegularGas: consumed}) if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil { - evm.Config.Tracer.OnGasChange(gas, gas-consumed, tracing.GasChangeWitnessContractInit) + evm.Config.Tracer.OnGasChange(prior, gas.RegularGas, tracing.GasChangeWitnessContractInit) } - gas = gas - consumed } evm.Context.Transfer(evm.StateDB, caller, address, value, &evm.chainRules) @@ -582,10 +584,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.RegularGas, evm.Config.Tracer, tracing.GasChangeCallFailedExecution) + contract.UseGas(GasCosts{RegularGas: contract.Gas.RegularGas}, evm.Config.Tracer, tracing.GasChangeCallFailedExecution) } } - return ret, address, contract.Gas.RegularGas, err + return ret, address, contract.Gas, err } // initNewContract runs a new contract's creation code, performs checks on the @@ -608,12 +610,12 @@ func (evm *EVM) initNewContract(contract *Contract, address common.Address) ([]b if !evm.chainRules.IsEIP4762 { createDataGas := uint64(len(ret)) * params.CreateDataGas - if !contract.UseGas(createDataGas, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) { + if !contract.UseGas(GasCosts{RegularGas: createDataGas}, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) { return ret, ErrCodeStoreOutOfGas } } else { 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) + contract.UseGas(GasCosts{RegularGas: consumed}, evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk) if len(ret) > 0 && (consumed < wanted) { return ret, ErrCodeStoreOutOfGas } @@ -626,7 +628,7 @@ func (evm *EVM) initNewContract(contract *Contract, address common.Address) ([]b } // Create creates a new contract using code as deployment code. -func (evm *EVM) Create(caller common.Address, code []byte, gas uint64, value *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) { +func (evm *EVM) Create(caller common.Address, code []byte, gas GasBudget, value *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas GasBudget, err error) { contractAddr = crypto.CreateAddress(caller, evm.StateDB.GetNonce(caller)) return evm.create(caller, code, gas, value, contractAddr, CREATE) } @@ -635,7 +637,7 @@ func (evm *EVM) Create(caller common.Address, code []byte, gas uint64, value *ui // // The different between Create2 with Create is Create2 uses keccak256(0xff ++ msg.sender ++ salt ++ keccak256(init_code))[12:] // instead of the usual sender-and-nonce-hash as the address where the contract is initialized at. -func (evm *EVM) Create2(caller common.Address, code []byte, gas uint64, endowment *uint256.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) { +func (evm *EVM) Create2(caller common.Address, code []byte, gas GasBudget, endowment *uint256.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas GasBudget, err error) { inithash := crypto.Keccak256Hash(code) contractAddr = crypto.CreateAddress2(caller, salt.Bytes32(), inithash[:]) return evm.create(caller, code, gas, endowment, contractAddr, CREATE2) diff --git a/core/vm/gas_table_test.go b/core/vm/gas_table_test.go index e9e56038dd..16ce651a7d 100644 --- a/core/vm/gas_table_test.go +++ b/core/vm/gas_table_test.go @@ -97,12 +97,12 @@ func TestEIP2200(t *testing.T) { Transfer: func(StateDB, common.Address, common.Address, *uint256.Int, *params.Rules) {}, } evm := NewEVM(vmctx, statedb, params.AllEthashProtocolChanges, Config{ExtraEips: []int{2200}}) - - _, gas, err := evm.Call(common.Address{}, address, nil, tt.gaspool, new(uint256.Int)) + initialGas := NewGasBudget(tt.gaspool) + _, leftOver, err := evm.Call(common.Address{}, address, nil, initialGas.Copy(), new(uint256.Int)) if !errors.Is(err, tt.failure) { t.Errorf("test %d: failure mismatch: have %v, want %v", i, err, tt.failure) } - if used := tt.gaspool - gas; used != tt.used { + if used := leftOver.Used(initialGas); used != tt.used { t.Errorf("test %d: gas used mismatch: have %v, want %v", i, used, tt.used) } if refund := evm.StateDB.GetRefund(); refund != tt.refund { @@ -157,12 +157,12 @@ func TestCreateGas(t *testing.T) { } evm := NewEVM(vmctx, statedb, chainConfig, config) - var startGas = uint64(testGas) - ret, gas, err := evm.Call(common.Address{}, address, nil, startGas, new(uint256.Int)) + initialGas := NewGasBudget(uint64(testGas)) + ret, leftOver, err := evm.Call(common.Address{}, address, nil, initialGas.Copy(), new(uint256.Int)) if err != nil { return false } - gasUsed = startGas - gas + gasUsed = leftOver.Used(initialGas) if len(ret) != 32 { t.Fatalf("test %d: expected 32 bytes returned, have %d", i, len(ret)) } diff --git a/core/vm/gascosts.go b/core/vm/gascosts.go index ba6746758b..cc90c54798 100644 --- a/core/vm/gascosts.go +++ b/core/vm/gascosts.go @@ -19,7 +19,8 @@ package vm import "fmt" // GasCosts denotes a vector of gas costs in the -// multidimensional metering paradigm. +// multidimensional metering paradigm. It represents the cost +// charged by an individual operation. type GasCosts struct { RegularGas uint64 StateGas uint64 @@ -34,3 +35,63 @@ func (g GasCosts) Sum() uint64 { func (g GasCosts) String() string { return fmt.Sprintf("<%v,%v>", g.RegularGas, g.StateGas) } + +// GasBudget denotes a vector of remaining gas allowances available +// for EVM execution in the multidimensional metering paradigm. +// Unlike GasCosts which represents the price of an operation, +// GasBudget tracks how much gas is left to spend. +type GasBudget struct { + RegularGas uint64 // The leftover gas for execution and state gas usage + StateGas uint64 // The state gas reservoir +} + +// NewGasBudget creates a GasBudget with the given initial regular gas allowance. +func NewGasBudget(gas uint64) GasBudget { + return GasBudget{RegularGas: gas} +} + +// Used returns the amount of regular gas consumed so far. +func (g GasBudget) Used(initial GasBudget) uint64 { + return initial.RegularGas - g.RegularGas +} + +// Exhaust sets all remaining gas to zero, preserving the initial amount +// for usage tracking. +func (g *GasBudget) Exhaust() { + g.RegularGas = 0 + g.StateGas = 0 +} + +func (g *GasBudget) Copy() GasBudget { + return GasBudget{RegularGas: g.RegularGas, StateGas: g.StateGas} +} + +// String returns a visual representation of the gas budget vector. +func (g GasBudget) String() string { + return fmt.Sprintf("<%v,%v>", g.RegularGas, g.StateGas) +} + +// CanAfford reports whether the budget has sufficient gas to cover the cost. +func (g GasBudget) CanAfford(cost GasCosts) bool { + return g.RegularGas >= cost.RegularGas +} + +// Charge deducts the given gas cost from the budget. It returns the +// pre-charge gas value and false if the budget does not have sufficient +// gas to cover the cost. +func (g *GasBudget) Charge(cost GasCosts) (uint64, bool) { + prior := g.RegularGas + if prior < cost.RegularGas { + return prior, false + } + g.RegularGas -= cost.RegularGas + return prior, true +} + +// Refund adds the given gas budget back. It returns the pre-refund gas +// value and whether the budget was actually changed. +func (g *GasBudget) Refund(other GasBudget) (uint64, bool) { + prior := g.RegularGas + g.RegularGas += other.RegularGas + return prior, g.RegularGas != prior +} diff --git a/core/vm/instructions.go b/core/vm/instructions.go index c3d1633c78..3311af0d22 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -667,9 +667,9 @@ func opCreate(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { // reuse size int for stackvalue stackvalue := size - scope.Contract.UseGas(gas, evm.Config.Tracer, tracing.GasChangeCallContractCreation) + scope.Contract.UseGas(GasCosts{RegularGas: gas}, evm.Config.Tracer, tracing.GasChangeCallContractCreation) - res, addr, returnGas, suberr := evm.Create(scope.Contract.Address(), input, gas, &value) + res, addr, returnGas, suberr := evm.Create(scope.Contract.Address(), input, NewGasBudget(gas), &value) // Push item on the stack based on the returned error. If the ruleset is // homestead we must check for CodeStoreOutOfGasError (homestead only // rule) and treat as an error, if the ruleset is frontier we must @@ -707,10 +707,10 @@ func opCreate2(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { // Apply EIP150 gas -= gas / 64 - scope.Contract.UseGas(gas, evm.Config.Tracer, tracing.GasChangeCallContractCreation2) + scope.Contract.UseGas(GasCosts{RegularGas: gas}, evm.Config.Tracer, tracing.GasChangeCallContractCreation2) // reuse size int for stackvalue stackvalue := size - res, addr, returnGas, suberr := evm.Create2(scope.Contract.Address(), input, gas, + res, addr, returnGas, suberr := evm.Create2(scope.Contract.Address(), input, NewGasBudget(gas), &endowment, &salt) // Push item on the stack based on the returned error. if suberr != nil { @@ -747,7 +747,7 @@ func opCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { if !value.IsZero() { gas += params.CallStipend } - ret, returnGas, err := evm.Call(scope.Contract.Address(), toAddr, args, gas, &value) + ret, returnGas, err := evm.Call(scope.Contract.Address(), toAddr, args, NewGasBudget(gas), &value) if err != nil { temp.Clear() @@ -781,7 +781,7 @@ func opCallCode(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { gas += params.CallStipend } - ret, returnGas, err := evm.CallCode(scope.Contract.Address(), toAddr, args, gas, &value) + ret, returnGas, err := evm.CallCode(scope.Contract.Address(), toAddr, args, NewGasBudget(gas), &value) if err != nil { temp.Clear() } else { @@ -810,7 +810,7 @@ func opDelegateCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { // Get arguments from the memory. args := scope.Memory.GetPtr(inOffset.Uint64(), inSize.Uint64()) - ret, returnGas, err := evm.DelegateCall(scope.Contract.Caller(), scope.Contract.Address(), toAddr, args, gas, scope.Contract.value) + ret, returnGas, err := evm.DelegateCall(scope.Contract.Caller(), scope.Contract.Address(), toAddr, args, NewGasBudget(gas), scope.Contract.value) if err != nil { temp.Clear() } else { @@ -839,7 +839,7 @@ func opStaticCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { // Get arguments from the memory. args := scope.Memory.GetPtr(inOffset.Uint64(), inSize.Uint64()) - ret, returnGas, err := evm.StaticCall(scope.Contract.Address(), toAddr, args, gas) + ret, returnGas, err := evm.StaticCall(scope.Contract.Address(), toAddr, args, NewGasBudget(gas)) if err != nil { temp.Clear() } else { diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index 5055ccb16d..dbe055f2ac 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -565,7 +565,7 @@ func TestOpTstore(t *testing.T) { mem = NewMemory() caller = common.Address{} to = common.Address{1} - contract = NewContract(caller, to, new(uint256.Int), 0, nil) + contract = NewContract(caller, to, new(uint256.Int), GasBudget{}, nil) scopeContext = ScopeContext{mem, stack, contract} value = common.Hex2Bytes("abcdef00000000000000abba000000000deaf000000c0de00100000000133700") ) diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index b507595fab..9dc0a0b787 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -174,7 +174,7 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte // associated costs. contractAddr := contract.Address() 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) + contract.UseGas(GasCosts{RegularGas: consumed}, evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk) if consumed < wanted { return nil, ErrOutOfGas } diff --git a/core/vm/interpreter_test.go b/core/vm/interpreter_test.go index 28df8546b5..69c2316907 100644 --- a/core/vm/interpreter_test.go +++ b/core/vm/interpreter_test.go @@ -55,7 +55,7 @@ func TestLoopInterrupt(t *testing.T) { timeout := make(chan bool) go func(evm *EVM) { - _, _, err := evm.Call(common.Address{}, address, nil, math.MaxUint64, new(uint256.Int)) + _, _, err := evm.Call(common.Address{}, address, nil, NewGasBudget(math.MaxUint64), new(uint256.Int)) errChannel <- err }(evm) @@ -85,7 +85,7 @@ func BenchmarkInterpreter(b *testing.B) { value = uint256.NewInt(0) stack = newstack() mem = NewMemory() - contract = NewContract(common.Address{}, common.Address{}, value, startGas, nil) + contract = NewContract(common.Address{}, common.Address{}, value, NewGasBudget(startGas), nil) ) stack.push(uint256.NewInt(123)) stack.push(uint256.NewInt(123)) diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go index 154c261cae..313d03819e 100644 --- a/core/vm/operations_acl.go +++ b/core/vm/operations_acl.go @@ -168,7 +168,7 @@ func makeCallVariantGasCallEIP2929(oldCalculator gasFunc, addressPosition int) g evm.StateDB.AddAddressToAccessList(addr) // Charge the remaining difference here already, to correctly calculate available // gas for call - if !contract.UseGas(coldCost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) { + if !contract.UseGas(GasCosts{RegularGas: coldCost}, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) { return GasCosts{}, ErrOutOfGas } } @@ -295,7 +295,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) { + if !contract.UseGas(GasCosts{RegularGas: eip2929Cost}, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) { return GasCosts{}, ErrOutOfGas } } @@ -324,7 +324,7 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc gasFunc) gasFunc { evm.StateDB.AddAddressToAccessList(target) eip7702Cost = params.ColdAccountAccessCostEIP2929 } - if !contract.UseGas(eip7702Cost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) { + if !contract.UseGas(GasCosts{RegularGas: eip7702Cost}, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) { return GasCosts{}, ErrOutOfGas } } diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go index af394aa054..4fafdf3a50 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -148,11 +148,11 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) { cfg.Origin, common.BytesToAddress([]byte("contract")), input, - cfg.GasLimit, + vm.NewGasBudget(cfg.GasLimit), uint256.MustFromBig(cfg.Value), ) if cfg.EVMConfig.Tracer != nil && cfg.EVMConfig.Tracer.OnTxEnd != nil { - cfg.EVMConfig.Tracer.OnTxEnd(&types.Receipt{GasUsed: cfg.GasLimit - leftOverGas}, err) + cfg.EVMConfig.Tracer.OnTxEnd(&types.Receipt{GasUsed: cfg.GasLimit - leftOverGas.RegularGas}, err) } return ret, cfg.State, err } @@ -182,13 +182,13 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) { code, address, leftOverGas, err := vmenv.Create( cfg.Origin, input, - cfg.GasLimit, + vm.NewGasBudget(cfg.GasLimit), uint256.MustFromBig(cfg.Value), ) if cfg.EVMConfig.Tracer != nil && cfg.EVMConfig.Tracer.OnTxEnd != nil { - cfg.EVMConfig.Tracer.OnTxEnd(&types.Receipt{GasUsed: cfg.GasLimit - leftOverGas}, err) + cfg.EVMConfig.Tracer.OnTxEnd(&types.Receipt{GasUsed: cfg.GasLimit - leftOverGas.RegularGas}, err) } - return code, address, leftOverGas, err + return code, address, leftOverGas.RegularGas, err } // Call executes the code given by the contract's address. It will return the @@ -217,11 +217,11 @@ func Call(address common.Address, input []byte, cfg *Config) ([]byte, uint64, er cfg.Origin, address, input, - cfg.GasLimit, + vm.NewGasBudget(cfg.GasLimit), uint256.MustFromBig(cfg.Value), ) if cfg.EVMConfig.Tracer != nil && cfg.EVMConfig.Tracer.OnTxEnd != nil { - cfg.EVMConfig.Tracer.OnTxEnd(&types.Receipt{GasUsed: cfg.GasLimit - leftOverGas}, err) + cfg.EVMConfig.Tracer.OnTxEnd(&types.Receipt{GasUsed: cfg.GasLimit - leftOverGas.RegularGas}, err) } - return ret, leftOverGas, err + return ret, leftOverGas.RegularGas, err } diff --git a/eth/tracers/js/tracer_test.go b/eth/tracers/js/tracer_test.go index 694debcf98..6570d73575 100644 --- a/eth/tracers/js/tracer_test.go +++ b/eth/tracers/js/tracer_test.go @@ -55,7 +55,7 @@ func runTrace(tracer *tracers.Tracer, vmctx *vmContext, chaincfg *params.ChainCo gasLimit uint64 = 31000 startGas uint64 = 10000 value = uint256.NewInt(0) - contract = vm.NewContract(common.Address{}, common.Address{}, value, startGas, nil) + contract = vm.NewContract(common.Address{}, common.Address{}, value, vm.NewGasBudget(startGas), nil) ) evm.SetTxContext(vmctx.txCtx) contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x1, 0x0} @@ -183,7 +183,7 @@ func TestHaltBetweenSteps(t *testing.T) { t.Fatal(err) } scope := &vm.ScopeContext{ - Contract: vm.NewContract(common.Address{}, common.Address{}, uint256.NewInt(0), 0, nil), + Contract: vm.NewContract(common.Address{}, common.Address{}, uint256.NewInt(0), vm.GasBudget{}, nil), } evm := vm.NewEVM(vm.BlockContext{BlockNumber: big.NewInt(1)}, &dummyStatedb{}, chainConfig, vm.Config{Tracer: tracer.Hooks}) evm.SetTxContext(vm.TxContext{GasPrice: uint256.NewInt(1)}) @@ -281,7 +281,7 @@ func TestEnterExit(t *testing.T) { t.Fatal(err) } scope := &vm.ScopeContext{ - Contract: vm.NewContract(common.Address{}, common.Address{}, uint256.NewInt(0), 0, nil), + Contract: vm.NewContract(common.Address{}, common.Address{}, uint256.NewInt(0), vm.GasBudget{}, nil), } tracer.OnEnter(1, byte(vm.CALL), scope.Contract.Caller(), scope.Contract.Address(), []byte{}, 1000, new(big.Int)) tracer.OnExit(1, []byte{}, 400, nil, false) diff --git a/eth/tracers/logger/logger_test.go b/eth/tracers/logger/logger_test.go index 554a37aff1..decdf588e1 100644 --- a/eth/tracers/logger/logger_test.go +++ b/eth/tracers/logger/logger_test.go @@ -47,7 +47,7 @@ func TestStoreCapture(t *testing.T) { var ( logger = NewStructLogger(nil) evm = vm.NewEVM(vm.BlockContext{}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Tracer: logger.Hooks()}) - contract = vm.NewContract(common.Address{}, common.Address{}, new(uint256.Int), 100000, nil) + contract = vm.NewContract(common.Address{}, common.Address{}, new(uint256.Int), vm.NewGasBudget(100000), nil) ) contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x0, byte(vm.SSTORE)} var index common.Hash diff --git a/tests/state_test.go b/tests/state_test.go index 25e90d388d..8444d211cf 100644 --- a/tests/state_test.go +++ b/tests/state_test.go @@ -312,7 +312,7 @@ func runBenchmark(b *testing.B, t *StateTest) { evm.SetTxContext(txContext) // Create "contract" for sender to cache code analysis. - sender := vm.NewContract(msg.From, msg.From, nil, 0, nil) + sender := vm.NewContract(msg.From, msg.From, nil, vm.GasBudget{}, nil) var ( gasUsed uint64 @@ -326,8 +326,10 @@ func runBenchmark(b *testing.B, t *StateTest) { b.StartTimer() start := time.Now() + initialGas := vm.NewGasBudget(msg.GasLimit) + // Execute the message. - _, leftOverGas, err := evm.Call(sender.Address(), *msg.To, msg.Data, msg.GasLimit, uint256.MustFromBig(msg.Value)) + _, leftOverGas, err := evm.Call(sender.Address(), *msg.To, msg.Data, initialGas.Copy(), uint256.MustFromBig(msg.Value)) if err != nil { b.Error(err) return @@ -336,7 +338,7 @@ func runBenchmark(b *testing.B, t *StateTest) { b.StopTimer() elapsed += uint64(time.Since(start)) refund += state.StateDB.GetRefund() - gasUsed += msg.GasLimit - leftOverGas + gasUsed += leftOverGas.Used(initialGas) state.StateDB.RevertToSnapshot(snapshot) } diff --git a/tests/transaction_test_util.go b/tests/transaction_test_util.go index a90c2d522f..a659a1f786 100644 --- a/tests/transaction_test_util.go +++ b/tests/transaction_test_util.go @@ -81,10 +81,11 @@ func (tt *TransactionTest) Run() error { return } // Intrinsic gas - requiredGas, err = core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai) + cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai) if err != nil { return } + requiredGas = cost.RegularGas if requiredGas > tx.Gas() { return sender, hash, 0, fmt.Errorf("insufficient gas ( %d < %d )", tx.Gas(), requiredGas) }