core/vm, eth, tests: introduce gas budget (#34712)
Some checks are pending
/ Linux Build (arm) (push) Waiting to run
/ Keeper Build (push) Waiting to run
/ Windows Build (push) Waiting to run
/ Docker Image (push) Waiting to run
/ Linux Build (push) Waiting to run

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 <m.vanderwijden@live.de>
This commit is contained in:
rjl493456442 2026-04-20 15:33:29 +08:00 committed by GitHub
parent 5af5510b1e
commit 29e0a6f404
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 235 additions and 168 deletions

View file

@ -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
}

View file

@ -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,
})

View file

@ -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 */

View file

@ -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)
}

View file

@ -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.

View file

@ -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 -
*/

View file

@ -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 {

View file

@ -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

View file

@ -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.

View file

@ -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)
}

View file

@ -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 {

View file

@ -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
}

View file

@ -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)

View file

@ -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))
}

View file

@ -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
}

View file

@ -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 {

View file

@ -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")
)

View file

@ -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
}

View file

@ -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))

View file

@ -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
}
}

View file

@ -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
}

View file

@ -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)

View file

@ -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

View file

@ -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)
}

View file

@ -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)
}