From 419e5e1438a604b912e9e6a27708e32dfaf1ec44 Mon Sep 17 00:00:00 2001
From: jonny rhea <5555162+jrhea@users.noreply.github.com>
Date: Wed, 10 Jun 2026 14:34:46 -0500
Subject: [PATCH] core/vm: replace interpreter dispatch with a generated switch
---
core/vm/evm.go | 5 +
core/vm/gen/main.go | 497 ++++
core/vm/genspec.go | 106 +
core/vm/interp_diff_test.go | 365 +++
core/vm/interp_gen.go | 1995 +++++++++++++++++
core/vm/interp_gen_test.go | 51 +
core/vm/interpreter.go | 57 +-
core/vm/runtime/evm_bench_test.go | 106 +
core/vm/runtime/testdata/erc20approval.hex | 1 +
core/vm/runtime/testdata/erc20mint.hex | 1 +
core/vm/runtime/testdata/erc20transfer.hex | 1 +
core/vm/runtime/testdata/evm-bench/README.md | 56 +
core/vm/runtime/testdata/evm-bench/compare.sh | 61 +
core/vm/runtime/testdata/evm-bench/gen.sh | 43 +
core/vm/runtime/testdata/snailtracer.hex | 1 +
.../vm/runtime/testdata/tenthousandhashes.hex | 1 +
16 files changed, 3331 insertions(+), 16 deletions(-)
create mode 100644 core/vm/gen/main.go
create mode 100644 core/vm/genspec.go
create mode 100644 core/vm/interp_diff_test.go
create mode 100644 core/vm/interp_gen.go
create mode 100644 core/vm/interp_gen_test.go
create mode 100644 core/vm/runtime/evm_bench_test.go
create mode 100644 core/vm/runtime/testdata/erc20approval.hex
create mode 100644 core/vm/runtime/testdata/erc20mint.hex
create mode 100644 core/vm/runtime/testdata/erc20transfer.hex
create mode 100644 core/vm/runtime/testdata/evm-bench/README.md
create mode 100755 core/vm/runtime/testdata/evm-bench/compare.sh
create mode 100755 core/vm/runtime/testdata/evm-bench/gen.sh
create mode 100644 core/vm/runtime/testdata/snailtracer.hex
create mode 100644 core/vm/runtime/testdata/tenthousandhashes.hex
diff --git a/core/vm/evm.go b/core/vm/evm.go
index 18aa50a8c8..b47adc8ed5 100644
--- a/core/vm/evm.go
+++ b/core/vm/evm.go
@@ -131,6 +131,11 @@ type EVM struct {
returnData []byte // Last CALL's return data for subsequent reuse
arena *stackArena
+
+ // forceTableLoop is a test-only switch. The differential test sets it so
+ // that Run uses the table loop (execTraced) for the whole call tree, which
+ // lets the test compare it against the generated dispatch.
+ forceTableLoop bool
}
// NewEVM constructs an EVM instance with the supplied block context, state
diff --git a/core/vm/gen/main.go b/core/vm/gen/main.go
new file mode 100644
index 0000000000..2e7c0b45a3
--- /dev/null
+++ b/core/vm/gen/main.go
@@ -0,0 +1,497 @@
+// Copyright 2026 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+// Command gen generates core/vm/interp_gen.go, the EVM interpreter's untraced
+// fast-path dispatch. The output is a switch over the opcode byte that:
+//
+// - inlines the hot, fork-stable opcodes (arithmetic / comparison / bitwise /
+// PUSH / DUP / SWAP / POP / JUMP / JUMPI / PC / MSIZE / JUMPDEST) by
+// splicing the existing opXxx handler bodies from instructions.go and
+// eips.go, with their static gas and stack bounds baked in as constants
+// derived from the per-fork instruction tables via vm.GenForks.
+//
+// - calls the fork-invariant cold ops (KECCAK256 / MLOAD / MSTORE / MSTORE8,
+// see directCold) directly by name, skipping the table's function
+// pointers, which Go cannot inline through.
+//
+// - dispatches everything fork-varying (CALL / CREATE / SSTORE / SLOAD / LOG /
+// the COPY family and so on) through the active per-fork JumpTable in the
+// default case, exactly as the legacy loop did, so volatile gas and opcode
+// logic stays shared rather than restated.
+//
+// The generated file is committed and a CI test asserts it matches `go generate`
+// output. Do not hand-edit interp_gen.go.
+//
+// Usage: go generate ./core/vm/...
+package main
+
+import (
+ "bytes"
+ "fmt"
+ "go/ast"
+ "go/format"
+ "go/parser"
+ "go/printer"
+ "go/token"
+ "os"
+ "path/filepath"
+ "regexp"
+ "runtime"
+ "strings"
+
+ "github.com/ethereum/go-ethereum/core/vm"
+)
+
+const stackLimit = 1024 // params.StackLimit
+
+// inlineHandler maps an opcode byte to the opXxx handler whose body is spliced
+// inline for that opcode. These are the hot, fork-stable opcodes with no dynamic
+// gas. Opcodes not listed here (or in directCold, or PUSH3-32 / DUP1-16, which
+// are handled specially) fall through to the default case, which dispatches via
+// the per-fork table.
+var inlineHandler = map[byte]string{
+ 0x01: "opAdd", 0x02: "opMul", 0x03: "opSub", 0x04: "opDiv", 0x05: "opSdiv",
+ 0x06: "opMod", 0x07: "opSmod", 0x08: "opAddmod", 0x09: "opMulmod", 0x0b: "opSignExtend",
+ 0x10: "opLt", 0x11: "opGt", 0x12: "opSlt", 0x13: "opSgt", 0x14: "opEq", 0x15: "opIszero",
+ 0x16: "opAnd", 0x17: "opOr", 0x18: "opXor", 0x19: "opNot", 0x1a: "opByte",
+ 0x1b: "opSHL", 0x1c: "opSHR", 0x1d: "opSAR", 0x1e: "opCLZ",
+ 0x50: "opPop", 0x56: "opJump", 0x57: "opJumpi", 0x58: "opPc", 0x59: "opMsize", 0x5b: "opJumpdest",
+ 0x5f: "opPush0", 0x60: "opPush1", 0x61: "opPush2",
+ 0x90: "opSwap1", 0x91: "opSwap2", 0x92: "opSwap3", 0x93: "opSwap4",
+ 0x94: "opSwap5", 0x95: "opSwap6", 0x96: "opSwap7", 0x97: "opSwap8",
+ 0x98: "opSwap9", 0x99: "opSwap10", 0x9a: "opSwap11", 0x9b: "opSwap12",
+ 0x9c: "opSwap13", 0x9d: "opSwap14", 0x9e: "opSwap15", 0x9f: "opSwap16",
+}
+
+// directCold lists cold opcodes (dynamic gas, not inlined) whose handler,
+// dynamic-gas, and memory-size functions are the same across every fork
+// (verified: untouched by any enableXxx). They are emitted as direct calls to
+// those functions by name instead of the indirect operation.* pointer calls
+// in the default case. Go inlines the plain functions, and the var-aliased gas
+// funcs (gasMLoad and friends alias pureMemoryGascost) at least skip the
+// table load. Measured at ~3.4% on snailtracer (p=0.000). Fork-varying cold ops
+// (CALL/SSTORE/SLOAD and friends) do not qualify and stay on the per-fork
+// table in the default case.
+//
+// opcode → {handler, dynamicGas, memorySize}
+//
+// Limited to the memory/hash ops that appear in hot loops. Adding more (e.g.
+// CALLDATACOPY/RETURN, typically once per call) grows the generated function
+// and regresses tiny benchmarks through code layout, for negligible gain.
+var directCold = map[byte][3]string{
+ 0x20: {"opKeccak256", "gasKeccak256", "memoryKeccak256"}, // KECCAK256
+ 0x51: {"opMload", "gasMLoad", "memoryMLoad"}, // MLOAD
+ 0x52: {"opMstore", "gasMStore", "memoryMStore"}, // MSTORE
+ 0x53: {"opMstore8", "gasMStore8", "memoryMStore8"}, // MSTORE8
+}
+
+// opMeta is the per-opcode metadata derived from the per-fork tables.
+type opMeta struct {
+ defined bool
+ name string // opcode mnemonic, e.g. "ADD"
+ introF string // params.Rules field activating it, empty for Frontier (always on)
+ constGas uint64
+ minStack int
+ maxStack int
+}
+
+type generator struct {
+ fset *token.FileSet
+ handlers map[string]*ast.FuncDecl
+ meta [256]opMeta
+ buf *bytes.Buffer
+}
+
+func (g *generator) p(format string, args ...any) { fmt.Fprintf(g.buf, format, args...) }
+
+// ---------------------------------------------------------------------------
+// Handler parsing + body splicing
+// ---------------------------------------------------------------------------
+
+// parseHandlers parses instructions.go and eips.go and returns every top-level
+// opXxx function declaration by name.
+func parseHandlers(vmDir string) (*token.FileSet, map[string]*ast.FuncDecl) {
+ fset := token.NewFileSet()
+ handlers := map[string]*ast.FuncDecl{}
+ for _, name := range []string{"instructions.go", "eips.go"} {
+ path := filepath.Join(vmDir, name)
+ f, err := parser.ParseFile(fset, path, nil, parser.ParseComments)
+ if err != nil {
+ fatalf("parse %s: %v", path, err)
+ }
+ for _, decl := range f.Decls {
+ fn, ok := decl.(*ast.FuncDecl)
+ if !ok || fn.Recv != nil || fn.Body == nil {
+ continue
+ }
+ handlers[fn.Name.Name] = fn
+ }
+ }
+ return fset, handlers
+}
+
+var returnRe = regexp.MustCompile(`^(\s*)return\s+([^,]+),\s*(.+)$`)
+
+// inlineBody returns the source of handler's body, rewritten so it can be
+// spliced into the dispatch loop: the `*pc` dereference becomes the loop's `pc`
+// local, and each `return r0, r1` becomes loop control flow. Success
+// (r1 == nil) advances pc and continues, an error sets err and breaks.
+func (g *generator) inlineBody(handler string) string {
+ fn := g.handlers[handler]
+ if fn == nil {
+ fatalf("no handler %q to inline", handler)
+ }
+ var raw bytes.Buffer
+ cfg := printer.Config{Mode: printer.UseSpaces | printer.TabIndent, Tabwidth: 8}
+ for _, stmt := range fn.Body.List {
+ if err := cfg.Fprint(&raw, g.fset, stmt); err != nil {
+ fatalf("print %s body: %v", handler, err)
+ }
+ raw.WriteByte('\n')
+ }
+ src := strings.ReplaceAll(raw.String(), "*pc", "pc")
+
+ var out strings.Builder
+ for _, line := range strings.Split(src, "\n") {
+ if m := returnRe.FindStringSubmatch(line); m != nil {
+ indent, r0, r1 := m[1], strings.TrimSpace(m[2]), strings.TrimSpace(m[3])
+ // The error and halt path must overwrite res and err. Otherwise a
+ // halting inlined op (JUMPI on an invalid jump, say) returns stale
+ // res from an earlier res-setting op such as a DELEGATECALL. The
+ // fuzzer caught exactly that bug. The success path advances pc and
+ // continues without writing res or err: stale res on a continuing
+ // op is always overwritten by whichever op ends the run, and err
+ // stays nil through normal iteration. Keeping these stores off the
+ // success path avoids growing every inlined case, which regresses
+ // tiny, layout-sensitive benchmarks.
+ if r1 == "nil" {
+ out.WriteString(indent + "pc++\n")
+ out.WriteString(indent + "continue mainLoop\n")
+ } else {
+ out.WriteString(indent + "res, err = " + r0 + ", " + r1 + "\n")
+ out.WriteString(indent + "break mainLoop\n")
+ }
+ continue
+ }
+ out.WriteString(line + "\n")
+ }
+ return out.String()
+}
+
+// ---------------------------------------------------------------------------
+// Metadata derivation (from the per-fork tables, via vm.GenForks)
+// ---------------------------------------------------------------------------
+
+func (g *generator) deriveMeta(forks []vm.GenFork) {
+ for code := 0; code < 256; code++ {
+ for _, fork := range forks {
+ o := fork.Ops[code]
+ if !o.Defined {
+ continue
+ }
+ g.meta[code] = opMeta{
+ defined: true,
+ name: o.Name,
+ introF: fork.RuleField,
+ constGas: o.ConstantGas,
+ minStack: o.MinStack,
+ maxStack: o.MaxStack,
+ }
+ break // first fork that defines it wins (its intro fork)
+ }
+ }
+ // Sanity: every inlined opcode must be defined and have fork-stable static
+ // gas / stack bounds across all forks where it appears (that is what makes
+ // it safe to bake as a constant). Bail loudly otherwise.
+ for code, handler := range inlineHandler {
+ g.checkStable(byte(code), handler, forks)
+ }
+ for code := 0x62; code <= 0x7f; code++ { // PUSH3-32
+ g.checkStable(byte(code), "makePush", forks)
+ }
+ for code := 0x80; code <= 0x8f; code++ { // DUP1-16
+ g.checkStable(byte(code), "makeDup", forks)
+ }
+}
+
+func (g *generator) checkStable(code byte, what string, forks []vm.GenFork) {
+ m := g.meta[code]
+ if !m.defined {
+ fatalf("opcode %#x (%s) selected for inlining but never defined", code, what)
+ }
+ for _, fork := range forks {
+ o := fork.Ops[code]
+ if !o.Defined {
+ continue
+ }
+ if o.ConstantGas != m.constGas || o.MinStack != m.minStack || o.MaxStack != m.maxStack || o.HasDynamicGas {
+ fatalf("opcode %#x (%s) is not fork-stable (fork %s): cannot inline", code, what, fork.Name)
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+// Case emission
+// ---------------------------------------------------------------------------
+
+// emitStackChecks emits the underflow/overflow guards for a baked opcode,
+// mirroring the legacy loop's order (stack validated before gas).
+func (g *generator) emitStackChecks(m opMeta) {
+ under := m.minStack > 0
+ over := m.maxStack < stackLimit
+ switch {
+ case under && over:
+ g.p("if sLen := stack.len(); sLen < %d {\nreturn nil, &ErrStackUnderflow{stackLen: sLen, required: %d}\n} else if sLen > %d {\nreturn nil, &ErrStackOverflow{stackLen: sLen, limit: %d}\n}\n", m.minStack, m.minStack, m.maxStack, m.maxStack)
+ case under:
+ g.p("if sLen := stack.len(); sLen < %d {\nreturn nil, &ErrStackUnderflow{stackLen: sLen, required: %d}\n}\n", m.minStack, m.minStack)
+ case over:
+ g.p("if sLen := stack.len(); sLen > %d {\nreturn nil, &ErrStackOverflow{stackLen: sLen, limit: %d}\n}\n", m.maxStack, m.maxStack)
+ }
+}
+
+func (g *generator) emitGasCheck(m opMeta) {
+ if m.constGas == 0 {
+ return
+ }
+ g.p("if contract.Gas.RegularGas < %d {\nreturn nil, ErrOutOfGas\n}\ncontract.Gas.RegularGas -= %d\n", m.constGas, m.constGas)
+}
+
+// emitWork emits the stack/gas guards and the opcode body (the portion that runs
+// when the opcode is active for the current fork).
+func (g *generator) emitWork(code byte) {
+ m := g.meta[code]
+ g.emitStackChecks(m)
+ g.emitGasCheck(m)
+
+ // PUSH1-PUSH32 swap their execute function under EIP-4762 (verkle) to charge
+ // code-chunk gas on the immediate bytes. Defer to the table handler there.
+ // The baked static gas and stack guard above already match.
+ if code >= 0x60 && code <= 0x7f {
+ g.p("if isEIP4762 {\nres, err = table[op].execute(&pc, evm, scope)\nif err != nil {\nbreak mainLoop\n}\npc++\ncontinue mainLoop\n}\n")
+ }
+
+ switch {
+ case code >= 0x62 && code <= 0x7f: // PUSH3-PUSH32: inline makePush(n,n)
+ g.emitPushFixed(int(code) - 0x5f)
+ case code >= 0x80 && code <= 0x8f: // DUP1-DUP16: inline makeDup(n)
+ g.p("scope.Stack.dup(%d)\npc++\ncontinue mainLoop\n", int(code)-0x7f)
+ default:
+ g.p("%s", g.inlineBody(inlineHandler[code]))
+ }
+}
+
+// emitPushFixed inlines makePush(n, n) for PUSH (n = 3..32).
+func (g *generator) emitPushFixed(n int) {
+ g.p("codeLen := len(scope.Contract.Code)\n")
+ g.p("start := min(codeLen, int(pc+1))\n")
+ g.p("end := min(codeLen, start+%d)\n", n)
+ g.p("a := scope.Stack.get()\n")
+ g.p("a.SetBytes(scope.Contract.Code[start:end])\n")
+ g.p("if missing := %d - (end - start); missing > 0 {\na.Lsh(a, uint(8*missing))\n}\n", n)
+ g.p("pc += %d\npc++\ncontinue mainLoop\n", n)
+}
+
+func (g *generator) emitInlineCase(code byte) {
+ m := g.meta[code]
+ g.p("case %s:\n", m.name)
+ if m.introF == "" {
+ g.emitWork(code)
+ return
+ }
+ // Fork-gated: run the inlined body only when the opcode is active for the
+ // current fork. Otherwise mirror the legacy loop's undefined-opcode handling.
+ g.p("if rules.%s {\n", m.introF)
+ g.emitWork(code)
+ g.p("}\n")
+ g.p("res, err = opUndefined(&pc, evm, scope)\nbreak mainLoop\n")
+}
+
+// emitDirectCold emits a cold opcode case identical to the default case, except
+// the handler, dynamic-gas, and memory-size functions are called by name
+// rather than through the indirect operation.* table pointers. Valid only for
+// fork-invariant ops (see directCold).
+func (g *generator) emitDirectCold(code byte) {
+ m := g.meta[code]
+ fns := directCold[code]
+ g.p("case %s:\n", m.name)
+ g.emitStackChecks(m)
+ g.emitGasCheck(m)
+ g.p("var memorySize uint64\n{\n")
+ g.p("memSize, overflow := %s(stack)\n", fns[2])
+ g.p("if overflow {\nreturn nil, ErrGasUintOverflow\n}\n")
+ g.p("if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow {\nreturn nil, ErrGasUintOverflow\n}\n}\n")
+ g.p("var dynamicCost GasCosts\n")
+ g.p("dynamicCost, err = %s(evm, contract, stack, mem, memorySize)\n", fns[1])
+ // WriteString: keep %w/%v literal (not generator format verbs).
+ g.buf.WriteString("if err != nil {\nreturn nil, fmt.Errorf(\"%w: %v\", ErrOutOfGas, err)\n}\n")
+ g.p("if contract.Gas.RegularGas < dynamicCost.RegularGas {\nreturn nil, ErrOutOfGas\n}\n")
+ g.p("contract.Gas.RegularGas -= dynamicCost.RegularGas\n")
+ g.p("if memorySize > 0 {\nmem.Resize(memorySize)\n}\n")
+ g.p("res, err = %s(&pc, evm, scope)\n", fns[0])
+ g.p("if err != nil {\nbreak mainLoop\n}\npc++\ncontinue mainLoop\n")
+}
+
+func (g *generator) emitDefault() {
+ // WriteString, not p(): this template contains %w/%v that must reach the
+ // output verbatim (they are not generator format verbs).
+ g.buf.WriteString(`default:
+operation := table[op]
+if sLen := stack.len(); sLen < operation.minStack {
+return nil, &ErrStackUnderflow{stackLen: sLen, required: operation.minStack}
+} else if sLen > operation.maxStack {
+return nil, &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack}
+}
+cost := operation.constantGas
+if contract.Gas.RegularGas < cost {
+return nil, ErrOutOfGas
+}
+contract.Gas.RegularGas -= cost
+var memorySize uint64
+if operation.dynamicGas != nil {
+if operation.memorySize != nil {
+memSize, overflow := operation.memorySize(stack)
+if overflow {
+return nil, ErrGasUintOverflow
+}
+if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow {
+return nil, ErrGasUintOverflow
+}
+}
+var dynamicCost GasCosts
+dynamicCost, err = operation.dynamicGas(evm, contract, stack, mem, memorySize)
+if err != nil {
+return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err)
+}
+if contract.Gas.RegularGas < dynamicCost.RegularGas {
+return nil, ErrOutOfGas
+}
+contract.Gas.RegularGas -= dynamicCost.RegularGas
+}
+if memorySize > 0 {
+mem.Resize(memorySize)
+}
+res, err = operation.execute(&pc, evm, scope)
+if err != nil {
+break mainLoop
+}
+pc++
+continue mainLoop
+`)
+}
+
+// ---------------------------------------------------------------------------
+// File emission
+// ---------------------------------------------------------------------------
+
+func (g *generator) emitFile() {
+ g.p("// Code generated by core/vm/gen; DO NOT EDIT.\n\n")
+ g.p("package vm\n\n")
+ g.p("import (\n")
+ g.p("\t\"fmt\"\n\n")
+ g.p("\t\"github.com/ethereum/go-ethereum/common/math\"\n")
+ g.p("\t\"github.com/ethereum/go-ethereum/core/tracing\"\n")
+ g.p(")\n\n")
+
+ g.buf.WriteString(`// execUntraced is the generated, tracing-free interpreter fast path. Hot,
+// fork-stable opcodes are inlined with their static gas and stack bounds baked
+// in. Fork-invariant cold ops (KECCAK256/MLOAD/MSTORE/MSTORE8) call their
+// handler and gas functions directly by name. Everything fork-varying is
+// dispatched through the active per-fork table in the default case. EVM.Run
+// selects this path when no tracer is configured.
+func (evm *EVM) execUntraced(scope *ScopeContext) (ret []byte, err error) {
+var (
+contract = scope.Contract
+mem = scope.Memory
+stack = scope.Stack
+table = evm.table
+rules = evm.chainRules
+isEIP4762 = rules.IsEIP4762
+pc = uint64(0)
+res []byte
+)
+_ = mem
+_ = rules
+_ = isEIP4762
+_ = table
+mainLoop:
+for {
+if isEIP4762 && !contract.IsDeployment && !contract.IsSystemCall {
+contractAddr := contract.Address()
+consumed, wanted := evm.TxContext.AccessEvents.CodeChunksRangeGas(contractAddr, pc, 1, uint64(len(contract.Code)), false, contract.Gas.RegularGas)
+contract.UseGas(GasCosts{RegularGas: consumed}, evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk)
+if consumed < wanted {
+return nil, ErrOutOfGas
+}
+}
+op := contract.GetOp(pc)
+switch op {
+`)
+ // Inlined hot cases, in opcode order for readability.
+ for code := 0; code < 256; code++ {
+ b := byte(code)
+ _, named := inlineHandler[b]
+ isPushFixed := code >= 0x62 && code <= 0x7f
+ isDup := code >= 0x80 && code <= 0x8f
+ if named || isPushFixed || isDup {
+ g.emitInlineCase(b)
+ } else if _, dc := directCold[b]; dc {
+ g.emitDirectCold(b)
+ }
+ }
+ g.emitDefault()
+ g.p("}\n") // switch
+ g.p("}\n") // for
+ g.p("if err == errStopToken {\nerr = nil\n}\n")
+ g.p("return res, err\n")
+ g.p("}\n") // func
+}
+
+// ---------------------------------------------------------------------------
+
+func fatalf(format string, args ...any) {
+ fmt.Fprintf(os.Stderr, "gen: "+format+"\n", args...)
+ os.Exit(1)
+}
+
+func main() {
+ _, self, _, ok := runtime.Caller(0)
+ if !ok {
+ fatalf("cannot resolve generator source path")
+ }
+ vmDir := filepath.Dir(filepath.Dir(self)) // .../core/vm/gen -> .../core/vm
+
+ fset, handlers := parseHandlers(vmDir)
+ g := &generator{fset: fset, handlers: handlers, buf: new(bytes.Buffer)}
+ g.deriveMeta(vm.GenForks())
+ g.emitFile()
+
+ formatted, err := format.Source(g.buf.Bytes())
+ if err != nil {
+ dbg := filepath.Join(vmDir, "interp_gen.go.broken")
+ os.WriteFile(dbg, g.buf.Bytes(), 0644)
+ fatalf("gofmt failed (%v); wrote unformatted output to %s", err, dbg)
+ }
+ // INTERP_GEN_OUT lets the CI-match test (interp_gen_test.go) regenerate to a
+ // temporary file and diff it against the committed one, without clobbering it.
+ out := filepath.Join(vmDir, "interp_gen.go")
+ if env := os.Getenv("INTERP_GEN_OUT"); env != "" {
+ out = env
+ }
+ if err := os.WriteFile(out, formatted, 0644); err != nil {
+ fatalf("write %s: %v", out, err)
+ }
+ fmt.Fprintf(os.Stderr, "gen: wrote %s (%d bytes)\n", out, len(formatted))
+}
diff --git a/core/vm/genspec.go b/core/vm/genspec.go
new file mode 100644
index 0000000000..c1e64cdc2a
--- /dev/null
+++ b/core/vm/genspec.go
@@ -0,0 +1,106 @@
+// Copyright 2026 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package vm
+
+//go:generate go run ./gen
+
+// This file exposes the interpreter's opcode metadata to the code generator in
+// core/vm/gen. It is not used at runtime. It exists so the generator can derive
+// the per-opcode spec (static gas, stack bounds, the fork an opcode first
+// appears in, and whether it carries dynamic gas or memory sizing) from the
+// existing per-fork instruction sets, rather than restating that metadata.
+//
+// The fork-varying dynamic-gas / memory-size / execute *functions* are not
+// surfaced here: several are closures (gasCall, the memoryCopierGas family,
+// makeGasLog) that cannot be recovered by name. The generated switch instead
+// reaches those volatile opcodes through the active per-fork JumpTable at
+// runtime (see interp_gen.go), so they need no generator-side restatement.
+
+// GenOp is the generator-facing scalar metadata for one opcode slot in one fork.
+type GenOp struct {
+ Name string // opcode mnemonic, e.g. "ADD" (valid only if Defined)
+ Defined bool // false if the slot is undefined/invalid in this fork
+ ConstantGas uint64
+ MinStack int
+ MaxStack int
+ HasDynamicGas bool
+ HasMemorySize bool
+}
+
+// GenFork bundles a fork's name, the params.Rules bool field that activates it
+// (empty for Frontier, which is always active), and its per-opcode metadata.
+type GenFork struct {
+ Name string
+ RuleField string
+ Ops [256]GenOp
+}
+
+// genForkOrder is the canonical fork progression for code generation, oldest to
+// newest, each paired with the params.Rules field that activates it.
+//
+// Petersburg is omitted: it shares Constantinople's opcode set and only changes
+// SSTORE dynamic gas, which flows through the shared gas function. Verkle/UBT is
+// omitted: over its Shanghai base it adds no new opcodes (it only swaps gas and
+// execute on existing opcodes), which the generated switch picks up from the
+// active table at runtime.
+var genForkOrder = []struct {
+ name string
+ rule string
+ set *JumpTable
+}{
+ {"Frontier", "", &frontierInstructionSet},
+ {"Homestead", "IsHomestead", &homesteadInstructionSet},
+ {"TangerineWhistle", "IsEIP150", &tangerineWhistleInstructionSet},
+ {"SpuriousDragon", "IsEIP158", &spuriousDragonInstructionSet},
+ {"Byzantium", "IsByzantium", &byzantiumInstructionSet},
+ {"Constantinople", "IsConstantinople", &constantinopleInstructionSet},
+ {"Istanbul", "IsIstanbul", &istanbulInstructionSet},
+ {"Berlin", "IsBerlin", &berlinInstructionSet},
+ {"London", "IsLondon", &londonInstructionSet},
+ {"Merge", "IsMerge", &mergeInstructionSet},
+ {"Shanghai", "IsShanghai", &shanghaiInstructionSet},
+ {"Cancun", "IsCancun", &cancunInstructionSet},
+ {"Prague", "IsPrague", &pragueInstructionSet},
+ {"Osaka", "IsOsaka", &osakaInstructionSet},
+ {"Amsterdam", "IsAmsterdam", &amsterdamInstructionSet},
+}
+
+// GenForks returns per-fork opcode metadata for the interpreter code generator
+// (core/vm/gen). It is exported solely for that purpose.
+func GenForks() []GenFork {
+ out := make([]GenFork, len(genForkOrder))
+ for i, f := range genForkOrder {
+ gf := GenFork{Name: f.name, RuleField: f.rule}
+ for code := 0; code < 256; code++ {
+ op := f.set[code]
+ if op == nil || op.undefined {
+ continue
+ }
+ gf.Ops[code] = GenOp{
+ Name: OpCode(code).String(),
+ Defined: true,
+ ConstantGas: op.constantGas,
+ MinStack: op.minStack,
+ MaxStack: op.maxStack,
+ HasDynamicGas: op.dynamicGas != nil,
+ HasMemorySize: op.memorySize != nil,
+ }
+ }
+ out[i] = gf
+ }
+ return out
+}
diff --git a/core/vm/interp_diff_test.go b/core/vm/interp_diff_test.go
new file mode 100644
index 0000000000..0b32959d86
--- /dev/null
+++ b/core/vm/interp_diff_test.go
@@ -0,0 +1,365 @@
+// Copyright 2026 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package vm
+
+// Differential test comparing the table loop against the generated dispatch.
+//
+// This file proves that the generated dispatch (execUntraced) is bit-identical
+// to the table-walking loop (execTraced, run here without a tracer via
+// EVM.forceTableLoop) for the observable surface of an EVM execution: return
+// data, gas left, error/halt, refund counter, emitted logs, and the resulting
+// state root. It runs the same program through both interpreters over freshly
+// built, identical state across several forks, plus a fuzz target over
+// arbitrary bytecode.
+//
+// execTraced is also the production tracing path, and
+// if it drifted from the generated dispatch then traced re-execution would
+// disagree with what consensus executed. Hook emission itself is covered by
+// the tracer test suites instead.
+
+import (
+ "bytes"
+ "math/big"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/state"
+ "github.com/ethereum/go-ethereum/core/tracing"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/params"
+ "github.com/holiman/uint256"
+)
+
+// diffForks is the set of fork configurations the diff test runs every program
+// under. Spanning forks exercises the baked runtime fork gates (e.g. SHL from
+// Constantinople, PUSH0 from Shanghai, CLZ from Osaka) in both the active and
+// the not-yet-activated states.
+var diffForks = func() []struct {
+ name string
+ cfg *params.ChainConfig
+ merged bool
+} {
+ // preConstantinople: Byzantium active, Constantinople and later not.
+ preCon := *params.TestChainConfig
+ preCon.ConstantinopleBlock = nil
+ preCon.PetersburgBlock = nil
+ preCon.IstanbulBlock = nil
+ preCon.MuirGlacierBlock = nil
+ preCon.BerlinBlock = nil
+ preCon.LondonBlock = nil
+ preCon.ArrowGlacierBlock = nil
+ preCon.GrayGlacierBlock = nil
+
+ return []struct {
+ name string
+ cfg *params.ChainConfig
+ merged bool
+ }{
+ {"Frontier", params.NonActivatedConfig, false},
+ {"Byzantium", &preCon, false},
+ {"London", params.TestChainConfig, false},
+ {"Merged", params.MergedTestChainConfig, true},
+ }
+}()
+
+var (
+ diffContractAddr = common.HexToAddress("0x000000000000000000000000000000000000c0de")
+ diffCalleeAddr = common.HexToAddress("0x000000000000000000000000000000000000ca11")
+ diffCaller = common.HexToAddress("0x000000000000000000000000000000000000face")
+)
+
+// diffCalleeCode is deployed at diffCalleeAddr as a CALL/CREATE target: it
+// writes a storage slot, logs, and returns 32 bytes of memory.
+//
+// PUSH1 0x2a PUSH1 0x07 SSTORE // sstore(7, 42)
+// PUSH1 0xbb PUSH1 0x00 MSTORE // mem[0..32] = 0xbb
+// PUSH1 0x20 PUSH1 0x00 LOG0 // log0(mem[0:32])
+// PUSH1 0x20 PUSH1 0x00 RETURN // return mem[0:32]
+var diffCalleeCode = []byte{
+ byte(PUSH1), 0x2a, byte(PUSH1), 0x07, byte(SSTORE),
+ byte(PUSH1), 0xbb, byte(PUSH1), 0x00, byte(MSTORE),
+ byte(PUSH1), 0x20, byte(PUSH1), 0x00, byte(LOG0),
+ byte(PUSH1), 0x20, byte(PUSH1), 0x00, byte(RETURN),
+}
+
+// asm is a tiny helper to build bytecode from opcodes/immediates.
+func asm(parts ...any) []byte {
+ var b []byte
+ for _, p := range parts {
+ switch v := p.(type) {
+ case OpCode:
+ b = append(b, byte(v))
+ case byte:
+ b = append(b, v)
+ case int:
+ b = append(b, byte(v))
+ case []byte:
+ b = append(b, v...)
+ default:
+ panic("asm: bad part")
+ }
+ }
+ return b
+}
+
+// diffPrograms is a curated set of bytecode snippets covering the inlined hot
+// opcodes, the volatile call-through opcodes, fork-gated opcodes, control flow,
+// and the principal error paths.
+var diffPrograms = []struct {
+ name string
+ code []byte
+ gas uint64
+}{
+ {"arith", asm(PUSH1, 0x07, PUSH1, 0x03, ADD, PUSH1, 0x02, MUL, PUSH1, 0x04, SUB, PUSH1, 0x03, DIV, PUSH1, 0x05, MOD, PUSH1, 0x00, MSTORE, PUSH1, 0x20, PUSH1, 0x00, RETURN), 100000},
+ {"signed-arith", asm(PUSH1, 0x07, PUSH1, 0xfd, SDIV, PUSH1, 0x03, SMOD, PUSH1, 0x02, SIGNEXTEND, PUSH1, 0x00, MSTORE, PUSH1, 0x20, PUSH1, 0x00, RETURN), 100000},
+ {"addmod-mulmod-exp", asm(PUSH1, 0x07, PUSH1, 0x05, PUSH1, 0x03, ADDMOD, PUSH1, 0x09, PUSH1, 0x04, PUSH1, 0x02, MULMOD, PUSH1, 0x03, PUSH1, 0x02, EXP, ADD, ADD, PUSH1, 0x00, MSTORE, PUSH1, 0x20, PUSH1, 0x00, RETURN), 100000},
+ {"cmp", asm(PUSH1, 0x07, PUSH1, 0x03, LT, PUSH1, 0x01, GT, PUSH1, 0x01, SLT, PUSH1, 0x01, SGT, PUSH1, 0x01, EQ, ISZERO, PUSH1, 0x00, MSTORE, PUSH1, 0x20, PUSH1, 0x00, RETURN), 100000},
+ {"bitwise", asm(PUSH1, 0xf0, PUSH1, 0x0f, AND, PUSH1, 0xaa, OR, PUSH1, 0x55, XOR, NOT, PUSH1, 0x01, BYTE, PUSH1, 0x00, MSTORE, PUSH1, 0x20, PUSH1, 0x00, RETURN), 100000},
+ {"shifts-clz", asm(PUSH1, 0xff, PUSH1, 0x04, SHL, PUSH1, 0x02, SHR, PUSH1, 0x01, SAR, CLZ, PUSH1, 0x00, MSTORE, PUSH1, 0x20, PUSH1, 0x00, RETURN), 100000},
+ {"dup-swap", asm(PUSH1, 0x01, PUSH1, 0x02, PUSH1, 0x03, DUP3, SWAP2, DUP1, SWAP1, POP, ADD, ADD, ADD, PUSH1, 0x00, MSTORE, PUSH1, 0x20, PUSH1, 0x00, RETURN), 100000},
+ {"push0-push32", asm(PUSH0, PUSH3, 0x01, 0x02, 0x03, ADD, PUSH5, 0x01, 0x02, 0x03, 0x04, 0x05, ADD, PUSH1, 0x00, MSTORE, PUSH1, 0x20, PUSH1, 0x00, RETURN), 100000},
+ {"keccak", asm(PUSH1, 0x20, PUSH1, 0x00, KECCAK256, PUSH1, 0x00, MSTORE, PUSH1, 0x20, PUSH1, 0x00, RETURN), 100000},
+ {"memory", asm(PUSH1, 0xab, PUSH1, 0x00, MSTORE8, PUSH1, 0xcd, PUSH2, 0x00, 0x40, MSTORE, MSIZE, PUSH1, 0x60, MSTORE, PUSH1, 0x80, PUSH1, 0x00, RETURN), 100000},
+ {"mcopy", asm(PUSH1, 0xff, PUSH1, 0x00, MSTORE, PUSH1, 0x20, PUSH1, 0x00, PUSH1, 0x20, MCOPY, PUSH1, 0x40, PUSH1, 0x00, RETURN), 100000},
+ {"storage", asm(PUSH1, 0x63, PUSH1, 0x07, SSTORE, PUSH1, 0x07, SLOAD, PUSH1, 0x00, MSTORE, PUSH1, 0x20, PUSH1, 0x00, RETURN), 100000},
+ {"transient", asm(PUSH1, 0x63, PUSH1, 0x07, TSTORE, PUSH1, 0x07, TLOAD, PUSH1, 0x00, MSTORE, PUSH1, 0x20, PUSH1, 0x00, RETURN), 100000},
+ {"loop", asm(PUSH1, 0x00, JUMPDEST, PUSH1, 0x01, ADD, DUP1, PUSH1, 0x05, LT, PUSH1, 0x02, JUMPI, PUSH1, 0x00, MSTORE, PUSH1, 0x20, PUSH1, 0x00, RETURN), 100000},
+ {"jump", asm(PUSH1, 0x06, JUMP, INVALID, INVALID, JUMPDEST, PUSH1, 0x2a, PUSH1, 0x00, MSTORE, PUSH1, 0x20, PUSH1, 0x00, RETURN), 100000},
+ {"env", asm(ADDRESS, CALLER, CALLVALUE, ORIGIN, GASPRICE, CODESIZE, GAS, PC, ADD, ADD, ADD, ADD, ADD, ADD, PUSH1, 0x00, MSTORE, PUSH1, 0x20, PUSH1, 0x00, RETURN), 100000},
+ {"block", asm(NUMBER, TIMESTAMP, COINBASE, GASLIMIT, CHAINID, SELFBALANCE, BASEFEE, DIFFICULTY, ADD, ADD, ADD, ADD, ADD, ADD, ADD, PUSH1, 0x00, MSTORE, PUSH1, 0x20, PUSH1, 0x00, RETURN), 100000},
+ {"calldata", asm(PUSH1, 0x00, CALLDATALOAD, CALLDATASIZE, PUSH1, 0x00, PUSH1, 0x00, CALLDATACOPY, ADD, PUSH1, 0x00, MSTORE, PUSH1, 0x20, PUSH1, 0x00, RETURN), 100000},
+ {"codecopy", asm(PUSH1, 0x10, PUSH1, 0x00, PUSH1, 0x00, CODECOPY, PUSH1, 0x10, PUSH1, 0x00, RETURN), 100000},
+ {"log", asm(PUSH1, 0x11, PUSH1, 0x00, MSTORE, PUSH1, 0x22, PUSH1, 0x33, PUSH1, 0x20, PUSH1, 0x00, LOG2, STOP), 100000},
+ // Fuzz-found regression (the stale-res bug): a res-setting DELEGATECALL
+ // followed by a halting inlined op (JUMPI to an invalid destination). The
+ // buggy build returned the DELEGATECALL output instead of nil.
+ {"delegatecall-then-invalid-jumpi", asm(
+ PUSH1, 0x30, PUSH1, 0x30, PUSH1, 0x30, PUSH1, 0x30,
+ PUSH20, diffCalleeAddr.Bytes(),
+ PUSH2, 0x30, 0x30, DELEGATECALL,
+ PC, PC, JUMPI), 100000},
+ {"extaccess", asm(PUSH20, diffCalleeAddr.Bytes(), EXTCODESIZE, PUSH20, diffCalleeAddr.Bytes(), BALANCE, ADD, PUSH1, 0x00, MSTORE, PUSH1, 0x20, PUSH1, 0x00, RETURN), 100000},
+ {"call", asm(
+ PUSH1, 0x20, PUSH1, 0x00, PUSH1, 0x00, PUSH1, 0x00, PUSH1, 0x00,
+ PUSH20, diffCalleeAddr.Bytes(), PUSH2, 0xff, 0xff, CALL,
+ PUSH1, 0x20, PUSH1, 0x00, RETURN), 200000},
+ {"staticcall", asm(
+ PUSH1, 0x20, PUSH1, 0x00, PUSH1, 0x00, PUSH1, 0x00,
+ PUSH20, diffCalleeAddr.Bytes(), PUSH2, 0xff, 0xff, STATICCALL,
+ PUSH1, 0x20, PUSH1, 0x00, RETURN), 200000},
+ {"delegatecall", asm(
+ PUSH1, 0x20, PUSH1, 0x00, PUSH1, 0x00, PUSH1, 0x00,
+ PUSH20, diffCalleeAddr.Bytes(), PUSH2, 0xff, 0xff, DELEGATECALL,
+ PUSH1, 0x20, PUSH1, 0x00, RETURN), 200000},
+ {"create", asm(
+ // store init code that returns empty, then CREATE
+ PUSH1, 0x00, PUSH1, 0x00, MSTORE,
+ PUSH1, 0x00, PUSH1, 0x00, PUSH1, 0x00, CREATE,
+ PUSH1, 0x00, MSTORE, PUSH1, 0x20, PUSH1, 0x00, RETURN), 200000},
+ {"revert", asm(PUSH1, 0xaa, PUSH1, 0x00, MSTORE, PUSH1, 0x20, PUSH1, 0x00, REVERT), 100000},
+ {"selfdestruct", asm(PUSH20, diffCaller.Bytes(), SELFDESTRUCT), 100000},
+ {"stop", asm(PUSH1, 0x01, STOP), 100000},
+ {"invalid-opcode", asm(PUSH1, 0x01, INVALID), 100000},
+ {"undefined-opcode", asm(PUSH1, 0x01, 0x0c), 100000},
+ {"stack-underflow", asm(ADD), 100000},
+ {"oog", asm(PUSH1, 0x07, PUSH1, 0x03, ADD, PUSH1, 0x00, MSTORE, PUSH1, 0x20, PUSH1, 0x00, RETURN), 7},
+ {"invalid-jump", asm(PUSH1, 0x03, JUMP, STOP), 100000},
+}
+
+// diffResult captures the observable outcome of running a program.
+type diffResult struct {
+ ret []byte
+ gasLeft uint64
+ errStr string // "" if no error
+ refund uint64
+ root common.Hash
+ logs []*types.Log
+}
+
+func (r diffResult) equal(o diffResult) (string, bool) {
+ if !bytes.Equal(r.ret, o.ret) {
+ return "return data", false
+ }
+ if r.gasLeft != o.gasLeft {
+ return "gas left", false
+ }
+ if r.errStr != o.errStr {
+ return "error", false
+ }
+ if r.refund != o.refund {
+ return "refund", false
+ }
+ if r.root != o.root {
+ return "state root", false
+ }
+ if len(r.logs) != len(o.logs) {
+ return "log count", false
+ }
+ for i := range r.logs {
+ a, b := r.logs[i], o.logs[i]
+ if a.Address != b.Address || !bytes.Equal(a.Data, b.Data) || len(a.Topics) != len(b.Topics) {
+ return "log content", false
+ }
+ for j := range a.Topics {
+ if a.Topics[j] != b.Topics[j] {
+ return "log topic", false
+ }
+ }
+ }
+ return "", true
+}
+
+func newDiffState(t testing.TB) *state.StateDB {
+ t.Helper()
+ statedb, err := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
+ if err != nil {
+ t.Fatalf("state.New: %v", err)
+ }
+ // Main contract: balance + a pre-set storage slot.
+ statedb.CreateAccount(diffContractAddr)
+ statedb.SetBalance(diffContractAddr, uint256.NewInt(1000), tracing.BalanceChangeUnspecified)
+ statedb.SetState(diffContractAddr, common.Hash{31: 0x07}, common.Hash{31: 0x07})
+ // Callee target for CALL/STATICCALL/DELEGATECALL.
+ statedb.CreateAccount(diffCalleeAddr)
+ statedb.SetBalance(diffCalleeAddr, uint256.NewInt(500), tracing.BalanceChangeUnspecified)
+ statedb.SetCode(diffCalleeAddr, diffCalleeCode, tracing.CodeChangeUnspecified)
+ // Caller EOA with a balance.
+ statedb.CreateAccount(diffCaller)
+ statedb.SetBalance(diffCaller, uint256.NewInt(1<<62), tracing.BalanceChangeUnspecified)
+ statedb.Finalise(true)
+ return statedb
+}
+
+func diffBlockCtx(merged bool) BlockContext {
+ ctx := BlockContext{
+ CanTransfer: func(StateDB, common.Address, *uint256.Int) bool { return true },
+ Transfer: func(StateDB, common.Address, common.Address, *uint256.Int, *params.Rules) {},
+ GetHash: func(uint64) common.Hash { return common.Hash{0xde, 0xad} },
+ Coinbase: common.HexToAddress("0xc01ba5e"),
+ BlockNumber: big.NewInt(8),
+ Time: 1234,
+ Difficulty: big.NewInt(0x20000),
+ GasLimit: 30_000_000,
+ BaseFee: big.NewInt(7),
+ BlobBaseFee: big.NewInt(3),
+ }
+ if merged {
+ h := common.HexToHash("0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20")
+ ctx.Random = &h
+ }
+ return ctx
+}
+
+// TestExtraEIPs checks that EIPs enabled via Config.ExtraEips take effect even
+// when they touch opcodes the generated dispatch inlines. PUSH0 (EIP-3855) on
+// a pre-Shanghai config is the canary: the runtime table has it enabled but
+// the baked fork gate does not, so execution must route through the table loop.
+func TestExtraEIPs(t *testing.T) {
+ code := asm(PUSH0, STOP)
+ statedb := newDiffState(t)
+ statedb.SetCode(diffContractAddr, code, tracing.CodeChangeUnspecified)
+ statedb.Finalise(true)
+
+ evm := NewEVM(diffBlockCtx(false), statedb, params.TestChainConfig, Config{ExtraEips: []int{3855}})
+ evm.SetTxContext(TxContext{
+ Origin: diffCaller,
+ GasPrice: uint256.NewInt(1),
+ })
+ _, _, err := evm.Call(diffCaller, diffContractAddr, nil, NewGasBudget(100000), new(uint256.Int))
+ if err != nil {
+ t.Fatalf("PUSH0 enabled via ExtraEips failed: %v", err)
+ }
+}
+
+// runOne executes code at diffContractAddr with the given interpreter selection
+// and returns the observable result.
+func runOne(t testing.TB, cfg *params.ChainConfig, merged, useTableLoop bool, code, input []byte, gas uint64) diffResult {
+ t.Helper()
+ statedb := newDiffState(t)
+ statedb.SetCode(diffContractAddr, code, tracing.CodeChangeUnspecified)
+ statedb.Finalise(true)
+
+ evm := NewEVM(diffBlockCtx(merged), statedb, cfg, Config{})
+ evm.SetTxContext(TxContext{
+ Origin: diffCaller,
+ GasPrice: uint256.NewInt(1),
+ BlobHashes: []common.Hash{{0xb1, 0x0b}},
+ })
+ evm.forceTableLoop = useTableLoop
+
+ ret, leftOver, err := evm.Call(diffCaller, diffContractAddr, input, NewGasBudget(gas), new(uint256.Int))
+ errStr := ""
+ if err != nil {
+ errStr = err.Error()
+ }
+ return diffResult{
+ ret: ret,
+ gasLeft: leftOver.RegularGas,
+ errStr: errStr,
+ refund: statedb.GetRefund(),
+ root: statedb.IntermediateRoot(true),
+ logs: statedb.Logs(),
+ }
+}
+
+func TestInterpreterDiff(t *testing.T) {
+ for _, fk := range diffForks {
+ for _, prog := range diffPrograms {
+ t.Run(fk.name+"/"+prog.name, func(t *testing.T) {
+ input := common.FromHex("0xdeadbeef00000000000000000000000000000000000000000000000000000042")
+ table := runOne(t, fk.cfg, fk.merged, true, prog.code, input, prog.gas)
+ gen := runOne(t, fk.cfg, fk.merged, false, prog.code, input, prog.gas)
+ if where, ok := gen.equal(table); !ok {
+ t.Fatalf("divergence in %s:\n table: ret=%x gas=%d err=%q refund=%d root=%x logs=%d\n gen: ret=%x gas=%d err=%q refund=%d root=%x logs=%d",
+ where,
+ table.ret, table.gasLeft, table.errStr, table.refund, table.root, len(table.logs),
+ gen.ret, gen.gasLeft, gen.errStr, gen.refund, gen.root, len(gen.logs))
+ }
+ })
+ }
+ }
+}
+
+// FuzzInterpreterDiff fuzzes arbitrary bytecode + calldata + gas and asserts the
+// generated dispatch matches the table-walking loop on every observable axis.
+func FuzzInterpreterDiff(f *testing.F) {
+ for _, prog := range diffPrograms {
+ f.Add(prog.code, []byte{0x01, 0x02, 0x03, 0x04}, uint64(100000))
+ }
+ // A couple of structurally-interesting seeds.
+ f.Add(asm(PUSH1, 0x00, JUMPDEST, PUSH1, 0x01, ADD, DUP1, PUSH1, 0xff, GT, PUSH1, 0x02, JUMPI, STOP), []byte{}, uint64(50000))
+ f.Add(bytes.Repeat([]byte{byte(PUSH1), 0x01}, 64), []byte{}, uint64(100000))
+
+ f.Fuzz(func(t *testing.T, code, input []byte, gas uint64) {
+ if len(code) > 24576 { // max contract code size, keep cases realistic
+ return
+ }
+ if gas > 5_000_000 {
+ gas = 5_000_000 // bound execution time
+ }
+ for _, fk := range diffForks {
+ table := runOne(t, fk.cfg, fk.merged, true, code, input, gas)
+ gen := runOne(t, fk.cfg, fk.merged, false, code, input, gas)
+ if where, ok := gen.equal(table); !ok {
+ t.Fatalf("divergence in %s (fork %s): code=%x input=%x gas=%d\n table: ret=%x gas=%d err=%q refund=%d root=%x logs=%d\n gen: ret=%x gas=%d err=%q refund=%d root=%x logs=%d",
+ where, fk.name, code, input, gas,
+ table.ret, table.gasLeft, table.errStr, table.refund, table.root, len(table.logs),
+ gen.ret, gen.gasLeft, gen.errStr, gen.refund, gen.root, len(gen.logs))
+ }
+ }
+ })
+}
diff --git a/core/vm/interp_gen.go b/core/vm/interp_gen.go
new file mode 100644
index 0000000000..a2b3c6ed71
--- /dev/null
+++ b/core/vm/interp_gen.go
@@ -0,0 +1,1995 @@
+// Code generated by core/vm/gen; DO NOT EDIT.
+
+package vm
+
+import (
+ "fmt"
+
+ "github.com/ethereum/go-ethereum/common/math"
+ "github.com/ethereum/go-ethereum/core/tracing"
+)
+
+// execUntraced is the generated, tracing-free interpreter fast path. Hot,
+// fork-stable opcodes are inlined with their static gas and stack bounds baked
+// in. Fork-invariant cold ops (KECCAK256/MLOAD/MSTORE/MSTORE8) call their
+// handler and gas functions directly by name. Everything fork-varying is
+// dispatched through the active per-fork table in the default case. EVM.Run
+// selects this path when no tracer is configured.
+func (evm *EVM) execUntraced(scope *ScopeContext) (ret []byte, err error) {
+ var (
+ contract = scope.Contract
+ mem = scope.Memory
+ stack = scope.Stack
+ table = evm.table
+ rules = evm.chainRules
+ isEIP4762 = rules.IsEIP4762
+ pc = uint64(0)
+ res []byte
+ )
+ _ = mem
+ _ = rules
+ _ = isEIP4762
+ _ = table
+mainLoop:
+ for {
+ if isEIP4762 && !contract.IsDeployment && !contract.IsSystemCall {
+ contractAddr := contract.Address()
+ consumed, wanted := evm.TxContext.AccessEvents.CodeChunksRangeGas(contractAddr, pc, 1, uint64(len(contract.Code)), false, contract.Gas.RegularGas)
+ contract.UseGas(GasCosts{RegularGas: consumed}, evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk)
+ if consumed < wanted {
+ return nil, ErrOutOfGas
+ }
+ }
+ op := contract.GetOp(pc)
+ switch op {
+ case ADD:
+ if sLen := stack.len(); sLen < 2 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 2}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ x, y := scope.Stack.pop(), scope.Stack.peek()
+ y.Add(&x, y)
+ pc++
+ continue mainLoop
+
+ case MUL:
+ if sLen := stack.len(); sLen < 2 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 2}
+ }
+ if contract.Gas.RegularGas < 5 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 5
+ x, y := scope.Stack.pop(), scope.Stack.peek()
+ y.Mul(&x, y)
+ pc++
+ continue mainLoop
+
+ case SUB:
+ if sLen := stack.len(); sLen < 2 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 2}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ x, y := scope.Stack.pop(), scope.Stack.peek()
+ y.Sub(&x, y)
+ pc++
+ continue mainLoop
+
+ case DIV:
+ if sLen := stack.len(); sLen < 2 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 2}
+ }
+ if contract.Gas.RegularGas < 5 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 5
+ x, y := scope.Stack.pop(), scope.Stack.peek()
+ y.Div(&x, y)
+ pc++
+ continue mainLoop
+
+ case SDIV:
+ if sLen := stack.len(); sLen < 2 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 2}
+ }
+ if contract.Gas.RegularGas < 5 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 5
+ x, y := scope.Stack.pop(), scope.Stack.peek()
+ y.SDiv(&x, y)
+ pc++
+ continue mainLoop
+
+ case MOD:
+ if sLen := stack.len(); sLen < 2 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 2}
+ }
+ if contract.Gas.RegularGas < 5 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 5
+ x, y := scope.Stack.pop(), scope.Stack.peek()
+ y.Mod(&x, y)
+ pc++
+ continue mainLoop
+
+ case SMOD:
+ if sLen := stack.len(); sLen < 2 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 2}
+ }
+ if contract.Gas.RegularGas < 5 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 5
+ x, y := scope.Stack.pop(), scope.Stack.peek()
+ y.SMod(&x, y)
+ pc++
+ continue mainLoop
+
+ case ADDMOD:
+ if sLen := stack.len(); sLen < 3 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 3}
+ }
+ if contract.Gas.RegularGas < 8 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 8
+ x, y, z := scope.Stack.pop(), scope.Stack.pop(), scope.Stack.peek()
+ z.AddMod(&x, &y, z)
+ pc++
+ continue mainLoop
+
+ case MULMOD:
+ if sLen := stack.len(); sLen < 3 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 3}
+ }
+ if contract.Gas.RegularGas < 8 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 8
+ x, y, z := scope.Stack.pop(), scope.Stack.pop(), scope.Stack.peek()
+ z.MulMod(&x, &y, z)
+ pc++
+ continue mainLoop
+
+ case SIGNEXTEND:
+ if sLen := stack.len(); sLen < 2 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 2}
+ }
+ if contract.Gas.RegularGas < 5 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 5
+ back, num := scope.Stack.pop(), scope.Stack.peek()
+ num.ExtendSign(num, &back)
+ pc++
+ continue mainLoop
+
+ case LT:
+ if sLen := stack.len(); sLen < 2 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 2}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ x, y := scope.Stack.pop(), scope.Stack.peek()
+ if x.Lt(y) {
+ y.SetOne()
+ } else {
+ y.Clear()
+ }
+ pc++
+ continue mainLoop
+
+ case GT:
+ if sLen := stack.len(); sLen < 2 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 2}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ x, y := scope.Stack.pop(), scope.Stack.peek()
+ if x.Gt(y) {
+ y.SetOne()
+ } else {
+ y.Clear()
+ }
+ pc++
+ continue mainLoop
+
+ case SLT:
+ if sLen := stack.len(); sLen < 2 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 2}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ x, y := scope.Stack.pop(), scope.Stack.peek()
+ if x.Slt(y) {
+ y.SetOne()
+ } else {
+ y.Clear()
+ }
+ pc++
+ continue mainLoop
+
+ case SGT:
+ if sLen := stack.len(); sLen < 2 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 2}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ x, y := scope.Stack.pop(), scope.Stack.peek()
+ if x.Sgt(y) {
+ y.SetOne()
+ } else {
+ y.Clear()
+ }
+ pc++
+ continue mainLoop
+
+ case EQ:
+ if sLen := stack.len(); sLen < 2 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 2}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ x, y := scope.Stack.pop(), scope.Stack.peek()
+ if x.Eq(y) {
+ y.SetOne()
+ } else {
+ y.Clear()
+ }
+ pc++
+ continue mainLoop
+
+ case ISZERO:
+ if sLen := stack.len(); sLen < 1 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 1}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ x := scope.Stack.peek()
+ if x.IsZero() {
+ x.SetOne()
+ } else {
+ x.Clear()
+ }
+ pc++
+ continue mainLoop
+
+ case AND:
+ if sLen := stack.len(); sLen < 2 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 2}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ x, y := scope.Stack.pop(), scope.Stack.peek()
+ y.And(&x, y)
+ pc++
+ continue mainLoop
+
+ case OR:
+ if sLen := stack.len(); sLen < 2 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 2}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ x, y := scope.Stack.pop(), scope.Stack.peek()
+ y.Or(&x, y)
+ pc++
+ continue mainLoop
+
+ case XOR:
+ if sLen := stack.len(); sLen < 2 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 2}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ x, y := scope.Stack.pop(), scope.Stack.peek()
+ y.Xor(&x, y)
+ pc++
+ continue mainLoop
+
+ case NOT:
+ if sLen := stack.len(); sLen < 1 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 1}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ x := scope.Stack.peek()
+ x.Not(x)
+ pc++
+ continue mainLoop
+
+ case BYTE:
+ if sLen := stack.len(); sLen < 2 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 2}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ th, val := scope.Stack.pop(), scope.Stack.peek()
+ val.Byte(&th)
+ pc++
+ continue mainLoop
+
+ case SHL:
+ if rules.IsConstantinople {
+ if sLen := stack.len(); sLen < 2 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 2}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ shift, value := scope.Stack.pop(), scope.Stack.peek()
+ if shift.LtUint64(256) {
+ value.Lsh(value, uint(shift.Uint64()))
+ } else {
+ value.Clear()
+ }
+ pc++
+ continue mainLoop
+
+ }
+ res, err = opUndefined(&pc, evm, scope)
+ break mainLoop
+ case SHR:
+ if rules.IsConstantinople {
+ if sLen := stack.len(); sLen < 2 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 2}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ shift, value := scope.Stack.pop(), scope.Stack.peek()
+ if shift.LtUint64(256) {
+ value.Rsh(value, uint(shift.Uint64()))
+ } else {
+ value.Clear()
+ }
+ pc++
+ continue mainLoop
+
+ }
+ res, err = opUndefined(&pc, evm, scope)
+ break mainLoop
+ case SAR:
+ if rules.IsConstantinople {
+ if sLen := stack.len(); sLen < 2 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 2}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ shift, value := scope.Stack.pop(), scope.Stack.peek()
+ if shift.GtUint64(256) {
+ if value.Sign() >= 0 {
+ value.Clear()
+ } else {
+
+ value.SetAllOne()
+ }
+ pc++
+ continue mainLoop
+ }
+ n := uint(shift.Uint64())
+ value.SRsh(value, n)
+ pc++
+ continue mainLoop
+
+ }
+ res, err = opUndefined(&pc, evm, scope)
+ break mainLoop
+ case CLZ:
+ if rules.IsOsaka {
+ if sLen := stack.len(); sLen < 1 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 1}
+ }
+ if contract.Gas.RegularGas < 5 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 5
+ x := scope.Stack.peek()
+ x.SetUint64(256 - uint64(x.BitLen()))
+ pc++
+ continue mainLoop
+
+ }
+ res, err = opUndefined(&pc, evm, scope)
+ break mainLoop
+ case KECCAK256:
+ if sLen := stack.len(); sLen < 2 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 2}
+ }
+ if contract.Gas.RegularGas < 30 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 30
+ var memorySize uint64
+ {
+ memSize, overflow := memoryKeccak256(stack)
+ if overflow {
+ return nil, ErrGasUintOverflow
+ }
+ if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow {
+ return nil, ErrGasUintOverflow
+ }
+ }
+ var dynamicCost GasCosts
+ dynamicCost, err = gasKeccak256(evm, contract, stack, mem, memorySize)
+ if err != nil {
+ return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err)
+ }
+ if contract.Gas.RegularGas < dynamicCost.RegularGas {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= dynamicCost.RegularGas
+ if memorySize > 0 {
+ mem.Resize(memorySize)
+ }
+ res, err = opKeccak256(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ case POP:
+ if sLen := stack.len(); sLen < 1 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 1}
+ }
+ if contract.Gas.RegularGas < 2 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 2
+ scope.Stack.pop()
+ pc++
+ continue mainLoop
+
+ case MLOAD:
+ if sLen := stack.len(); sLen < 1 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 1}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ var memorySize uint64
+ {
+ memSize, overflow := memoryMLoad(stack)
+ if overflow {
+ return nil, ErrGasUintOverflow
+ }
+ if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow {
+ return nil, ErrGasUintOverflow
+ }
+ }
+ var dynamicCost GasCosts
+ dynamicCost, err = gasMLoad(evm, contract, stack, mem, memorySize)
+ if err != nil {
+ return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err)
+ }
+ if contract.Gas.RegularGas < dynamicCost.RegularGas {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= dynamicCost.RegularGas
+ if memorySize > 0 {
+ mem.Resize(memorySize)
+ }
+ res, err = opMload(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ case MSTORE:
+ if sLen := stack.len(); sLen < 2 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 2}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ var memorySize uint64
+ {
+ memSize, overflow := memoryMStore(stack)
+ if overflow {
+ return nil, ErrGasUintOverflow
+ }
+ if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow {
+ return nil, ErrGasUintOverflow
+ }
+ }
+ var dynamicCost GasCosts
+ dynamicCost, err = gasMStore(evm, contract, stack, mem, memorySize)
+ if err != nil {
+ return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err)
+ }
+ if contract.Gas.RegularGas < dynamicCost.RegularGas {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= dynamicCost.RegularGas
+ if memorySize > 0 {
+ mem.Resize(memorySize)
+ }
+ res, err = opMstore(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ case MSTORE8:
+ if sLen := stack.len(); sLen < 2 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 2}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ var memorySize uint64
+ {
+ memSize, overflow := memoryMStore8(stack)
+ if overflow {
+ return nil, ErrGasUintOverflow
+ }
+ if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow {
+ return nil, ErrGasUintOverflow
+ }
+ }
+ var dynamicCost GasCosts
+ dynamicCost, err = gasMStore8(evm, contract, stack, mem, memorySize)
+ if err != nil {
+ return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err)
+ }
+ if contract.Gas.RegularGas < dynamicCost.RegularGas {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= dynamicCost.RegularGas
+ if memorySize > 0 {
+ mem.Resize(memorySize)
+ }
+ res, err = opMstore8(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ case JUMP:
+ if sLen := stack.len(); sLen < 1 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 1}
+ }
+ if contract.Gas.RegularGas < 8 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 8
+ if evm.abort.Load() {
+ res, err = nil, errStopToken
+ break mainLoop
+ }
+ pos := scope.Stack.pop()
+ if !scope.Contract.validJumpdest(&pos) {
+ res, err = nil, ErrInvalidJump
+ break mainLoop
+ }
+ pc = pos.Uint64() - 1
+ pc++
+ continue mainLoop
+
+ case JUMPI:
+ if sLen := stack.len(); sLen < 2 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 2}
+ }
+ if contract.Gas.RegularGas < 10 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 10
+ if evm.abort.Load() {
+ res, err = nil, errStopToken
+ break mainLoop
+ }
+ pos, cond := scope.Stack.pop(), scope.Stack.pop()
+ if !cond.IsZero() {
+ if !scope.Contract.validJumpdest(&pos) {
+ res, err = nil, ErrInvalidJump
+ break mainLoop
+ }
+ pc = pos.Uint64() - 1
+ }
+ pc++
+ continue mainLoop
+
+ case PC:
+ if sLen := stack.len(); sLen > 1023 {
+ return nil, &ErrStackOverflow{stackLen: sLen, limit: 1023}
+ }
+ if contract.Gas.RegularGas < 2 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 2
+ scope.Stack.get().SetUint64(pc)
+ pc++
+ continue mainLoop
+
+ case MSIZE:
+ if sLen := stack.len(); sLen > 1023 {
+ return nil, &ErrStackOverflow{stackLen: sLen, limit: 1023}
+ }
+ if contract.Gas.RegularGas < 2 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 2
+ scope.Stack.get().SetUint64(uint64(scope.Memory.Len()))
+ pc++
+ continue mainLoop
+
+ case JUMPDEST:
+ if contract.Gas.RegularGas < 1 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 1
+ pc++
+ continue mainLoop
+
+ case PUSH0:
+ if rules.IsShanghai {
+ if sLen := stack.len(); sLen > 1023 {
+ return nil, &ErrStackOverflow{stackLen: sLen, limit: 1023}
+ }
+ if contract.Gas.RegularGas < 2 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 2
+ scope.Stack.get().Clear()
+ pc++
+ continue mainLoop
+
+ }
+ res, err = opUndefined(&pc, evm, scope)
+ break mainLoop
+ case PUSH1:
+ if sLen := stack.len(); sLen > 1023 {
+ return nil, &ErrStackOverflow{stackLen: sLen, limit: 1023}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ var (
+ codeLen = uint64(len(scope.Contract.Code))
+ elem = scope.Stack.get()
+ )
+ pc += 1
+ if pc < codeLen {
+ elem.SetUint64(uint64(scope.Contract.Code[pc]))
+ } else {
+ elem.Clear()
+ }
+ pc++
+ continue mainLoop
+
+ case PUSH2:
+ if sLen := stack.len(); sLen > 1023 {
+ return nil, &ErrStackOverflow{stackLen: sLen, limit: 1023}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ var (
+ codeLen = uint64(len(scope.Contract.Code))
+ elem = scope.Stack.get()
+ )
+ if pc+2 < codeLen {
+ elem.SetBytes2(scope.Contract.Code[pc+1 : pc+3])
+ } else if pc+1 < codeLen {
+ elem.SetUint64(uint64(scope.Contract.Code[pc+1]) << 8)
+ } else {
+ elem.Clear()
+ }
+ pc += 2
+ pc++
+ continue mainLoop
+
+ case PUSH3:
+ if sLen := stack.len(); sLen > 1023 {
+ return nil, &ErrStackOverflow{stackLen: sLen, limit: 1023}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ codeLen := len(scope.Contract.Code)
+ start := min(codeLen, int(pc+1))
+ end := min(codeLen, start+3)
+ a := scope.Stack.get()
+ a.SetBytes(scope.Contract.Code[start:end])
+ if missing := 3 - (end - start); missing > 0 {
+ a.Lsh(a, uint(8*missing))
+ }
+ pc += 3
+ pc++
+ continue mainLoop
+ case PUSH4:
+ if sLen := stack.len(); sLen > 1023 {
+ return nil, &ErrStackOverflow{stackLen: sLen, limit: 1023}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ codeLen := len(scope.Contract.Code)
+ start := min(codeLen, int(pc+1))
+ end := min(codeLen, start+4)
+ a := scope.Stack.get()
+ a.SetBytes(scope.Contract.Code[start:end])
+ if missing := 4 - (end - start); missing > 0 {
+ a.Lsh(a, uint(8*missing))
+ }
+ pc += 4
+ pc++
+ continue mainLoop
+ case PUSH5:
+ if sLen := stack.len(); sLen > 1023 {
+ return nil, &ErrStackOverflow{stackLen: sLen, limit: 1023}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ codeLen := len(scope.Contract.Code)
+ start := min(codeLen, int(pc+1))
+ end := min(codeLen, start+5)
+ a := scope.Stack.get()
+ a.SetBytes(scope.Contract.Code[start:end])
+ if missing := 5 - (end - start); missing > 0 {
+ a.Lsh(a, uint(8*missing))
+ }
+ pc += 5
+ pc++
+ continue mainLoop
+ case PUSH6:
+ if sLen := stack.len(); sLen > 1023 {
+ return nil, &ErrStackOverflow{stackLen: sLen, limit: 1023}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ codeLen := len(scope.Contract.Code)
+ start := min(codeLen, int(pc+1))
+ end := min(codeLen, start+6)
+ a := scope.Stack.get()
+ a.SetBytes(scope.Contract.Code[start:end])
+ if missing := 6 - (end - start); missing > 0 {
+ a.Lsh(a, uint(8*missing))
+ }
+ pc += 6
+ pc++
+ continue mainLoop
+ case PUSH7:
+ if sLen := stack.len(); sLen > 1023 {
+ return nil, &ErrStackOverflow{stackLen: sLen, limit: 1023}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ codeLen := len(scope.Contract.Code)
+ start := min(codeLen, int(pc+1))
+ end := min(codeLen, start+7)
+ a := scope.Stack.get()
+ a.SetBytes(scope.Contract.Code[start:end])
+ if missing := 7 - (end - start); missing > 0 {
+ a.Lsh(a, uint(8*missing))
+ }
+ pc += 7
+ pc++
+ continue mainLoop
+ case PUSH8:
+ if sLen := stack.len(); sLen > 1023 {
+ return nil, &ErrStackOverflow{stackLen: sLen, limit: 1023}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ codeLen := len(scope.Contract.Code)
+ start := min(codeLen, int(pc+1))
+ end := min(codeLen, start+8)
+ a := scope.Stack.get()
+ a.SetBytes(scope.Contract.Code[start:end])
+ if missing := 8 - (end - start); missing > 0 {
+ a.Lsh(a, uint(8*missing))
+ }
+ pc += 8
+ pc++
+ continue mainLoop
+ case PUSH9:
+ if sLen := stack.len(); sLen > 1023 {
+ return nil, &ErrStackOverflow{stackLen: sLen, limit: 1023}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ codeLen := len(scope.Contract.Code)
+ start := min(codeLen, int(pc+1))
+ end := min(codeLen, start+9)
+ a := scope.Stack.get()
+ a.SetBytes(scope.Contract.Code[start:end])
+ if missing := 9 - (end - start); missing > 0 {
+ a.Lsh(a, uint(8*missing))
+ }
+ pc += 9
+ pc++
+ continue mainLoop
+ case PUSH10:
+ if sLen := stack.len(); sLen > 1023 {
+ return nil, &ErrStackOverflow{stackLen: sLen, limit: 1023}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ codeLen := len(scope.Contract.Code)
+ start := min(codeLen, int(pc+1))
+ end := min(codeLen, start+10)
+ a := scope.Stack.get()
+ a.SetBytes(scope.Contract.Code[start:end])
+ if missing := 10 - (end - start); missing > 0 {
+ a.Lsh(a, uint(8*missing))
+ }
+ pc += 10
+ pc++
+ continue mainLoop
+ case PUSH11:
+ if sLen := stack.len(); sLen > 1023 {
+ return nil, &ErrStackOverflow{stackLen: sLen, limit: 1023}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ codeLen := len(scope.Contract.Code)
+ start := min(codeLen, int(pc+1))
+ end := min(codeLen, start+11)
+ a := scope.Stack.get()
+ a.SetBytes(scope.Contract.Code[start:end])
+ if missing := 11 - (end - start); missing > 0 {
+ a.Lsh(a, uint(8*missing))
+ }
+ pc += 11
+ pc++
+ continue mainLoop
+ case PUSH12:
+ if sLen := stack.len(); sLen > 1023 {
+ return nil, &ErrStackOverflow{stackLen: sLen, limit: 1023}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ codeLen := len(scope.Contract.Code)
+ start := min(codeLen, int(pc+1))
+ end := min(codeLen, start+12)
+ a := scope.Stack.get()
+ a.SetBytes(scope.Contract.Code[start:end])
+ if missing := 12 - (end - start); missing > 0 {
+ a.Lsh(a, uint(8*missing))
+ }
+ pc += 12
+ pc++
+ continue mainLoop
+ case PUSH13:
+ if sLen := stack.len(); sLen > 1023 {
+ return nil, &ErrStackOverflow{stackLen: sLen, limit: 1023}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ codeLen := len(scope.Contract.Code)
+ start := min(codeLen, int(pc+1))
+ end := min(codeLen, start+13)
+ a := scope.Stack.get()
+ a.SetBytes(scope.Contract.Code[start:end])
+ if missing := 13 - (end - start); missing > 0 {
+ a.Lsh(a, uint(8*missing))
+ }
+ pc += 13
+ pc++
+ continue mainLoop
+ case PUSH14:
+ if sLen := stack.len(); sLen > 1023 {
+ return nil, &ErrStackOverflow{stackLen: sLen, limit: 1023}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ codeLen := len(scope.Contract.Code)
+ start := min(codeLen, int(pc+1))
+ end := min(codeLen, start+14)
+ a := scope.Stack.get()
+ a.SetBytes(scope.Contract.Code[start:end])
+ if missing := 14 - (end - start); missing > 0 {
+ a.Lsh(a, uint(8*missing))
+ }
+ pc += 14
+ pc++
+ continue mainLoop
+ case PUSH15:
+ if sLen := stack.len(); sLen > 1023 {
+ return nil, &ErrStackOverflow{stackLen: sLen, limit: 1023}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ codeLen := len(scope.Contract.Code)
+ start := min(codeLen, int(pc+1))
+ end := min(codeLen, start+15)
+ a := scope.Stack.get()
+ a.SetBytes(scope.Contract.Code[start:end])
+ if missing := 15 - (end - start); missing > 0 {
+ a.Lsh(a, uint(8*missing))
+ }
+ pc += 15
+ pc++
+ continue mainLoop
+ case PUSH16:
+ if sLen := stack.len(); sLen > 1023 {
+ return nil, &ErrStackOverflow{stackLen: sLen, limit: 1023}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ codeLen := len(scope.Contract.Code)
+ start := min(codeLen, int(pc+1))
+ end := min(codeLen, start+16)
+ a := scope.Stack.get()
+ a.SetBytes(scope.Contract.Code[start:end])
+ if missing := 16 - (end - start); missing > 0 {
+ a.Lsh(a, uint(8*missing))
+ }
+ pc += 16
+ pc++
+ continue mainLoop
+ case PUSH17:
+ if sLen := stack.len(); sLen > 1023 {
+ return nil, &ErrStackOverflow{stackLen: sLen, limit: 1023}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ codeLen := len(scope.Contract.Code)
+ start := min(codeLen, int(pc+1))
+ end := min(codeLen, start+17)
+ a := scope.Stack.get()
+ a.SetBytes(scope.Contract.Code[start:end])
+ if missing := 17 - (end - start); missing > 0 {
+ a.Lsh(a, uint(8*missing))
+ }
+ pc += 17
+ pc++
+ continue mainLoop
+ case PUSH18:
+ if sLen := stack.len(); sLen > 1023 {
+ return nil, &ErrStackOverflow{stackLen: sLen, limit: 1023}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ codeLen := len(scope.Contract.Code)
+ start := min(codeLen, int(pc+1))
+ end := min(codeLen, start+18)
+ a := scope.Stack.get()
+ a.SetBytes(scope.Contract.Code[start:end])
+ if missing := 18 - (end - start); missing > 0 {
+ a.Lsh(a, uint(8*missing))
+ }
+ pc += 18
+ pc++
+ continue mainLoop
+ case PUSH19:
+ if sLen := stack.len(); sLen > 1023 {
+ return nil, &ErrStackOverflow{stackLen: sLen, limit: 1023}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ codeLen := len(scope.Contract.Code)
+ start := min(codeLen, int(pc+1))
+ end := min(codeLen, start+19)
+ a := scope.Stack.get()
+ a.SetBytes(scope.Contract.Code[start:end])
+ if missing := 19 - (end - start); missing > 0 {
+ a.Lsh(a, uint(8*missing))
+ }
+ pc += 19
+ pc++
+ continue mainLoop
+ case PUSH20:
+ if sLen := stack.len(); sLen > 1023 {
+ return nil, &ErrStackOverflow{stackLen: sLen, limit: 1023}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ codeLen := len(scope.Contract.Code)
+ start := min(codeLen, int(pc+1))
+ end := min(codeLen, start+20)
+ a := scope.Stack.get()
+ a.SetBytes(scope.Contract.Code[start:end])
+ if missing := 20 - (end - start); missing > 0 {
+ a.Lsh(a, uint(8*missing))
+ }
+ pc += 20
+ pc++
+ continue mainLoop
+ case PUSH21:
+ if sLen := stack.len(); sLen > 1023 {
+ return nil, &ErrStackOverflow{stackLen: sLen, limit: 1023}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ codeLen := len(scope.Contract.Code)
+ start := min(codeLen, int(pc+1))
+ end := min(codeLen, start+21)
+ a := scope.Stack.get()
+ a.SetBytes(scope.Contract.Code[start:end])
+ if missing := 21 - (end - start); missing > 0 {
+ a.Lsh(a, uint(8*missing))
+ }
+ pc += 21
+ pc++
+ continue mainLoop
+ case PUSH22:
+ if sLen := stack.len(); sLen > 1023 {
+ return nil, &ErrStackOverflow{stackLen: sLen, limit: 1023}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ codeLen := len(scope.Contract.Code)
+ start := min(codeLen, int(pc+1))
+ end := min(codeLen, start+22)
+ a := scope.Stack.get()
+ a.SetBytes(scope.Contract.Code[start:end])
+ if missing := 22 - (end - start); missing > 0 {
+ a.Lsh(a, uint(8*missing))
+ }
+ pc += 22
+ pc++
+ continue mainLoop
+ case PUSH23:
+ if sLen := stack.len(); sLen > 1023 {
+ return nil, &ErrStackOverflow{stackLen: sLen, limit: 1023}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ codeLen := len(scope.Contract.Code)
+ start := min(codeLen, int(pc+1))
+ end := min(codeLen, start+23)
+ a := scope.Stack.get()
+ a.SetBytes(scope.Contract.Code[start:end])
+ if missing := 23 - (end - start); missing > 0 {
+ a.Lsh(a, uint(8*missing))
+ }
+ pc += 23
+ pc++
+ continue mainLoop
+ case PUSH24:
+ if sLen := stack.len(); sLen > 1023 {
+ return nil, &ErrStackOverflow{stackLen: sLen, limit: 1023}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ codeLen := len(scope.Contract.Code)
+ start := min(codeLen, int(pc+1))
+ end := min(codeLen, start+24)
+ a := scope.Stack.get()
+ a.SetBytes(scope.Contract.Code[start:end])
+ if missing := 24 - (end - start); missing > 0 {
+ a.Lsh(a, uint(8*missing))
+ }
+ pc += 24
+ pc++
+ continue mainLoop
+ case PUSH25:
+ if sLen := stack.len(); sLen > 1023 {
+ return nil, &ErrStackOverflow{stackLen: sLen, limit: 1023}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ codeLen := len(scope.Contract.Code)
+ start := min(codeLen, int(pc+1))
+ end := min(codeLen, start+25)
+ a := scope.Stack.get()
+ a.SetBytes(scope.Contract.Code[start:end])
+ if missing := 25 - (end - start); missing > 0 {
+ a.Lsh(a, uint(8*missing))
+ }
+ pc += 25
+ pc++
+ continue mainLoop
+ case PUSH26:
+ if sLen := stack.len(); sLen > 1023 {
+ return nil, &ErrStackOverflow{stackLen: sLen, limit: 1023}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ codeLen := len(scope.Contract.Code)
+ start := min(codeLen, int(pc+1))
+ end := min(codeLen, start+26)
+ a := scope.Stack.get()
+ a.SetBytes(scope.Contract.Code[start:end])
+ if missing := 26 - (end - start); missing > 0 {
+ a.Lsh(a, uint(8*missing))
+ }
+ pc += 26
+ pc++
+ continue mainLoop
+ case PUSH27:
+ if sLen := stack.len(); sLen > 1023 {
+ return nil, &ErrStackOverflow{stackLen: sLen, limit: 1023}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ codeLen := len(scope.Contract.Code)
+ start := min(codeLen, int(pc+1))
+ end := min(codeLen, start+27)
+ a := scope.Stack.get()
+ a.SetBytes(scope.Contract.Code[start:end])
+ if missing := 27 - (end - start); missing > 0 {
+ a.Lsh(a, uint(8*missing))
+ }
+ pc += 27
+ pc++
+ continue mainLoop
+ case PUSH28:
+ if sLen := stack.len(); sLen > 1023 {
+ return nil, &ErrStackOverflow{stackLen: sLen, limit: 1023}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ codeLen := len(scope.Contract.Code)
+ start := min(codeLen, int(pc+1))
+ end := min(codeLen, start+28)
+ a := scope.Stack.get()
+ a.SetBytes(scope.Contract.Code[start:end])
+ if missing := 28 - (end - start); missing > 0 {
+ a.Lsh(a, uint(8*missing))
+ }
+ pc += 28
+ pc++
+ continue mainLoop
+ case PUSH29:
+ if sLen := stack.len(); sLen > 1023 {
+ return nil, &ErrStackOverflow{stackLen: sLen, limit: 1023}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ codeLen := len(scope.Contract.Code)
+ start := min(codeLen, int(pc+1))
+ end := min(codeLen, start+29)
+ a := scope.Stack.get()
+ a.SetBytes(scope.Contract.Code[start:end])
+ if missing := 29 - (end - start); missing > 0 {
+ a.Lsh(a, uint(8*missing))
+ }
+ pc += 29
+ pc++
+ continue mainLoop
+ case PUSH30:
+ if sLen := stack.len(); sLen > 1023 {
+ return nil, &ErrStackOverflow{stackLen: sLen, limit: 1023}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ codeLen := len(scope.Contract.Code)
+ start := min(codeLen, int(pc+1))
+ end := min(codeLen, start+30)
+ a := scope.Stack.get()
+ a.SetBytes(scope.Contract.Code[start:end])
+ if missing := 30 - (end - start); missing > 0 {
+ a.Lsh(a, uint(8*missing))
+ }
+ pc += 30
+ pc++
+ continue mainLoop
+ case PUSH31:
+ if sLen := stack.len(); sLen > 1023 {
+ return nil, &ErrStackOverflow{stackLen: sLen, limit: 1023}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ codeLen := len(scope.Contract.Code)
+ start := min(codeLen, int(pc+1))
+ end := min(codeLen, start+31)
+ a := scope.Stack.get()
+ a.SetBytes(scope.Contract.Code[start:end])
+ if missing := 31 - (end - start); missing > 0 {
+ a.Lsh(a, uint(8*missing))
+ }
+ pc += 31
+ pc++
+ continue mainLoop
+ case PUSH32:
+ if sLen := stack.len(); sLen > 1023 {
+ return nil, &ErrStackOverflow{stackLen: sLen, limit: 1023}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ codeLen := len(scope.Contract.Code)
+ start := min(codeLen, int(pc+1))
+ end := min(codeLen, start+32)
+ a := scope.Stack.get()
+ a.SetBytes(scope.Contract.Code[start:end])
+ if missing := 32 - (end - start); missing > 0 {
+ a.Lsh(a, uint(8*missing))
+ }
+ pc += 32
+ pc++
+ continue mainLoop
+ case DUP1:
+ if sLen := stack.len(); sLen < 1 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 1}
+ } else if sLen > 1023 {
+ return nil, &ErrStackOverflow{stackLen: sLen, limit: 1023}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ scope.Stack.dup(1)
+ pc++
+ continue mainLoop
+ case DUP2:
+ if sLen := stack.len(); sLen < 2 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 2}
+ } else if sLen > 1023 {
+ return nil, &ErrStackOverflow{stackLen: sLen, limit: 1023}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ scope.Stack.dup(2)
+ pc++
+ continue mainLoop
+ case DUP3:
+ if sLen := stack.len(); sLen < 3 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 3}
+ } else if sLen > 1023 {
+ return nil, &ErrStackOverflow{stackLen: sLen, limit: 1023}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ scope.Stack.dup(3)
+ pc++
+ continue mainLoop
+ case DUP4:
+ if sLen := stack.len(); sLen < 4 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 4}
+ } else if sLen > 1023 {
+ return nil, &ErrStackOverflow{stackLen: sLen, limit: 1023}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ scope.Stack.dup(4)
+ pc++
+ continue mainLoop
+ case DUP5:
+ if sLen := stack.len(); sLen < 5 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 5}
+ } else if sLen > 1023 {
+ return nil, &ErrStackOverflow{stackLen: sLen, limit: 1023}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ scope.Stack.dup(5)
+ pc++
+ continue mainLoop
+ case DUP6:
+ if sLen := stack.len(); sLen < 6 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 6}
+ } else if sLen > 1023 {
+ return nil, &ErrStackOverflow{stackLen: sLen, limit: 1023}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ scope.Stack.dup(6)
+ pc++
+ continue mainLoop
+ case DUP7:
+ if sLen := stack.len(); sLen < 7 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 7}
+ } else if sLen > 1023 {
+ return nil, &ErrStackOverflow{stackLen: sLen, limit: 1023}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ scope.Stack.dup(7)
+ pc++
+ continue mainLoop
+ case DUP8:
+ if sLen := stack.len(); sLen < 8 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 8}
+ } else if sLen > 1023 {
+ return nil, &ErrStackOverflow{stackLen: sLen, limit: 1023}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ scope.Stack.dup(8)
+ pc++
+ continue mainLoop
+ case DUP9:
+ if sLen := stack.len(); sLen < 9 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 9}
+ } else if sLen > 1023 {
+ return nil, &ErrStackOverflow{stackLen: sLen, limit: 1023}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ scope.Stack.dup(9)
+ pc++
+ continue mainLoop
+ case DUP10:
+ if sLen := stack.len(); sLen < 10 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 10}
+ } else if sLen > 1023 {
+ return nil, &ErrStackOverflow{stackLen: sLen, limit: 1023}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ scope.Stack.dup(10)
+ pc++
+ continue mainLoop
+ case DUP11:
+ if sLen := stack.len(); sLen < 11 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 11}
+ } else if sLen > 1023 {
+ return nil, &ErrStackOverflow{stackLen: sLen, limit: 1023}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ scope.Stack.dup(11)
+ pc++
+ continue mainLoop
+ case DUP12:
+ if sLen := stack.len(); sLen < 12 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 12}
+ } else if sLen > 1023 {
+ return nil, &ErrStackOverflow{stackLen: sLen, limit: 1023}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ scope.Stack.dup(12)
+ pc++
+ continue mainLoop
+ case DUP13:
+ if sLen := stack.len(); sLen < 13 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 13}
+ } else if sLen > 1023 {
+ return nil, &ErrStackOverflow{stackLen: sLen, limit: 1023}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ scope.Stack.dup(13)
+ pc++
+ continue mainLoop
+ case DUP14:
+ if sLen := stack.len(); sLen < 14 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 14}
+ } else if sLen > 1023 {
+ return nil, &ErrStackOverflow{stackLen: sLen, limit: 1023}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ scope.Stack.dup(14)
+ pc++
+ continue mainLoop
+ case DUP15:
+ if sLen := stack.len(); sLen < 15 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 15}
+ } else if sLen > 1023 {
+ return nil, &ErrStackOverflow{stackLen: sLen, limit: 1023}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ scope.Stack.dup(15)
+ pc++
+ continue mainLoop
+ case DUP16:
+ if sLen := stack.len(); sLen < 16 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 16}
+ } else if sLen > 1023 {
+ return nil, &ErrStackOverflow{stackLen: sLen, limit: 1023}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ scope.Stack.dup(16)
+ pc++
+ continue mainLoop
+ case SWAP1:
+ if sLen := stack.len(); sLen < 2 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 2}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ scope.Stack.swap1()
+ pc++
+ continue mainLoop
+
+ case SWAP2:
+ if sLen := stack.len(); sLen < 3 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 3}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ scope.Stack.swap2()
+ pc++
+ continue mainLoop
+
+ case SWAP3:
+ if sLen := stack.len(); sLen < 4 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 4}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ scope.Stack.swap3()
+ pc++
+ continue mainLoop
+
+ case SWAP4:
+ if sLen := stack.len(); sLen < 5 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 5}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ scope.Stack.swap4()
+ pc++
+ continue mainLoop
+
+ case SWAP5:
+ if sLen := stack.len(); sLen < 6 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 6}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ scope.Stack.swap5()
+ pc++
+ continue mainLoop
+
+ case SWAP6:
+ if sLen := stack.len(); sLen < 7 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 7}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ scope.Stack.swap6()
+ pc++
+ continue mainLoop
+
+ case SWAP7:
+ if sLen := stack.len(); sLen < 8 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 8}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ scope.Stack.swap7()
+ pc++
+ continue mainLoop
+
+ case SWAP8:
+ if sLen := stack.len(); sLen < 9 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 9}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ scope.Stack.swap8()
+ pc++
+ continue mainLoop
+
+ case SWAP9:
+ if sLen := stack.len(); sLen < 10 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 10}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ scope.Stack.swap9()
+ pc++
+ continue mainLoop
+
+ case SWAP10:
+ if sLen := stack.len(); sLen < 11 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 11}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ scope.Stack.swap10()
+ pc++
+ continue mainLoop
+
+ case SWAP11:
+ if sLen := stack.len(); sLen < 12 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 12}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ scope.Stack.swap11()
+ pc++
+ continue mainLoop
+
+ case SWAP12:
+ if sLen := stack.len(); sLen < 13 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 13}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ scope.Stack.swap12()
+ pc++
+ continue mainLoop
+
+ case SWAP13:
+ if sLen := stack.len(); sLen < 14 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 14}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ scope.Stack.swap13()
+ pc++
+ continue mainLoop
+
+ case SWAP14:
+ if sLen := stack.len(); sLen < 15 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 15}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ scope.Stack.swap14()
+ pc++
+ continue mainLoop
+
+ case SWAP15:
+ if sLen := stack.len(); sLen < 16 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 16}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ scope.Stack.swap15()
+ pc++
+ continue mainLoop
+
+ case SWAP16:
+ if sLen := stack.len(); sLen < 17 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 17}
+ }
+ if contract.Gas.RegularGas < 3 {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= 3
+ scope.Stack.swap16()
+ pc++
+ continue mainLoop
+
+ default:
+ operation := table[op]
+ if sLen := stack.len(); sLen < operation.minStack {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: operation.minStack}
+ } else if sLen > operation.maxStack {
+ return nil, &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack}
+ }
+ cost := operation.constantGas
+ if contract.Gas.RegularGas < cost {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= cost
+ var memorySize uint64
+ if operation.dynamicGas != nil {
+ if operation.memorySize != nil {
+ memSize, overflow := operation.memorySize(stack)
+ if overflow {
+ return nil, ErrGasUintOverflow
+ }
+ if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow {
+ return nil, ErrGasUintOverflow
+ }
+ }
+ var dynamicCost GasCosts
+ dynamicCost, err = operation.dynamicGas(evm, contract, stack, mem, memorySize)
+ if err != nil {
+ return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err)
+ }
+ if contract.Gas.RegularGas < dynamicCost.RegularGas {
+ return nil, ErrOutOfGas
+ }
+ contract.Gas.RegularGas -= dynamicCost.RegularGas
+ }
+ if memorySize > 0 {
+ mem.Resize(memorySize)
+ }
+ res, err = operation.execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ }
+ if err == errStopToken {
+ err = nil
+ }
+ return res, err
+}
diff --git a/core/vm/interp_gen_test.go b/core/vm/interp_gen_test.go
new file mode 100644
index 0000000000..83b4bb1ed9
--- /dev/null
+++ b/core/vm/interp_gen_test.go
@@ -0,0 +1,51 @@
+// Copyright 2026 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package vm
+
+import (
+ "os"
+ "os/exec"
+ "path/filepath"
+ "testing"
+)
+
+// TestGeneratedDispatchUpToDate asserts that the committed interp_gen.go matches
+// what `go generate` (core/vm/gen) produces from the current opcode/gas/fork
+// definitions. It is the CI guard against hand-edits to the generated file and
+// against the generator drifting from the committed output.
+func TestGeneratedDispatchUpToDate(t *testing.T) {
+ if testing.Short() {
+ t.Skip("skipping generator round-trip in -short mode")
+ }
+ tmp := filepath.Join(t.TempDir(), "interp_gen.go")
+ cmd := exec.Command("go", "run", "./gen")
+ cmd.Env = append(os.Environ(), "INTERP_GEN_OUT="+tmp)
+ if out, err := cmd.CombinedOutput(); err != nil {
+ t.Fatalf("running generator: %v\n%s", err, out)
+ }
+ got, err := os.ReadFile(tmp)
+ if err != nil {
+ t.Fatalf("reading regenerated output: %v", err)
+ }
+ want, err := os.ReadFile("interp_gen.go")
+ if err != nil {
+ t.Fatalf("reading committed interp_gen.go: %v", err)
+ }
+ if string(got) != string(want) {
+ t.Fatalf("interp_gen.go is out of date; run `go generate ./core/vm/...` and commit the result")
+ }
+}
diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go
index c2dfe3769c..4c0dd799dc 100644
--- a/core/vm/interpreter.go
+++ b/core/vm/interpreter.go
@@ -92,6 +92,9 @@ 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.
+//
+// Run sets up the per-call scope and picks the interpreter loop. Without a
+// tracer it runs the generated execUntraced, otherwise execTraced.
func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte, err error) {
// Increment the call depth which is restricted to 1024
evm.depth++
@@ -112,17 +115,48 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte
if len(contract.Code) == 0 {
return nil, nil
}
+ contract.Input = input
+ mem := NewMemory() // bound memory
+ stack := evm.arena.stack() // local stack
+ scope := &ScopeContext{
+ Memory: mem,
+ Stack: stack,
+ Contract: contract,
+ }
+
+ // The exit hooks in execTraced still need the stack and memory, so this
+ // teardown must run after them. Defers in execTraced fire before this one,
+ // so the ordering holds.
+ defer func() {
+ stack.release()
+ mem.Free()
+ }()
+
+ // The generated fast path bakes its fork gates at generate time, so it
+ // cannot see table changes made by ExtraEips. Those configs run the table
+ // loop, as do tracing and the differential test via forceTableLoop.
+ if evm.forceTableLoop || len(evm.Config.ExtraEips) > 0 || evm.Config.Tracer != nil {
+ return evm.execTraced(scope)
+ }
+ return evm.execUntraced(scope)
+}
+
+// execTraced is the old table-walking interpreter loop. It dispatches every
+// opcode through the per-fork operation table and emits the tracing hooks
+// exactly as before. It stays hand-written so trace output does not change.
+//
+// The differential test also runs this loop without a tracer and compares it
+// against the generated execUntraced, which keeps the two from drifting
+// apart.
+func (evm *EVM) execTraced(scope *ScopeContext) (ret []byte, err error) {
var (
op OpCode // current opcode
jumpTable *JumpTable = evm.table
- mem = NewMemory() // bound memory
- stack = evm.arena.stack() // local stack
- callContext = &ScopeContext{
- Memory: mem,
- Stack: stack,
- Contract: contract,
- }
+ mem = scope.Memory
+ stack = scope.Stack
+ contract = scope.Contract
+ callContext = scope
// For optimisation reason we're using uint64 as the program counter.
// It's theoretically possible to go above 2^64. The YP defines the PC
// to be uint256. Practically much less so feasible.
@@ -136,15 +170,6 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte
debug = evm.Config.Tracer != nil
isEIP4762 = evm.chainRules.IsEIP4762
)
- // Don't move this deferred function, it's placed before the OnOpcode-deferred method,
- // so that it gets executed _after_: the OnOpcode needs the stacks before
- // they are returned to the pools
- defer func() {
- stack.release()
- mem.Free()
- }()
- contract.Input = input
-
if debug {
defer func() { // this deferred method handles exit-with-error
if err == nil {
diff --git a/core/vm/runtime/evm_bench_test.go b/core/vm/runtime/evm_bench_test.go
new file mode 100644
index 0000000000..31a265c1f6
--- /dev/null
+++ b/core/vm/runtime/evm_bench_test.go
@@ -0,0 +1,106 @@
+// Copyright 2026 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+// Whole-contract interpreter benchmarks from the evm-bench suite
+// (github.com/ziyadedher/evm-bench). Each contract exposes a Benchmark()
+// entrypoint (selector 0x30627b7c) that performs the whole workload internally
+// (e.g. 5000 mints), so the benchmark is a single Call per iteration. These
+// complement the opcode/loop micro-benchmarks in runtime_test.go with realistic
+// workloads (a ray tracer, ERC-20 transfer/mint/approval, a hashing loop) and
+// use the same names as evm-bench / gevm for cross-comparison.
+//
+// Run: go test ./core/vm/runtime/ -run '^$' -bench 'Benchmark(Snailtracer|TenThousandHashes|ERC20)' -benchmem -count=10
+// Compare A/B: ./core/vm/runtime/testdata/evm-bench/compare.sh
+//
+// The committed runtime bytecode in testdata/*.hex is regenerated by
+// testdata/evm-bench/gen.sh (solc via docker). See that script for provenance
+// and exact compiler versions.
+
+package runtime
+
+import (
+ _ "embed"
+ "encoding/hex"
+ "strings"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/state"
+ "github.com/ethereum/go-ethereum/core/tracing"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/holiman/uint256"
+)
+
+//go:embed testdata/snailtracer.hex
+var benchSnailtracerHex string
+
+//go:embed testdata/tenthousandhashes.hex
+var benchTenThousandHashesHex string
+
+//go:embed testdata/erc20transfer.hex
+var benchERC20TransferHex string
+
+//go:embed testdata/erc20mint.hex
+var benchERC20MintHex string
+
+//go:embed testdata/erc20approval.hex
+var benchERC20ApprovalHex string
+
+// benchmarkSelector is keccak256("Benchmark()")[:4], the evm-bench entrypoint.
+var benchmarkSelector = []byte{0x30, 0x62, 0x7b, 0x7c}
+
+func benchHexToBytes(s string) []byte {
+ b, err := hex.DecodeString(strings.TrimSpace(s))
+ if err != nil {
+ panic(err)
+ }
+ return b
+}
+
+// runEVMBench deploys the contract's runtime bytecode and repeatedly invokes its
+// Benchmark() entrypoint. The contract's constructor is not run, so storage
+// starts empty. The evm-bench contracts set up whatever state they need inside
+// Benchmark(), which matches the work the evm-bench harness measures.
+func runEVMBench(b *testing.B, codeHex string) {
+ code := benchHexToBytes(codeHex)
+ statedb, err := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
+ if err != nil {
+ b.Fatal(err)
+ }
+ contract := common.BytesToAddress([]byte{0x10})
+ caller := common.BytesToAddress([]byte{0x01})
+ statedb.CreateAccount(caller)
+ statedb.AddBalance(caller, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified)
+ statedb.SetCode(contract, code, tracing.CodeChangeUnspecified)
+
+ cfg := &Config{Origin: caller, State: statedb, GasLimit: 10_000_000_000}
+
+ // Sanity: the workload must run to completion, not revert/out-of-gas.
+ if _, _, err := Call(contract, benchmarkSelector, cfg); err != nil {
+ b.Fatalf("Benchmark() did not complete: %v", err)
+ }
+ b.ReportAllocs()
+ b.ResetTimer()
+ for b.Loop() {
+ Call(contract, benchmarkSelector, cfg)
+ }
+}
+
+func BenchmarkSnailtracer(b *testing.B) { runEVMBench(b, benchSnailtracerHex) }
+func BenchmarkTenThousandHashes(b *testing.B) { runEVMBench(b, benchTenThousandHashesHex) }
+func BenchmarkERC20Transfer(b *testing.B) { runEVMBench(b, benchERC20TransferHex) }
+func BenchmarkERC20Mint(b *testing.B) { runEVMBench(b, benchERC20MintHex) }
+func BenchmarkERC20ApprovalTransfer(b *testing.B) { runEVMBench(b, benchERC20ApprovalHex) }
diff --git a/core/vm/runtime/testdata/erc20approval.hex b/core/vm/runtime/testdata/erc20approval.hex
new file mode 100644
index 0000000000..0700413573
--- /dev/null
+++ b/core/vm/runtime/testdata/erc20approval.hex
@@ -0,0 +1 @@
+608060405234801561001057600080fd5b50600436106100b45760003560e01c80633950935111610071578063395093511461013857806370a082311461014b57806395d89b4114610174578063a457c2d71461017c578063a9059cbb1461018f578063dd62ed3e146101a257600080fd5b806306fdde03146100b9578063095ea7b3146100d757806318160ddd146100fa57806323b872dd1461010c57806330627b7c1461011f578063313ce56714610129575b600080fd5b6100c16101b5565b6040516100ce91906108ca565b60405180910390f35b6100ea6100e5366004610934565b610247565b60405190151581526020016100ce565b6002545b6040519081526020016100ce565b6100ea61011a36600461095e565b610261565b610127610285565b005b604051601281526020016100ce565b6100ea610146366004610934565b6103e4565b6100fe61015936600461099a565b6001600160a01b031660009081526020819052604090205490565b6100c1610406565b6100ea61018a366004610934565b610415565b6100ea61019d366004610934565b610490565b6100fe6101b03660046109bc565b61049e565b6060600380546101c4906109ef565b80601f01602080910402602001604051908101604052809291908181526020018280546101f0906109ef565b801561023d5780601f106102125761010080835404028352916020019161023d565b820191906000526020600020905b81548152906001019060200180831161022057829003601f168201915b5050505050905090565b6000336102558185856104c9565b60019150505b92915050565b60003361026f8582856105ed565b61027a858585610667565b506001949350505050565b6102a8336102956012600a610b23565b6102a390633b9aca00610b32565b61080b565b60015b6103e88110156103e1576102bf333361049e565b156103115760405162461bcd60e51b815260206004820152601b60248201527f6e6f6e2d7a65726f20616c6c6f77616e636520746f207374617274000000000060448201526064015b60405180910390fd5b61031b3382610247565b5080610327333361049e565b1461036c5760405162461bcd60e51b81526020600482015260156024820152746469646e2774206769766520616c6c6f77616e636560581b6044820152606401610308565b610377333383610261565b50610382333361049e565b156103cf5760405162461bcd60e51b815260206004820152601960248201527f6e6f6e2d7a65726f20616c6c6f77616e636520746f20656e64000000000000006044820152606401610308565b806103d981610b49565b9150506102ab565b50565b6000336102558185856103f7838361049e565b6104019190610b62565b6104c9565b6060600480546101c4906109ef565b60003381610423828661049e565b9050838110156104835760405162461bcd60e51b815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b6064820152608401610308565b61027a82868684036104c9565b600033610255818585610667565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b6001600160a01b03831661052b5760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608401610308565b6001600160a01b03821661058c5760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608401610308565b6001600160a01b0383811660008181526001602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a3505050565b60006105f9848461049e565b9050600019811461066157818110156106545760405162461bcd60e51b815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e63650000006044820152606401610308565b61066184848484036104c9565b50505050565b6001600160a01b0383166106cb5760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b6064820152608401610308565b6001600160a01b03821661072d5760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b6064820152608401610308565b6001600160a01b038316600090815260208190526040902054818110156107a55760405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b6064820152608401610308565b6001600160a01b03848116600081815260208181526040808320878703905593871680835291849020805487019055925185815290927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a3610661565b6001600160a01b0382166108615760405162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f2061646472657373006044820152606401610308565b80600260008282546108739190610b62565b90915550506001600160a01b038216600081815260208181526040808320805486019055518481527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a35050565b600060208083528351808285015260005b818110156108f7578581018301518582016040015282016108db565b506000604082860101526040601f19601f8301168501019250505092915050565b80356001600160a01b038116811461092f57600080fd5b919050565b6000806040838503121561094757600080fd5b61095083610918565b946020939093013593505050565b60008060006060848603121561097357600080fd5b61097c84610918565b925061098a60208501610918565b9150604084013590509250925092565b6000602082840312156109ac57600080fd5b6109b582610918565b9392505050565b600080604083850312156109cf57600080fd5b6109d883610918565b91506109e660208401610918565b90509250929050565b600181811c90821680610a0357607f821691505b602082108103610a2357634e487b7160e01b600052602260045260246000fd5b50919050565b634e487b7160e01b600052601160045260246000fd5b600181815b80851115610a7a578160001904821115610a6057610a60610a29565b80851615610a6d57918102915b93841c9390800290610a44565b509250929050565b600082610a915750600161025b565b81610a9e5750600061025b565b8160018114610ab45760028114610abe57610ada565b600191505061025b565b60ff841115610acf57610acf610a29565b50506001821b61025b565b5060208310610133831016604e8410600b8410161715610afd575081810a61025b565b610b078383610a3f565b8060001904821115610b1b57610b1b610a29565b029392505050565b60006109b560ff841683610a82565b808202811582820484141761025b5761025b610a29565b600060018201610b5b57610b5b610a29565b5060010190565b8082018082111561025b5761025b610a2956fea2646970667358221220c33117c9d08b3ca92e5865f957cca670ed19c2990ba76736dd40b693ae62098064736f6c63430008110033
\ No newline at end of file
diff --git a/core/vm/runtime/testdata/erc20mint.hex b/core/vm/runtime/testdata/erc20mint.hex
new file mode 100644
index 0000000000..ed06ae6b14
--- /dev/null
+++ b/core/vm/runtime/testdata/erc20mint.hex
@@ -0,0 +1 @@
+608060405234801561001057600080fd5b50600436106100b45760003560e01c80633950935111610071578063395093511461013857806370a082311461014b57806395d89b4114610174578063a457c2d71461017c578063a9059cbb1461018f578063dd62ed3e146101a257600080fd5b806306fdde03146100b9578063095ea7b3146100d757806318160ddd146100fa57806323b872dd1461010c57806330627b7c1461011f578063313ce56714610129575b600080fd5b6100c16101b5565b6040516100ce919061079c565b60405180910390f35b6100ea6100e5366004610806565b610247565b60405190151581526020016100ce565b6002545b6040519081526020016100ce565b6100ea61011a366004610830565b610261565b610127610285565b005b604051601281526020016100ce565b6100ea610146366004610806565b6102b1565b6100fe61015936600461086c565b6001600160a01b031660009081526020819052604090205490565b6100c16102d3565b6100ea61018a366004610806565b6102e2565b6100ea61019d366004610806565b610362565b6100fe6101b036600461088e565b610370565b6060600380546101c4906108c1565b80601f01602080910402602001604051908101604052809291908181526020018280546101f0906108c1565b801561023d5780601f106102125761010080835404028352916020019161023d565b820191906000526020600020905b81548152906001019060200180831161022057829003601f168201915b5050505050905090565b60003361025581858561039b565b60019150505b92915050565b60003361026f8582856104bf565b61027a858585610539565b506001949350505050565b60005b6113888110156102ae5761029c33826106dd565b806102a681610911565b915050610288565b50565b6000336102558185856102c48383610370565b6102ce919061092a565b61039b565b6060600480546101c4906108c1565b600033816102f08286610370565b9050838110156103555760405162461bcd60e51b815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b60648201526084015b60405180910390fd5b61027a828686840361039b565b600033610255818585610539565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b6001600160a01b0383166103fd5760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b606482015260840161034c565b6001600160a01b03821661045e5760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b606482015260840161034c565b6001600160a01b0383811660008181526001602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a3505050565b60006104cb8484610370565b9050600019811461053357818110156105265760405162461bcd60e51b815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e6365000000604482015260640161034c565b610533848484840361039b565b50505050565b6001600160a01b03831661059d5760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b606482015260840161034c565b6001600160a01b0382166105ff5760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b606482015260840161034c565b6001600160a01b038316600090815260208190526040902054818110156106775760405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b606482015260840161034c565b6001600160a01b03848116600081815260208181526040808320878703905593871680835291849020805487019055925185815290927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a3610533565b6001600160a01b0382166107335760405162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f206164647265737300604482015260640161034c565b8060026000828254610745919061092a565b90915550506001600160a01b038216600081815260208181526040808320805486019055518481527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a35050565b600060208083528351808285015260005b818110156107c9578581018301518582016040015282016107ad565b506000604082860101526040601f19601f8301168501019250505092915050565b80356001600160a01b038116811461080157600080fd5b919050565b6000806040838503121561081957600080fd5b610822836107ea565b946020939093013593505050565b60008060006060848603121561084557600080fd5b61084e846107ea565b925061085c602085016107ea565b9150604084013590509250925092565b60006020828403121561087e57600080fd5b610887826107ea565b9392505050565b600080604083850312156108a157600080fd5b6108aa836107ea565b91506108b8602084016107ea565b90509250929050565b600181811c908216806108d557607f821691505b6020821081036108f557634e487b7160e01b600052602260045260246000fd5b50919050565b634e487b7160e01b600052601160045260246000fd5b600060018201610923576109236108fb565b5060010190565b8082018082111561025b5761025b6108fb56fea2646970667358221220fa5dc8b94ca8266a9f37b573a3ac8993380ce8da74caa7732abd6e7ed31cd37e64736f6c63430008110033
\ No newline at end of file
diff --git a/core/vm/runtime/testdata/erc20transfer.hex b/core/vm/runtime/testdata/erc20transfer.hex
new file mode 100644
index 0000000000..cc01470901
--- /dev/null
+++ b/core/vm/runtime/testdata/erc20transfer.hex
@@ -0,0 +1 @@
+608060405234801561001057600080fd5b50600436106100b45760003560e01c80633950935111610071578063395093511461013857806370a082311461014b57806395d89b4114610174578063a457c2d71461017c578063a9059cbb1461018f578063dd62ed3e146101a257600080fd5b806306fdde03146100b9578063095ea7b3146100d757806318160ddd146100fa57806323b872dd1461010c57806330627b7c1461011f578063313ce56714610129575b600080fd5b6100c16101b5565b6040516100ce91906107bf565b60405180910390f35b6100ea6100e5366004610829565b610247565b60405190151581526020016100ce565b6002545b6040519081526020016100ce565b6100ea61011a366004610853565b610261565b610127610285565b005b604051601281526020016100ce565b6100ea610146366004610829565b6102d4565b6100fe61015936600461088f565b6001600160a01b031660009081526020819052604090205490565b6100c16102f6565b6100ea61018a366004610829565b610305565b6100ea61019d366004610829565b610385565b6100fe6101b03660046108b1565b610393565b6060600380546101c4906108e4565b80601f01602080910402602001604051908101604052809291908181526020018280546101f0906108e4565b801561023d5780601f106102125761010080835404028352916020019161023d565b820191906000526020600020905b81548152906001019060200180831161022057829003601f168201915b5050505050905090565b6000336102558185856103be565b60019150505b92915050565b60003361026f8582856104e2565b61027a85858561055c565b506001949350505050565b6102a6336102956012600a610a18565b6102a190612710610a27565b610700565b60005b6113888110156102d1576102be600182610385565b50806102c981610a3e565b9150506102a9565b50565b6000336102558185856102e78383610393565b6102f19190610a57565b6103be565b6060600480546101c4906108e4565b600033816103138286610393565b9050838110156103785760405162461bcd60e51b815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b60648201526084015b60405180910390fd5b61027a82868684036103be565b60003361025581858561055c565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b6001600160a01b0383166104205760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b606482015260840161036f565b6001600160a01b0382166104815760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b606482015260840161036f565b6001600160a01b0383811660008181526001602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a3505050565b60006104ee8484610393565b9050600019811461055657818110156105495760405162461bcd60e51b815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e6365000000604482015260640161036f565b61055684848484036103be565b50505050565b6001600160a01b0383166105c05760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b606482015260840161036f565b6001600160a01b0382166106225760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b606482015260840161036f565b6001600160a01b0383166000908152602081905260409020548181101561069a5760405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b606482015260840161036f565b6001600160a01b03848116600081815260208181526040808320878703905593871680835291849020805487019055925185815290927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a3610556565b6001600160a01b0382166107565760405162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f206164647265737300604482015260640161036f565b80600260008282546107689190610a57565b90915550506001600160a01b038216600081815260208181526040808320805486019055518481527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a35050565b600060208083528351808285015260005b818110156107ec578581018301518582016040015282016107d0565b506000604082860101526040601f19601f8301168501019250505092915050565b80356001600160a01b038116811461082457600080fd5b919050565b6000806040838503121561083c57600080fd5b6108458361080d565b946020939093013593505050565b60008060006060848603121561086857600080fd5b6108718461080d565b925061087f6020850161080d565b9150604084013590509250925092565b6000602082840312156108a157600080fd5b6108aa8261080d565b9392505050565b600080604083850312156108c457600080fd5b6108cd8361080d565b91506108db6020840161080d565b90509250929050565b600181811c908216806108f857607f821691505b60208210810361091857634e487b7160e01b600052602260045260246000fd5b50919050565b634e487b7160e01b600052601160045260246000fd5b600181815b8085111561096f5781600019048211156109555761095561091e565b8085161561096257918102915b93841c9390800290610939565b509250929050565b6000826109865750600161025b565b816109935750600061025b565b81600181146109a957600281146109b3576109cf565b600191505061025b565b60ff8411156109c4576109c461091e565b50506001821b61025b565b5060208310610133831016604e8410600b84101617156109f2575081810a61025b565b6109fc8383610934565b8060001904821115610a1057610a1061091e565b029392505050565b60006108aa60ff841683610977565b808202811582820484141761025b5761025b61091e565b600060018201610a5057610a5061091e565b5060010190565b8082018082111561025b5761025b61091e56fea26469706673582212200111a8455f1c6aba553f7e7a1fe413d6a44eececa7e001df4c190aef76f39b5864736f6c63430008110033
\ No newline at end of file
diff --git a/core/vm/runtime/testdata/evm-bench/README.md b/core/vm/runtime/testdata/evm-bench/README.md
new file mode 100644
index 0000000000..cc5ec51620
--- /dev/null
+++ b/core/vm/runtime/testdata/evm-bench/README.md
@@ -0,0 +1,56 @@
+# evm-bench whole-contract interpreter benchmarks
+
+Realistic-workload benchmarks for the `core/vm` interpreter, driven by contracts
+from the [evm-bench](https://github.com/ziyadedher/evm-bench) suite. They
+complement the opcode/loop micro-benchmarks in `runtime_test.go`.
+
+- Benchmark code: [`core/vm/runtime/evm_bench_test.go`](../../evm_bench_test.go)
+- Runtime bytecode: `core/vm/runtime/testdata/*.hex` (committed, `//go:embed`-ed)
+
+Each contract exposes a `Benchmark()` entrypoint (selector `0x30627b7c`) that
+performs the entire workload internally, so one `runtime.Call` = one full
+workload:
+
+| benchmark | workload |
+|---|---|
+| `Snailtracer` | a ray tracer, compute and memory heavy |
+| `ERC20Transfer` | ERC-20 transfers in a loop |
+| `ERC20Mint` | 5000 `_mint`s (SSTORE/keccak/LOG-heavy) |
+| `ERC20ApprovalTransfer` | 1000 approve+transferFrom cycles |
+| `TenThousandHashes` | a keccak loop |
+
+## Running
+
+```sh
+# all of them, current build
+go test ./core/vm/runtime/ -run '^$' -bench 'Benchmark(Snailtracer|TenThousandHashes|ERC20)' -benchmem -count=10
+
+# A/B vs a baseline (default master), with benchstat
+core/vm/runtime/testdata/evm-bench/compare.sh # vs master
+core/vm/runtime/testdata/evm-bench/compare.sh HEAD~1 8 # vs another ref, count=8
+```
+
+`compare.sh` benches the current working tree and a baseline ref (checked out in
+a throwaway worktree with this suite copied in, so it works whether or not the
+interpreter change is committed) and runs `benchstat`.
+
+## Regenerating the bytecode
+
+[`gen.sh`](gen.sh) regenerates the ERC-20 and ten-thousand-hashes `.hex` from
+the evm-bench Solidity sources (solc 0.8.17 via docker). These contracts set up
+their state inside `Benchmark()`, so the runtime bytecode is callable directly.
+
+`snailtracer.hex` is **vendored, not regenerated by gen.sh**: evm-bench's
+`SnailTracer` initializes its scene in the constructor, so its runtime bytecode
+(no constructor) renders an empty scene. The committed file is a runtime-callable
+build (scene encoded in code) vendored from
+[Giulio2002/gevm](https://github.com/Giulio2002/gevm)'s gethbench testdata.
+
+The `.hex` files are committed, so running the benchmarks needs no toolchain.
+
+## Notes
+
+- The contract constructor is not executed, so storage starts empty. The
+ evm-bench contracts set up whatever state they need inside `Benchmark()`,
+ matching the work the evm-bench harness measures.
+- Names match evm-bench / gevm for cross-comparison.
diff --git a/core/vm/runtime/testdata/evm-bench/compare.sh b/core/vm/runtime/testdata/evm-bench/compare.sh
new file mode 100755
index 0000000000..7ae00603a1
--- /dev/null
+++ b/core/vm/runtime/testdata/evm-bench/compare.sh
@@ -0,0 +1,61 @@
+#!/usr/bin/env bash
+# A/B benchmark of the codegen interpreter vs a baseline: runs the evm-bench
+# contract workloads (core/vm/runtime/evm_bench_test.go) plus the synthetic
+# dispatch loops (BenchmarkSimpleLoop/loop*) on the current working tree and on
+# a baseline ref, then benchstats them.
+#
+# The baseline is checked out in a throwaway git worktree and this suite is
+# copied into it, so the comparison works whether or not the interpreter changes
+# are committed yet. Requires: go, git, and benchstat
+# (go install golang.org/x/perf/cmd/benchstat@latest).
+#
+# Usage: core/vm/runtime/testdata/evm-bench/compare.sh [baseref] [count]
+# baseref defaults to "master", count to 10.
+set -euo pipefail
+
+ROOT="$(cd "$(dirname "$0")/../../../../.." && pwd)" # repo root
+cd "$ROOT"
+BASEREF="${1:-master}"
+COUNT="${2:-10}"
+# Each benchmark runs a FIXED iteration count instead of the default 1s of
+# benchtime. With time-based benchtime the faster side runs more iterations,
+# so one-time costs (map growth, pool warmup) amortize over a different N and
+# B/op picks up phantom deltas, and GC pacing can do the same to sec/op. Fixed
+# N makes both sides do identical work. The counts target roughly one second
+# per count on a fast box. Each entry is pattern:iterations, and SimpleLoop
+# needs the /^loop element to select only its loop variants (go test splits
+# -bench on / and benchmarks without sub-benchmarks cannot match a two-element
+# pattern, so the loops run as their own invocation anyway).
+BENCHES=(
+ '^BenchmarkSnailtracer$:20x'
+ '^BenchmarkTenThousandHashes$:200x'
+ '^BenchmarkERC20Transfer$:100x'
+ '^BenchmarkERC20Mint$:150x'
+ '^BenchmarkERC20ApprovalTransfer$:120x'
+ '^BenchmarkSimpleLoop$/^loop:7x'
+)
+NEW="$(mktemp)"; OLD="$(mktemp)"
+
+run_suite() { # run_suite
+ local dir="$1" out="$2" entry pat n
+ for entry in "${BENCHES[@]}"; do
+ pat="${entry%:*}"
+ n="${entry##*:}"
+ ( cd "$dir" && go test ./core/vm/runtime/ -run '^$' -bench "$pat" -benchmem -benchtime "$n" -count="$COUNT" ) | tee -a "$out"
+ done
+}
+
+echo "==> current working tree"
+run_suite "$ROOT" "$NEW"
+
+echo "==> baseline: $BASEREF (throwaway worktree, suite copied in)"
+WT="$(mktemp -d)"
+git worktree add --quiet --detach "$WT" "$BASEREF"
+cp core/vm/runtime/evm_bench_test.go "$WT/core/vm/runtime/"
+mkdir -p "$WT/core/vm/runtime/testdata"
+cp core/vm/runtime/testdata/*.hex "$WT/core/vm/runtime/testdata/"
+run_suite "$WT" "$OLD"
+git worktree remove --force "$WT"
+
+echo "==> benchstat: $BASEREF (left) vs working tree (right)"
+benchstat "$OLD" "$NEW"
diff --git a/core/vm/runtime/testdata/evm-bench/gen.sh b/core/vm/runtime/testdata/evm-bench/gen.sh
new file mode 100755
index 0000000000..6b69134823
--- /dev/null
+++ b/core/vm/runtime/testdata/evm-bench/gen.sh
@@ -0,0 +1,43 @@
+#!/usr/bin/env bash
+# Regenerates core/vm/runtime/testdata/*.hex, the runtime bytecode for the
+# whole-contract interpreter benchmarks (see core/vm/runtime/evm_bench_test.go).
+#
+# Source: the evm-bench suite (github.com/ziyadedher/evm-bench). Each contract
+# exposes Benchmark() (selector 0x30627b7c) which performs the whole workload.
+# Compiled with solc via docker (no local solc needed). solc versions match each
+# contract's pragma: 0.8.17 for the ERC-20/hashing contracts, 0.4.26 for the
+# legacy SnailTracer.
+#
+# Usage: core/vm/runtime/testdata/evm-bench/gen.sh
+set -euo pipefail
+
+TD="$(cd "$(dirname "$0")/.." && pwd)" # .../core/vm/runtime/testdata
+WORK="$(mktemp -d)"
+trap 'rm -rf "$WORK"' EXIT
+
+git clone --depth 1 https://github.com/ziyadedher/evm-bench "$WORK/evm-bench" >/dev/null 2>&1
+B="$WORK/evm-bench/benchmarks"
+
+# ERC-20 (transfer/mint/approval) + ten-thousand-hashes: pragma ^0.8.17. These
+# set up all their state inside Benchmark(), so the runtime bytecode is callable
+# directly with no constructor, which is how the benchmark drives them.
+docker run --rm -v "$B":/src -w /src ethereum/solc:0.8.17 \
+ --optimize --bin-runtime --overwrite -o /src/out \
+ erc20/transfer/ERC20Transfer.sol \
+ erc20/mint/ERC20Mint.sol \
+ erc20/approval-transfer/ERC20ApprovalTransfer.sol \
+ ten-thousand-hashes/TenThousandHashes.sol
+
+cp "$B/out/TenThousandHashes.bin-runtime" "$TD/tenthousandhashes.hex"
+cp "$B/out/ERC20Transfer.bin-runtime" "$TD/erc20transfer.hex"
+cp "$B/out/ERC20Mint.bin-runtime" "$TD/erc20mint.hex"
+cp "$B/out/ERC20ApprovalTransfer.bin-runtime" "$TD/erc20approval.hex"
+
+# NOTE: snailtracer.hex is not regenerated here. evm-bench's SnailTracer
+# initializes its scene in the constructor, so its --bin-runtime (no constructor)
+# renders an empty scene. The committed snailtracer.hex is a runtime-callable
+# build (scene encoded in code, Benchmark() self-contained) vendored from
+# Giulio2002/gevm's gethbench testdata. Leave it as-is.
+
+echo "regenerated (snailtracer.hex left vendored):"
+ls -l "$TD"/*.hex
diff --git a/core/vm/runtime/testdata/snailtracer.hex b/core/vm/runtime/testdata/snailtracer.hex
new file mode 100644
index 0000000000..9546187294
--- /dev/null
+++ b/core/vm/runtime/testdata/snailtracer.hex
@@ -0,0 +1 @@
+608060405234801561001057600080fd5b506004361061004c5760003560e01c806330627b7c1461005157806375ac892a14610085578063784f13661461011d578063c294360114610146575b600080fd5b610059610163565b604080516001600160f81b03199485168152928416602084015292168183015290519081900360600190f35b6100a86004803603604081101561009b57600080fd5b50803590602001356102d1565b6040805160208082528351818301528351919283929083019185019080838360005b838110156100e25781810151838201526020016100ca565b50505050905090810190601f16801561010f5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6100596004803603606081101561013357600080fd5b508035906020810135906040013561055b565b6100a86004803603602081101561015c57600080fd5b5035610590565b6000806000610176610400610300610834565b60405180606001604052806001546000546207d5dc028161019357fe5b058152600060208083018290526040928301919091528251600b81905583820151600c81905593830151600d819055835160608082018652928152808401959095528484015282519081018352600654815260075491810191909152600854918101919091526102259161021c916102139161020e91612ef7565b612f64565b6207d5dc612feb565b620f424061301e565b8051600e556020810151600f55604001516010556102416142dd565b61025a816102556102006101806008613064565b613212565b90506102708161025561014561021c6008613064565b905061028481610255610258806008613064565b905061029a8161025561020a61020c6008613064565b90506102a781600461301e565b90506102b1613250565b8051602082015160409092015160f891821b9692821b9550901b92509050565b606060005b6000548112156104c95760006102ed828686613064565b90506002816000015160f81b90808054603f811680603e811461032a576002830184556001831661031c578192505b600160028404019350610342565b600084815260209081902060ff198516905560419094555b505050600190038154600116156103685790600052602060002090602091828204019190065b909190919091601f036101000a81548160ff02191690600160f81b840402179055506002816020015160f81b90808054603f811680603e81146103c557600283018455600183166103b7578192505b6001600284040193506103dd565b600084815260209081902060ff198516905560419094555b505050600190038154600116156104035790600052602060002090602091828204019190065b909190919091601f036101000a81548160ff02191690600160f81b840402179055506002816040015160f81b90808054603f811680603e81146104605760028301845560018316610452578192505b600160028404019350610478565b600084815260209081902060ff198516905560419094555b5050506001900381546001161561049e5790600052602060002090602091828204019190065b815460ff601f929092036101000a9182021916600160f81b90930402919091179055506001016102d6565b506002805460408051602060018416156101000260001901909316849004601f8101849004840282018401909252818152929183018282801561054d5780601f106105225761010080835404028352916020019161054d565b820191906000526020600020905b81548152906001019060200180831161053057829003601f168201915b505050505090505b92915050565b60008060008061056c878787613064565b8051602082015160409092015160f891821b9a92821b9950901b9650945050505050565b600154606090600019015b600081126107a35760005b6000548112156107995760006105bd828487613064565b90506002816000015160f81b90808054603f811680603e81146105fa57600283018455600183166105ec578192505b600160028404019350610612565b600084815260209081902060ff198516905560419094555b505050600190038154600116156106385790600052602060002090602091828204019190065b909190919091601f036101000a81548160ff02191690600160f81b840402179055506002816020015160f81b90808054603f811680603e81146106955760028301845560018316610687578192505b6001600284040193506106ad565b600084815260209081902060ff198516905560419094555b505050600190038154600116156106d35790600052602060002090602091828204019190065b909190919091601f036101000a81548160ff02191690600160f81b840402179055506002816040015160f81b90808054603f811680603e81146107305760028301845560018316610722578192505b600160028404019350610748565b600084815260209081902060ff198516905560419094555b5050506001900381546001161561076e5790600052602060002090602091828204019190065b815460ff601f929092036101000a9182021916600160f81b90930402919091179055506001016105a6565b506000190161059b565b506002805460408051602060018416156101000260001901909316849004601f810184900484028201840190925281815292918301828280156108275780601f106107fc57610100808354040283529160200191610827565b820191906000526020600020905b81548152906001019060200180831161080a57829003601f168201915b505050505090505b919050565b8160008190555080600181905550604051806080016040528060405180606001604052806302faf08081526020016303197500815260200163119e7f8081525081526020016108a460405180606001604052806000815260200161a673198152602001620f423f19815250612f64565b815260006020808301829052604092830182905283518051600355808201516004558301516005558381015180516006559081015160075582015160085582820151600955606092830151600a805460ff1916911515919091179055815192830190915260015490548291906207d5dc028161091c57fe5b058152600060208083018290526040928301919091528251600b81905583820151600c81905593830151600d819055835160608082018652928152808401959095528484015282519081018352600654815260075491810191909152600854918101919091526109979161021c916102139161020e91612ef7565b8051600e55602080820151600f55604091820151601055815160a08101835264174876e8008152825160608082018552641748862a40825263026e8f00828501526304dd1e008286015282840191825284518082018652600080825281860181905281870181905284870191825286518084018852620b71b081526203d09081880181905281890152928501928352608085018181526011805460018082018355919093528651600b9093027f31ecc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c688101938455955180517f31ecc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c69880155808901517f31ecc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c6a8801558901517f31ecc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c6b870155925180517f31ecc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c6c870155808801517f31ecc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c6d8701558801517f31ecc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c6e860155925180517f31ecc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c6f860155958601517f31ecc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c7085015594909501517f31ecc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c71830155517f31ecc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c72909101805492949192909160ff1990911690836002811115610c1057fe5b0217905550505060116040518060a0016040528064174876e8008152602001604051806060016040528064174290493f19815260200163026e8f0081526020016304dd1e008152508152602001604051806060016040528060008152602001600081526020016000815250815260200160405180606001604052806203d09081526020016203d0908152602001620b71b0815250815260200160006002811115610cb657fe5b905281546001818101845560009384526020938490208351600b90930201918255838301518051838301558085015160028085019190915560409182015160038501558185015180516004860155808701516005860155820151600685015560608501518051600786015595860151600885015594015160098301556080830151600a83018054949593949193909260ff1990921691908490811115610d5857fe5b0217905550505060116040518060a0016040528064174876e800815260200160405180606001604052806302faf080815260200163026e8f00815260200164174876e800815250815260200160405180606001604052806000815260200160008152602001600081525081526020016040518060600160405280620b71b08152602001620b71b08152602001620b71b0815250815260200160006002811115610dfd57fe5b905281546001818101845560009384526020938490208351600b90930201918255838301518051838301558085015160028085019190915560409182015160038501558185015180516004860155808701516005860155820151600685015560608501518051600786015595860151600885015594015160098301556080830151600a83018054949593949193909260ff1990921691908490811115610e9f57fe5b0217905550505060116040518060a0016040528064174876e800815260200160405180606001604052806302faf080815260200163026e8f00815260200164173e54e97f1981525081526020016040518060600160405280600081526020016000815260200160008152508152602001604051806060016040528060008152602001600081526020016000815250815260200160006002811115610f3f57fe5b905281546001818101845560009384526020938490208351600b90930201918255838301518051838301558085015160028085019190915560409182015160038501558185015180516004860155808701516005860155820151600685015560608501518051600786015595860151600885015594015160098301556080830151600a83018054949593949193909260ff1990921691908490811115610fe157fe5b0217905550505060116040518060a0016040528064174876e800815260200160405180606001604052806302faf080815260200164174876e80081526020016304dd1e00815250815260200160405180606001604052806000815260200160008152602001600081525081526020016040518060600160405280620b71b08152602001620b71b08152602001620b71b081525081526020016000600281111561108657fe5b905281546001818101845560009384526020938490208351600b90930201918255838301518051838301558085015160028085019190915560409182015160038501558185015180516004860155808701516005860155820151600685015560608501518051600786015595860151600885015594015160098301556080830151600a83018054949593949193909260ff199092169190849081111561112857fe5b0217905550505060116040518060a0016040528064174876e800815260200160405180606001604052806302faf080815260200164174399c9ff1981526020016304dd1e00815250815260200160405180606001604052806000815260200160008152602001600081525081526020016040518060600160405280620b71b08152602001620b71b08152602001620b71b08152508152602001600060028111156111ce57fe5b905281546001818101845560009384526020938490208351600b90930201918255838301518051838301558085015160028085019190915560409182015160038501558185015180516004860155808701516005860155820151600685015560608501518051600786015595860151600885015594015160098301556080830151600a83018054949593949193909260ff199092169190849081111561127057fe5b0217905550505060116040518060a0016040528062fbc5208152602001604051806060016040528063019bfcc0815260200162fbc52081526020016302cd29c0815250815260200160405180606001604052806000815260200160008152602001600081525081526020016040518060600160405280620f3e588152602001620f3e588152602001620f3e5881525081526020016001600281111561131157fe5b905281546001818101845560009384526020938490208351600b90930201918255838301518051838301558085015160028085019190915560409182015160038501558185015180516004860155808701516005860155820151600685015560608501518051600786015595860151600885015594015160098301556080830151600a83018054949593949193909260ff19909216919084908111156113b357fe5b0217905550505060116040518060a001604052806323c34600815260200160405180606001604052806302faf080815260200163289c455081526020016304dd1e008152508152602001604051806060016040528062b71b00815260200162b71b00815260200162b71b00815250815260200160405180606001604052806000815260200160008152602001600081525081526020016000600281111561145657fe5b905281546001818101845560009384526020938490208351600b90930201918255838301518051838301558085015160028085019190915560409182015160038501558185015180516004860155808701516005860155820151600685015560608501518051600786015595860151600885015594015160098301556080830151600a83018054949593949193909260ff19909216919084908111156114f857fe5b0217905550505060126040518060e00160405280604051806060016040528063035e1f208152602001630188c2e081526020016304a62f8081525081526020016040518060600160405280630459e4408152602001630188c2e081526020016305a1f4a081525081526020016040518060600160405280630459e44081526020016302f34f6081526020016304a62f808152508152602001604051806060016040528060008152602001600081526020016000815250815260200160405180606001604052806000815260200160008152602001600081525081526020016040518060600160405280620f3e588152602001620f3e588152602001620f3e5881525081526020016001600281111561160c57fe5b905281546001818101845560009384526020938490208351805160139094029091019283558085015183830155604090810151600280850191909155858501518051600386015580870151600486015582015160058501558185015180516006860155808701516007860155820151600885015560608501518051600986015580870151600a860155820151600b85015560808501518051600c86015580870151600d860155820151600e85015560a08501518051600f860155958601516010850155940151601183015560c0830151601283018054949593949193909260ff19909216919084908111156116fd57fe5b0217905550505060126040518060e00160405280604051806060016040528063035e1f20815260200163016a8c8081526020016304a62f8081525081526020016040518060600160405280630459e4408152602001600081526020016304a62f8081525081526020016040518060600160405280630459e440815260200163016a8c8081526020016305a1f4a08152508152602001604051806060016040528060008152602001600081526020016000815250815260200160405180606001604052806000815260200160008152602001600081525081526020016040518060600160405280620f3e588152602001620f3e588152602001620f3e5881525081526020016001600281111561180e57fe5b905281546001818101845560009384526020938490208351805160139094029091019283558085015183830155604090810151600280850191909155858501518051600386015580870151600486015582015160058501558185015180516006860155808701516007860155820151600885015560608501518051600986015580870151600a860155820151600b85015560808501518051600c86015580870151600d860155820151600e85015560a08501518051600f860155958601516010850155940151601183015560c0830151601283018054949593949193909260ff19909216919084908111156118ff57fe5b0217905550505060126040518060e001604052806040518060600160405280630555a9608152602001630188c2e081526020016304a62f8081525081526020016040518060600160405280630459e44081526020016302f34f6081526020016304a62f8081525081526020016040518060600160405280630459e4408152602001630188c2e081526020016305a1f4a08152508152602001604051806060016040528060008152602001600081526020016000815250815260200160405180606001604052806000815260200160008152602001600081525081526020016040518060600160405280620f3e588152602001620f3e588152602001620f3e58815250815260200160016002811115611a1357fe5b905281546001818101845560009384526020938490208351805160139094029091019283558085015183830155604090810151600280850191909155858501518051600386015580870151600486015582015160058501558185015180516006860155808701516007860155820151600885015560608501518051600986015580870151600a860155820151600b85015560808501518051600c86015580870151600d860155820151600e85015560a08501518051600f860155958601516010850155940151601183015560c0830151601283018054949593949193909260ff1990921691908490811115611b0457fe5b0217905550505060126040518060e001604052806040518060600160405280630555a960815260200163016a8c8081526020016304a62f8081525081526020016040518060600160405280630459e440815260200163016a8c8081526020016305a1f4a081525081526020016040518060600160405280630459e4408152602001600081526020016304a62f808152508152602001604051806060016040528060008152602001600081526020016000815250815260200160405180606001604052806000815260200160008152602001600081525081526020016040518060600160405280620f3e588152602001620f3e588152602001620f3e58815250815260200160016002811115611c1557fe5b905281546001818101845560009384526020938490208351805160139094029091019283558085015183830155604090810151600280850191909155858501518051600386015580870151600486015582015160058501558185015180516006860155808701516007860155820151600885015560608501518051600986015580870151600a860155820151600b85015560808501518051600c86015580870151600d860155820151600e85015560a08501518051600f860155958601516010850155940151601183015560c0830151601283018054949593949193909260ff1990921691908490811115611d0657fe5b0217905550505060126040518060e00160405280604051806060016040528063035e1f208152602001630188c2e081526020016304a62f8081525081526020016040518060600160405280630459e44081526020016302f34f6081526020016304a62f8081525081526020016040518060600160405280630459e4408152602001630188c2e081526020016303aa6a608152508152602001604051806060016040528060008152602001600081526020016000815250815260200160405180606001604052806000815260200160008152602001600081525081526020016040518060600160405280620f3e588152602001620f3e588152602001620f3e58815250815260200160016002811115611e1a57fe5b905281546001818101845560009384526020938490208351805160139094029091019283558085015183830155604090810151600280850191909155858501518051600386015580870151600486015582015160058501558185015180516006860155808701516007860155820151600885015560608501518051600986015580870151600a860155820151600b85015560808501518051600c86015580870151600d860155820151600e85015560a08501518051600f860155958601516010850155940151601183015560c0830151601283018054949593949193909260ff1990921691908490811115611f0b57fe5b0217905550505060126040518060e00160405280604051806060016040528063035e1f20815260200163016a8c8081526020016304a62f8081525081526020016040518060600160405280630459e440815260200163016a8c8081526020016303aa6a6081525081526020016040518060600160405280630459e4408152602001600081526020016304a62f808152508152602001604051806060016040528060008152602001600081526020016000815250815260200160405180606001604052806000815260200160008152602001600081525081526020016040518060600160405280620f3e588152602001620f3e588152602001620f3e5881525081526020016001600281111561201c57fe5b905281546001818101845560009384526020938490208351805160139094029091019283558085015183830155604090810151600280850191909155858501518051600386015580870151600486015582015160058501558185015180516006860155808701516007860155820151600885015560608501518051600986015580870151600a860155820151600b85015560808501518051600c86015580870151600d860155820151600e85015560a08501518051600f860155958601516010850155940151601183015560c0830151601283018054949593949193909260ff199092169190849081111561210d57fe5b0217905550505060126040518060e001604052806040518060600160405280630555a9608152602001630188c2e081526020016304a62f8081525081526020016040518060600160405280630459e4408152602001630188c2e081526020016303aa6a6081525081526020016040518060600160405280630459e44081526020016302f34f6081526020016304a62f808152508152602001604051806060016040528060008152602001600081526020016000815250815260200160405180606001604052806000815260200160008152602001600081525081526020016040518060600160405280620f3e588152602001620f3e588152602001620f3e5881525081526020016001600281111561222157fe5b905281546001818101845560009384526020938490208351805160139094029091019283558085015183830155604090810151600280850191909155858501518051600386015580870151600486015582015160058501558185015180516006860155808701516007860155820151600885015560608501518051600986015580870151600a860155820151600b85015560808501518051600c86015580870151600d860155820151600e85015560a08501518051600f860155958601516010850155940151601183015560c0830151601283018054949593949193909260ff199092169190849081111561231257fe5b0217905550505060126040518060e001604052806040518060600160405280630555a960815260200163016a8c8081526020016304a62f8081525081526020016040518060600160405280630459e4408152602001600081526020016304a62f8081525081526020016040518060600160405280630459e440815260200163016a8c8081526020016303aa6a608152508152602001604051806060016040528060008152602001600081526020016000815250815260200160405180606001604052806000815260200160008152602001600081525081526020016040518060600160405280620f3e588152602001620f3e588152602001620f3e5881525081526020016001600281111561242357fe5b905281546001818101845560009384526020938490208351805160139094029091019283558085015183830155604090810151600280850191909155858501518051600386015580870151600486015582015160058501558185015180516006860155808701516007860155820151600885015560608501518051600986015580870151600a860155820151600b85015560808501518051600c86015580870151600d860155820151600e85015560a08501518051600f860155958601516010850155940151601183015560c0830151601283018054949593949193909260ff199092169190849081111561251457fe5b0217905550505060126040518060e00160405280604051806060016040528063035e1f208152602001630188c2e081526020016304a62f8081525081526020016040518060600160405280630459e4408152602001630188c2e081526020016303aa6a6081525081526020016040518060600160405280630555a9608152602001630188c2e081526020016304a62f808152508152602001604051806060016040528060008152602001600081526020016000815250815260200160405180606001604052806000815260200160008152602001600081525081526020016040518060600160405280620f3e588152602001620f3e588152602001620f3e5881525081526020016001600281111561262857fe5b905281546001818101845560009384526020938490208351805160139094029091019283558085015183830155604090810151600280850191909155858501518051600386015580870151600486015582015160058501558185015180516006860155808701516007860155820151600885015560608501518051600986015580870151600a860155820151600b85015560808501518051600c86015580870151600d860155820151600e85015560a08501518051600f860155958601516010850155940151601183015560c0830151601283018054949593949193909260ff199092169190849081111561271957fe5b0217905550505060126040518060e00160405280604051806060016040528063035e1f208152602001630188c2e081526020016304a62f8081525081526020016040518060600160405280630555a9608152602001630188c2e081526020016304a62f8081525081526020016040518060600160405280630459e4408152602001630188c2e081526020016305a1f4a08152508152602001604051806060016040528060008152602001600081526020016000815250815260200160405180606001604052806000815260200160008152602001600081525081526020016040518060600160405280620f3e588152602001620f3e588152602001620f3e5881525081526020016001600281111561282d57fe5b905281546001818101845560009384526020938490208351805160139094029091019283558085015183830155604090810151600280850191909155858501518051600386015580870151600486015582015160058501558185015180516006860155808701516007860155820151600885015560608501518051600986015580870151600a860155820151600b85015560808501518051600c86015580870151600d860155820151600e85015560a08501518051600f860155958601516010850155940151601183015560c0830151601283018054949593949193909260ff199092169190849081111561291e57fe5b0217905550505060126040518060e00160405280604051806060016040528063035e1f20815260200163016a8c8081526020016304a62f8081525081526020016040518060600160405280630555a960815260200163016a8c8081526020016304a62f8081525081526020016040518060600160405280630459e440815260200163016a8c8081526020016303aa6a608152508152602001604051806060016040528060008152602001600081526020016000815250815260200160405180606001604052806000815260200160008152602001600081525081526020016040518060600160405280620f3e588152602001620f3e588152602001620f3e58815250815260200160016002811115612a3257fe5b905281546001818101845560009384526020938490208351805160139094029091019283558085015183830155604090810151600280850191909155858501518051600386015580870151600486015582015160058501558185015180516006860155808701516007860155820151600885015560608501518051600986015580870151600a860155820151600b85015560808501518051600c86015580870151600d860155820151600e85015560a08501518051600f860155958601516010850155940151601183015560c0830151601283018054949593949193909260ff1990921691908490811115612b2357fe5b0217905550505060126040518060e00160405280604051806060016040528063035e1f20815260200163016a8c8081526020016304a62f8081525081526020016040518060600160405280630459e440815260200163016a8c8081526020016305a1f4a081525081526020016040518060600160405280630555a960815260200163016a8c8081526020016304a62f808152508152602001604051806060016040528060008152602001600081526020016000815250815260200160405180606001604052806000815260200160008152602001600081525081526020016040518060600160405280620f3e588152602001620f3e588152602001620f3e58815250815260200160016002811115612c3757fe5b905281546001818101845560009384526020938490208351805160139094029091019283558085015183830155604090810151600280850191909155858501518051600386015580870151600486015582015160058501558185015180516006860155808701516007860155820151600885015560608501518051600986015580870151600a860155820151600b85015560808501518051600c86015580870151600d860155820151600e85015560a08501518051600f860155958601516010850155940151601183015560c0830151601283018054949593949193909260ff1990921691908490811115612d2857fe5b0217905550505060005b601254811015612ef257600060128281548110612d4b57fe5b600091825260209182902060408051610140810182526013909302909101805460e08401908152600182015461010085015260028083015461012086015290845282516060818101855260038401548252600484015482880152600584015482860152858701919091528351808201855260068401548152600784015481880152600884015481860152858501528351808201855260098401548152600a84015481880152600b840154818601528186015283518082018552600c8401548152600d84015481880152600e84015481860152608086015283519081018452600f830154815260108301549581019590955260118201549285019290925260a0830193909352601283015491929160c084019160ff90911690811115612e6c57fe5b6002811115612e7757fe5b815250509050612eac61020e612e95836020015184600001516132cd565b612ea7846040015185600001516132cd565b612ef7565b60128381548110612eb957fe5b60009182526020918290208351600960139093029091019182015590820151600a820155604090910151600b9091015550600101612d32565b505050565b612eff6142dd565b604051806060016040528083602001518560400151028460400151866020015102038152602001836040015185600001510284600001518660400151020381526020018360000151856020015102846020015186600001510203815250905092915050565b612f6c6142dd565b604082015160208301518351600092612f9292918002918002919091019080020161330c565b90506040518060600160405280828560000151620f42400281612fb157fe5b058152602001828560200151620f42400281612fc957fe5b058152602001828560400151620f42400281612fe157fe5b0590529392505050565b612ff36142dd565b5060408051606081018252835183028152602080850151840290820152928101519091029082015290565b6130266142dd565b60405180606001604052808385600001518161303e57fe5b0581526020018385602001518161305157fe5b05815260200183856040015181612fe157fe5b61306c6142dd565b6000546013805463ffffffff1916918502860163ffffffff169190911790556130936142dd565b905060005b828112156131f157600061317261314c61021c613115600b60405180606001604052908160008201548152602001600182015481526020016002820154815250506207a1206000546207a1206130ec613343565b63ffffffff16816130f957fe5b0663ffffffff168d620f424002018161310e57fe5b0503612feb565b60408051606081018252600e548152600f5460208201526010549181019190915260015461025591906207a12090816130ec613343565b604080516060810182526006548152600754602082015260085491810191909152613212565b6040805160e081019091526003546080820190815260045460a083015260055460c083015291925060009181906131ae9061025586608c612feb565b81526020016131bc84612f64565b815260006020820181905260409091015290506131e5846102556131df8461336c565b8861301e565b93505050600101613098565b5061320861021c61320183613753565b60ff612feb565b90505b9392505050565b61321a6142dd565b50604080516060810182528251845101815260208084015181860151019082015291810151928101519092019181019190915290565b60008080556001819055613266906002906142fe565b60006003819055600481905560058190556006819055600781905560088190556009819055600a805460ff19169055600b819055600c819055600d819055600e819055600f81905560108190556132bf90601190614345565b6132cb60126000614366565b565b6132d56142dd565b5060408051606081018252825184510381526020808401518186015103908201528282015184830151039181019190915292915050565b80600260018201055b8181121561333d5780915060028182858161332c57fe5b05018161333557fe5b059050613315565b50919050565b6013805463ffffffff19811663ffffffff9182166341c64e6d0261303901821617918290551690565b6133746142dd565b600a826040015113156133a657604051806060016040528060008152602001600081526020016000815250905061082f565b60008060006133b48561379f565b91945092509050826133e857604051806060016040528060008152602001600081526020016000815250935050505061082f565b6133f0614387565b6133f86143c7565b6134006142dd565b6134086142dd565b600086600181111561341657fe5b1415613505576011858154811061342957fe5b60009182526020918290206040805160a081018252600b90930290910180548352815160608082018452600183015482526002808401548388015260038401548386015285870192909252835180820185526004840154815260058401548188015260068401548186015285850152835180820185526007840154815260088401549681019690965260098301549386019390935291830193909352600a830154919291608084019160ff909116908111156134e157fe5b60028111156134ec57fe5b8152505093508360600151915083604001519050613653565b6012858154811061351257fe5b600091825260209182902060408051610140810182526013909302909101805460e08401908152600182015461010085015260028083015461012086015290845282516060818101855260038401548252600484015482880152600584015482860152858701919091528351808201855260068401548152600784015481880152600884015481860152858501528351808201855260098401548152600a84015481880152600b840154818601528186015283518082018552600c8401548152600d84015481880152600e84015481860152608086015283519081018452600f830154815260108301549581019590955260118201549285019290925260a0830193909352601283015491929160c084019160ff9091169081111561363357fe5b600281111561363e57fe5b8152505092508260a001519150826080015190505b6040820151600190811215613669575060408201515b808360200151131561367c575060208201515b808360400151131561368f575060408201515b60408a01805160010190819052600512156136f75780620f42406136b1613343565b63ffffffff16816136be57fe5b0663ffffffff1612156136e8576136e16136db84620f4240612feb565b8261301e565b92506136f7565b50965061082f95505050505050565b6136ff6142dd565b600088600181111561370d57fe5b14156137255761371e8b878b613a57565b9050613733565b6137308b868b613aec565b90505b6137448361025561021c8785613baa565b9b9a5050505050505050505050565b61375b6142dd565b60405180606001604052806137738460000151613be8565b81526020016137858460200151613be8565b81526020016137978460400151613be8565b905292915050565b60008080808080805b6011548110156138c2576000613890601183815481106137c457fe5b60009182526020918290206040805160a081018252600b90930290910180548352815160608082018452600183015482526002808401548388015260038401548386015285870192909252835180820185526004840154815260058401548188015260068401548186015285850152835180820185526007840154815260088401549681019690965260098301549386019390935291830193909352600a830154919291608084019160ff9091169081111561387c57fe5b600281111561388757fe5b9052508a613c13565b90506000811380156138a957508415806138a957508481125b156138b957809450600093508192505b506001016137a8565b5060005b601254811015613a49576000613a17601283815481106138e257fe5b600091825260209182902060408051610140810182526013909302909101805460e08401908152600182015461010085015260028083015461012086015290845282516060818101855260038401548252600484015482880152600584015482860152858701919091528351808201855260068401548152600784015481880152600884015481860152858501528351808201855260098401548152600a84015481880152600b840154818601528186015283518082018552600c8401548152600d84015481880152600e84015481860152608086015283519081018452600f830154815260108301549581019590955260118201549285019290925260a0830193909352601283015491929160c084019160ff90911690811115613a0357fe5b6002811115613a0e57fe5b9052508a613cbb565b9050600081138015613a305750841580613a3057508481125b15613a4057809450600193508192505b506001016138c6565b509196909550909350915050565b613a5f6142dd565b6000613a7a856000015161025561021c886020015187612feb565b90506000613a8f61020e8387602001516132cd565b9050600085608001516002811115613aa357fe5b1415613ae1576000613ab9828860200151613e0c565b12613acd57613aca81600019612feb565b90505b613ad8868383613e31565b9250505061320b565b613ad8868383613fc1565b613af46142dd565b6000613b0f856000015161025561021c886020015187612feb565b6060860151909150620a2c2a9015613b2757506216e3605b6000620f4240613b3f87606001518960200151613e0c565b81613b4657fe5b05905060008112613b55576000035b64e8d4a5100081800281038380020281900590036000811215613b8c57613b8188858960600151613fc1565b94505050505061320b565b613b9e88858960600151868686614039565b98975050505050505050565b613bb26142dd565b50604080516060810182528251845102815260208084015181860151029082015291810151928101519092029181019190915290565b600080821215613bfa5750600061082f565b620f4240821315613c0f5750620f424061082f565b5090565b600080613c28846020015184600001516132cd565b90506000620f4240613c3e838660200151613e0c565b81613c4557fe5b865191900591506000908002613c5b8480613e0c565b838402030190506000811215613c775760009350505050610555565b613c808161330c565b90506103e88183031315613c9957900391506105559050565b6103e88183011315613caf570191506105559050565b50600095945050505050565b600080613cd0846020015185600001516132cd565b90506000613ce6856040015186600001516132cd565b90506000613cf8856020015183612ef7565b90506000620f4240613d0a8584613e0c565b81613d1157fe5b0590506103e71981138015613d2757506103e881125b15613d39576000945050505050610555565b85518751600091613d49916132cd565b9050600082613d588386613e0c565b81613d5f57fe5b0590506000811280613d735750620f424081135b15613d875760009650505050505050610555565b6000613d938388612ef7565b9050600084613da68b6020015184613e0c565b81613dad57fe5b0590506000811280613dc35750620f4240818401135b15613dd957600098505050505050505050610555565b600085613de68985613e0c565b81613ded57fe5b0590506103e88112156137445760009950505050505050505050610555565b6040808201519083015160208084015190850151845186510291020191020192915050565b613e396142dd565b6000620f424080613e48613343565b63ffffffff1681613e5557fe5b0663ffffffff16625fdfb00281613e6857fe5b0590506000620f4240613e79613343565b63ffffffff1681613e8657fe5b0663ffffffff1690506000613e9a8261330c565b6103e8029050613ea86142dd565b620186a0613eb98760000151614216565b1315613ee657604051806060016040528060008152602001620f4240815260200160008152509050613f09565b6040518060600160405280620f4240815260200160008152602001600081525090505b613f1661020e8288612ef7565b90506000613f2761020e8884612ef7565b9050613f7f61020e613f64613f5285620f424088613f448c61422e565b0281613f4c57fe5b05612feb565b61025585620f424089613f448d61424e565b6102558a613f7689620f42400361330c565b6103e802612feb565b9150613fb460405180608001604052808a81526020018481526020018b6040015181526020018b60600151151581525061336c565b9998505050505050505050565b613fc96142dd565b6000613ffb61020e8660200151613ff686620f4240613fec898c60200151613e0c565b60020281613f4c57fe5b6132cd565b90506140306040518060800160405280868152602001838152602001876040015181526020018760600151151581525061336c565b95945050505050565b6140416142dd565b60608701516000199015614053575060015b600061408961020e61021c61406c8c602001518a612feb565b613ff68b6140798a61330c565b620f42408c8e0205018802612feb565b60608a0151909150620f42408601906140ba57620f42406140aa838a613e0c565b816140b157fe5b05620f42400390505b60408a0151619c406c0c9f2c9cd04674edea40000000620ea6008480028502850285020205019060021261415e5761412a61411f60405180608001604052808d81526020018681526020018e6040015181526020018e6060015115151581525061336c565b82620f424003612feb565b92506141448361025561413e8e8e8e613fc1565b84612feb565b925061415383620f424061301e565b94505050505061420c565b600281056203d09001620f4240614173613343565b63ffffffff168161418057fe5b0663ffffffff1612156141b2576141536141a461419e8d8d8d613fc1565b83612feb565b600283056203d0900161301e565b6142056141f76141ec60405180608001604052808e81526020018781526020018f6040015181526020018f6060015115151581525061336c565b83620f424003612feb565b60028305620b71b00361301e565b9450505050505b9695505050505050565b60008082131561422757508061082f565b5060000390565b60008061423a8361424e565b905061320b81820264e8d4a510000361330c565b60005b600082121561426757625fdfb082019150614251565b5b625fdfb0821261427f57625fdfb082039150614268565b6001828160025b818313156142d457818385028161429957fe5b0585019450620f4240808788860202816142af57fe5b05816142b757fe5b600095909503940592506001810181029190910290600201614286565b50505050919050565b60405180606001604052806000815260200160008152602001600081525090565b50805460018160011615610100020316600290046000825580601f106143245750614342565b601f0160209004906000526020600020908101906143429190614401565b50565b50805460008255600b02906000526020600020908101906143429190614416565b50805460008255601302906000526020600020908101906143429190614475565b6040518060a00160405280600081526020016143a16142dd565b81526020016143ae6142dd565b81526020016143bb6142dd565b81526020016000905290565b6040518060e001604052806143da6142dd565b81526020016143e76142dd565b81526020016143f46142dd565b81526020016143a16142dd565b5b80821115613c0f5760008155600101614402565b5b80821115613c0f57600080825560018201819055600282018190556003820181905560048201819055600582018190556006820181905560078201819055600882018190556009820155600a8101805460ff19169055600b01614417565b5b80821115613c0f576000808255600182018190556002820181905560038201819055600482018190556005820181905560068201819055600782018190556008820181905560098201819055600a8201819055600b8201819055600c8201819055600d8201819055600e8201819055600f820181905560108201819055601182015560128101805460ff1916905560130161447656fea2646970667358221220037024f5647853879c58fbcc61ac3616455f6f731cc6e84f91eb5a3b4e06c00464736f6c63430007060033
\ No newline at end of file
diff --git a/core/vm/runtime/testdata/tenthousandhashes.hex b/core/vm/runtime/testdata/tenthousandhashes.hex
new file mode 100644
index 0000000000..2892c1d984
--- /dev/null
+++ b/core/vm/runtime/testdata/tenthousandhashes.hex
@@ -0,0 +1 @@
+6080604052348015600f57600080fd5b506004361060285760003560e01c806330627b7c14602d575b600080fd5b60336035565b005b60005b614e20811015606a5760408051602081018390520160408051601f198184030190525280606381606d565b9150506038565b50565b600060018201608c57634e487b7160e01b600052601160045260246000fd5b506001019056fea2646970667358221220e499ea6d1ccd5fce28e8cd2507f6fef33f0d005543e04580f290bbd01df0655464736f6c63430008110033
\ No newline at end of file