diff --git a/core/vm/contract.go b/core/vm/contract.go index 165ca833f88..0a4fd7bb6c1 100644 --- a/core/vm/contract.go +++ b/core/vm/contract.go @@ -18,7 +18,6 @@ package vm import ( "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/tracing" "github.com/holiman/uint256" ) @@ -125,29 +124,6 @@ func (c *Contract) Caller() common.Address { return c.caller } -// UseGas attempts the use gas and subtracts it and returns true on success -func (c *Contract) UseGas(gas uint64, logger *tracing.Hooks, reason tracing.GasChangeReason) (ok bool) { - if c.Gas < gas { - return false - } - if logger != nil && logger.OnGasChange != nil && reason != tracing.GasChangeIgnored { - logger.OnGasChange(c.Gas, c.Gas-gas, reason) - } - c.Gas -= gas - return true -} - -// RefundGas refunds gas to the contract -func (c *Contract) RefundGas(gas uint64, logger *tracing.Hooks, reason tracing.GasChangeReason) { - if gas == 0 { - return - } - if logger != nil && logger.OnGasChange != nil && reason != tracing.GasChangeIgnored { - logger.OnGasChange(c.Gas, c.Gas+gas, reason) - } - c.Gas += gas -} - // Address returns the contracts address func (c *Contract) Address() common.Address { return c.address diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 21307ff5ace..3dc6061b8c1 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -257,12 +257,12 @@ func ActivePrecompiles(rules params.Rules) []common.Address { // - the returned bytes, // - the _remaining_ gas, // - any error that occurred -func RunPrecompiledContract(p PrecompiledContract, input []byte, suppliedGas uint64, logger *tracing.Hooks) (ret []byte, remainingGas uint64, err error) { +func RunPrecompiledContract[TS TracingSwitch](p PrecompiledContract, input []byte, suppliedGas uint64, logger *tracing.Hooks) (ret []byte, remainingGas uint64, err error) { gasCost := p.RequiredGas(input) if suppliedGas < gasCost { return nil, 0, ErrOutOfGas } - if logger != nil && logger.OnGasChange != nil { + if tracingIsEnabled[TS]() && logger.OnGasChange != nil { logger.OnGasChange(suppliedGas, suppliedGas-gasCost, tracing.GasChangeCallPrecompiledContract) } suppliedGas -= gasCost diff --git a/core/vm/contracts_fuzz_test.go b/core/vm/contracts_fuzz_test.go index 1e5cc800747..26922a7c204 100644 --- a/core/vm/contracts_fuzz_test.go +++ b/core/vm/contracts_fuzz_test.go @@ -36,7 +36,7 @@ func FuzzPrecompiledContracts(f *testing.F) { return } inWant := string(input) - RunPrecompiledContract(p, input, gas, nil) + RunPrecompiledContract[TracingDisabled](p, input, gas, nil) if inHave := string(input); inWant != inHave { t.Errorf("Precompiled %v modified input data", a) } diff --git a/core/vm/contracts_test.go b/core/vm/contracts_test.go index da44e250e1e..3b2ae921a8b 100644 --- a/core/vm/contracts_test.go +++ b/core/vm/contracts_test.go @@ -99,7 +99,7 @@ func testPrecompiled(addr string, test precompiledTest, t *testing.T) { in := common.Hex2Bytes(test.Input) gas := p.RequiredGas(in) t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) { - if res, _, err := RunPrecompiledContract(p, in, gas, nil); err != nil { + if res, _, err := RunPrecompiledContract[TracingDisabled](p, in, 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 := p.RequiredGas(in) - 1 t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) { - _, _, err := RunPrecompiledContract(p, in, gas, nil) + _, _, err := RunPrecompiledContract[TracingDisabled](p, in, 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(p, in, gas, nil) + _, _, err := RunPrecompiledContract[TracingDisabled](p, in, gas, nil) if err.Error() != test.ExpectedError { t.Errorf("Expected error [%v], got [%v]", test.ExpectedError, err) } @@ -170,7 +170,7 @@ func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) { bench.ResetTimer() for i := 0; i < bench.N; i++ { copy(data, in) - res, _, err = RunPrecompiledContract(p, data, reqGas, nil) + res, _, err = RunPrecompiledContract[TracingDisabled](p, data, reqGas, nil) } bench.StopTimer() elapsed := uint64(time.Since(start)) diff --git a/core/vm/eips.go b/core/vm/eips.go index 10ca1fe9abb..a0f3b027886 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -27,32 +27,36 @@ import ( "github.com/holiman/uint256" ) -var activators = map[int]func(*JumpTable){ - 5656: enable5656, - 6780: enable6780, - 3855: enable3855, - 3860: enable3860, - 3529: enable3529, - 3198: enable3198, - 2929: enable2929, - 2200: enable2200, - 1884: enable1884, - 1344: enable1344, - 1153: enable1153, - 4762: enable4762, - 7702: enable7702, - 7939: enable7939, +var activators = map[int][2]func(*JumpTable){ + 5656: {enable5656}, + 6780: {enable6780}, + 3855: {enable3855}, + 3860: {enable3860}, + 3529: {enable3529}, + 3198: {enable3198}, + 2929: {enable2929[TracingEnabled], enable2929[TracingDisabled]}, + 2200: {enable2200}, + 1884: {enable1884}, + 1344: {enable1344}, + 1153: {enable1153}, + 4762: {enable4762[TracingEnabled], enable4762[TracingDisabled]}, + 7702: {enable7702[TracingEnabled], enable7702[TracingDisabled]}, + 7939: {enable7939}, } // EnableEIP enables the given EIP on the config. // This operation writes in-place, and callers need to ensure that the globally // defined jump tables are not polluted. -func EnableEIP(eipNum int, jt *JumpTable) error { - enablerFn, ok := activators[eipNum] +func EnableEIP(eipNum int, jt *JumpTable, traced bool) error { + enablerFns, ok := activators[eipNum] if !ok { return fmt.Errorf("undefined eip %d", eipNum) } - enablerFn(jt) + if traced || enablerFns[1] == nil { + enablerFns[0](jt) + } else { + enablerFns[1](jt) + } return nil } @@ -122,7 +126,7 @@ func enable2200(jt *JumpTable) { // enable2929 enables "EIP-2929: Gas cost increases for state access opcodes" // https://eips.ethereum.org/EIPS/eip-2929 -func enable2929(jt *JumpTable) { +func enable2929[TS TracingSwitch](jt *JumpTable) { jt[SSTORE].dynamicGas = gasSStoreEIP2929 jt[SLOAD].constantGas = 0 @@ -141,16 +145,16 @@ func enable2929(jt *JumpTable) { jt[BALANCE].dynamicGas = gasEip2929AccountCheck jt[CALL].constantGas = params.WarmStorageReadCostEIP2929 - jt[CALL].dynamicGas = gasCallEIP2929 + jt[CALL].dynamicGas = makeCallVariantGasCallEIP2929[TS](gasCall, 1) jt[CALLCODE].constantGas = params.WarmStorageReadCostEIP2929 - jt[CALLCODE].dynamicGas = gasCallCodeEIP2929 + jt[CALLCODE].dynamicGas = makeCallVariantGasCallEIP2929[TS](gasCallCode, 1) jt[STATICCALL].constantGas = params.WarmStorageReadCostEIP2929 - jt[STATICCALL].dynamicGas = gasStaticCallEIP2929 + jt[STATICCALL].dynamicGas = makeCallVariantGasCallEIP2929[TS](gasStaticCall, 1) jt[DELEGATECALL].constantGas = params.WarmStorageReadCostEIP2929 - jt[DELEGATECALL].dynamicGas = gasDelegateCallEIP2929 + jt[DELEGATECALL].dynamicGas = makeCallVariantGasCallEIP2929[TS](gasDelegateCall, 1) // This was previously part of the dynamic cost, but we're using it as a constantGas // factor here @@ -342,7 +346,7 @@ func enable6780(jt *JumpTable) { } } -func opExtCodeCopyEIP4762(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { +func opExtCodeCopyEIP4762[TS TracingSwitch](pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { var ( stack = scope.Stack a = stack.pop() @@ -358,7 +362,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) - scope.Contract.UseGas(consumed, evm.Config.Tracer, tracing.GasChangeUnspecified) + useGas[TS](scope.Contract, consumed, evm.Config.Tracer, tracing.GasChangeUnspecified) if consumed < wanted { return nil, ErrOutOfGas } @@ -370,7 +374,7 @@ func opExtCodeCopyEIP4762(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, er // opPush1EIP4762 handles the special case of PUSH1 opcode for EIP-4762, which // need not worry about the adjusted bound logic when adding the PUSHDATA to // the list of access events. -func opPush1EIP4762(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { +func opPush1EIP4762[TS TracingSwitch](pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { var ( codeLen = uint64(len(scope.Contract.Code)) integer = new(uint256.Int) @@ -384,7 +388,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) - scope.Contract.UseGas(wanted, evm.Config.Tracer, tracing.GasChangeUnspecified) + useGas[TS](scope.Contract, wanted, evm.Config.Tracer, tracing.GasChangeUnspecified) if consumed < wanted { return nil, ErrOutOfGas } @@ -395,7 +399,7 @@ func opPush1EIP4762(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { return nil, nil } -func makePushEIP4762(size uint64, pushByteSize int) executionFunc { +func makePushEIP4762[TS TracingSwitch](size uint64, pushByteSize int) executionFunc { return func(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { var ( codeLen = len(scope.Contract.Code) @@ -412,7 +416,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) - scope.Contract.UseGas(consumed, evm.Config.Tracer, tracing.GasChangeUnspecified) + useGas[TS](scope.Contract, consumed, evm.Config.Tracer, tracing.GasChangeUnspecified) if consumed < wanted { return nil, ErrOutOfGas } @@ -423,7 +427,7 @@ func makePushEIP4762(size uint64, pushByteSize int) executionFunc { } } -func enable4762(jt *JumpTable) { +func enable4762[TS TracingSwitch](jt *JumpTable) { jt[SSTORE] = &operation{ dynamicGas: gasSStore4762, execute: opSstore, @@ -459,7 +463,7 @@ func enable4762(jt *JumpTable) { } jt[EXTCODECOPY] = &operation{ - execute: opExtCodeCopyEIP4762, + execute: opExtCodeCopyEIP4762[TS], dynamicGas: gasExtCodeCopyEIP4762, minStack: minStack(4, 0), maxStack: maxStack(4, 0), @@ -484,7 +488,7 @@ func enable4762(jt *JumpTable) { } jt[CREATE] = &operation{ - execute: opCreate, + execute: opCreate[TS], constantGas: params.CreateNGasEip4762, dynamicGas: gasCreateEip3860, minStack: minStack(3, 1), @@ -493,7 +497,7 @@ func enable4762(jt *JumpTable) { } jt[CREATE2] = &operation{ - execute: opCreate2, + execute: opCreate2[TS], constantGas: params.CreateNGasEip4762, dynamicGas: gasCreate2Eip3860, minStack: minStack(4, 1), @@ -502,7 +506,7 @@ func enable4762(jt *JumpTable) { } jt[CALL] = &operation{ - execute: opCall, + execute: opCall[TS], dynamicGas: gasCallEIP4762, minStack: minStack(7, 1), maxStack: maxStack(7, 1), @@ -510,7 +514,7 @@ func enable4762(jt *JumpTable) { } jt[CALLCODE] = &operation{ - execute: opCallCode, + execute: opCallCode[TS], dynamicGas: gasCallCodeEIP4762, minStack: minStack(7, 1), maxStack: maxStack(7, 1), @@ -518,7 +522,7 @@ func enable4762(jt *JumpTable) { } jt[STATICCALL] = &operation{ - execute: opStaticCall, + execute: opStaticCall[TS], dynamicGas: gasStaticCallEIP4762, minStack: minStack(6, 1), maxStack: maxStack(6, 1), @@ -526,7 +530,7 @@ func enable4762(jt *JumpTable) { } jt[DELEGATECALL] = &operation{ - execute: opDelegateCall, + execute: opDelegateCall[TS], dynamicGas: gasDelegateCallEIP4762, minStack: minStack(6, 1), maxStack: maxStack(6, 1), @@ -534,14 +538,14 @@ func enable4762(jt *JumpTable) { } jt[PUSH1] = &operation{ - execute: opPush1EIP4762, + execute: opPush1EIP4762[TS], constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), } for i := 1; i < 32; i++ { jt[PUSH1+OpCode(i)] = &operation{ - execute: makePushEIP4762(uint64(i+1), i+1), + execute: makePushEIP4762[TS](uint64(i+1), i+1), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), @@ -550,9 +554,9 @@ func enable4762(jt *JumpTable) { } // enable7702 the EIP-7702 changes to support delegation designators. -func enable7702(jt *JumpTable) { - jt[CALL].dynamicGas = gasCallEIP7702 - jt[CALLCODE].dynamicGas = gasCallCodeEIP7702 - jt[STATICCALL].dynamicGas = gasStaticCallEIP7702 - jt[DELEGATECALL].dynamicGas = gasDelegateCallEIP7702 +func enable7702[TS TracingSwitch](jt *JumpTable) { + jt[CALL].dynamicGas = makeCallVariantGasCallEIP7702[TS](gasCall) + jt[CALLCODE].dynamicGas = makeCallVariantGasCallEIP7702[TS](gasCallCode) + jt[STATICCALL].dynamicGas = makeCallVariantGasCallEIP7702[TS](gasStaticCall) + jt[DELEGATECALL].dynamicGas = makeCallVariantGasCallEIP7702[TS](gasDelegateCall) } diff --git a/core/vm/evm.go b/core/vm/evm.go index e360187f7b2..5dff1208dab 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -148,38 +148,44 @@ func NewEVM(blockCtx BlockContext, statedb StateDB, chainConfig *params.ChainCon } evm.precompiles = activePrecompiledContracts(evm.chainRules) + var table, tableNT *JumpTable switch { case evm.chainRules.IsOsaka: - evm.table = &osakaInstructionSet + table, tableNT = &osakaInstructionSet, &osakaInstructionSetNT case evm.chainRules.IsVerkle: // TODO replace with proper instruction set when fork is specified - evm.table = &verkleInstructionSet + table, tableNT = &verkleInstructionSet, &verkleInstructionSetNT case evm.chainRules.IsPrague: - evm.table = &pragueInstructionSet + table, tableNT = &pragueInstructionSet, &pragueInstructionSetNT case evm.chainRules.IsCancun: - evm.table = &cancunInstructionSet + table, tableNT = &cancunInstructionSet, &cancunInstructionSetNT case evm.chainRules.IsShanghai: - evm.table = &shanghaiInstructionSet + table, tableNT = &shanghaiInstructionSet, &shanghaiInstructionSetNT case evm.chainRules.IsMerge: - evm.table = &mergeInstructionSet + table, tableNT = &mergeInstructionSet, &mergeInstructionSetNT case evm.chainRules.IsLondon: - evm.table = &londonInstructionSet + table, tableNT = &londonInstructionSet, &londonInstructionSetNT case evm.chainRules.IsBerlin: - evm.table = &berlinInstructionSet + table, tableNT = &berlinInstructionSet, &berlinInstructionSetNT case evm.chainRules.IsIstanbul: - evm.table = &istanbulInstructionSet + table, tableNT = &istanbulInstructionSet, &istanbulInstructionSetNT case evm.chainRules.IsConstantinople: - evm.table = &constantinopleInstructionSet + table, tableNT = &constantinopleInstructionSet, &constantinopleInstructionSetNT case evm.chainRules.IsByzantium: - evm.table = &byzantiumInstructionSet + table, tableNT = &byzantiumInstructionSet, &byzantiumInstructionSetNT case evm.chainRules.IsEIP158: - evm.table = &spuriousDragonInstructionSet + table, tableNT = &spuriousDragonInstructionSet, &spuriousDragonInstructionSetNT case evm.chainRules.IsEIP150: - evm.table = &tangerineWhistleInstructionSet + table, tableNT = &tangerineWhistleInstructionSet, &tangerineWhistleInstructionSetNT case evm.chainRules.IsHomestead: - evm.table = &homesteadInstructionSet + table, tableNT = &homesteadInstructionSet, &homesteadInstructionSetNT default: - evm.table = &frontierInstructionSet + table, tableNT = &frontierInstructionSet, &frontierInstructionSetNT + } + if config.Tracer != nil { + evm.table = table + } else { + evm.table = tableNT } var extraEips []int if len(evm.Config.ExtraEips) > 0 { @@ -187,7 +193,7 @@ func NewEVM(blockCtx BlockContext, statedb StateDB, chainConfig *params.ChainCon evm.table = copyJumpTable(evm.table) } for _, eip := range evm.Config.ExtraEips { - if err := EnableEIP(eip, evm.table); err != nil { + if err := EnableEIP(eip, evm.table, evm.Config.Tracer != nil); err != nil { // Disable it, so caller can check if it's activated or not log.Error("EIP activation failed", "eip", eip, "error", err) } else { @@ -239,8 +245,15 @@ func isSystemCall(caller common.Address) bool { // 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) { - // Capture the tracer start/end events in debug mode if evm.Config.Tracer != nil { + return call[TracingEnabled](evm, caller, addr, input, gas, value) + } + return call[TracingDisabled](evm, caller, addr, input, gas, value) +} + +func call[TS TracingSwitch](evm *EVM, caller common.Address, addr common.Address, input []byte, gas uint64, value *uint256.Int) (ret []byte, leftOverGas uint64, err error) { + // Capture the tracer start/end events in debug mode + if tracingIsEnabled[TS]() { evm.captureBegin(evm.depth, CALL, caller, addr, input, gas, value.ToBig()) defer func(startGas uint64) { evm.captureEnd(evm.depth, startGas, leftOverGas, ret, err) @@ -283,7 +296,7 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g evm.Context.Transfer(evm.StateDB, caller, addr, value) if isPrecompile { - ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer) + ret, gas, err = RunPrecompiledContract[TS](p, input, gas, evm.Config.Tracer) } else { // Initialise a new contract and set the code that is to be used by the EVM. code := evm.resolveCode(addr) @@ -294,7 +307,7 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g contract := NewContract(caller, addr, value, gas, evm.jumpDests) contract.IsSystemCall = isSystemCall(caller) contract.SetCallCode(evm.resolveCodeHash(addr), code) - ret, err = evm.Run(contract, input, false) + ret, err = Run[TS](evm, contract, input, false) gas = contract.Gas } } @@ -304,7 +317,7 @@ 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 { + if tracingIsEnabled[TS]() && evm.Config.Tracer.OnGasChange != nil { evm.Config.Tracer.OnGasChange(gas, 0, tracing.GasChangeCallFailedExecution) } @@ -324,9 +337,9 @@ 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 callCode[TS TracingSwitch](evm *EVM, caller common.Address, addr common.Address, input []byte, gas uint64, value *uint256.Int) (ret []byte, leftOverGas uint64, err error) { // Invoke tracer hooks that signal entering/exiting a call frame - if evm.Config.Tracer != nil { + if tracingIsEnabled[TS]() { evm.captureBegin(evm.depth, CALLCODE, caller, addr, input, gas, value.ToBig()) defer func(startGas uint64) { evm.captureEnd(evm.depth, startGas, leftOverGas, ret, err) @@ -347,19 +360,19 @@ func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byt // It is allowed to call precompiles, even via delegatecall if p, isPrecompile := evm.precompile(addr); isPrecompile { - ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer) + ret, gas, err = RunPrecompiledContract[TS](p, input, gas, evm.Config.Tracer) } else { // Initialise a new contract and set the code that is to be used by the EVM. // The contract is a scoped environment for this execution context only. contract := NewContract(caller, caller, value, gas, evm.jumpDests) contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr)) - ret, err = evm.Run(contract, input, false) + ret, err = Run[TS](evm, contract, input, false) gas = contract.Gas } if err != nil { evm.StateDB.RevertToSnapshot(snapshot) if err != ErrExecutionReverted { - if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil { + if tracingIsEnabled[TS]() && evm.Config.Tracer.OnGasChange != nil { evm.Config.Tracer.OnGasChange(gas, 0, tracing.GasChangeCallFailedExecution) } gas = 0 @@ -373,9 +386,9 @@ 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 delegateCall[TS TracingSwitch](evm *EVM, originCaller common.Address, caller common.Address, addr common.Address, input []byte, gas uint64, value *uint256.Int) (ret []byte, leftOverGas uint64, err error) { // Invoke tracer hooks that signal entering/exiting a call frame - if evm.Config.Tracer != nil { + if tracingIsEnabled[TS]() { // DELEGATECALL inherits value from parent call evm.captureBegin(evm.depth, DELEGATECALL, caller, addr, input, gas, value.ToBig()) defer func(startGas uint64) { @@ -390,20 +403,20 @@ func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address, // It is allowed to call precompiles, even via delegatecall if p, isPrecompile := evm.precompile(addr); isPrecompile { - ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer) + ret, gas, err = RunPrecompiledContract[TS](p, input, gas, evm.Config.Tracer) } else { // Initialise a new contract and make initialise the delegate values // // Note: The value refers to the original value from the parent call. contract := NewContract(originCaller, caller, value, gas, evm.jumpDests) contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr)) - ret, err = evm.Run(contract, input, false) + ret, err = Run[TS](evm, contract, input, false) gas = contract.Gas } if err != nil { evm.StateDB.RevertToSnapshot(snapshot) if err != ErrExecutionReverted { - if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil { + if tracingIsEnabled[TS]() && evm.Config.Tracer.OnGasChange != nil { evm.Config.Tracer.OnGasChange(gas, 0, tracing.GasChangeCallFailedExecution) } gas = 0 @@ -416,9 +429,9 @@ 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 staticCall[TS TracingSwitch](evm *EVM, caller common.Address, 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 { + if tracingIsEnabled[TS]() { evm.captureBegin(evm.depth, STATICCALL, caller, addr, input, gas, nil) defer func(startGas uint64) { evm.captureEnd(evm.depth, startGas, leftOverGas, ret, err) @@ -442,7 +455,7 @@ func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []b evm.StateDB.AddBalance(addr, new(uint256.Int), tracing.BalanceChangeTouchAccount) if p, isPrecompile := evm.precompile(addr); isPrecompile { - ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer) + ret, gas, err = RunPrecompiledContract[TS](p, input, gas, evm.Config.Tracer) } else { // Initialise a new contract and set the code that is to be used by the EVM. // The contract is a scoped environment for this execution context only. @@ -452,13 +465,13 @@ func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []b // When an error was returned by the EVM or when setting the creation code // 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) + ret, err = Run[TS](evm, contract, input, true) gas = contract.Gas } if err != nil { evm.StateDB.RevertToSnapshot(snapshot) if err != ErrExecutionReverted { - if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil { + if tracingIsEnabled[TS]() && evm.Config.Tracer.OnGasChange != nil { evm.Config.Tracer.OnGasChange(gas, 0, tracing.GasChangeCallFailedExecution) } @@ -469,8 +482,8 @@ func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []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, address common.Address, typ OpCode) (ret []byte, createAddress common.Address, leftOverGas uint64, err error) { - if evm.Config.Tracer != nil { +func create[TS TracingSwitch](evm *EVM, caller common.Address, code []byte, gas uint64, value *uint256.Int, address common.Address, typ OpCode) (ret []byte, createAddress common.Address, leftOverGas uint64, err error) { + if tracingIsEnabled[TS]() { evm.captureBegin(evm.depth, typ, caller, address, code, gas, value.ToBig()) defer func(startGas uint64) { evm.captureEnd(evm.depth, startGas, leftOverGas, ret, err) @@ -496,7 +509,7 @@ func (evm *EVM) create(caller common.Address, code []byte, gas uint64, value *ui if statelessGas > gas { return nil, common.Address{}, 0, ErrOutOfGas } - if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil { + if tracingIsEnabled[TS]() && evm.Config.Tracer.OnGasChange != nil { evm.Config.Tracer.OnGasChange(gas, gas-statelessGas, tracing.GasChangeWitnessContractCollisionCheck) } gas = gas - statelessGas @@ -517,7 +530,7 @@ func (evm *EVM) create(caller common.Address, code []byte, gas uint64, value *ui if evm.StateDB.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != types.EmptyCodeHash) || // non-empty code (storageRoot != (common.Hash{}) && storageRoot != types.EmptyRootHash) { // non-empty storage - if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil { + if tracingIsEnabled[TS]() && evm.Config.Tracer.OnGasChange != nil { evm.Config.Tracer.OnGasChange(gas, 0, tracing.GasChangeCallFailedExecution) } return nil, common.Address{}, 0, ErrContractAddressCollision @@ -544,7 +557,7 @@ func (evm *EVM) create(caller common.Address, code []byte, gas uint64, value *ui if consumed < wanted { return nil, common.Address{}, 0, ErrOutOfGas } - if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil { + if tracingIsEnabled[TS]() && evm.Config.Tracer.OnGasChange != nil { evm.Config.Tracer.OnGasChange(gas, gas-consumed, tracing.GasChangeWitnessContractInit) } gas = gas - consumed @@ -560,11 +573,11 @@ func (evm *EVM) create(caller common.Address, code []byte, gas uint64, value *ui contract.SetCallCode(common.Hash{}, code) contract.IsDeployment = true - ret, err = evm.initNewContract(contract, address) + ret, err = initNewContract[TS](evm, contract, address) if err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas) { evm.StateDB.RevertToSnapshot(snapshot) if err != ErrExecutionReverted { - contract.UseGas(contract.Gas, evm.Config.Tracer, tracing.GasChangeCallFailedExecution) + useGas[TS](contract, contract.Gas, evm.Config.Tracer, tracing.GasChangeCallFailedExecution) } } return ret, address, contract.Gas, err @@ -572,8 +585,8 @@ func (evm *EVM) create(caller common.Address, code []byte, gas uint64, value *ui // initNewContract runs a new contract's creation code, performs checks on the // resulting code that is to be deployed, and consumes necessary gas. -func (evm *EVM) initNewContract(contract *Contract, address common.Address) ([]byte, error) { - ret, err := evm.Run(contract, nil, false) +func initNewContract[TS TracingSwitch](evm *EVM, contract *Contract, address common.Address) ([]byte, error) { + ret, err := Run[TS](evm, contract, nil, false) if err != nil { return ret, err } @@ -590,12 +603,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 !useGas[TS](contract, 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) - contract.UseGas(consumed, evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk) + useGas[TS](contract, consumed, evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk) if len(ret) > 0 && (consumed < wanted) { return ret, ErrCodeStoreOutOfGas } @@ -608,17 +621,20 @@ 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) { contractAddr = crypto.CreateAddress(caller, evm.StateDB.GetNonce(caller)) - return evm.create(caller, code, gas, value, contractAddr, CREATE) + if evm.Config.Tracer != nil { + return create[TracingEnabled](evm, caller, code, gas, value, contractAddr, CREATE) + } + return create[TracingDisabled](evm, caller, code, gas, value, contractAddr, CREATE) } // Create2 creates a new contract using code as deployment code. // // 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 create2[TS TracingSwitch](evm *EVM, caller common.Address, code []byte, gas uint64, endowment *uint256.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) { inithash := crypto.HashData(evm.hasher, code) contractAddr = crypto.CreateAddress2(caller, salt.Bytes32(), inithash[:]) - return evm.create(caller, code, gas, endowment, contractAddr, CREATE2) + return create[TS](evm, caller, code, gas, endowment, contractAddr, CREATE2) } // resolveCode returns the code associated with the provided account. After diff --git a/core/vm/instructions.go b/core/vm/instructions.go index fffa65fd6a9..46ed11e1347 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -22,6 +22,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" "github.com/holiman/uint256" ) @@ -652,7 +653,7 @@ func opSwap16(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { return nil, nil } -func opCreate(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { +func opCreate[TS TracingSwitch](pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { if evm.readOnly { return nil, ErrWriteProtection } @@ -669,9 +670,11 @@ 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) + useGas[TS](scope.Contract, gas, evm.Config.Tracer, tracing.GasChangeCallContractCreation) - res, addr, returnGas, suberr := evm.Create(scope.Contract.Address(), input, gas, &value) + caller := scope.Contract.Address() + contractAddr := crypto.CreateAddress(caller, evm.StateDB.GetNonce(caller)) + res, addr, returnGas, suberr := create[TS](evm, caller, input, gas, &value, contractAddr, CREATE) // 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 @@ -685,7 +688,7 @@ func opCreate(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { } scope.Stack.push(&stackvalue) - scope.Contract.RefundGas(returnGas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) + refundGas[TS](scope.Contract, returnGas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) if suberr == ErrExecutionReverted { evm.returnData = res // set REVERT data to return data buffer @@ -695,7 +698,7 @@ func opCreate(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { return nil, nil } -func opCreate2(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { +func opCreate2[TS TracingSwitch](pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { if evm.readOnly { return nil, ErrWriteProtection } @@ -709,10 +712,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) + useGas[TS](scope.Contract, 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 := create2[TS](evm, scope.Contract.Address(), input, gas, &endowment, &salt) // Push item on the stack based on the returned error. if suberr != nil { @@ -721,7 +724,7 @@ func opCreate2(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { stackvalue.SetBytes(addr.Bytes()) } scope.Stack.push(&stackvalue) - scope.Contract.RefundGas(returnGas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) + refundGas[TS](scope.Contract, returnGas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) if suberr == ErrExecutionReverted { evm.returnData = res // set REVERT data to return data buffer @@ -731,7 +734,7 @@ func opCreate2(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { return nil, nil } -func opCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { +func opCall[TS TracingSwitch](pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { stack := scope.Stack // Pop gas. The actual gas in evm.callGasTemp. // We can use this as a temporary value @@ -749,7 +752,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 := call[TS](evm, scope.Contract.Address(), toAddr, args, gas, &value) if err != nil { temp.Clear() @@ -761,13 +764,13 @@ func opCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) } - scope.Contract.RefundGas(returnGas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) + refundGas[TS](scope.Contract, returnGas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) evm.returnData = ret return ret, nil } -func opCallCode(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { +func opCallCode[TS TracingSwitch](pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { // Pop gas. The actual gas is in evm.callGasTemp. stack := scope.Stack // We use it as a temporary value @@ -783,7 +786,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 := callCode[TS](evm, scope.Contract.Address(), toAddr, args, gas, &value) if err != nil { temp.Clear() } else { @@ -794,13 +797,13 @@ func opCallCode(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) } - scope.Contract.RefundGas(returnGas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) + refundGas[TS](scope.Contract, returnGas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) evm.returnData = ret return ret, nil } -func opDelegateCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { +func opDelegateCall[TS TracingSwitch](pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { stack := scope.Stack // Pop gas. The actual gas is in evm.callGasTemp. // We use it as a temporary value @@ -812,7 +815,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 := delegateCall[TS](evm, scope.Contract.Caller(), scope.Contract.Address(), toAddr, args, gas, scope.Contract.value) if err != nil { temp.Clear() } else { @@ -823,13 +826,13 @@ func opDelegateCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) } - scope.Contract.RefundGas(returnGas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) + refundGas[TS](scope.Contract, returnGas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) evm.returnData = ret return ret, nil } -func opStaticCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { +func opStaticCall[TS TracingSwitch](pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { // Pop gas. The actual gas is in evm.callGasTemp. stack := scope.Stack // We use it as a temporary value @@ -841,7 +844,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 := staticCall[TS](evm, scope.Contract.Address(), toAddr, args, gas) if err != nil { temp.Clear() } else { @@ -852,7 +855,7 @@ func opStaticCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) } - scope.Contract.RefundGas(returnGas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) + refundGas[TS](scope.Contract, returnGas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) evm.returnData = ret return ret, nil diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index a0637a6800e..76b57b627d1 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -18,6 +18,7 @@ package vm import ( "fmt" + "unsafe" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" @@ -25,6 +26,17 @@ import ( "github.com/holiman/uint256" ) +type TracingEnabled uint8 +type TracingDisabled uint16 + +type TracingSwitch interface { + TracingDisabled | TracingEnabled +} + +func tracingIsEnabled[TS TracingSwitch]() bool { + return unsafe.Sizeof(*new(TS)) == 1 +} + // Config are the configuration options for the Interpreter type Config struct { Tracer *tracing.Hooks @@ -93,7 +105,7 @@ func (ctx *ScopeContext) ContractCode() []byte { // It's important to note that any errors returned by the interpreter should be // considered a revert-and-consume-all-gas operation except for // ErrExecutionReverted which means revert-and-keep-gas-left. -func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte, err error) { +func Run[TS TracingSwitch](evm *EVM, contract *Contract, input []byte, readOnly bool) (ret []byte, err error) { // Increment the call depth which is restricted to 1024 evm.depth++ defer func() { evm.depth-- }() @@ -134,7 +146,6 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte gasCopy uint64 // for EVMLogger to log gas remaining before execution logged bool // deferred EVMLogger should ignore already logged steps res []byte // result of the opcode execution function - debug = evm.Config.Tracer != nil isEIP4762 = evm.chainRules.IsEIP4762 ) // Don't move this deferred function, it's placed before the OnOpcode-deferred method, @@ -146,7 +157,7 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte }() contract.Input = input - if debug { + if tracingIsEnabled[TS]() { defer func() { // this deferred method handles exit-with-error if err == nil { return @@ -165,7 +176,7 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte // parent context. _ = jumpTable[0] // nil-check the jumpTable out of the loop for { - if debug { + if tracingIsEnabled[TS]() { // Capture pre-execution values for tracing. logged, pcCopy, gasCopy = false, pc, contract.Gas } @@ -175,7 +186,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) - contract.UseGas(consumed, evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk) + useGas[TS](contract, consumed, evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk) if consumed < wanted { return nil, ErrOutOfGas } @@ -234,7 +245,7 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte } // Do tracing before potential memory expansion - if debug { + if tracingIsEnabled[TS]() { if evm.Config.Tracer.OnGasChange != nil { evm.Config.Tracer.OnGasChange(gasCopy, gasCopy-cost, tracing.GasChangeCallOpCode) } @@ -261,3 +272,26 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte return res, err } + +// useGas attempts the use gas and subtracts it and returns true on success +func useGas[TS TracingSwitch](c *Contract, gas uint64, hooks *tracing.Hooks, reason tracing.GasChangeReason) (ok bool) { + if c.Gas < gas { + return false + } + if tracingIsEnabled[TS]() && hooks.OnGasChange != nil && reason != tracing.GasChangeIgnored { + hooks.OnGasChange(c.Gas, c.Gas-gas, reason) + } + c.Gas -= gas + return true +} + +// refundGas refunds gas to the contract +func refundGas[TS TracingSwitch](c *Contract, gas uint64, hooks *tracing.Hooks, reason tracing.GasChangeReason) { + if gas == 0 { + return + } + if tracingIsEnabled[TS]() && hooks.OnGasChange != nil && reason != tracing.GasChangeIgnored { + hooks.OnGasChange(c.Gas, c.Gas+gas, reason) + } + c.Gas += gas +} diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index d7a4d9da1d9..d3a9dd453db 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -48,21 +48,21 @@ type operation struct { } var ( - frontierInstructionSet = newFrontierInstructionSet() - homesteadInstructionSet = newHomesteadInstructionSet() - tangerineWhistleInstructionSet = newTangerineWhistleInstructionSet() - spuriousDragonInstructionSet = newSpuriousDragonInstructionSet() - byzantiumInstructionSet = newByzantiumInstructionSet() - constantinopleInstructionSet = newConstantinopleInstructionSet() - istanbulInstructionSet = newIstanbulInstructionSet() - berlinInstructionSet = newBerlinInstructionSet() - londonInstructionSet = newLondonInstructionSet() - mergeInstructionSet = newMergeInstructionSet() - shanghaiInstructionSet = newShanghaiInstructionSet() - cancunInstructionSet = newCancunInstructionSet() - verkleInstructionSet = newVerkleInstructionSet() - pragueInstructionSet = newPragueInstructionSet() - osakaInstructionSet = newOsakaInstructionSet() + frontierInstructionSet, frontierInstructionSetNT = newFrontierInstructionSet[TracingEnabled](), newFrontierInstructionSet[TracingDisabled]() + homesteadInstructionSet, homesteadInstructionSetNT = newHomesteadInstructionSet[TracingEnabled](), newHomesteadInstructionSet[TracingDisabled]() + tangerineWhistleInstructionSet, tangerineWhistleInstructionSetNT = newTangerineWhistleInstructionSet[TracingEnabled](), newTangerineWhistleInstructionSet[TracingDisabled]() + spuriousDragonInstructionSet, spuriousDragonInstructionSetNT = newSpuriousDragonInstructionSet[TracingEnabled](), newSpuriousDragonInstructionSet[TracingDisabled]() + byzantiumInstructionSet, byzantiumInstructionSetNT = newByzantiumInstructionSet[TracingEnabled](), newByzantiumInstructionSet[TracingDisabled]() + constantinopleInstructionSet, constantinopleInstructionSetNT = newConstantinopleInstructionSet[TracingEnabled](), newConstantinopleInstructionSet[TracingDisabled]() + istanbulInstructionSet, istanbulInstructionSetNT = newIstanbulInstructionSet[TracingEnabled](), newIstanbulInstructionSet[TracingDisabled]() + berlinInstructionSet, berlinInstructionSetNT = newBerlinInstructionSet[TracingEnabled](), newBerlinInstructionSet[TracingDisabled]() + londonInstructionSet, londonInstructionSetNT = newLondonInstructionSet[TracingEnabled](), newLondonInstructionSet[TracingDisabled]() + mergeInstructionSet, mergeInstructionSetNT = newMergeInstructionSet[TracingEnabled](), newMergeInstructionSet[TracingDisabled]() + shanghaiInstructionSet, shanghaiInstructionSetNT = newShanghaiInstructionSet[TracingEnabled](), newShanghaiInstructionSet[TracingDisabled]() + cancunInstructionSet, cancunInstructionSetNT = newCancunInstructionSet[TracingEnabled](), newCancunInstructionSet[TracingDisabled]() + verkleInstructionSet, verkleInstructionSetNT = newVerkleInstructionSet[TracingEnabled](), newVerkleInstructionSet[TracingDisabled]() + pragueInstructionSet, pragueInstructionSetNT = newPragueInstructionSet[TracingEnabled](), newPragueInstructionSet[TracingDisabled]() + osakaInstructionSet, osakaInstructionSetNT = newOsakaInstructionSet[TracingEnabled](), newOsakaInstructionSet[TracingDisabled]() ) // JumpTable contains the EVM opcodes supported at a given fork. @@ -86,26 +86,26 @@ func validate(jt JumpTable) JumpTable { return jt } -func newVerkleInstructionSet() JumpTable { - instructionSet := newShanghaiInstructionSet() - enable4762(&instructionSet) +func newVerkleInstructionSet[TS TracingSwitch]() JumpTable { + instructionSet := newShanghaiInstructionSet[TS]() + enable4762[TS](&instructionSet) return validate(instructionSet) } -func newOsakaInstructionSet() JumpTable { - instructionSet := newPragueInstructionSet() +func newOsakaInstructionSet[TS TracingSwitch]() JumpTable { + instructionSet := newPragueInstructionSet[TS]() enable7939(&instructionSet) // EIP-7939 (CLZ opcode) return validate(instructionSet) } -func newPragueInstructionSet() JumpTable { - instructionSet := newCancunInstructionSet() - enable7702(&instructionSet) // EIP-7702 Setcode transaction type +func newPragueInstructionSet[TS TracingSwitch]() JumpTable { + instructionSet := newCancunInstructionSet[TS]() + enable7702[TS](&instructionSet) // EIP-7702 Setcode transaction type return validate(instructionSet) } -func newCancunInstructionSet() JumpTable { - instructionSet := newShanghaiInstructionSet() +func newCancunInstructionSet[TS TracingSwitch]() JumpTable { + instructionSet := newShanghaiInstructionSet[TS]() enable4844(&instructionSet) // EIP-4844 (BLOBHASH opcode) enable7516(&instructionSet) // EIP-7516 (BLOBBASEFEE opcode) enable1153(&instructionSet) // EIP-1153 "Transient Storage" @@ -114,16 +114,16 @@ func newCancunInstructionSet() JumpTable { return validate(instructionSet) } -func newShanghaiInstructionSet() JumpTable { - instructionSet := newMergeInstructionSet() +func newShanghaiInstructionSet[TS TracingSwitch]() JumpTable { + instructionSet := newMergeInstructionSet[TS]() enable3855(&instructionSet) // PUSH0 instruction enable3860(&instructionSet) // Limit and meter initcode return validate(instructionSet) } -func newMergeInstructionSet() JumpTable { - instructionSet := newLondonInstructionSet() +func newMergeInstructionSet[TS TracingSwitch]() JumpTable { + instructionSet := newLondonInstructionSet[TS]() instructionSet[PREVRANDAO] = &operation{ execute: opRandom, constantGas: GasQuickStep, @@ -135,8 +135,8 @@ func newMergeInstructionSet() JumpTable { // newLondonInstructionSet returns the frontier, homestead, byzantium, // constantinople, istanbul, petersburg, berlin and london instructions. -func newLondonInstructionSet() JumpTable { - instructionSet := newBerlinInstructionSet() +func newLondonInstructionSet[TS TracingSwitch]() JumpTable { + instructionSet := newBerlinInstructionSet[TS]() enable3529(&instructionSet) // EIP-3529: Reduction in refunds https://eips.ethereum.org/EIPS/eip-3529 enable3198(&instructionSet) // Base fee opcode https://eips.ethereum.org/EIPS/eip-3198 return validate(instructionSet) @@ -144,16 +144,16 @@ func newLondonInstructionSet() JumpTable { // newBerlinInstructionSet returns the frontier, homestead, byzantium, // constantinople, istanbul, petersburg and berlin instructions. -func newBerlinInstructionSet() JumpTable { - instructionSet := newIstanbulInstructionSet() - enable2929(&instructionSet) // Gas cost increases for state access opcodes https://eips.ethereum.org/EIPS/eip-2929 +func newBerlinInstructionSet[TS TracingSwitch]() JumpTable { + instructionSet := newIstanbulInstructionSet[TS]() + enable2929[TS](&instructionSet) // Gas cost increases for state access opcodes https://eips.ethereum.org/EIPS/eip-2929 return validate(instructionSet) } // newIstanbulInstructionSet returns the frontier, homestead, byzantium, // constantinople, istanbul and petersburg instructions. -func newIstanbulInstructionSet() JumpTable { - instructionSet := newConstantinopleInstructionSet() +func newIstanbulInstructionSet[TS TracingSwitch]() JumpTable { + instructionSet := newConstantinopleInstructionSet[TS]() enable1344(&instructionSet) // ChainID opcode - https://eips.ethereum.org/EIPS/eip-1344 enable1884(&instructionSet) // Reprice reader opcodes - https://eips.ethereum.org/EIPS/eip-1884 @@ -164,8 +164,8 @@ func newIstanbulInstructionSet() JumpTable { // newConstantinopleInstructionSet returns the frontier, homestead, // byzantium and constantinople instructions. -func newConstantinopleInstructionSet() JumpTable { - instructionSet := newByzantiumInstructionSet() +func newConstantinopleInstructionSet[TS TracingSwitch]() JumpTable { + instructionSet := newByzantiumInstructionSet[TS]() instructionSet[SHL] = &operation{ execute: opSHL, constantGas: GasFastestStep, @@ -191,7 +191,7 @@ func newConstantinopleInstructionSet() JumpTable { maxStack: maxStack(1, 1), } instructionSet[CREATE2] = &operation{ - execute: opCreate2, + execute: opCreate2[TS], constantGas: params.Create2Gas, dynamicGas: gasCreate2, minStack: minStack(4, 1), @@ -203,10 +203,10 @@ func newConstantinopleInstructionSet() JumpTable { // newByzantiumInstructionSet returns the frontier, homestead and // byzantium instructions. -func newByzantiumInstructionSet() JumpTable { - instructionSet := newSpuriousDragonInstructionSet() +func newByzantiumInstructionSet[TS TracingSwitch]() JumpTable { + instructionSet := newSpuriousDragonInstructionSet[TS]() instructionSet[STATICCALL] = &operation{ - execute: opStaticCall, + execute: opStaticCall[TS], constantGas: params.CallGasEIP150, dynamicGas: gasStaticCall, minStack: minStack(6, 1), @@ -238,15 +238,15 @@ func newByzantiumInstructionSet() JumpTable { } // EIP 158 a.k.a Spurious Dragon -func newSpuriousDragonInstructionSet() JumpTable { - instructionSet := newTangerineWhistleInstructionSet() +func newSpuriousDragonInstructionSet[TS TracingSwitch]() JumpTable { + instructionSet := newTangerineWhistleInstructionSet[TS]() instructionSet[EXP].dynamicGas = gasExpEIP158 return validate(instructionSet) } // EIP 150 a.k.a Tangerine Whistle -func newTangerineWhistleInstructionSet() JumpTable { - instructionSet := newHomesteadInstructionSet() +func newTangerineWhistleInstructionSet[TS TracingSwitch]() JumpTable { + instructionSet := newHomesteadInstructionSet[TS]() instructionSet[BALANCE].constantGas = params.BalanceGasEIP150 instructionSet[EXTCODESIZE].constantGas = params.ExtcodeSizeGasEIP150 instructionSet[SLOAD].constantGas = params.SloadGasEIP150 @@ -259,10 +259,10 @@ func newTangerineWhistleInstructionSet() JumpTable { // newHomesteadInstructionSet returns the frontier and homestead // instructions that can be executed during the homestead phase. -func newHomesteadInstructionSet() JumpTable { - instructionSet := newFrontierInstructionSet() +func newHomesteadInstructionSet[TS TracingSwitch]() JumpTable { + instructionSet := newFrontierInstructionSet[TS]() instructionSet[DELEGATECALL] = &operation{ - execute: opDelegateCall, + execute: opDelegateCall[TS], dynamicGas: gasDelegateCall, constantGas: params.CallGasFrontier, minStack: minStack(6, 1), @@ -274,7 +274,7 @@ func newHomesteadInstructionSet() JumpTable { // newFrontierInstructionSet returns the frontier instructions // that can be executed during the frontier phase. -func newFrontierInstructionSet() JumpTable { +func newFrontierInstructionSet[TS TracingSwitch]() JumpTable { tbl := JumpTable{ STOP: { execute: opStop, @@ -1040,7 +1040,7 @@ func newFrontierInstructionSet() JumpTable { memorySize: memoryLog, }, CREATE: { - execute: opCreate, + execute: opCreate[TS], constantGas: params.CreateGas, dynamicGas: gasCreate, minStack: minStack(3, 1), @@ -1048,7 +1048,7 @@ func newFrontierInstructionSet() JumpTable { memorySize: memoryCreate, }, CALL: { - execute: opCall, + execute: opCall[TS], constantGas: params.CallGasFrontier, dynamicGas: gasCall, minStack: minStack(7, 1), @@ -1056,7 +1056,7 @@ func newFrontierInstructionSet() JumpTable { memorySize: memoryCall, }, CALLCODE: { - execute: opCallCode, + execute: opCallCode[TS], constantGas: params.CallGasFrontier, dynamicGas: gasCallCode, minStack: minStack(7, 1), diff --git a/core/vm/jump_table_export.go b/core/vm/jump_table_export.go index 89a2ebf6f4f..aad48cf09ab 100644 --- a/core/vm/jump_table_export.go +++ b/core/vm/jump_table_export.go @@ -24,38 +24,38 @@ import ( // LookupInstructionSet returns the instruction set for the fork configured by // the rules. -func LookupInstructionSet(rules params.Rules) (JumpTable, error) { +func LookupInstructionSet[TS TracingSwitch](rules params.Rules) (JumpTable, error) { switch { case rules.IsVerkle: - return newCancunInstructionSet(), errors.New("verkle-fork not defined yet") + return newCancunInstructionSet[TS](), errors.New("verkle-fork not defined yet") case rules.IsOsaka: - return newOsakaInstructionSet(), nil + return newOsakaInstructionSet[TS](), nil case rules.IsPrague: - return newPragueInstructionSet(), nil + return newPragueInstructionSet[TS](), nil case rules.IsCancun: - return newCancunInstructionSet(), nil + return newCancunInstructionSet[TS](), nil case rules.IsShanghai: - return newShanghaiInstructionSet(), nil + return newShanghaiInstructionSet[TS](), nil case rules.IsMerge: - return newMergeInstructionSet(), nil + return newMergeInstructionSet[TS](), nil case rules.IsLondon: - return newLondonInstructionSet(), nil + return newLondonInstructionSet[TS](), nil case rules.IsBerlin: - return newBerlinInstructionSet(), nil + return newBerlinInstructionSet[TS](), nil case rules.IsIstanbul: - return newIstanbulInstructionSet(), nil + return newIstanbulInstructionSet[TS](), nil case rules.IsConstantinople: - return newConstantinopleInstructionSet(), nil + return newConstantinopleInstructionSet[TS](), nil case rules.IsByzantium: - return newByzantiumInstructionSet(), nil + return newByzantiumInstructionSet[TS](), nil case rules.IsEIP158: - return newSpuriousDragonInstructionSet(), nil + return newSpuriousDragonInstructionSet[TS](), nil case rules.IsEIP150: - return newTangerineWhistleInstructionSet(), nil + return newTangerineWhistleInstructionSet[TS](), nil case rules.IsHomestead: - return newHomesteadInstructionSet(), nil + return newHomesteadInstructionSet[TS](), nil } - return newFrontierInstructionSet(), nil + return newFrontierInstructionSet[TS](), nil } // Stack returns the minimum and maximum stack requirements. diff --git a/core/vm/jump_table_test.go b/core/vm/jump_table_test.go index a4f9759ed2b..81dffb1188f 100644 --- a/core/vm/jump_table_test.go +++ b/core/vm/jump_table_test.go @@ -24,7 +24,7 @@ import ( // TestJumpTableCopy tests that deep copy is necessary to prevent modify shared jump table func TestJumpTableCopy(t *testing.T) { - tbl := newMergeInstructionSet() + tbl := newMergeInstructionSet[TracingEnabled]() require.Equal(t, uint64(0), tbl[SLOAD].constantGas) // a deep copy won't modify the shared jump table diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go index 085b018e4c4..df15c103d44 100644 --- a/core/vm/operations_acl.go +++ b/core/vm/operations_acl.go @@ -152,7 +152,7 @@ func gasEip2929AccountCheck(evm *EVM, contract *Contract, stack *Stack, mem *Mem return 0, nil } -func makeCallVariantGasCallEIP2929(oldCalculator gasFunc, addressPosition int) gasFunc { +func makeCallVariantGasCallEIP2929[TS TracingSwitch](oldCalculator gasFunc, addressPosition int) gasFunc { return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { addr := common.Address(stack.Back(addressPosition).Bytes20()) // Check slot presence in the access list @@ -164,7 +164,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 !useGas[TS](contract, coldCost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) { return 0, ErrOutOfGas } } @@ -192,10 +192,6 @@ func makeCallVariantGasCallEIP2929(oldCalculator gasFunc, addressPosition int) g } var ( - gasCallEIP2929 = makeCallVariantGasCallEIP2929(gasCall, 1) - gasDelegateCallEIP2929 = makeCallVariantGasCallEIP2929(gasDelegateCall, 1) - gasStaticCallEIP2929 = makeCallVariantGasCallEIP2929(gasStaticCall, 1) - gasCallCodeEIP2929 = makeCallVariantGasCallEIP2929(gasCallCode, 1) gasSelfdestructEIP2929 = makeSelfdestructGasFn(true) // gasSelfdestructEIP3529 implements the changes in EIP-3529 (no refunds) gasSelfdestructEIP3529 = makeSelfdestructGasFn(false) @@ -243,14 +239,7 @@ func makeSelfdestructGasFn(refundsEnabled bool) gasFunc { return gasFunc } -var ( - gasCallEIP7702 = makeCallVariantGasCallEIP7702(gasCall) - gasDelegateCallEIP7702 = makeCallVariantGasCallEIP7702(gasDelegateCall) - gasStaticCallEIP7702 = makeCallVariantGasCallEIP7702(gasStaticCall) - gasCallCodeEIP7702 = makeCallVariantGasCallEIP7702(gasCallCode) -) - -func makeCallVariantGasCallEIP7702(oldCalculator gasFunc) gasFunc { +func makeCallVariantGasCallEIP7702[TS TracingSwitch](oldCalculator gasFunc) gasFunc { return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { var ( total uint64 // total dynamic gas used @@ -265,7 +254,7 @@ func makeCallVariantGasCallEIP7702(oldCalculator gasFunc) gasFunc { coldCost := params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929 // Charge the remaining difference here already, to correctly calculate available // gas for call - if !contract.UseGas(coldCost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) { + if !useGas[TS](contract, coldCost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) { return 0, ErrOutOfGas } total += coldCost @@ -280,7 +269,7 @@ func makeCallVariantGasCallEIP7702(oldCalculator gasFunc) gasFunc { evm.StateDB.AddAddressToAccessList(target) cost = params.ColdAccountAccessCostEIP2929 } - if !contract.UseGas(cost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) { + if !useGas[TS](contract, cost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) { return 0, ErrOutOfGas } total += cost diff --git a/eth/tracers/js/tracer_test.go b/eth/tracers/js/tracer_test.go index 7f376a27fc2..66ad8b9210a 100644 --- a/eth/tracers/js/tracer_test.go +++ b/eth/tracers/js/tracer_test.go @@ -65,7 +65,7 @@ func runTrace(tracer *tracers.Tracer, vmctx *vmContext, chaincfg *params.ChainCo tracer.OnTxStart(evm.GetVMContext(), types.NewTx(&types.LegacyTx{Gas: gasLimit, GasPrice: vmctx.txCtx.GasPrice}), contract.Caller()) tracer.OnEnter(0, byte(vm.CALL), contract.Caller(), contract.Address(), []byte{}, startGas, value.ToBig()) - ret, err := evm.Run(contract, []byte{}, false) + ret, err := vm.Run[vm.TracingEnabled](evm, contract, []byte{}, false) tracer.OnExit(0, ret, startGas-contract.Gas, err, true) // Rest gas assumes no refund tracer.OnTxEnd(&types.Receipt{GasUsed: gasLimit - contract.Gas}, nil) diff --git a/eth/tracers/logger/logger_test.go b/eth/tracers/logger/logger_test.go index acc3069e70a..051644d0368 100644 --- a/eth/tracers/logger/logger_test.go +++ b/eth/tracers/logger/logger_test.go @@ -52,7 +52,7 @@ func TestStoreCapture(t *testing.T) { contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x0, byte(vm.SSTORE)} var index common.Hash logger.OnTxStart(evm.GetVMContext(), nil, common.Address{}) - _, err := evm.Run(contract, []byte{}, false) + _, err := vm.Run[vm.TracingEnabled](evm, contract, []byte{}, false) if err != nil { t.Fatal(err) }