From 0494cdce23dcbad953f488a62b15eb024cdc7e16 Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Wed, 13 May 2026 16:53:47 +0800 Subject: [PATCH] core: introduce GasChangeHook v2 (#34946) This PR introduces OnGasChangeV2 tracing hook, as the pre-requisite for landing EIP-8037. --------- Co-authored-by: Sina M <1591639+s1na@users.noreply.github.com> --- core/state_transition.go | 27 +++++++----- core/tracing/hooks.go | 87 ++++++++++++++++++++++++++++++++++---- core/vm/contract.go | 8 ++-- core/vm/contracts.go | 4 +- core/vm/evm.go | 38 +++++++++-------- core/vm/gascosts.go | 29 ++++++++----- core/vm/interpreter.go | 8 +++- eth/tracers/live/noop.go | 4 ++ eth/tracers/native/mux.go | 19 +++++---- eth/tracers/native/noop.go | 3 ++ 10 files changed, 164 insertions(+), 63 deletions(-) diff --git a/core/state_transition.go b/core/state_transition.go index fcd483eeb7..0a6994505d 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -420,8 +420,10 @@ func (st *stateTransition) buyGas() error { return err } - if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil { - st.evm.Config.Tracer.OnGasChange(0, st.msg.GasLimit, tracing.GasChangeTxInitialBalance) + if st.evm.Config.Tracer.HasGasHook() { + empty := vm.GasBudget{} + initial := vm.NewGasBudget(st.msg.GasLimit) + st.evm.Config.Tracer.EmitGasChange(empty.AsTracing(), initial.AsTracing(), tracing.GasChangeTxInitialBalance) } st.gasRemaining = vm.NewGasBudget(st.msg.GasLimit) st.initialBudget = st.gasRemaining.Copy() @@ -566,8 +568,8 @@ func (st *stateTransition) execute() (*ExecutionResult, error) { 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) + if st.evm.Config.Tracer.HasGasHook() { + st.evm.Config.Tracer.EmitGasChange(prior.AsTracing(), st.gasRemaining.AsTracing(), tracing.GasChangeTxIntrinsicGas) } // Gas limit suffices for the floor data cost (EIP-7623) if rules.IsPrague { @@ -651,8 +653,8 @@ func (st *stateTransition) execute() (*ExecutionResult, error) { // After EIP-7623: Data-heavy transactions pay the floor gas. 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(prior, st.gasRemaining.RegularGas, tracing.GasChangeTxDataFloor) + if st.evm.Config.Tracer.HasGasHook() { + st.evm.Config.Tracer.EmitGasChange(prior.AsTracing(), st.gasRemaining.AsTracing(), tracing.GasChangeTxDataFloor) } } if peakGasUsed < floorDataGas { @@ -780,8 +782,11 @@ func (st *stateTransition) calcRefund() vm.GasBudget { if refund > st.state.GetRefund() { 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.RegularGas, st.gasRemaining.RegularGas+refund, tracing.GasChangeTxRefunds) + if refund > 0 && st.evm.Config.Tracer.HasGasHook() { + after := st.gasRemaining + after.RegularGas += refund + + st.evm.Config.Tracer.EmitGasChange(st.gasRemaining.AsTracing(), after.AsTracing(), tracing.GasChangeTxRefunds) } return vm.NewGasBudget(refund) } @@ -793,8 +798,10 @@ func (st *stateTransition) returnGas() { remaining.Mul(remaining, 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.RegularGas > 0 { - st.evm.Config.Tracer.OnGasChange(st.gasRemaining.RegularGas, 0, tracing.GasChangeTxLeftOverReturned) + if st.gasRemaining.RegularGas > 0 && st.evm.Config.Tracer.HasGasHook() { + after := st.gasRemaining + after.RegularGas = 0 + st.evm.Config.Tracer.EmitGasChange(st.gasRemaining.AsTracing(), after.AsTracing(), tracing.GasChangeTxLeftOverReturned) } } diff --git a/core/tracing/hooks.go b/core/tracing/hooks.go index 62c62ac3b3..6ea3f7ebbf 100644 --- a/core/tracing/hooks.go +++ b/core/tracing/hooks.go @@ -164,10 +164,36 @@ type ( // FaultHook is invoked when an error occurs during the execution of an opcode. FaultHook = func(pc uint64, op byte, gas, cost uint64, scope OpContext, depth int, err error) - // GasChangeHook is invoked when the gas changes. + // GasChangeHook reports changes to the regular execution gas. Tracers + // that don't need visibility into the state-access gas dimension + // introduced by EIP-8037 (Amsterdam) can implement only this hook; it + // will continue to fire across the Amsterdam fork unchanged. + // + // If both this hook and GasChangeHookV2 are implemented on the same + // tracer, only V2 will be invoked. Implement exactly one to avoid + // double-counting. GasChangeHook = func(old, new uint64, reason GasChangeReason) - // TODO(sina, rjl), please add GasChangeV2Hook by landing the multi-dimensional gas + // GasChangeHookV2 is invoked when any gas dimension changes. It is the + // multi-dimensional successor to GasChangeHook, exposing the state-access + // gas dimension introduced by EIP-8037 (Amsterdam) alongside the regular + // dimension. + // + // Compatibility: + // - Post-Amsterdam: fires for changes to either the regular or the + // state-access dimension. The non-changing dimension is passed through + // unchanged in both `old` and `new` so consumers always observe the + // complete gas vector. + // - Pre-Amsterdam: no state-access gas events occur, so the State field + // of both `old` and `new` is always zero. Tracers that register only + // V2 still receive every regular-gas change as Gas{State: 0} and + // behave identically to a V1 tracer; there is no pre-Amsterdam event + // a V2-only tracer misses. + // + // V1 and V2 coexist: when both are registered on a tracer, only V2 is + // invoked. Tracers SHOULD register at most one of the two to avoid + // double-counting. + GasChangeHookV2 = func(old, new Gas, reason GasChangeReason) /* - Chain events - @@ -250,13 +276,14 @@ type ( type Hooks struct { // VM events - OnTxStart TxStartHook - OnTxEnd TxEndHook - OnEnter EnterHook - OnExit ExitHook - OnOpcode OpcodeHook - OnFault FaultHook - OnGasChange GasChangeHook + OnTxStart TxStartHook + OnTxEnd TxEndHook + OnEnter EnterHook + OnExit ExitHook + OnOpcode OpcodeHook + OnFault FaultHook + OnGasChange GasChangeHook + OnGasChangeV2 GasChangeHookV2 // Chain events OnBlockchainInit BlockchainInitHook OnClose CloseHook @@ -280,6 +307,35 @@ type Hooks struct { OnBlockHashRead BlockHashReadHook } +// HasGasHook reports whether any gas-change hook is registered. Call sites +// should use this to short-circuit before constructing the Gas / GasBudget +// arguments to EmitGasChange when tracing is off — the dispatch is otherwise +// always paid the cost of evaluating those args. +func (h *Hooks) HasGasHook() bool { + return h != nil && (h.OnGasChangeV2 != nil || h.OnGasChange != nil) +} + +// EmitGasChange dispatches a gas change event to the registered hooks. If the +// multi-dimensional OnGasChangeV2 hook is set it is invoked with the full Gas +// vectors; otherwise the single-dimensional OnGasChange hook is invoked with +// the regular-gas dimension only. The call is a no-op when the receiver is +// nil, when neither hook is registered, or when the reason is GasChangeIgnored. +// +// Call sites SHOULD use this helper instead of invoking the hooks directly so +// that both variants stay consistent across the Amsterdam fork boundary. +func (h *Hooks) EmitGasChange(old, new Gas, reason GasChangeReason) { + if h == nil || reason == GasChangeIgnored { + return + } + if h.OnGasChangeV2 != nil { + h.OnGasChangeV2(old, new, reason) + return + } + if h.OnGasChange != nil { + h.OnGasChange(old.Regular, new.Regular, reason) + } +} + // BalanceChangeReason is used to indicate the reason for a balance change, useful // for tracing and reporting. type BalanceChangeReason byte @@ -335,6 +391,19 @@ const ( BalanceChangeRevert BalanceChangeReason = 15 ) +// Gas represents a multi-dimensional gas budget introduced by EIP-8037. +// It carries the regular execution gas and the state-access gas, which are +// metered independently from the Amsterdam fork onwards. +// +// Before Amsterdam, gas metering is single-dimensional and only the Regular +// field is meaningful; State is always zero. The struct is shaped so that +// pre-Amsterdam call sites can populate it as Gas{Regular: g} without loss +// of fidelity relative to the legacy single-uint64 hook. +type Gas struct { + Regular uint64 // Regular is the budget for ordinary execution gas. + State uint64 // State is the budget dedicated to state-access gas (zero pre-Amsterdam). +} + // GasChangeReason is used to indicate the reason for a gas change, useful // for tracing and reporting. // diff --git a/core/vm/contract.go b/core/vm/contract.go index a55a5dde8b..45c879c80f 100644 --- a/core/vm/contract.go +++ b/core/vm/contract.go @@ -131,8 +131,8 @@ func (c *Contract) UseGas(cost GasCosts, logger *tracing.Hooks, reason tracing.G if !ok { return false } - if logger != nil && logger.OnGasChange != nil && reason != tracing.GasChangeIgnored { - logger.OnGasChange(prior, c.Gas.RegularGas, reason) + if logger.HasGasHook() && reason != tracing.GasChangeIgnored { + logger.EmitGasChange(prior.AsTracing(), c.Gas.AsTracing(), reason) } return true } @@ -143,8 +143,8 @@ func (c *Contract) RefundGas(refund GasBudget, logger *tracing.Hooks, reason tra if !changed { return } - if logger != nil && logger.OnGasChange != nil && reason != tracing.GasChangeIgnored { - logger.OnGasChange(prior, c.Gas.RegularGas, reason) + if logger.HasGasHook() && reason != tracing.GasChangeIgnored { + logger.EmitGasChange(prior.AsTracing(), c.Gas.AsTracing(), reason) } } diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 6dadb64873..71cfdbc527 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -269,8 +269,8 @@ func RunPrecompiledContract(stateDB StateDB, p PrecompiledContract, address comm gas.Exhaust() return nil, gas, ErrOutOfGas } - if logger != nil && logger.OnGasChange != nil { - logger.OnGasChange(prior, gas.RegularGas, tracing.GasChangeCallPrecompiledContract) + if logger.HasGasHook() { + logger.EmitGasChange(prior.AsTracing(), gas.AsTracing(), tracing.GasChangeCallPrecompiledContract) } // Touch the precompile for block-level accessList recording once Amsterdam // fork is activated. diff --git a/core/vm/evm.go b/core/vm/evm.go index 26b2f73a00..9fe6faa3a2 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -317,8 +317,8 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g 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.RegularGas, 0, tracing.GasChangeCallFailedExecution) + if evm.Config.Tracer.HasGasHook() { + evm.Config.Tracer.EmitGasChange(gas.AsTracing(), tracing.Gas{}, tracing.GasChangeCallFailedExecution) } gas.Exhaust() } @@ -371,8 +371,8 @@ func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byt 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.RegularGas, 0, tracing.GasChangeCallFailedExecution) + if evm.Config.Tracer.HasGasHook() { + evm.Config.Tracer.EmitGasChange(gas.AsTracing(), tracing.Gas{}, tracing.GasChangeCallFailedExecution) } gas.Exhaust() } @@ -415,8 +415,8 @@ func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address, 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.RegularGas, 0, tracing.GasChangeCallFailedExecution) + if evm.Config.Tracer.HasGasHook() { + evm.Config.Tracer.EmitGasChange(gas.AsTracing(), tracing.Gas{}, tracing.GasChangeCallFailedExecution) } gas.Exhaust() } @@ -470,8 +470,8 @@ func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []b 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.RegularGas, 0, tracing.GasChangeCallFailedExecution) + if evm.Config.Tracer.HasGasHook() { + evm.Config.Tracer.EmitGasChange(gas.AsTracing(), tracing.Gas{}, tracing.GasChangeCallFailedExecution) } gas.Exhaust() } @@ -509,8 +509,8 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value gas.Exhaust() return nil, common.Address{}, gas, ErrOutOfGas } - if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil { - evm.Config.Tracer.OnGasChange(prior, gas.RegularGas, tracing.GasChangeWitnessContractCollisionCheck) + if evm.Config.Tracer.HasGasHook() { + evm.Config.Tracer.EmitGasChange(prior.AsTracing(), gas.AsTracing(), tracing.GasChangeWitnessContractCollisionCheck) } } @@ -528,8 +528,8 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value if evm.StateDB.GetNonce(address) != 0 || (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.RegularGas, 0, tracing.GasChangeCallFailedExecution) + if evm.Config.Tracer.HasGasHook() { + evm.Config.Tracer.EmitGasChange(gas.AsTracing(), tracing.Gas{}, tracing.GasChangeCallFailedExecution) } gas.Exhaust() return nil, common.Address{}, gas, ErrContractAddressCollision @@ -558,8 +558,8 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value 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(prior, gas.RegularGas, tracing.GasChangeWitnessContractInit) + if evm.Config.Tracer.HasGasHook() { + evm.Config.Tracer.EmitGasChange(prior.AsTracing(), gas.AsTracing(), tracing.GasChangeWitnessContractInit) } } evm.Context.Transfer(evm.StateDB, caller, address, value, &evm.chainRules) @@ -673,15 +673,17 @@ func (evm *EVM) captureBegin(depth int, typ OpCode, from common.Address, to comm if tracer.OnEnter != nil { tracer.OnEnter(depth, byte(typ), from, to, input, startGas, value) } - if tracer.OnGasChange != nil { - tracer.OnGasChange(0, startGas, tracing.GasChangeCallInitialBalance) + if tracer.HasGasHook() { + initial := NewGasBudget(startGas) + tracer.EmitGasChange(tracing.Gas{}, initial.AsTracing(), tracing.GasChangeCallInitialBalance) } } func (evm *EVM) captureEnd(depth int, startGas uint64, leftOverGas uint64, ret []byte, err error) { tracer := evm.Config.Tracer - if leftOverGas != 0 && tracer.OnGasChange != nil { - tracer.OnGasChange(leftOverGas, 0, tracing.GasChangeCallLeftOverReturned) + if leftOverGas != 0 && tracer.HasGasHook() { + leftover := NewGasBudget(leftOverGas) + tracer.EmitGasChange(leftover.AsTracing(), tracing.Gas{}, tracing.GasChangeCallLeftOverReturned) } var reverted bool if err != nil { diff --git a/core/vm/gascosts.go b/core/vm/gascosts.go index cc90c54798..ed938ae41f 100644 --- a/core/vm/gascosts.go +++ b/core/vm/gascosts.go @@ -16,7 +16,11 @@ package vm -import "fmt" +import ( + "fmt" + + "github.com/ethereum/go-ethereum/core/tracing" +) // GasCosts denotes a vector of gas costs in the // multidimensional metering paradigm. It represents the cost @@ -77,21 +81,26 @@ func (g GasBudget) CanAfford(cost GasCosts) bool { } // 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 +// pre-charge budget 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 { +func (g *GasBudget) Charge(cost GasCosts) (GasBudget, bool) { + prior := *g + if g.RegularGas < 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 +// Refund adds the given gas budget back. It returns the pre-refund budget +// and whether the budget was actually changed. +func (g *GasBudget) Refund(other GasBudget) (GasBudget, bool) { + prior := *g g.RegularGas += other.RegularGas - return prior, g.RegularGas != prior + return prior, g.RegularGas != prior.RegularGas +} + +// AsTracing converts the GasBudget into the tracing-facing Gas vector. +func (g GasBudget) AsTracing() tracing.Gas { + return tracing.Gas{Regular: g.RegularGas, State: g.StateGas} } diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 4c278fc857..3994327247 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -234,8 +234,12 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte // Do tracing before potential memory expansion if debug { - if evm.Config.Tracer.OnGasChange != nil { - evm.Config.Tracer.OnGasChange(gasCopy, gasCopy-cost, tracing.GasChangeCallOpCode) + if evm.Config.Tracer.HasGasHook() { + evm.Config.Tracer.EmitGasChange( + tracing.Gas{Regular: gasCopy, State: contract.Gas.StateGas}, + tracing.Gas{Regular: gasCopy - cost, State: contract.Gas.StateGas}, + tracing.GasChangeCallOpCode, + ) } if evm.Config.Tracer.OnOpcode != nil { evm.Config.Tracer.OnOpcode(pc, byte(op), gasCopy, cost, callContext, evm.returnData, evm.depth, VMErrorFromErr(err)) diff --git a/eth/tracers/live/noop.go b/eth/tracers/live/noop.go index f3def85606..b1784dbd91 100644 --- a/eth/tracers/live/noop.go +++ b/eth/tracers/live/noop.go @@ -47,6 +47,7 @@ func newNoopTracer(_ json.RawMessage) (*tracing.Hooks, error) { OnOpcode: t.OnOpcode, OnFault: t.OnFault, OnGasChange: t.OnGasChange, + OnGasChangeV2: t.OnGasChangeV2, OnBlockchainInit: t.OnBlockchainInit, OnBlockStart: t.OnBlockStart, OnBlockEnd: t.OnBlockEnd, @@ -113,3 +114,6 @@ func (t *noop) OnBlockHashRead(number uint64, hash common.Hash) {} func (t *noop) OnGasChange(old, new uint64, reason tracing.GasChangeReason) { } + +func (t *noop) OnGasChangeV2(old, new tracing.Gas, reason tracing.GasChangeReason) { +} diff --git a/eth/tracers/native/mux.go b/eth/tracers/native/mux.go index bb2101deec..73f8585a6b 100644 --- a/eth/tracers/native/mux.go +++ b/eth/tracers/native/mux.go @@ -65,10 +65,11 @@ func newMuxTracerFromConfig(ctx *tracers.Context, cfg json.RawMessage, chainConf // the aggregated JSON result returned by GetResult. // // For hooks that have both a V1 and V2 form (OnCodeChange / OnCodeChangeV2, -// OnNonceChange / OnNonceChangeV2, OnSystemCallStart / OnSystemCallStartV2), -// the mux exposes only the V2 variant upward. The fanout then prefers each -// child's V2 hook and falls back to V1 if only V1 is set, mirroring the -// precedence already used in core/state_processor.go. +// OnNonceChange / OnNonceChangeV2, OnGasChange / OnGasChangeV2, +// OnSystemCallStart / OnSystemCallStartV2), the mux exposes only the V2 +// variant upward. The fanout then prefers each child's V2 hook and falls +// back to V1 if only V1 is set, mirroring the precedence already used in +// core/state_processor.go. func NewMuxTracer(names []string, objects []*tracers.Tracer) (*tracers.Tracer, error) { t := &muxTracer{names: names, tracers: objects} return &tracers.Tracer{ @@ -79,7 +80,7 @@ func NewMuxTracer(names []string, objects []*tracers.Tracer) (*tracers.Tracer, e OnExit: t.OnExit, OnOpcode: t.OnOpcode, OnFault: t.OnFault, - OnGasChange: t.OnGasChange, + OnGasChangeV2: t.OnGasChangeV2, OnBalanceChange: t.OnBalanceChange, OnNonceChangeV2: t.OnNonceChangeV2, OnCodeChangeV2: t.OnCodeChangeV2, @@ -109,10 +110,12 @@ func (t *muxTracer) OnFault(pc uint64, op byte, gas, cost uint64, scope tracing. } } -func (t *muxTracer) OnGasChange(old, new uint64, reason tracing.GasChangeReason) { +func (t *muxTracer) OnGasChangeV2(old, new tracing.Gas, reason tracing.GasChangeReason) { for _, t := range t.tracers { - if t.OnGasChange != nil { - t.OnGasChange(old, new, reason) + if t.OnGasChangeV2 != nil { + t.OnGasChangeV2(old, new, reason) + } else if t.OnGasChange != nil { + t.OnGasChange(old.Regular, new.Regular, reason) } } } diff --git a/eth/tracers/native/noop.go b/eth/tracers/native/noop.go index ac174cc25e..323bf4338f 100644 --- a/eth/tracers/native/noop.go +++ b/eth/tracers/native/noop.go @@ -47,6 +47,7 @@ func newNoopTracer(ctx *tracers.Context, cfg json.RawMessage, chainConfig *param OnOpcode: t.OnOpcode, OnFault: t.OnFault, OnGasChange: t.OnGasChange, + OnGasChangeV2: t.OnGasChangeV2, OnBalanceChange: t.OnBalanceChange, OnNonceChange: t.OnNonceChange, OnCodeChange: t.OnCodeChange, @@ -66,6 +67,8 @@ func (t *noopTracer) OnFault(pc uint64, op byte, gas, cost uint64, _ tracing.OpC func (t *noopTracer) OnGasChange(old, new uint64, reason tracing.GasChangeReason) {} +func (t *noopTracer) OnGasChangeV2(old, new tracing.Gas, reason tracing.GasChangeReason) {} + func (t *noopTracer) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { }