Full OnGasConsumed loop and added GasChangeReason (#16)

This commit is contained in:
Matthieu Vachon 2023-09-12 11:19:15 -04:00 committed by GitHub
parent 74c1f30811
commit ff3c15ff90
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 189 additions and 49 deletions

View file

@ -263,6 +263,11 @@ func (st *StateTransition) buyGas() error {
if err := st.gp.SubGas(st.msg.GasLimit); err != nil {
return err
}
if st.evm.Config.Tracer != nil {
st.evm.Config.Tracer.OnGasChange(0, st.msg.GasLimit, vm.GasChangeTxInitialBalance)
}
st.gasRemaining += st.msg.GasLimit
st.initialGas = st.msg.GasLimit
@ -386,7 +391,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gasRemaining, gas)
}
if t := st.evm.Config.Tracer; t != nil {
t.OnGasConsumed(st.gasRemaining, gas)
t.OnGasChange(st.gasRemaining, st.gasRemaining-gas, vm.GasChangeTxIntrinsicGas)
}
st.gasRemaining -= gas
@ -452,12 +457,21 @@ func (st *StateTransition) refundGas(refundQuotient uint64) {
if refund > st.state.GetRefund() {
refund = st.state.GetRefund()
}
if st.evm.Config.Tracer != nil && refund > 0 {
st.evm.Config.Tracer.OnGasChange(st.gasRemaining, st.gasRemaining+refund, vm.GasChangeTxRefunds)
}
st.gasRemaining += refund
// Return ETH for remaining gas, exchanged at the original rate.
remaining := new(big.Int).Mul(new(big.Int).SetUint64(st.gasRemaining), st.msg.GasPrice)
st.state.AddBalance(st.msg.From, remaining, state.BalanceChangeGasRefund)
if st.evm.Config.Tracer != nil && st.gasRemaining > 0 {
st.evm.Config.Tracer.OnGasChange(st.gasRemaining, 0, vm.GasChangeTxLeftOverReturned)
}
// Also return remaining gas to the block gas counter so it is
// available for the next transaction.
st.gp.AddGas(st.gasRemaining)

View file

@ -159,12 +159,12 @@ 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 EVMLogger) (ok bool) {
func (c *Contract) UseGas(gas uint64, logger EVMLogger, reason GasChangeReason) (ok bool) {
if c.Gas < gas {
return false
}
if logger != nil {
logger.OnGasConsumed(c.Gas, gas)
if logger != nil && reason != GasChangeIgnored {
logger.OnGasChange(c.Gas, c.Gas-gas, reason)
}
c.Gas -= gas
return true

View file

@ -174,7 +174,7 @@ func RunPrecompiledContract(p PrecompiledContract, input []byte, suppliedGas uin
return nil, 0, ErrOutOfGas
}
if logger != nil {
logger.OnGasConsumed(suppliedGas, gasCost)
logger.OnGasChange(suppliedGas, suppliedGas-gasCost, GasChangeCallPrecompiledContract)
}
suppliedGas -= gasCost
output, err := p.Run(input)

View file

@ -176,18 +176,10 @@ func (evm *EVM) SetBlockContext(blockCtx BlockContext) {
func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) {
// Capture the tracer start/end events in debug mode
if evm.Config.Tracer != nil {
if evm.depth == 0 {
evm.Config.Tracer.CaptureStart(caller.Address(), addr, false, input, gas, value)
defer func(startGas uint64) { // Lazy evaluation of the parameters
evm.Config.Tracer.CaptureEnd(ret, startGas-gas, err)
}(gas)
} else {
// Handle tracer events for entering and exiting a call frame
evm.Config.Tracer.CaptureEnter(CALL, caller.Address(), addr, input, gas, value)
defer func(startGas uint64) {
evm.Config.Tracer.CaptureExit(ret, startGas-gas, err)
}(gas)
}
evm.captureBegin(evm.depth == 0, CALL, caller.Address(), addr, input, gas, value)
defer func(startGas uint64) {
evm.captureEnd(evm.depth == 0, CALL, startGas, leftOverGas, ret, err)
}(gas)
}
// Fail if we're trying to execute above the call depth limit
if evm.depth > int(params.CallCreateDepth) {
@ -233,6 +225,10 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
if err != nil {
evm.StateDB.RevertToSnapshot(snapshot)
if err != ErrExecutionReverted {
if evm.Config.Tracer != nil {
evm.Config.Tracer.OnGasChange(gas, 0, GasChangeCallFailedExecution)
}
gas = 0
}
// TODO: consider clearing up unused snapshots:
@ -252,9 +248,9 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) {
// Invoke tracer hooks that signal entering/exiting a call frame
if evm.Config.Tracer != nil {
evm.Config.Tracer.CaptureEnter(CALLCODE, caller.Address(), addr, input, gas, value)
evm.captureBegin(false, CALLCODE, caller.Address(), addr, input, gas, value)
defer func(startGas uint64) {
evm.Config.Tracer.CaptureExit(ret, startGas-gas, err)
evm.captureEnd(false, CALLCODE, startGas, leftOverGas, ret, err)
}(gas)
}
// Fail if we're trying to execute above the call depth limit
@ -285,6 +281,10 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
if err != nil {
evm.StateDB.RevertToSnapshot(snapshot)
if err != ErrExecutionReverted {
if evm.Config.Tracer != nil {
evm.Config.Tracer.OnGasChange(gas, 0, GasChangeCallFailedExecution)
}
gas = 0
}
}
@ -303,9 +303,9 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
// that caller is something other than a Contract.
parent := caller.(*Contract)
// DELEGATECALL inherits value from parent call
evm.Config.Tracer.CaptureEnter(DELEGATECALL, caller.Address(), addr, input, gas, parent.value)
evm.captureBegin(false, DELEGATECALL, caller.Address(), addr, input, gas, parent.value)
defer func(startGas uint64) {
evm.Config.Tracer.CaptureExit(ret, startGas-gas, err)
evm.captureEnd(false, DELEGATECALL, startGas, leftOverGas, ret, err)
}(gas)
}
// Fail if we're trying to execute above the call depth limit
@ -328,6 +328,10 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
if err != nil {
evm.StateDB.RevertToSnapshot(snapshot)
if err != ErrExecutionReverted {
if evm.Config.Tracer != nil {
evm.Config.Tracer.OnGasChange(gas, 0, GasChangeCallFailedExecution)
}
gas = 0
}
}
@ -341,9 +345,9 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte, gas uint64) (ret []byte, leftOverGas uint64, err error) {
// Invoke tracer hooks that signal entering/exiting a call frame
if evm.Config.Tracer != nil {
evm.Config.Tracer.CaptureEnter(STATICCALL, caller.Address(), addr, input, gas, nil)
evm.captureBegin(false, STATICCALL, caller.Address(), addr, input, gas, nil)
defer func(startGas uint64) {
evm.Config.Tracer.CaptureExit(ret, startGas-gas, err)
evm.captureEnd(false, STATICCALL, startGas, leftOverGas, ret, err)
}(gas)
}
// Fail if we're trying to execute above the call depth limit
@ -383,6 +387,10 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
if err != nil {
evm.StateDB.RevertToSnapshot(snapshot)
if err != ErrExecutionReverted {
if evm.Config.Tracer != nil {
evm.Config.Tracer.OnGasChange(gas, 0, GasChangeCallFailedExecution)
}
gas = 0
}
}
@ -402,19 +410,12 @@ func (c *codeAndHash) Hash() common.Hash {
}
// create creates a new contract using code as deployment code.
func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, value *big.Int, address common.Address, typ OpCode) (ret []byte, createAddress common.Address, leftoverGas uint64, err error) {
func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, value *big.Int, address common.Address, typ OpCode) (ret []byte, createAddress common.Address, leftOverGas uint64, err error) {
if evm.Config.Tracer != nil {
if evm.depth == 0 {
evm.Config.Tracer.CaptureStart(caller.Address(), address, true, codeAndHash.code, gas, value)
defer func() {
evm.Config.Tracer.CaptureEnd(ret, gas-leftoverGas, err)
}()
} else {
evm.Config.Tracer.CaptureEnter(typ, caller.Address(), address, codeAndHash.code, gas, value)
defer func() {
evm.Config.Tracer.CaptureExit(ret, gas-leftoverGas, err)
}()
}
evm.captureBegin(evm.depth == 0, typ, caller.Address(), address, codeAndHash.code, gas, value)
defer func(startGas uint64) {
evm.captureEnd(evm.depth == 0, typ, startGas, leftOverGas, ret, err)
}(gas)
}
// Depth check execution. Fail if we're trying to execute above the
// limit.
@ -437,6 +438,10 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
// Ensure there's no existing contract already at the designated address
contractHash := evm.StateDB.GetCodeHash(address)
if evm.StateDB.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != types.EmptyCodeHash) {
if evm.Config.Tracer != nil {
evm.Config.Tracer.OnGasChange(gas, 0, GasChangeCallFailedExecution)
}
return nil, common.Address{}, 0, ErrContractAddressCollision
}
// Create a new account on the state
@ -470,7 +475,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
// by the error checking condition below.
if err == nil {
createDataGas := uint64(len(ret)) * params.CreateDataGas
if contract.UseGas(createDataGas, evm.Config.Tracer) {
if contract.UseGas(createDataGas, evm.Config.Tracer, GasChangeCallCodeStorage) {
evm.StateDB.SetCode(address, ret)
} else {
err = ErrCodeStoreOutOfGas
@ -483,7 +488,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
if err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas) {
evm.StateDB.RevertToSnapshot(snapshot)
if err != ErrExecutionReverted {
contract.UseGas(contract.Gas, evm.Config.Tracer)
contract.UseGas(contract.Gas, evm.Config.Tracer, GasChangeCallFailedExecution)
}
}
@ -508,3 +513,29 @@ func (evm *EVM) Create2(caller ContractRef, code []byte, gas uint64, endowment *
// ChainConfig returns the environment's chain configuration
func (evm *EVM) ChainConfig() *params.ChainConfig { return evm.chainConfig }
func (evm *EVM) captureBegin(isRoot bool, typ OpCode, from common.Address, to common.Address, input []byte, startGas uint64, value *big.Int) {
tracer := evm.Config.Tracer
if isRoot {
tracer.CaptureStart(from, to, typ == CREATE || typ == CREATE2, input, startGas, value)
} else {
tracer.CaptureEnter(typ, from, to, input, startGas, value)
}
tracer.OnGasChange(0, startGas, GasChangeCallInitialBalance)
}
func (evm *EVM) captureEnd(isRoot bool, typ OpCode, startGas uint64, leftOverGas uint64, ret []byte, err error) {
tracer := evm.Config.Tracer
if leftOverGas != 0 {
tracer.OnGasChange(leftOverGas, 0, GasChangeCallLeftOverReturned)
}
if isRoot {
tracer.CaptureEnd(ret, startGas-leftOverGas, err)
} else {
tracer.CaptureExit(ret, startGas-leftOverGas, err)
}
}

View file

@ -592,7 +592,7 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b
// reuse size int for stackvalue
stackvalue := size
scope.Contract.UseGas(gas, interpreter.evm.Config.Tracer)
scope.Contract.UseGas(gas, interpreter.evm.Config.Tracer, GasChangeCallContractCreation)
//TODO: use uint256.Int instead of converting with toBig()
var bigVal = big0
if !value.IsZero() {
@ -612,6 +612,11 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b
stackvalue.SetBytes(addr.Bytes())
}
scope.Stack.push(&stackvalue)
if interpreter.evm.Config.Tracer != nil && returnGas > 0 {
interpreter.evm.Config.Tracer.OnGasChange(scope.Contract.Gas, scope.Contract.Gas+returnGas, GasChangeCallLeftOverRefunded)
}
scope.Contract.Gas += returnGas
if suberr == ErrExecutionReverted {
@ -635,7 +640,7 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]
)
// Apply EIP150
gas -= gas / 64
scope.Contract.UseGas(gas, interpreter.evm.Config.Tracer)
scope.Contract.UseGas(gas, interpreter.evm.Config.Tracer, GasChangeCallContractCreation2)
// reuse size int for stackvalue
stackvalue := size
//TODO: use uint256.Int instead of converting with toBig()
@ -652,6 +657,11 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]
stackvalue.SetBytes(addr.Bytes())
}
scope.Stack.push(&stackvalue)
if interpreter.evm.Config.Tracer != nil && returnGas > 0 {
interpreter.evm.Config.Tracer.OnGasChange(scope.Contract.Gas, scope.Contract.Gas+returnGas, GasChangeCallLeftOverRefunded)
}
scope.Contract.Gas += returnGas
if suberr == ErrExecutionReverted {
@ -697,6 +707,11 @@ func opCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt
if err == nil || err == ErrExecutionReverted {
scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
}
if interpreter.evm.Config.Tracer != nil && returnGas > 0 {
interpreter.evm.Config.Tracer.OnGasChange(scope.Contract.Gas, scope.Contract.Gas+returnGas, GasChangeCallLeftOverRefunded)
}
scope.Contract.Gas += returnGas
interpreter.returnData = ret
@ -732,6 +747,11 @@ func opCallCode(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([
if err == nil || err == ErrExecutionReverted {
scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
}
if interpreter.evm.Config.Tracer != nil && returnGas > 0 {
interpreter.evm.Config.Tracer.OnGasChange(scope.Contract.Gas, scope.Contract.Gas+returnGas, GasChangeCallLeftOverRefunded)
}
scope.Contract.Gas += returnGas
interpreter.returnData = ret
@ -760,6 +780,11 @@ func opDelegateCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext
if err == nil || err == ErrExecutionReverted {
scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
}
if interpreter.evm.Config.Tracer != nil && returnGas > 0 {
interpreter.evm.Config.Tracer.OnGasChange(scope.Contract.Gas, scope.Contract.Gas+returnGas, GasChangeCallLeftOverRefunded)
}
scope.Contract.Gas += returnGas
interpreter.returnData = ret
@ -788,6 +813,11 @@ func opStaticCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext)
if err == nil || err == ErrExecutionReverted {
scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
}
if interpreter.evm.Config.Tracer != nil && returnGas > 0 {
interpreter.evm.Config.Tracer.OnGasChange(scope.Contract.Gas, scope.Contract.Gas+returnGas, GasChangeCallLeftOverRefunded)
}
scope.Contract.Gas += returnGas
interpreter.returnData = ret

View file

@ -185,9 +185,10 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
} else if sLen > operation.maxStack {
return nil, &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack}
}
if !contract.UseGas(cost, in.evm.Config.Tracer) {
if !contract.UseGas(cost, in.evm.Config.Tracer, GasChangeIgnored) {
return nil, ErrOutOfGas
}
if operation.dynamicGas != nil {
// All ops with a dynamic memory usage also has a dynamic gas cost.
var memorySize uint64
@ -211,11 +212,13 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
var dynamicCost uint64
dynamicCost, err = operation.dynamicGas(in.evm, contract, stack, mem, memorySize)
cost += dynamicCost // for tracing
if err != nil || !contract.UseGas(dynamicCost, in.evm.Config.Tracer) {
if err != nil || !contract.UseGas(dynamicCost, in.evm.Config.Tracer, GasChangeIgnored) {
return nil, ErrOutOfGas
}
// Do tracing before memory expansion
if debug {
in.evm.Config.Tracer.OnGasChange(gasCopy, gasCopy-cost, GasChangeCallOpCode)
in.evm.Config.Tracer.CaptureState(pc, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err)
logged = true
}
@ -223,9 +226,11 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
mem.Resize(memorySize)
}
} else if debug {
in.evm.Config.Tracer.OnGasChange(gasCopy, gasCopy-cost, GasChangeCallOpCode)
in.evm.Config.Tracer.CaptureState(pc, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err)
logged = true
}
// execute the operation
res, err = operation.execute(&pc, in, callContext)
if err != nil {

View file

@ -43,5 +43,65 @@ type EVMLogger interface {
CaptureFault(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error)
CaptureKeccakPreimage(hash common.Hash, data []byte)
// Misc
OnGasConsumed(gas, amount uint64)
OnGasChange(old, new uint64, reason GasChangeReason)
}
// GasChangeReason is used to indicate the reason for a gas change, useful
// for tracing and reporting.
//
// There is essentially two types of gas changes, those that can be emitted once per transaction
// and those that can be emitted on a call basis, so possibly multiple times per transaction.
//
// They can be recognized easily by their name, those that start with `GasChangeTx` are emitted
// once per transaction, while those that start with `GasChangeCall` are emitted on a call basis.
type GasChangeReason byte
const (
GasChangeUnspecified GasChangeReason = iota
// GasChangeTxInitialBalance is the initial balance for the call which will be equal to the gasLimit of the call. There is only
// one such gas change per transaction.
GasChangeTxInitialBalance
// GasChangeTxIntrinsicGas is the amount of gas that will be charged for the intrinsic cost of the transaction, there is
// always exactly one of those per transaction.
GasChangeTxIntrinsicGas
// GasChangeTxRefunds is the sum of all refunds which happened during the tx execution (e.g. storage slot being cleared)
// this generates an increase in gas. There is at most one of such gas change per transaction.
GasChangeTxRefunds
// GasChangeTxLeftOverReturned is the amount of gas left over at the end of transaction's execution that will be returned
// to the chain. This change will always be a negative change as we "drain" left over gas towards 0. If there was no gas
// left at the end of execution, no such even will be emitted. The returned gas's value in Wei is returned to caller.
// There is at most one of such gas change per transaction.
GasChangeTxLeftOverReturned
// GasChangeCallInitialBalance is the initial balance for the call which will be equal to the gasLimit of the call. There is only
// one such gas change per call.
GasChangeCallInitialBalance
// GasChangeCallLeftOverReturned is the amount of gas left over that will be returned to the caller, this change will always
// be a negative change as we "drain" left over gas towards 0. If there was no gas left at the end of execution, no such even
// will be emitted.
GasChangeCallLeftOverReturned
// GasChangeCallLeftOverRefunded is the amount of gas that will be refunded to the call after the child call execution it
// executed completed. This value is always positive as we are giving gas back to the you, the left over gas of the child.
// If there was no gas left to be refunded, no such even will be emitted.
GasChangeCallLeftOverRefunded
// GasChangeCallContractCreation is the amount of gas that will be burned for a CREATE.
GasChangeCallContractCreation
// GasChangeContractCreation is the amount of gas that will be burned for a CREATE2.
GasChangeCallContractCreation2
// GasChangeCallCodeStorage is the amount of gas that will be charged for code storage.
GasChangeCallCodeStorage
// GasChangeCallOpCode is the amount of gas that will be charged for an opcode executed by the EVM, exact opcode that was
// performed can be check by `CaptureState` handling.
GasChangeCallOpCode
// GasChangeCallPrecompiledContract is the amount of gas that will be charged for a precompiled contract execution.
GasChangeCallPrecompiledContract
// GasChangeCallStorageColdAccess is the amount of gas that will be charged for a cold storage access as controlled by EIP2929 rules.
GasChangeCallStorageColdAccess
// GasChangeCallFailedExecution is the burning of the remaining gas when the execution failed without a revert.
GasChangeCallFailedExecution
// GasChangeIgnored is a special value that can be used to indicate that the gas change should be ignored as
// it will be "manually" tracked by a direct emit of the gas change event.
GasChangeIgnored GasChangeReason = 0xFF
)

View file

@ -169,7 +169,7 @@ func makeCallVariantGasCallEIP2929(oldCalculator gasFunc) gasFunc {
evm.StateDB.AddAddressToAccessList(addr)
// Charge the remaining difference here already, to correctly calculate available
// gas for call
if !contract.UseGas(coldCost, evm.Config.Tracer) {
if !contract.UseGas(coldCost, evm.Config.Tracer, GasChangeCallStorageColdAccess) {
return 0, ErrOutOfGas
}
}

View file

@ -58,8 +58,8 @@ func (t *NoopTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, _ *
// CaptureKeccakPreimage is called during the KECCAK256 opcode.
func (t *NoopTracer) CaptureKeccakPreimage(hash common.Hash, data []byte) {}
// OnGasConsumed is called when gas is consumed.
func (t *NoopTracer) OnGasConsumed(gas, amount uint64) {}
// OnGasChange is called when gas is either consumed or refunded.
func (t *NoopTracer) OnGasChange(old, new uint64, reason vm.GasChangeReason) {}
// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct).
func (t *NoopTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {

View file

@ -126,6 +126,6 @@ func (p *Printer) OnNewAccount(a common.Address) {
fmt.Printf("OnNewAccount: a=%v\n", a)
}
func (p *Printer) OnGasConsumed(gas, amount uint64) {
fmt.Printf("OnGasConsumed: gas=%v, amount=%v\n", gas, amount)
func (p *Printer) OnGasChange(old, new uint64, reason vm.GasChangeReason) {
fmt.Printf("OnGasChange: old=%v, new=%v, diff=%v\n", old, new, new-old)
}

View file

@ -96,9 +96,9 @@ func (t *muxTracer) CaptureKeccakPreimage(hash common.Hash, data []byte) {
}
// CaptureGasConsumed is called when gas is consumed.
func (t *muxTracer) OnGasConsumed(gas, amount uint64) {
func (t *muxTracer) OnGasChange(old, new uint64, reason vm.GasChangeReason) {
for _, t := range t.tracers {
t.OnGasConsumed(gas, amount)
t.OnGasChange(old, new, reason)
}
}