core/vm: generic not-traced EVM implementation

this allows the EVM to follow the not-traced codepaths when
it is not configured for tracing and skip the runtime checks
This commit is contained in:
Ömer Faruk IRMAK 2025-08-12 13:11:13 +03:00
parent c3ef6c77c2
commit 94fe5c62e4
14 changed files with 260 additions and 238 deletions

View file

@ -18,7 +18,6 @@ package vm
import ( import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/holiman/uint256" "github.com/holiman/uint256"
) )
@ -125,29 +124,6 @@ func (c *Contract) Caller() common.Address {
return c.caller 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 // Address returns the contracts address
func (c *Contract) Address() common.Address { func (c *Contract) Address() common.Address {
return c.address return c.address

View file

@ -257,12 +257,12 @@ func ActivePrecompiles(rules params.Rules) []common.Address {
// - the returned bytes, // - the returned bytes,
// - the _remaining_ gas, // - the _remaining_ gas,
// - any error that occurred // - 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) gasCost := p.RequiredGas(input)
if suppliedGas < gasCost { if suppliedGas < gasCost {
return nil, 0, ErrOutOfGas return nil, 0, ErrOutOfGas
} }
if logger != nil && logger.OnGasChange != nil { if tracingIsEnabled[TS]() && logger.OnGasChange != nil {
logger.OnGasChange(suppliedGas, suppliedGas-gasCost, tracing.GasChangeCallPrecompiledContract) logger.OnGasChange(suppliedGas, suppliedGas-gasCost, tracing.GasChangeCallPrecompiledContract)
} }
suppliedGas -= gasCost suppliedGas -= gasCost

View file

@ -36,7 +36,7 @@ func FuzzPrecompiledContracts(f *testing.F) {
return return
} }
inWant := string(input) inWant := string(input)
RunPrecompiledContract(p, input, gas, nil) RunPrecompiledContract[TracingDisabled](p, input, gas, nil)
if inHave := string(input); inWant != inHave { if inHave := string(input); inWant != inHave {
t.Errorf("Precompiled %v modified input data", a) t.Errorf("Precompiled %v modified input data", a)
} }

View file

@ -99,7 +99,7 @@ func testPrecompiled(addr string, test precompiledTest, t *testing.T) {
in := common.Hex2Bytes(test.Input) in := common.Hex2Bytes(test.Input)
gas := p.RequiredGas(in) gas := p.RequiredGas(in)
t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) { 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) t.Error(err)
} else if common.Bytes2Hex(res) != test.Expected { } else if common.Bytes2Hex(res) != test.Expected {
t.Errorf("Expected %v, got %v", test.Expected, common.Bytes2Hex(res)) 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 gas := p.RequiredGas(in) - 1
t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) { 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" { if err.Error() != "out of gas" {
t.Errorf("Expected error [out of gas], got [%v]", err) 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) in := common.Hex2Bytes(test.Input)
gas := p.RequiredGas(in) gas := p.RequiredGas(in)
t.Run(test.Name, func(t *testing.T) { 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 { if err.Error() != test.ExpectedError {
t.Errorf("Expected error [%v], got [%v]", test.ExpectedError, err) 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() bench.ResetTimer()
for i := 0; i < bench.N; i++ { for i := 0; i < bench.N; i++ {
copy(data, in) copy(data, in)
res, _, err = RunPrecompiledContract(p, data, reqGas, nil) res, _, err = RunPrecompiledContract[TracingDisabled](p, data, reqGas, nil)
} }
bench.StopTimer() bench.StopTimer()
elapsed := uint64(time.Since(start)) elapsed := uint64(time.Since(start))

View file

@ -27,32 +27,36 @@ import (
"github.com/holiman/uint256" "github.com/holiman/uint256"
) )
var activators = map[int]func(*JumpTable){ var activators = map[int][2]func(*JumpTable){
5656: enable5656, 5656: {enable5656},
6780: enable6780, 6780: {enable6780},
3855: enable3855, 3855: {enable3855},
3860: enable3860, 3860: {enable3860},
3529: enable3529, 3529: {enable3529},
3198: enable3198, 3198: {enable3198},
2929: enable2929, 2929: {enable2929[TracingEnabled], enable2929[TracingDisabled]},
2200: enable2200, 2200: {enable2200},
1884: enable1884, 1884: {enable1884},
1344: enable1344, 1344: {enable1344},
1153: enable1153, 1153: {enable1153},
4762: enable4762, 4762: {enable4762[TracingEnabled], enable4762[TracingDisabled]},
7702: enable7702, 7702: {enable7702[TracingEnabled], enable7702[TracingDisabled]},
7939: enable7939, 7939: {enable7939},
} }
// EnableEIP enables the given EIP on the config. // EnableEIP enables the given EIP on the config.
// This operation writes in-place, and callers need to ensure that the globally // This operation writes in-place, and callers need to ensure that the globally
// defined jump tables are not polluted. // defined jump tables are not polluted.
func EnableEIP(eipNum int, jt *JumpTable) error { func EnableEIP(eipNum int, jt *JumpTable, traced bool) error {
enablerFn, ok := activators[eipNum] enablerFns, ok := activators[eipNum]
if !ok { if !ok {
return fmt.Errorf("undefined eip %d", eipNum) return fmt.Errorf("undefined eip %d", eipNum)
} }
enablerFn(jt) if traced || enablerFns[1] == nil {
enablerFns[0](jt)
} else {
enablerFns[1](jt)
}
return nil return nil
} }
@ -122,7 +126,7 @@ func enable2200(jt *JumpTable) {
// enable2929 enables "EIP-2929: Gas cost increases for state access opcodes" // enable2929 enables "EIP-2929: Gas cost increases for state access opcodes"
// https://eips.ethereum.org/EIPS/eip-2929 // https://eips.ethereum.org/EIPS/eip-2929
func enable2929(jt *JumpTable) { func enable2929[TS TracingSwitch](jt *JumpTable) {
jt[SSTORE].dynamicGas = gasSStoreEIP2929 jt[SSTORE].dynamicGas = gasSStoreEIP2929
jt[SLOAD].constantGas = 0 jt[SLOAD].constantGas = 0
@ -141,16 +145,16 @@ func enable2929(jt *JumpTable) {
jt[BALANCE].dynamicGas = gasEip2929AccountCheck jt[BALANCE].dynamicGas = gasEip2929AccountCheck
jt[CALL].constantGas = params.WarmStorageReadCostEIP2929 jt[CALL].constantGas = params.WarmStorageReadCostEIP2929
jt[CALL].dynamicGas = gasCallEIP2929 jt[CALL].dynamicGas = makeCallVariantGasCallEIP2929[TS](gasCall, 1)
jt[CALLCODE].constantGas = params.WarmStorageReadCostEIP2929 jt[CALLCODE].constantGas = params.WarmStorageReadCostEIP2929
jt[CALLCODE].dynamicGas = gasCallCodeEIP2929 jt[CALLCODE].dynamicGas = makeCallVariantGasCallEIP2929[TS](gasCallCode, 1)
jt[STATICCALL].constantGas = params.WarmStorageReadCostEIP2929 jt[STATICCALL].constantGas = params.WarmStorageReadCostEIP2929
jt[STATICCALL].dynamicGas = gasStaticCallEIP2929 jt[STATICCALL].dynamicGas = makeCallVariantGasCallEIP2929[TS](gasStaticCall, 1)
jt[DELEGATECALL].constantGas = params.WarmStorageReadCostEIP2929 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 // This was previously part of the dynamic cost, but we're using it as a constantGas
// factor here // 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 ( var (
stack = scope.Stack stack = scope.Stack
a = stack.pop() a = stack.pop()
@ -358,7 +362,7 @@ func opExtCodeCopyEIP4762(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, er
code := evm.StateDB.GetCode(addr) code := evm.StateDB.GetCode(addr)
paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(code, uint64CodeOffset, length.Uint64()) paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(code, uint64CodeOffset, length.Uint64())
consumed, wanted := evm.AccessEvents.CodeChunksRangeGas(addr, copyOffset, nonPaddedCopyLength, uint64(len(code)), false, scope.Contract.Gas) 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 { if consumed < wanted {
return nil, ErrOutOfGas 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 // 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 // need not worry about the adjusted bound logic when adding the PUSHDATA to
// the list of access events. // 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 ( var (
codeLen = uint64(len(scope.Contract.Code)) codeLen = uint64(len(scope.Contract.Code))
integer = new(uint256.Int) integer = new(uint256.Int)
@ -384,7 +388,7 @@ func opPush1EIP4762(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
// advanced past this boundary. // advanced past this boundary.
contractAddr := scope.Contract.Address() contractAddr := scope.Contract.Address()
consumed, wanted := evm.AccessEvents.CodeChunksRangeGas(contractAddr, *pc+1, uint64(1), uint64(len(scope.Contract.Code)), false, scope.Contract.Gas) 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 { if consumed < wanted {
return nil, ErrOutOfGas return nil, ErrOutOfGas
} }
@ -395,7 +399,7 @@ func opPush1EIP4762(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
return nil, nil 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) { return func(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
var ( var (
codeLen = len(scope.Contract.Code) codeLen = len(scope.Contract.Code)
@ -412,7 +416,7 @@ func makePushEIP4762(size uint64, pushByteSize int) executionFunc {
if !scope.Contract.IsDeployment && !scope.Contract.IsSystemCall { if !scope.Contract.IsDeployment && !scope.Contract.IsSystemCall {
contractAddr := scope.Contract.Address() contractAddr := scope.Contract.Address()
consumed, wanted := evm.AccessEvents.CodeChunksRangeGas(contractAddr, uint64(start), uint64(pushByteSize), uint64(len(scope.Contract.Code)), false, scope.Contract.Gas) 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 { if consumed < wanted {
return nil, ErrOutOfGas 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{ jt[SSTORE] = &operation{
dynamicGas: gasSStore4762, dynamicGas: gasSStore4762,
execute: opSstore, execute: opSstore,
@ -459,7 +463,7 @@ func enable4762(jt *JumpTable) {
} }
jt[EXTCODECOPY] = &operation{ jt[EXTCODECOPY] = &operation{
execute: opExtCodeCopyEIP4762, execute: opExtCodeCopyEIP4762[TS],
dynamicGas: gasExtCodeCopyEIP4762, dynamicGas: gasExtCodeCopyEIP4762,
minStack: minStack(4, 0), minStack: minStack(4, 0),
maxStack: maxStack(4, 0), maxStack: maxStack(4, 0),
@ -484,7 +488,7 @@ func enable4762(jt *JumpTable) {
} }
jt[CREATE] = &operation{ jt[CREATE] = &operation{
execute: opCreate, execute: opCreate[TS],
constantGas: params.CreateNGasEip4762, constantGas: params.CreateNGasEip4762,
dynamicGas: gasCreateEip3860, dynamicGas: gasCreateEip3860,
minStack: minStack(3, 1), minStack: minStack(3, 1),
@ -493,7 +497,7 @@ func enable4762(jt *JumpTable) {
} }
jt[CREATE2] = &operation{ jt[CREATE2] = &operation{
execute: opCreate2, execute: opCreate2[TS],
constantGas: params.CreateNGasEip4762, constantGas: params.CreateNGasEip4762,
dynamicGas: gasCreate2Eip3860, dynamicGas: gasCreate2Eip3860,
minStack: minStack(4, 1), minStack: minStack(4, 1),
@ -502,7 +506,7 @@ func enable4762(jt *JumpTable) {
} }
jt[CALL] = &operation{ jt[CALL] = &operation{
execute: opCall, execute: opCall[TS],
dynamicGas: gasCallEIP4762, dynamicGas: gasCallEIP4762,
minStack: minStack(7, 1), minStack: minStack(7, 1),
maxStack: maxStack(7, 1), maxStack: maxStack(7, 1),
@ -510,7 +514,7 @@ func enable4762(jt *JumpTable) {
} }
jt[CALLCODE] = &operation{ jt[CALLCODE] = &operation{
execute: opCallCode, execute: opCallCode[TS],
dynamicGas: gasCallCodeEIP4762, dynamicGas: gasCallCodeEIP4762,
minStack: minStack(7, 1), minStack: minStack(7, 1),
maxStack: maxStack(7, 1), maxStack: maxStack(7, 1),
@ -518,7 +522,7 @@ func enable4762(jt *JumpTable) {
} }
jt[STATICCALL] = &operation{ jt[STATICCALL] = &operation{
execute: opStaticCall, execute: opStaticCall[TS],
dynamicGas: gasStaticCallEIP4762, dynamicGas: gasStaticCallEIP4762,
minStack: minStack(6, 1), minStack: minStack(6, 1),
maxStack: maxStack(6, 1), maxStack: maxStack(6, 1),
@ -526,7 +530,7 @@ func enable4762(jt *JumpTable) {
} }
jt[DELEGATECALL] = &operation{ jt[DELEGATECALL] = &operation{
execute: opDelegateCall, execute: opDelegateCall[TS],
dynamicGas: gasDelegateCallEIP4762, dynamicGas: gasDelegateCallEIP4762,
minStack: minStack(6, 1), minStack: minStack(6, 1),
maxStack: maxStack(6, 1), maxStack: maxStack(6, 1),
@ -534,14 +538,14 @@ func enable4762(jt *JumpTable) {
} }
jt[PUSH1] = &operation{ jt[PUSH1] = &operation{
execute: opPush1EIP4762, execute: opPush1EIP4762[TS],
constantGas: GasFastestStep, constantGas: GasFastestStep,
minStack: minStack(0, 1), minStack: minStack(0, 1),
maxStack: maxStack(0, 1), maxStack: maxStack(0, 1),
} }
for i := 1; i < 32; i++ { for i := 1; i < 32; i++ {
jt[PUSH1+OpCode(i)] = &operation{ jt[PUSH1+OpCode(i)] = &operation{
execute: makePushEIP4762(uint64(i+1), i+1), execute: makePushEIP4762[TS](uint64(i+1), i+1),
constantGas: GasFastestStep, constantGas: GasFastestStep,
minStack: minStack(0, 1), minStack: minStack(0, 1),
maxStack: maxStack(0, 1), maxStack: maxStack(0, 1),
@ -550,9 +554,9 @@ func enable4762(jt *JumpTable) {
} }
// enable7702 the EIP-7702 changes to support delegation designators. // enable7702 the EIP-7702 changes to support delegation designators.
func enable7702(jt *JumpTable) { func enable7702[TS TracingSwitch](jt *JumpTable) {
jt[CALL].dynamicGas = gasCallEIP7702 jt[CALL].dynamicGas = makeCallVariantGasCallEIP7702[TS](gasCall)
jt[CALLCODE].dynamicGas = gasCallCodeEIP7702 jt[CALLCODE].dynamicGas = makeCallVariantGasCallEIP7702[TS](gasCallCode)
jt[STATICCALL].dynamicGas = gasStaticCallEIP7702 jt[STATICCALL].dynamicGas = makeCallVariantGasCallEIP7702[TS](gasStaticCall)
jt[DELEGATECALL].dynamicGas = gasDelegateCallEIP7702 jt[DELEGATECALL].dynamicGas = makeCallVariantGasCallEIP7702[TS](gasDelegateCall)
} }

View file

@ -148,38 +148,44 @@ func NewEVM(blockCtx BlockContext, statedb StateDB, chainConfig *params.ChainCon
} }
evm.precompiles = activePrecompiledContracts(evm.chainRules) evm.precompiles = activePrecompiledContracts(evm.chainRules)
var table, tableNT *JumpTable
switch { switch {
case evm.chainRules.IsOsaka: case evm.chainRules.IsOsaka:
evm.table = &osakaInstructionSet table, tableNT = &osakaInstructionSet, &osakaInstructionSetNT
case evm.chainRules.IsVerkle: case evm.chainRules.IsVerkle:
// TODO replace with proper instruction set when fork is specified // TODO replace with proper instruction set when fork is specified
evm.table = &verkleInstructionSet table, tableNT = &verkleInstructionSet, &verkleInstructionSetNT
case evm.chainRules.IsPrague: case evm.chainRules.IsPrague:
evm.table = &pragueInstructionSet table, tableNT = &pragueInstructionSet, &pragueInstructionSetNT
case evm.chainRules.IsCancun: case evm.chainRules.IsCancun:
evm.table = &cancunInstructionSet table, tableNT = &cancunInstructionSet, &cancunInstructionSetNT
case evm.chainRules.IsShanghai: case evm.chainRules.IsShanghai:
evm.table = &shanghaiInstructionSet table, tableNT = &shanghaiInstructionSet, &shanghaiInstructionSetNT
case evm.chainRules.IsMerge: case evm.chainRules.IsMerge:
evm.table = &mergeInstructionSet table, tableNT = &mergeInstructionSet, &mergeInstructionSetNT
case evm.chainRules.IsLondon: case evm.chainRules.IsLondon:
evm.table = &londonInstructionSet table, tableNT = &londonInstructionSet, &londonInstructionSetNT
case evm.chainRules.IsBerlin: case evm.chainRules.IsBerlin:
evm.table = &berlinInstructionSet table, tableNT = &berlinInstructionSet, &berlinInstructionSetNT
case evm.chainRules.IsIstanbul: case evm.chainRules.IsIstanbul:
evm.table = &istanbulInstructionSet table, tableNT = &istanbulInstructionSet, &istanbulInstructionSetNT
case evm.chainRules.IsConstantinople: case evm.chainRules.IsConstantinople:
evm.table = &constantinopleInstructionSet table, tableNT = &constantinopleInstructionSet, &constantinopleInstructionSetNT
case evm.chainRules.IsByzantium: case evm.chainRules.IsByzantium:
evm.table = &byzantiumInstructionSet table, tableNT = &byzantiumInstructionSet, &byzantiumInstructionSetNT
case evm.chainRules.IsEIP158: case evm.chainRules.IsEIP158:
evm.table = &spuriousDragonInstructionSet table, tableNT = &spuriousDragonInstructionSet, &spuriousDragonInstructionSetNT
case evm.chainRules.IsEIP150: case evm.chainRules.IsEIP150:
evm.table = &tangerineWhistleInstructionSet table, tableNT = &tangerineWhistleInstructionSet, &tangerineWhistleInstructionSetNT
case evm.chainRules.IsHomestead: case evm.chainRules.IsHomestead:
evm.table = &homesteadInstructionSet table, tableNT = &homesteadInstructionSet, &homesteadInstructionSetNT
default: default:
evm.table = &frontierInstructionSet table, tableNT = &frontierInstructionSet, &frontierInstructionSetNT
}
if config.Tracer != nil {
evm.table = table
} else {
evm.table = tableNT
} }
var extraEips []int var extraEips []int
if len(evm.Config.ExtraEips) > 0 { if len(evm.Config.ExtraEips) > 0 {
@ -187,7 +193,7 @@ func NewEVM(blockCtx BlockContext, statedb StateDB, chainConfig *params.ChainCon
evm.table = copyJumpTable(evm.table) evm.table = copyJumpTable(evm.table)
} }
for _, eip := range evm.Config.ExtraEips { 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 // Disable it, so caller can check if it's activated or not
log.Error("EIP activation failed", "eip", eip, "error", err) log.Error("EIP activation failed", "eip", eip, "error", err)
} else { } 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 // the necessary steps to create accounts and reverses the state in case of an
// execution error or failed value transfer. // execution error or failed value transfer.
func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, gas uint64, value *uint256.Int) (ret []byte, leftOverGas uint64, err error) { func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, gas uint64, value *uint256.Int) (ret []byte, leftOverGas uint64, err error) {
// Capture the tracer start/end events in debug mode
if evm.Config.Tracer != nil { 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()) evm.captureBegin(evm.depth, CALL, caller, addr, input, gas, value.ToBig())
defer func(startGas uint64) { defer func(startGas uint64) {
evm.captureEnd(evm.depth, startGas, leftOverGas, ret, err) 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) evm.Context.Transfer(evm.StateDB, caller, addr, value)
if isPrecompile { if isPrecompile {
ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer) ret, gas, err = RunPrecompiledContract[TS](p, input, gas, evm.Config.Tracer)
} else { } else {
// Initialise a new contract and set the code that is to be used by the EVM. // Initialise a new contract and set the code that is to be used by the EVM.
code := evm.resolveCode(addr) 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 := NewContract(caller, addr, value, gas, evm.jumpDests)
contract.IsSystemCall = isSystemCall(caller) contract.IsSystemCall = isSystemCall(caller)
contract.SetCallCode(evm.resolveCodeHash(addr), code) contract.SetCallCode(evm.resolveCodeHash(addr), code)
ret, err = evm.Run(contract, input, false) ret, err = Run[TS](evm, contract, input, false)
gas = contract.Gas gas = contract.Gas
} }
} }
@ -304,7 +317,7 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g
if err != nil { if err != nil {
evm.StateDB.RevertToSnapshot(snapshot) evm.StateDB.RevertToSnapshot(snapshot)
if err != ErrExecutionReverted { 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) 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' // CallCode differs from Call in the sense that it executes the given address'
// code with the caller as context. // 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 // 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()) evm.captureBegin(evm.depth, CALLCODE, caller, addr, input, gas, value.ToBig())
defer func(startGas uint64) { defer func(startGas uint64) {
evm.captureEnd(evm.depth, startGas, leftOverGas, ret, err) 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 // It is allowed to call precompiles, even via delegatecall
if p, isPrecompile := evm.precompile(addr); isPrecompile { 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 { } else {
// Initialise a new contract and set the code that is to be used by the EVM. // 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. // The contract is a scoped environment for this execution context only.
contract := NewContract(caller, caller, value, gas, evm.jumpDests) contract := NewContract(caller, caller, value, gas, evm.jumpDests)
contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr)) 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 gas = contract.Gas
} }
if err != nil { if err != nil {
evm.StateDB.RevertToSnapshot(snapshot) evm.StateDB.RevertToSnapshot(snapshot)
if err != ErrExecutionReverted { 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) evm.Config.Tracer.OnGasChange(gas, 0, tracing.GasChangeCallFailedExecution)
} }
gas = 0 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' // 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. // 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 // Invoke tracer hooks that signal entering/exiting a call frame
if evm.Config.Tracer != nil { if tracingIsEnabled[TS]() {
// DELEGATECALL inherits value from parent call // DELEGATECALL inherits value from parent call
evm.captureBegin(evm.depth, DELEGATECALL, caller, addr, input, gas, value.ToBig()) evm.captureBegin(evm.depth, DELEGATECALL, caller, addr, input, gas, value.ToBig())
defer func(startGas uint64) { 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 // It is allowed to call precompiles, even via delegatecall
if p, isPrecompile := evm.precompile(addr); isPrecompile { 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 { } else {
// Initialise a new contract and make initialise the delegate values // Initialise a new contract and make initialise the delegate values
// //
// Note: The value refers to the original value from the parent call. // Note: The value refers to the original value from the parent call.
contract := NewContract(originCaller, caller, value, gas, evm.jumpDests) contract := NewContract(originCaller, caller, value, gas, evm.jumpDests)
contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr)) 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 gas = contract.Gas
} }
if err != nil { if err != nil {
evm.StateDB.RevertToSnapshot(snapshot) evm.StateDB.RevertToSnapshot(snapshot)
if err != ErrExecutionReverted { 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) evm.Config.Tracer.OnGasChange(gas, 0, tracing.GasChangeCallFailedExecution)
} }
gas = 0 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. // as parameters while disallowing any modifications to the state during the call.
// Opcodes that attempt to perform such modifications will result in exceptions // Opcodes that attempt to perform such modifications will result in exceptions
// instead of performing the modifications. // 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 // 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) evm.captureBegin(evm.depth, STATICCALL, caller, addr, input, gas, nil)
defer func(startGas uint64) { defer func(startGas uint64) {
evm.captureEnd(evm.depth, startGas, leftOverGas, ret, err) 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) evm.StateDB.AddBalance(addr, new(uint256.Int), tracing.BalanceChangeTouchAccount)
if p, isPrecompile := evm.precompile(addr); isPrecompile { 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 { } else {
// Initialise a new contract and set the code that is to be used by the EVM. // 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. // 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 // 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 // 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. // 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 gas = contract.Gas
} }
if err != nil { if err != nil {
evm.StateDB.RevertToSnapshot(snapshot) evm.StateDB.RevertToSnapshot(snapshot)
if err != ErrExecutionReverted { 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) 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. // create creates a new contract using code as deployment code.
func (evm *EVM) create(caller common.Address, code []byte, gas uint64, value *uint256.Int, address common.Address, typ OpCode) (ret []byte, createAddress common.Address, leftOverGas uint64, err error) { func 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 evm.Config.Tracer != nil { if tracingIsEnabled[TS]() {
evm.captureBegin(evm.depth, typ, caller, address, code, gas, value.ToBig()) evm.captureBegin(evm.depth, typ, caller, address, code, gas, value.ToBig())
defer func(startGas uint64) { defer func(startGas uint64) {
evm.captureEnd(evm.depth, startGas, leftOverGas, ret, err) 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 { if statelessGas > gas {
return nil, common.Address{}, 0, ErrOutOfGas 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) evm.Config.Tracer.OnGasChange(gas, gas-statelessGas, tracing.GasChangeWitnessContractCollisionCheck)
} }
gas = gas - statelessGas 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 || if evm.StateDB.GetNonce(address) != 0 ||
(contractHash != (common.Hash{}) && contractHash != types.EmptyCodeHash) || // non-empty code (contractHash != (common.Hash{}) && contractHash != types.EmptyCodeHash) || // non-empty code
(storageRoot != (common.Hash{}) && storageRoot != types.EmptyRootHash) { // non-empty storage (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) evm.Config.Tracer.OnGasChange(gas, 0, tracing.GasChangeCallFailedExecution)
} }
return nil, common.Address{}, 0, ErrContractAddressCollision 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 { if consumed < wanted {
return nil, common.Address{}, 0, ErrOutOfGas 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) evm.Config.Tracer.OnGasChange(gas, gas-consumed, tracing.GasChangeWitnessContractInit)
} }
gas = gas - consumed 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.SetCallCode(common.Hash{}, code)
contract.IsDeployment = true contract.IsDeployment = true
ret, err = evm.initNewContract(contract, address) ret, err = initNewContract[TS](evm, contract, address)
if err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas) { if err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas) {
evm.StateDB.RevertToSnapshot(snapshot) evm.StateDB.RevertToSnapshot(snapshot)
if err != ErrExecutionReverted { 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 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 // initNewContract runs a new contract's creation code, performs checks on the
// resulting code that is to be deployed, and consumes necessary gas. // resulting code that is to be deployed, and consumes necessary gas.
func (evm *EVM) initNewContract(contract *Contract, address common.Address) ([]byte, error) { func initNewContract[TS TracingSwitch](evm *EVM, contract *Contract, address common.Address) ([]byte, error) {
ret, err := evm.Run(contract, nil, false) ret, err := Run[TS](evm, contract, nil, false)
if err != nil { if err != nil {
return ret, err return ret, err
} }
@ -590,12 +603,12 @@ func (evm *EVM) initNewContract(contract *Contract, address common.Address) ([]b
if !evm.chainRules.IsEIP4762 { if !evm.chainRules.IsEIP4762 {
createDataGas := uint64(len(ret)) * params.CreateDataGas 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 return ret, ErrCodeStoreOutOfGas
} }
} else { } else {
consumed, wanted := evm.AccessEvents.CodeChunksRangeGas(address, 0, uint64(len(ret)), uint64(len(ret)), true, contract.Gas) 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) { if len(ret) > 0 && (consumed < wanted) {
return ret, ErrCodeStoreOutOfGas 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. // Create creates a new contract using code as deployment code.
func (evm *EVM) Create(caller common.Address, code []byte, gas uint64, value *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) { func (evm *EVM) Create(caller common.Address, code []byte, gas uint64, value *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) {
contractAddr = crypto.CreateAddress(caller, evm.StateDB.GetNonce(caller)) 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. // 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:] // 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. // 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) inithash := crypto.HashData(evm.hasher, code)
contractAddr = crypto.CreateAddress2(caller, salt.Bytes32(), inithash[:]) 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 // resolveCode returns the code associated with the provided account. After

View file

@ -22,6 +22,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256" "github.com/holiman/uint256"
) )
@ -652,7 +653,7 @@ func opSwap16(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
return nil, nil 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 { if evm.readOnly {
return nil, ErrWriteProtection return nil, ErrWriteProtection
} }
@ -669,9 +670,11 @@ func opCreate(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
// reuse size int for stackvalue // reuse size int for stackvalue
stackvalue := size 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 // Push item on the stack based on the returned error. If the ruleset is
// homestead we must check for CodeStoreOutOfGasError (homestead only // homestead we must check for CodeStoreOutOfGasError (homestead only
// rule) and treat as an error, if the ruleset is frontier we must // 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.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 { if suberr == ErrExecutionReverted {
evm.returnData = res // set REVERT data to return data buffer 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 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 { if evm.readOnly {
return nil, ErrWriteProtection return nil, ErrWriteProtection
} }
@ -709,10 +712,10 @@ func opCreate2(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
// Apply EIP150 // Apply EIP150
gas -= gas / 64 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 // reuse size int for stackvalue
stackvalue := size 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) &endowment, &salt)
// Push item on the stack based on the returned error. // Push item on the stack based on the returned error.
if suberr != nil { if suberr != nil {
@ -721,7 +724,7 @@ func opCreate2(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
stackvalue.SetBytes(addr.Bytes()) stackvalue.SetBytes(addr.Bytes())
} }
scope.Stack.push(&stackvalue) 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 { if suberr == ErrExecutionReverted {
evm.returnData = res // set REVERT data to return data buffer 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 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 stack := scope.Stack
// Pop gas. The actual gas in evm.callGasTemp. // Pop gas. The actual gas in evm.callGasTemp.
// We can use this as a temporary value // 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() { if !value.IsZero() {
gas += params.CallStipend 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 { if err != nil {
temp.Clear() 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.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 evm.returnData = ret
return ret, nil 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. // Pop gas. The actual gas is in evm.callGasTemp.
stack := scope.Stack stack := scope.Stack
// We use it as a temporary value // We use it as a temporary value
@ -783,7 +786,7 @@ func opCallCode(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
gas += params.CallStipend 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 { if err != nil {
temp.Clear() temp.Clear()
} else { } else {
@ -794,13 +797,13 @@ func opCallCode(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) 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 evm.returnData = ret
return ret, nil 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 stack := scope.Stack
// Pop gas. The actual gas is in evm.callGasTemp. // Pop gas. The actual gas is in evm.callGasTemp.
// We use it as a temporary value // 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. // Get arguments from the memory.
args := scope.Memory.GetPtr(inOffset.Uint64(), inSize.Uint64()) 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 { if err != nil {
temp.Clear() temp.Clear()
} else { } else {
@ -823,13 +826,13 @@ func opDelegateCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) 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 evm.returnData = ret
return ret, nil 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. // Pop gas. The actual gas is in evm.callGasTemp.
stack := scope.Stack stack := scope.Stack
// We use it as a temporary value // 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. // Get arguments from the memory.
args := scope.Memory.GetPtr(inOffset.Uint64(), inSize.Uint64()) 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 { if err != nil {
temp.Clear() temp.Clear()
} else { } else {
@ -852,7 +855,7 @@ func opStaticCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) 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 evm.returnData = ret
return ret, nil return ret, nil

View file

@ -18,6 +18,7 @@ package vm
import ( import (
"fmt" "fmt"
"unsafe"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/common/math"
@ -25,6 +26,17 @@ import (
"github.com/holiman/uint256" "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 // Config are the configuration options for the Interpreter
type Config struct { type Config struct {
Tracer *tracing.Hooks 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 // It's important to note that any errors returned by the interpreter should be
// considered a revert-and-consume-all-gas operation except for // considered a revert-and-consume-all-gas operation except for
// ErrExecutionReverted which means revert-and-keep-gas-left. // 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 // Increment the call depth which is restricted to 1024
evm.depth++ evm.depth++
defer func() { 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 gasCopy uint64 // for EVMLogger to log gas remaining before execution
logged bool // deferred EVMLogger should ignore already logged steps logged bool // deferred EVMLogger should ignore already logged steps
res []byte // result of the opcode execution function res []byte // result of the opcode execution function
debug = evm.Config.Tracer != nil
isEIP4762 = evm.chainRules.IsEIP4762 isEIP4762 = evm.chainRules.IsEIP4762
) )
// Don't move this deferred function, it's placed before the OnOpcode-deferred method, // 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 contract.Input = input
if debug { if tracingIsEnabled[TS]() {
defer func() { // this deferred method handles exit-with-error defer func() { // this deferred method handles exit-with-error
if err == nil { if err == nil {
return return
@ -165,7 +176,7 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte
// parent context. // parent context.
_ = jumpTable[0] // nil-check the jumpTable out of the loop _ = jumpTable[0] // nil-check the jumpTable out of the loop
for { for {
if debug { if tracingIsEnabled[TS]() {
// Capture pre-execution values for tracing. // Capture pre-execution values for tracing.
logged, pcCopy, gasCopy = false, pc, contract.Gas 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. // associated costs.
contractAddr := contract.Address() contractAddr := contract.Address()
consumed, wanted := evm.TxContext.AccessEvents.CodeChunksRangeGas(contractAddr, pc, 1, uint64(len(contract.Code)), false, contract.Gas) 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 { if consumed < wanted {
return nil, ErrOutOfGas 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 // Do tracing before potential memory expansion
if debug { if tracingIsEnabled[TS]() {
if evm.Config.Tracer.OnGasChange != nil { if evm.Config.Tracer.OnGasChange != nil {
evm.Config.Tracer.OnGasChange(gasCopy, gasCopy-cost, tracing.GasChangeCallOpCode) 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 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
}

View file

@ -48,21 +48,21 @@ type operation struct {
} }
var ( var (
frontierInstructionSet = newFrontierInstructionSet() frontierInstructionSet, frontierInstructionSetNT = newFrontierInstructionSet[TracingEnabled](), newFrontierInstructionSet[TracingDisabled]()
homesteadInstructionSet = newHomesteadInstructionSet() homesteadInstructionSet, homesteadInstructionSetNT = newHomesteadInstructionSet[TracingEnabled](), newHomesteadInstructionSet[TracingDisabled]()
tangerineWhistleInstructionSet = newTangerineWhistleInstructionSet() tangerineWhistleInstructionSet, tangerineWhistleInstructionSetNT = newTangerineWhistleInstructionSet[TracingEnabled](), newTangerineWhistleInstructionSet[TracingDisabled]()
spuriousDragonInstructionSet = newSpuriousDragonInstructionSet() spuriousDragonInstructionSet, spuriousDragonInstructionSetNT = newSpuriousDragonInstructionSet[TracingEnabled](), newSpuriousDragonInstructionSet[TracingDisabled]()
byzantiumInstructionSet = newByzantiumInstructionSet() byzantiumInstructionSet, byzantiumInstructionSetNT = newByzantiumInstructionSet[TracingEnabled](), newByzantiumInstructionSet[TracingDisabled]()
constantinopleInstructionSet = newConstantinopleInstructionSet() constantinopleInstructionSet, constantinopleInstructionSetNT = newConstantinopleInstructionSet[TracingEnabled](), newConstantinopleInstructionSet[TracingDisabled]()
istanbulInstructionSet = newIstanbulInstructionSet() istanbulInstructionSet, istanbulInstructionSetNT = newIstanbulInstructionSet[TracingEnabled](), newIstanbulInstructionSet[TracingDisabled]()
berlinInstructionSet = newBerlinInstructionSet() berlinInstructionSet, berlinInstructionSetNT = newBerlinInstructionSet[TracingEnabled](), newBerlinInstructionSet[TracingDisabled]()
londonInstructionSet = newLondonInstructionSet() londonInstructionSet, londonInstructionSetNT = newLondonInstructionSet[TracingEnabled](), newLondonInstructionSet[TracingDisabled]()
mergeInstructionSet = newMergeInstructionSet() mergeInstructionSet, mergeInstructionSetNT = newMergeInstructionSet[TracingEnabled](), newMergeInstructionSet[TracingDisabled]()
shanghaiInstructionSet = newShanghaiInstructionSet() shanghaiInstructionSet, shanghaiInstructionSetNT = newShanghaiInstructionSet[TracingEnabled](), newShanghaiInstructionSet[TracingDisabled]()
cancunInstructionSet = newCancunInstructionSet() cancunInstructionSet, cancunInstructionSetNT = newCancunInstructionSet[TracingEnabled](), newCancunInstructionSet[TracingDisabled]()
verkleInstructionSet = newVerkleInstructionSet() verkleInstructionSet, verkleInstructionSetNT = newVerkleInstructionSet[TracingEnabled](), newVerkleInstructionSet[TracingDisabled]()
pragueInstructionSet = newPragueInstructionSet() pragueInstructionSet, pragueInstructionSetNT = newPragueInstructionSet[TracingEnabled](), newPragueInstructionSet[TracingDisabled]()
osakaInstructionSet = newOsakaInstructionSet() osakaInstructionSet, osakaInstructionSetNT = newOsakaInstructionSet[TracingEnabled](), newOsakaInstructionSet[TracingDisabled]()
) )
// JumpTable contains the EVM opcodes supported at a given fork. // JumpTable contains the EVM opcodes supported at a given fork.
@ -86,26 +86,26 @@ func validate(jt JumpTable) JumpTable {
return jt return jt
} }
func newVerkleInstructionSet() JumpTable { func newVerkleInstructionSet[TS TracingSwitch]() JumpTable {
instructionSet := newShanghaiInstructionSet() instructionSet := newShanghaiInstructionSet[TS]()
enable4762(&instructionSet) enable4762[TS](&instructionSet)
return validate(instructionSet) return validate(instructionSet)
} }
func newOsakaInstructionSet() JumpTable { func newOsakaInstructionSet[TS TracingSwitch]() JumpTable {
instructionSet := newPragueInstructionSet() instructionSet := newPragueInstructionSet[TS]()
enable7939(&instructionSet) // EIP-7939 (CLZ opcode) enable7939(&instructionSet) // EIP-7939 (CLZ opcode)
return validate(instructionSet) return validate(instructionSet)
} }
func newPragueInstructionSet() JumpTable { func newPragueInstructionSet[TS TracingSwitch]() JumpTable {
instructionSet := newCancunInstructionSet() instructionSet := newCancunInstructionSet[TS]()
enable7702(&instructionSet) // EIP-7702 Setcode transaction type enable7702[TS](&instructionSet) // EIP-7702 Setcode transaction type
return validate(instructionSet) return validate(instructionSet)
} }
func newCancunInstructionSet() JumpTable { func newCancunInstructionSet[TS TracingSwitch]() JumpTable {
instructionSet := newShanghaiInstructionSet() instructionSet := newShanghaiInstructionSet[TS]()
enable4844(&instructionSet) // EIP-4844 (BLOBHASH opcode) enable4844(&instructionSet) // EIP-4844 (BLOBHASH opcode)
enable7516(&instructionSet) // EIP-7516 (BLOBBASEFEE opcode) enable7516(&instructionSet) // EIP-7516 (BLOBBASEFEE opcode)
enable1153(&instructionSet) // EIP-1153 "Transient Storage" enable1153(&instructionSet) // EIP-1153 "Transient Storage"
@ -114,16 +114,16 @@ func newCancunInstructionSet() JumpTable {
return validate(instructionSet) return validate(instructionSet)
} }
func newShanghaiInstructionSet() JumpTable { func newShanghaiInstructionSet[TS TracingSwitch]() JumpTable {
instructionSet := newMergeInstructionSet() instructionSet := newMergeInstructionSet[TS]()
enable3855(&instructionSet) // PUSH0 instruction enable3855(&instructionSet) // PUSH0 instruction
enable3860(&instructionSet) // Limit and meter initcode enable3860(&instructionSet) // Limit and meter initcode
return validate(instructionSet) return validate(instructionSet)
} }
func newMergeInstructionSet() JumpTable { func newMergeInstructionSet[TS TracingSwitch]() JumpTable {
instructionSet := newLondonInstructionSet() instructionSet := newLondonInstructionSet[TS]()
instructionSet[PREVRANDAO] = &operation{ instructionSet[PREVRANDAO] = &operation{
execute: opRandom, execute: opRandom,
constantGas: GasQuickStep, constantGas: GasQuickStep,
@ -135,8 +135,8 @@ func newMergeInstructionSet() JumpTable {
// newLondonInstructionSet returns the frontier, homestead, byzantium, // newLondonInstructionSet returns the frontier, homestead, byzantium,
// constantinople, istanbul, petersburg, berlin and london instructions. // constantinople, istanbul, petersburg, berlin and london instructions.
func newLondonInstructionSet() JumpTable { func newLondonInstructionSet[TS TracingSwitch]() JumpTable {
instructionSet := newBerlinInstructionSet() instructionSet := newBerlinInstructionSet[TS]()
enable3529(&instructionSet) // EIP-3529: Reduction in refunds https://eips.ethereum.org/EIPS/eip-3529 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 enable3198(&instructionSet) // Base fee opcode https://eips.ethereum.org/EIPS/eip-3198
return validate(instructionSet) return validate(instructionSet)
@ -144,16 +144,16 @@ func newLondonInstructionSet() JumpTable {
// newBerlinInstructionSet returns the frontier, homestead, byzantium, // newBerlinInstructionSet returns the frontier, homestead, byzantium,
// constantinople, istanbul, petersburg and berlin instructions. // constantinople, istanbul, petersburg and berlin instructions.
func newBerlinInstructionSet() JumpTable { func newBerlinInstructionSet[TS TracingSwitch]() JumpTable {
instructionSet := newIstanbulInstructionSet() instructionSet := newIstanbulInstructionSet[TS]()
enable2929(&instructionSet) // Gas cost increases for state access opcodes https://eips.ethereum.org/EIPS/eip-2929 enable2929[TS](&instructionSet) // Gas cost increases for state access opcodes https://eips.ethereum.org/EIPS/eip-2929
return validate(instructionSet) return validate(instructionSet)
} }
// newIstanbulInstructionSet returns the frontier, homestead, byzantium, // newIstanbulInstructionSet returns the frontier, homestead, byzantium,
// constantinople, istanbul and petersburg instructions. // constantinople, istanbul and petersburg instructions.
func newIstanbulInstructionSet() JumpTable { func newIstanbulInstructionSet[TS TracingSwitch]() JumpTable {
instructionSet := newConstantinopleInstructionSet() instructionSet := newConstantinopleInstructionSet[TS]()
enable1344(&instructionSet) // ChainID opcode - https://eips.ethereum.org/EIPS/eip-1344 enable1344(&instructionSet) // ChainID opcode - https://eips.ethereum.org/EIPS/eip-1344
enable1884(&instructionSet) // Reprice reader opcodes - https://eips.ethereum.org/EIPS/eip-1884 enable1884(&instructionSet) // Reprice reader opcodes - https://eips.ethereum.org/EIPS/eip-1884
@ -164,8 +164,8 @@ func newIstanbulInstructionSet() JumpTable {
// newConstantinopleInstructionSet returns the frontier, homestead, // newConstantinopleInstructionSet returns the frontier, homestead,
// byzantium and constantinople instructions. // byzantium and constantinople instructions.
func newConstantinopleInstructionSet() JumpTable { func newConstantinopleInstructionSet[TS TracingSwitch]() JumpTable {
instructionSet := newByzantiumInstructionSet() instructionSet := newByzantiumInstructionSet[TS]()
instructionSet[SHL] = &operation{ instructionSet[SHL] = &operation{
execute: opSHL, execute: opSHL,
constantGas: GasFastestStep, constantGas: GasFastestStep,
@ -191,7 +191,7 @@ func newConstantinopleInstructionSet() JumpTable {
maxStack: maxStack(1, 1), maxStack: maxStack(1, 1),
} }
instructionSet[CREATE2] = &operation{ instructionSet[CREATE2] = &operation{
execute: opCreate2, execute: opCreate2[TS],
constantGas: params.Create2Gas, constantGas: params.Create2Gas,
dynamicGas: gasCreate2, dynamicGas: gasCreate2,
minStack: minStack(4, 1), minStack: minStack(4, 1),
@ -203,10 +203,10 @@ func newConstantinopleInstructionSet() JumpTable {
// newByzantiumInstructionSet returns the frontier, homestead and // newByzantiumInstructionSet returns the frontier, homestead and
// byzantium instructions. // byzantium instructions.
func newByzantiumInstructionSet() JumpTable { func newByzantiumInstructionSet[TS TracingSwitch]() JumpTable {
instructionSet := newSpuriousDragonInstructionSet() instructionSet := newSpuriousDragonInstructionSet[TS]()
instructionSet[STATICCALL] = &operation{ instructionSet[STATICCALL] = &operation{
execute: opStaticCall, execute: opStaticCall[TS],
constantGas: params.CallGasEIP150, constantGas: params.CallGasEIP150,
dynamicGas: gasStaticCall, dynamicGas: gasStaticCall,
minStack: minStack(6, 1), minStack: minStack(6, 1),
@ -238,15 +238,15 @@ func newByzantiumInstructionSet() JumpTable {
} }
// EIP 158 a.k.a Spurious Dragon // EIP 158 a.k.a Spurious Dragon
func newSpuriousDragonInstructionSet() JumpTable { func newSpuriousDragonInstructionSet[TS TracingSwitch]() JumpTable {
instructionSet := newTangerineWhistleInstructionSet() instructionSet := newTangerineWhistleInstructionSet[TS]()
instructionSet[EXP].dynamicGas = gasExpEIP158 instructionSet[EXP].dynamicGas = gasExpEIP158
return validate(instructionSet) return validate(instructionSet)
} }
// EIP 150 a.k.a Tangerine Whistle // EIP 150 a.k.a Tangerine Whistle
func newTangerineWhistleInstructionSet() JumpTable { func newTangerineWhistleInstructionSet[TS TracingSwitch]() JumpTable {
instructionSet := newHomesteadInstructionSet() instructionSet := newHomesteadInstructionSet[TS]()
instructionSet[BALANCE].constantGas = params.BalanceGasEIP150 instructionSet[BALANCE].constantGas = params.BalanceGasEIP150
instructionSet[EXTCODESIZE].constantGas = params.ExtcodeSizeGasEIP150 instructionSet[EXTCODESIZE].constantGas = params.ExtcodeSizeGasEIP150
instructionSet[SLOAD].constantGas = params.SloadGasEIP150 instructionSet[SLOAD].constantGas = params.SloadGasEIP150
@ -259,10 +259,10 @@ func newTangerineWhistleInstructionSet() JumpTable {
// newHomesteadInstructionSet returns the frontier and homestead // newHomesteadInstructionSet returns the frontier and homestead
// instructions that can be executed during the homestead phase. // instructions that can be executed during the homestead phase.
func newHomesteadInstructionSet() JumpTable { func newHomesteadInstructionSet[TS TracingSwitch]() JumpTable {
instructionSet := newFrontierInstructionSet() instructionSet := newFrontierInstructionSet[TS]()
instructionSet[DELEGATECALL] = &operation{ instructionSet[DELEGATECALL] = &operation{
execute: opDelegateCall, execute: opDelegateCall[TS],
dynamicGas: gasDelegateCall, dynamicGas: gasDelegateCall,
constantGas: params.CallGasFrontier, constantGas: params.CallGasFrontier,
minStack: minStack(6, 1), minStack: minStack(6, 1),
@ -274,7 +274,7 @@ func newHomesteadInstructionSet() JumpTable {
// newFrontierInstructionSet returns the frontier instructions // newFrontierInstructionSet returns the frontier instructions
// that can be executed during the frontier phase. // that can be executed during the frontier phase.
func newFrontierInstructionSet() JumpTable { func newFrontierInstructionSet[TS TracingSwitch]() JumpTable {
tbl := JumpTable{ tbl := JumpTable{
STOP: { STOP: {
execute: opStop, execute: opStop,
@ -1040,7 +1040,7 @@ func newFrontierInstructionSet() JumpTable {
memorySize: memoryLog, memorySize: memoryLog,
}, },
CREATE: { CREATE: {
execute: opCreate, execute: opCreate[TS],
constantGas: params.CreateGas, constantGas: params.CreateGas,
dynamicGas: gasCreate, dynamicGas: gasCreate,
minStack: minStack(3, 1), minStack: minStack(3, 1),
@ -1048,7 +1048,7 @@ func newFrontierInstructionSet() JumpTable {
memorySize: memoryCreate, memorySize: memoryCreate,
}, },
CALL: { CALL: {
execute: opCall, execute: opCall[TS],
constantGas: params.CallGasFrontier, constantGas: params.CallGasFrontier,
dynamicGas: gasCall, dynamicGas: gasCall,
minStack: minStack(7, 1), minStack: minStack(7, 1),
@ -1056,7 +1056,7 @@ func newFrontierInstructionSet() JumpTable {
memorySize: memoryCall, memorySize: memoryCall,
}, },
CALLCODE: { CALLCODE: {
execute: opCallCode, execute: opCallCode[TS],
constantGas: params.CallGasFrontier, constantGas: params.CallGasFrontier,
dynamicGas: gasCallCode, dynamicGas: gasCallCode,
minStack: minStack(7, 1), minStack: minStack(7, 1),

View file

@ -24,38 +24,38 @@ import (
// LookupInstructionSet returns the instruction set for the fork configured by // LookupInstructionSet returns the instruction set for the fork configured by
// the rules. // the rules.
func LookupInstructionSet(rules params.Rules) (JumpTable, error) { func LookupInstructionSet[TS TracingSwitch](rules params.Rules) (JumpTable, error) {
switch { switch {
case rules.IsVerkle: 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: case rules.IsOsaka:
return newOsakaInstructionSet(), nil return newOsakaInstructionSet[TS](), nil
case rules.IsPrague: case rules.IsPrague:
return newPragueInstructionSet(), nil return newPragueInstructionSet[TS](), nil
case rules.IsCancun: case rules.IsCancun:
return newCancunInstructionSet(), nil return newCancunInstructionSet[TS](), nil
case rules.IsShanghai: case rules.IsShanghai:
return newShanghaiInstructionSet(), nil return newShanghaiInstructionSet[TS](), nil
case rules.IsMerge: case rules.IsMerge:
return newMergeInstructionSet(), nil return newMergeInstructionSet[TS](), nil
case rules.IsLondon: case rules.IsLondon:
return newLondonInstructionSet(), nil return newLondonInstructionSet[TS](), nil
case rules.IsBerlin: case rules.IsBerlin:
return newBerlinInstructionSet(), nil return newBerlinInstructionSet[TS](), nil
case rules.IsIstanbul: case rules.IsIstanbul:
return newIstanbulInstructionSet(), nil return newIstanbulInstructionSet[TS](), nil
case rules.IsConstantinople: case rules.IsConstantinople:
return newConstantinopleInstructionSet(), nil return newConstantinopleInstructionSet[TS](), nil
case rules.IsByzantium: case rules.IsByzantium:
return newByzantiumInstructionSet(), nil return newByzantiumInstructionSet[TS](), nil
case rules.IsEIP158: case rules.IsEIP158:
return newSpuriousDragonInstructionSet(), nil return newSpuriousDragonInstructionSet[TS](), nil
case rules.IsEIP150: case rules.IsEIP150:
return newTangerineWhistleInstructionSet(), nil return newTangerineWhistleInstructionSet[TS](), nil
case rules.IsHomestead: 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. // Stack returns the minimum and maximum stack requirements.

View file

@ -24,7 +24,7 @@ import (
// TestJumpTableCopy tests that deep copy is necessary to prevent modify shared jump table // TestJumpTableCopy tests that deep copy is necessary to prevent modify shared jump table
func TestJumpTableCopy(t *testing.T) { func TestJumpTableCopy(t *testing.T) {
tbl := newMergeInstructionSet() tbl := newMergeInstructionSet[TracingEnabled]()
require.Equal(t, uint64(0), tbl[SLOAD].constantGas) require.Equal(t, uint64(0), tbl[SLOAD].constantGas)
// a deep copy won't modify the shared jump table // a deep copy won't modify the shared jump table

View file

@ -152,7 +152,7 @@ func gasEip2929AccountCheck(evm *EVM, contract *Contract, stack *Stack, mem *Mem
return 0, nil 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) { return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
addr := common.Address(stack.Back(addressPosition).Bytes20()) addr := common.Address(stack.Back(addressPosition).Bytes20())
// Check slot presence in the access list // Check slot presence in the access list
@ -164,7 +164,7 @@ func makeCallVariantGasCallEIP2929(oldCalculator gasFunc, addressPosition int) g
evm.StateDB.AddAddressToAccessList(addr) evm.StateDB.AddAddressToAccessList(addr)
// Charge the remaining difference here already, to correctly calculate available // Charge the remaining difference here already, to correctly calculate available
// gas for call // 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 return 0, ErrOutOfGas
} }
} }
@ -192,10 +192,6 @@ func makeCallVariantGasCallEIP2929(oldCalculator gasFunc, addressPosition int) g
} }
var ( var (
gasCallEIP2929 = makeCallVariantGasCallEIP2929(gasCall, 1)
gasDelegateCallEIP2929 = makeCallVariantGasCallEIP2929(gasDelegateCall, 1)
gasStaticCallEIP2929 = makeCallVariantGasCallEIP2929(gasStaticCall, 1)
gasCallCodeEIP2929 = makeCallVariantGasCallEIP2929(gasCallCode, 1)
gasSelfdestructEIP2929 = makeSelfdestructGasFn(true) gasSelfdestructEIP2929 = makeSelfdestructGasFn(true)
// gasSelfdestructEIP3529 implements the changes in EIP-3529 (no refunds) // gasSelfdestructEIP3529 implements the changes in EIP-3529 (no refunds)
gasSelfdestructEIP3529 = makeSelfdestructGasFn(false) gasSelfdestructEIP3529 = makeSelfdestructGasFn(false)
@ -243,14 +239,7 @@ func makeSelfdestructGasFn(refundsEnabled bool) gasFunc {
return gasFunc return gasFunc
} }
var ( func makeCallVariantGasCallEIP7702[TS TracingSwitch](oldCalculator gasFunc) gasFunc {
gasCallEIP7702 = makeCallVariantGasCallEIP7702(gasCall)
gasDelegateCallEIP7702 = makeCallVariantGasCallEIP7702(gasDelegateCall)
gasStaticCallEIP7702 = makeCallVariantGasCallEIP7702(gasStaticCall)
gasCallCodeEIP7702 = makeCallVariantGasCallEIP7702(gasCallCode)
)
func makeCallVariantGasCallEIP7702(oldCalculator gasFunc) gasFunc {
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
var ( var (
total uint64 // total dynamic gas used total uint64 // total dynamic gas used
@ -265,7 +254,7 @@ func makeCallVariantGasCallEIP7702(oldCalculator gasFunc) gasFunc {
coldCost := params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929 coldCost := params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929
// Charge the remaining difference here already, to correctly calculate available // Charge the remaining difference here already, to correctly calculate available
// gas for call // 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 return 0, ErrOutOfGas
} }
total += coldCost total += coldCost
@ -280,7 +269,7 @@ func makeCallVariantGasCallEIP7702(oldCalculator gasFunc) gasFunc {
evm.StateDB.AddAddressToAccessList(target) evm.StateDB.AddAddressToAccessList(target)
cost = params.ColdAccountAccessCostEIP2929 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 return 0, ErrOutOfGas
} }
total += cost total += cost

View file

@ -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.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()) 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) tracer.OnExit(0, ret, startGas-contract.Gas, err, true)
// Rest gas assumes no refund // Rest gas assumes no refund
tracer.OnTxEnd(&types.Receipt{GasUsed: gasLimit - contract.Gas}, nil) tracer.OnTxEnd(&types.Receipt{GasUsed: gasLimit - contract.Gas}, nil)

View file

@ -52,7 +52,7 @@ func TestStoreCapture(t *testing.T) {
contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x0, byte(vm.SSTORE)} contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x0, byte(vm.SSTORE)}
var index common.Hash var index common.Hash
logger.OnTxStart(evm.GetVMContext(), nil, common.Address{}) 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 { if err != nil {
t.Fatal(err) t.Fatal(err)
} }