diff --git a/core/vm/eips.go b/core/vm/eips.go
index 8a09856029..a7785fb019 100644
--- a/core/vm/eips.go
+++ b/core/vm/eips.go
@@ -237,7 +237,8 @@ func enable3855(jt *JumpTable) {
// opPush0 implements the PUSH0 opcode
func opPush0(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
- scope.Stack.get().Clear()
+ elem := scope.Stack.get()
+ elem.Clear()
return nil, nil
}
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/gascosts.go b/core/vm/gascosts.go
index 220e7d650f..d1dbc1104f 100644
--- a/core/vm/gascosts.go
+++ b/core/vm/gascosts.go
@@ -84,15 +84,14 @@ func (g *GasBudget) Charge(cost GasCosts) (GasBudget, bool) {
return prior, ok
}
-// ChargeRegularOnly deducts a regular-only cost. It's always preferred for
-// performance consideration if the opcode doesn't have any state cost.
-func (g *GasBudget) ChargeRegularOnly(r uint64) bool {
+// ChargeRegularOnly deducts a regular-only cost.
+func (g *GasBudget) ChargeRegularOnly(r uint64) error {
if g.RegularGas < r {
- return false
+ return ErrOutOfGas
}
g.RegularGas -= r
g.UsedRegularGas += r
- return true
+ return nil
}
// CanAfford reports whether the running budget can cover the given cost vector
diff --git a/core/vm/gen/gen.go b/core/vm/gen/gen.go
new file mode 100644
index 0000000000..f2d2691779
--- /dev/null
+++ b/core/vm/gen/gen.go
@@ -0,0 +1,972 @@
+// 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 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"
+ "github.com/ethereum/go-ethereum/params"
+)
+
+// inlineOps selects the opcodes whose handler bodies are spliced inline: the
+// hot, fork-stable opcodes with no dynamic gas. Which handler that is comes
+// from the per-fork tables via vm.GenForks (see deriveSpecs), not from a
+// restated name. Most resolve to a top-level opXxx handler. PUSH3-PUSH32 and
+// DUP1-DUP16 resolve to makePush / makeDup closures, so emitInlineOp splices
+// the factory body with the per-opcode size instead. Opcodes not selected here
+// (or in directCallOps) fall through to the default case, which dispatches via
+// the per-fork table.
+var inlineOps = func() map[byte]bool {
+ m := map[byte]bool{
+ 0x01: true, 0x02: true, 0x03: true, 0x04: true, 0x05: true, // ADD MUL SUB DIV SDIV
+ 0x06: true, 0x07: true, 0x08: true, 0x09: true, 0x0b: true, // MOD SMOD ADDMOD MULMOD SIGNEXTEND
+ 0x10: true, 0x11: true, 0x12: true, 0x13: true, 0x14: true, 0x15: true, // LT GT SLT SGT EQ ISZERO
+ 0x16: true, 0x17: true, 0x18: true, 0x19: true, 0x1a: true, // AND OR XOR NOT BYTE
+ 0x1b: true, 0x1c: true, 0x1d: true, 0x1e: true, // SHL SHR SAR CLZ
+ 0x50: true, 0x56: true, 0x57: true, 0x58: true, 0x59: true, 0x5b: true, // POP JUMP JUMPI PC MSIZE JUMPDEST
+ 0x5f: true, 0x60: true, 0x61: true, // PUSH0 PUSH1 PUSH2
+ }
+ for code := 0x62; code <= 0x7f; code++ { // PUSH3-PUSH32
+ m[byte(code)] = true
+ }
+ for code := 0x80; code <= 0x8f; code++ { // DUP1-DUP16
+ m[byte(code)] = true
+ }
+ for code := 0x90; code <= 0x9f; code++ { // SWAP1-SWAP16
+ m[byte(code)] = true
+ }
+ return m
+}()
+
+// directCallOps selects the opcodes (dynamic gas, not inlined) whose handler,
+// dynamic-gas, and memory-size functions are the same across every fork
+// (verified by checkDirectCallStable). They are emitted as direct calls to
+// those functions by name, with the names derived from the per-fork tables,
+// instead of the indirect operation.* pointer calls in the default case. An
+// aliased gas var derives as its underlying function, so MLOAD's charge is
+// emitted as pureMemoryGascost rather than through the gasMLoad func var.
+var directCallOps = map[byte]bool{
+ 0x20: true, // KECCAK256
+ 0x51: true, // MLOAD
+ 0x52: true, // MSTORE
+ 0x53: true, // MSTORE8
+}
+
+// opSpec holds the per-opcode facts the generator emits from: the constants
+// (gas, stack bounds, intro fork) and the FuncForPC names of the opcode's
+// handler, dynamic-gas and memory-size functions, all derived from the
+// per-fork tables.
+type opSpec struct {
+ defined bool
+ name string
+ fork string
+ constGas uint64
+ minStack int
+ maxStack int
+ execFn string
+ dynFn string
+ memFn string
+}
+
+type generator struct {
+ fset *token.FileSet
+ opcodeHandlers map[string]*ast.FuncDecl
+ stackHelpers map[string]*ast.FuncDecl
+ gasHelpers map[string]*ast.FuncDecl
+ specs [256]opSpec
+ buf *bytes.Buffer
+}
+
+// p is the writer of the generated file. Every line of output is appended
+// to g.buf through it.
+func (g *generator) p(format string, args ...any) {
+ format = strings.TrimRight(strings.TrimPrefix(format, "\n"), " \t")
+ fmt.Fprintf(g.buf, format, args...)
+}
+
+// parseHandlers parses instructions.go, eips.go, stack.go, gascosts.go and
+// interpreter.go. It returns the top-level opXxx handlers by name, the
+// //gen:inline *Stack helper methods, and the gas/memory helper functions
+// (ChargeRegularOnly, computeMemorySize, chargeDynamicGas) whose bodies are
+// spliced into the generated dispatch (all by name).
+func parseHandlers(vmDir string) (fset *token.FileSet, opcodeHandlers, stackHelpers, gasHelpers map[string]*ast.FuncDecl) {
+ fset = token.NewFileSet()
+ opcodeHandlers = map[string]*ast.FuncDecl{}
+ stackHelpers = map[string]*ast.FuncDecl{}
+ gasHelpers = map[string]*ast.FuncDecl{}
+ for _, name := range []string{"instructions.go", "eips.go", "stack.go", "gascosts.go", "interpreter.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.Body == nil {
+ continue
+ }
+ switch {
+ case fn.Name.Name == "ChargeRegularOnly" || fn.Name.Name == "computeMemorySize" || fn.Name.Name == "chargeDynamicGas" || fn.Name.Name == "chargeVerkleCodeChunkGas": // spliced gas/memory helpers
+ gasHelpers[fn.Name.Name] = fn
+ case fn.Recv == nil: // top-level opXxx handler
+ opcodeHandlers[fn.Name.Name] = fn
+ case methodReceiver(fn) == "Stack" && hasInlineMarker(fn): // (s *Stack) helper tagged //gen:inline
+ stackHelpers[fn.Name.Name] = fn
+ }
+ }
+ }
+ return fset, opcodeHandlers, stackHelpers, gasHelpers
+}
+
+// methodReceiver returns the receiver type name of a pointer-receiver method
+// (e.g. "Stack" for (s *Stack)), or "" if fn is not such a method.
+func methodReceiver(fn *ast.FuncDecl) string {
+ if fn.Recv == nil || len(fn.Recv.List) != 1 {
+ return ""
+ }
+ star, ok := fn.Recv.List[0].Type.(*ast.StarExpr)
+ if !ok {
+ return ""
+ }
+ id, ok := star.X.(*ast.Ident)
+ if !ok {
+ return ""
+ }
+ return id.Name
+}
+
+// hasInlineMarker reports whether fn is tagged //gen:inline, which marks a stack
+// helper for splicing into the generated dispatch.
+func hasInlineMarker(fn *ast.FuncDecl) bool {
+ if fn.Doc == nil {
+ return false
+ }
+ for _, c := range fn.Doc.List {
+ if c.Text == "//gen:inline" {
+ return true
+ }
+ }
+ return false
+}
+
+var opcodeReturnRe = regexp.MustCompile(`^(\s*)return\s+([^,]+),\s*(.+)$`)
+
+// spliceOpcodeBody returns a named handler's body, rewritten so it can be spliced
+// into the dispatch loop (see rewriteOpcodeReturns). The caller emits it with p.
+func (g *generator) spliceOpcodeBody(handler string) string {
+ fn := g.opcodeHandlers[handler]
+ if fn == nil {
+ fatalf("no handler %q to inline", handler)
+ }
+ return g.rewriteOpcodeReturns(g.inlineStackHelpers(fn.Body.List, nil))
+}
+
+// spliceOpcodeFactoryBody splices the body of the executionFunc closure that a make*
+// factory returns, substituting the factory's parameters with the per-opcode
+// constants in args (positional, matching the factory signature). This lets
+// closure-built handlers (makePush, makeDup) be derived from their single
+// definition rather than restated in the generator. The caller emits the
+// result with p.
+func (g *generator) spliceOpcodeFactoryBody(factory string, args ...int) string {
+ fn := g.opcodeHandlers[factory]
+ if fn == nil {
+ fatalf("no factory %q to inline", factory)
+ }
+ lit := factoryClosure(factory, fn)
+ // Bind the factory parameters to the per-opcode constants, then inline.
+ names := paramNames(fn)
+ if len(names) != len(args) {
+ fatalf("factory %q takes %d params, got %d args", factory, len(names), len(args))
+ }
+ params := map[string]int{}
+ for i, nm := range names {
+ params[nm] = args[i]
+ }
+ return g.rewriteOpcodeReturns(g.inlineStackHelpers(lit.Body.List, params))
+}
+
+// factoryClosure returns the executionFunc literal that a make* factory's body
+// is a single `return func(...) {...}` of.
+func factoryClosure(name string, fn *ast.FuncDecl) *ast.FuncLit {
+ if len(fn.Body.List) != 1 {
+ fatalf("factory %q body is not a single return", name)
+ }
+ ret, ok := fn.Body.List[0].(*ast.ReturnStmt)
+ if !ok || len(ret.Results) != 1 {
+ fatalf("factory %q does not return a single value", name)
+ }
+ lit, ok := ret.Results[0].(*ast.FuncLit)
+ if !ok {
+ fatalf("factory %q does not return a func literal", name)
+ }
+ return lit
+}
+
+// renderAst converts AST statements back to formatted Go source text, the
+// inverse of parsing. It uses the generator's fileset and emits nothing itself
+// (the caller passes the result to p).
+func (g *generator) renderAst(stmts []ast.Stmt) string {
+ var raw bytes.Buffer
+ cfg := printer.Config{Mode: printer.UseSpaces | printer.TabIndent, Tabwidth: 8}
+ for _, stmt := range stmts {
+ if err := cfg.Fprint(&raw, g.fset, stmt); err != nil {
+ fatalf("print stmt: %v", err)
+ }
+ raw.WriteByte('\n')
+ }
+ return raw.String()
+}
+
+// rewriteOpcodeReturns rewrites a printed handler body so it runs inside 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. (Stack helpers were already
+// inlined by inlineStackHelpers before the body was printed.)
+func (g *generator) rewriteOpcodeReturns(src string) string {
+ src = strings.ReplaceAll(src, "*pc", "pc")
+
+ var out bytes.Buffer
+ for _, line := range strings.Split(src, "\n") {
+ if m := opcodeReturnRe.FindStringSubmatch(line); m != nil {
+ indent, r0, r1 := m[1], strings.TrimSpace(m[2]), strings.TrimSpace(m[3])
+ 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()
+}
+
+var gasReturnRe = regexp.MustCompile(`^(\s*)return\s+(\S.*)$`)
+
+// rewriteGasReturns rewrites a spliced charge body so it runs as a gas step in
+// the dispatch loop: a `return ` becomes the out-of-gas break, and the
+// trailing `return nil` (success) is dropped so the opcode continues.
+func (g *generator) rewriteGasReturns(src string) string {
+ var out bytes.Buffer
+ for _, line := range strings.Split(src, "\n") {
+ if m := gasReturnRe.FindStringSubmatch(line); m != nil {
+ indent, val := m[1], strings.TrimSpace(m[2])
+ if val == "nil" {
+ continue // success: fall through to the rest of the op
+ }
+ out.WriteString(indent + "res, err = nil, " + val + "\n")
+ out.WriteString(indent + "break mainLoop\n")
+ continue
+ }
+ out.WriteString(line + "\n")
+ }
+ return out.String()
+}
+
+// rewriteStepReturns rewrites a spliced gas-step body's (value, error) returns so
+// it runs inline in the dispatch loop: a non-nil error becomes the out-of-gas
+// break; on success the value is assigned to target (or dropped when target is
+// empty) and the op falls through.
+func (g *generator) rewriteStepReturns(src, target string) string {
+ var out bytes.Buffer
+ for _, line := range strings.Split(src, "\n") {
+ if m := opcodeReturnRe.FindStringSubmatch(line); m != nil {
+ indent, val, errVal := m[1], strings.TrimSpace(m[2]), strings.TrimSpace(m[3])
+ if errVal == "nil" {
+ if target != "" {
+ out.WriteString(indent + target + " = " + val + "\n")
+ }
+ continue
+ }
+ out.WriteString(indent + "res, err = nil, " + errVal + "\n")
+ out.WriteString(indent + "break mainLoop\n")
+ continue
+ }
+ out.WriteString(line + "\n")
+ }
+ return out.String()
+}
+
+// stackCall is a matched call to a tagged helper.
+type stackCall struct {
+ helper string // helper method name
+ lhs []ast.Expr // assignment targets, nil for a void call like dup
+ tok token.Token // the assignment token, := or =
+ args []ast.Expr // call arguments (only dup has one)
+}
+
+// matchStackHelper matches a statement that is a single must-expand helper call,
+// in one of the two normalized forms: an assignment `lhs... := scope.Stack.H(args)`
+// or a bare `scope.Stack.H(args)`.
+func (g *generator) matchStackHelper(stmt ast.Stmt) (stackCall, bool) {
+ switch s := stmt.(type) {
+ case *ast.AssignStmt:
+ if len(s.Rhs) == 1 {
+ if h, args, ok := g.stackHelperCall(s.Rhs[0]); ok {
+ return stackCall{helper: h, lhs: s.Lhs, tok: s.Tok, args: args}, true
+ }
+ }
+ case *ast.ExprStmt:
+ if h, args, ok := g.stackHelperCall(s.X); ok {
+ return stackCall{helper: h, args: args}, true
+ }
+ }
+ return stackCall{}, false
+}
+
+// stackHelperCall unwraps scope.Stack.H(args) where H is a must-expand helper.
+func (g *generator) stackHelperCall(e ast.Expr) (helper string, args []ast.Expr, ok bool) {
+ call, isCall := e.(*ast.CallExpr)
+ if !isCall {
+ return "", nil, false
+ }
+ sel, isSel := call.Fun.(*ast.SelectorExpr) // .H
+ if !isSel || g.stackHelpers[sel.Sel.Name] == nil || !isStackExpr(sel.X) {
+ return "", nil, false
+ }
+ return sel.Sel.Name, call.Args, true
+}
+
+// isStackExpr reports whether e is the stack receiver: the `stack` local or
+// scope.Stack.
+func isStackExpr(e ast.Expr) bool {
+ switch x := e.(type) {
+ case *ast.Ident:
+ return x.Name == "stack"
+ case *ast.SelectorExpr:
+ return x.Sel.Name == "Stack"
+ }
+ return false
+}
+
+// inlineStackHelpers renders a handler body to source, inlining every must-expand
+// helper call and printing other statements unchanged. params maps the factory
+// parameters (makePush/makeDup) to their per-opcode constants.
+func (g *generator) inlineStackHelpers(stmts []ast.Stmt, params map[string]int) string {
+ var out strings.Builder
+ // Walk the handler body one statement at a time. A statement that is a
+ // tagged stack-helper call gets the helper's body spliced in: the generated
+ // dispatch is past Go's big-function inline budget, so the call would not be
+ // inlined otherwise. Every other statement is printed as written.
+ for _, stmt := range stmts {
+ if call, ok := g.matchStackHelper(stmt); ok {
+ // e.g. `x, y := scope.Stack.pop1Peek1()` becomes the body of pop1Peek1.
+ out.WriteString(g.inlineStackHelper(call, params))
+ } else {
+ // A plain statement: print it verbatim, then fill in any makePush or
+ // makeDup factory params with this opcode's constants.
+ out.WriteString(substParams(g.renderAst([]ast.Stmt{stmt}), params))
+ }
+ }
+ return out.String()
+}
+
+// substParams replaces each factory parameter with its constant. It runs only
+// on printed non-helper statements and on helper arguments, never on a helper
+// expansion, so it cannot touch a field like stack.size. The parameter names do
+// not textually overlap, so map order does not affect the result.
+func substParams(src string, params map[string]int) string {
+ for name, val := range params {
+ src = regexp.MustCompile(`\b`+name+`\b`).ReplaceAllString(src, fmt.Sprint(val))
+ }
+ return src
+}
+
+// inlineStackHelper expands one helper call to its stack.go body. The single
+// rule: the helper is straight-line statements then an optional final return
+// whose result count matches the call's targets. Anything else is not in
+// inlinable form and is a hard error (the shape post-condition). The receiver
+// maps to the loop's `stack` local and each parameter to its call argument.
+func (g *generator) inlineStackHelper(call stackCall, params map[string]int) string {
+ fn := g.stackHelpers[call.helper]
+ if fn == nil {
+ fatalf("no stack helper %q to inline", call.helper)
+ }
+ // Peel an optional trailing return off the body.
+ body := fn.Body.List
+ var ret *ast.ReturnStmt
+ if n := len(body); n > 0 {
+ if r, isRet := body[n-1].(*ast.ReturnStmt); isRet {
+ ret, body = r, body[:n-1]
+ }
+ }
+ results := 0
+ if ret != nil {
+ results = len(ret.Results)
+ }
+ if len(call.lhs) != results {
+ fatalf("stack helper %q returns %d values, call assigns %d", call.helper, results, len(call.lhs))
+ }
+ // Map the receiver to the loop local and each parameter to its argument.
+ names := paramNames(fn)
+ if len(names) != len(call.args) {
+ fatalf("stack helper %q takes %d params, call passes %d", call.helper, len(names), len(call.args))
+ }
+ subst := map[string]string{recvName(fn): "stack"}
+ for i, name := range names {
+ subst[name] = substParams(renderInlineExpr(call.args[i], nil), params)
+ }
+ // The leading bookkeeping statements, then bind each return expression to
+ // its assignment target.
+ var out strings.Builder
+ for _, stmt := range body {
+ out.WriteString(renderInlineStmt(stmt, subst) + "\n")
+ }
+ for i, lhs := range call.lhs {
+ out.WriteString(renderInlineExpr(lhs, nil) + " " + call.tok.String() + " " + renderInlineExpr(ret.Results[i], subst) + "\n")
+ }
+ return out.String()
+}
+
+// recvName returns a method's receiver name (e.g. "s").
+func recvName(fn *ast.FuncDecl) string {
+ if names := fn.Recv.List[0].Names; len(names) > 0 {
+ return names[0].Name
+ }
+ return ""
+}
+
+// paramNames returns a function's parameter names, in order.
+func paramNames(fn *ast.FuncDecl) []string {
+ var names []string
+ for _, f := range fn.Type.Params.List {
+ for _, nm := range f.Names {
+ names = append(names, nm.Name)
+ }
+ }
+ return names
+}
+
+// renderInlineStmt prints one helper-body statement with subst applied. Only the
+// statement shapes the helpers use are handled; any other is not inlinable.
+func renderInlineStmt(stmt ast.Stmt, subst map[string]string) string {
+ switch s := stmt.(type) {
+ case *ast.IncDecStmt: // s.inner.top++
+ return renderInlineExpr(s.X, subst) + s.Tok.String()
+ case *ast.AssignStmt: // s.size -= 2, data[x] = data[y], or the swap tuple a, b = b, a
+ if len(s.Lhs) == len(s.Rhs) && len(s.Lhs) >= 1 {
+ lhs := make([]string, len(s.Lhs))
+ rhs := make([]string, len(s.Rhs))
+ for i := range s.Lhs {
+ lhs[i] = renderInlineExpr(s.Lhs[i], subst)
+ rhs[i] = renderInlineExpr(s.Rhs[i], subst)
+ }
+ return strings.Join(lhs, ", ") + " " + s.Tok.String() + " " + strings.Join(rhs, ", ")
+ }
+ }
+ fatalf("inline: unsupported statement %T in stack helper", stmt)
+ return ""
+}
+
+// renderInlineExpr prints one helper-body expression, substituting any
+// identifier found in subst. Only the shapes the helpers use are handled.
+func renderInlineExpr(expr ast.Expr, subst map[string]string) string {
+ switch e := expr.(type) {
+ case *ast.Ident:
+ if r, ok := subst[e.Name]; ok {
+ return r
+ }
+ return e.Name
+ case *ast.BasicLit:
+ return e.Value
+ case *ast.SelectorExpr: // x.field
+ return renderInlineExpr(e.X, subst) + "." + e.Sel.Name
+ case *ast.IndexExpr: // x[i]
+ return renderInlineExpr(e.X, subst) + "[" + renderInlineExpr(e.Index, subst) + "]"
+ case *ast.BinaryExpr: // x op y
+ return renderInlineExpr(e.X, subst) + " " + e.Op.String() + " " + renderInlineExpr(e.Y, subst)
+ case *ast.UnaryExpr: // &x
+ return e.Op.String() + renderInlineExpr(e.X, subst)
+ }
+ fatalf("inline: unsupported expression %T in stack helper", expr)
+ return ""
+}
+
+// deriveSpecs records each opcode's constant values (name, intro fork, static
+// gas, stack bounds) and its handler, dynamic-gas and memory-size function
+// names from the first fork that defines it, then checks that the opcodes
+// chosen for inlining and direct-calling are safe to emit from those specs by
+// verifying they are fork-stable (see checkStable and checkDirectCallStable).
+func (g *generator) deriveSpecs(forks []vm.GenFork) {
+ for code := range 256 {
+ for _, fork := range forks {
+ o := fork.Ops[code]
+ if !o.Defined {
+ continue
+ }
+ g.specs[code] = opSpec{
+ defined: true,
+ name: o.Name,
+ fork: fork.RuleField,
+ constGas: o.ConstantGas,
+ minStack: o.MinStack,
+ maxStack: o.MaxStack,
+ execFn: o.ExecuteFn,
+ dynFn: o.DynamicGasFn,
+ memFn: o.MemorySizeFn,
+ }
+ break // first fork that defines it wins (its intro fork)
+ }
+ }
+
+ // Every inlined opcode must be defined and keep the same handler and static
+ // gas / stack bounds across all forks where it appears. Bail loudly otherwise.
+ for code := range inlineOps {
+ g.checkStable(code, forks)
+ }
+
+ // directCallOps opcodes emit their static gas and stack bounds as constants the
+ // same way, so they must be fork-stable too. Dynamic gas is allowed (it is
+ // charged through the named gas function, not a constant).
+ for code := range directCallOps {
+ g.checkDirectCallStable(code, forks)
+ }
+}
+
+// checkStable verifies an opcode selected for inlining is safe to inline: it must
+// be defined, its handler and its static gas and stack bounds must be the same
+// across every fork it appears in (the body and constants are emitted from the
+// first defining fork's spec), and it must have no dynamic gas, since an inlined
+// op charges only its constant static gas. It bails loudly otherwise.
+func (g *generator) checkStable(code byte, forks []vm.GenFork) {
+ spec := g.specs[code]
+ if !spec.defined {
+ fatalf("opcode %#x selected for inlining but never defined", code)
+ }
+ for _, fork := range forks {
+ o := fork.Ops[code]
+ if !o.Defined {
+ continue
+ }
+ if o.ExecuteFn != spec.execFn || o.ConstantGas != spec.constGas || o.MinStack != spec.minStack || o.MaxStack != spec.maxStack || o.DynamicGasFn != "" {
+ fatalf("opcode %#x (%s) is not fork-stable (fork %s): cannot inline", code, spec.name, fork.Name)
+ }
+ }
+}
+
+// checkDirectCallStable verifies a directCallOps opcode is safe to direct-call. Its static
+// gas and stack bounds must be the same across every fork it appears in (they are
+// emitted as constants), and its handler, gas and memory functions must be the same
+// across those forks too (they are called by the first defining fork's names, so a
+// fork that swapped one would otherwise be missed). Unlike checkStable it allows
+// dynamic gas, which directCallOps ops carry by definition.
+func (g *generator) checkDirectCallStable(code byte, forks []vm.GenFork) {
+ spec := g.specs[code]
+ if !spec.defined {
+ fatalf("opcode %#x (directCallOps) is never defined", code)
+ }
+ for _, fork := range forks {
+ o := fork.Ops[code]
+ if !o.Defined {
+ continue
+ }
+ if o.ConstantGas != spec.constGas || o.MinStack != spec.minStack || o.MaxStack != spec.maxStack {
+ fatalf("opcode %#x (%s) is in directCallOps but not fork-stable (fork %s): static gas or stack bounds vary, cannot emit as constants", code, spec.name, fork.Name)
+ }
+ if o.ExecuteFn != spec.execFn || o.DynamicGasFn != spec.dynFn || o.MemorySizeFn != spec.memFn {
+ fatalf("opcode %#x (%s) is in directCallOps but its functions vary by fork (fork %s): got %s/%s/%s, want %s/%s/%s, cannot direct-call",
+ code, spec.name, fork.Name, o.ExecuteFn, o.DynamicGasFn, o.MemorySizeFn, spec.execFn, spec.dynFn, spec.memFn)
+ }
+ }
+}
+
+// generateStackChecks returns the underflow/overflow guards, mirroring the legacy
+// loop's order (stack validated before gas). minExpr and maxExpr are the
+// stack-bound expressions (constants on the inlined/direct paths,
+// operation.minStack/maxStack in the table path). under and over select which
+// guards to emit, so those paths can omit a guard whose bound is trivial.
+func (g *generator) generateStackChecks(minExpr, maxExpr any, under, over bool) string {
+ switch {
+ case under && over:
+ return fmt.Sprintf(`if sLen := stack.len(); sLen < %v {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: %v}
+} else if sLen > %v {
+ return nil, &ErrStackOverflow{stackLen: sLen, limit: %v}
+}
+`, minExpr, minExpr, maxExpr, maxExpr)
+ case under:
+ return fmt.Sprintf(`if sLen := stack.len(); sLen < %v {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: %v}
+}
+`, minExpr, minExpr)
+ case over:
+ return fmt.Sprintf(`if sLen := stack.len(); sLen > %v {
+ return nil, &ErrStackOverflow{stackLen: sLen, limit: %v}
+}
+`, maxExpr, maxExpr)
+ }
+ return ""
+}
+
+// generateStaticGas returns the static-gas charge, spliced call-free from the
+// ChargeRegularOnly body for amount: a constant on the inlined and
+// direct-call paths, operation.constantGas in the table path. The receiver maps
+// to contract.Gas and the method's single uint64 parameter to amount, substituted
+// textually on word boundaries (which cannot touch fields like RegularGas). Its
+// `return ` becomes the loop's out-of-gas exit and its trailing `return nil`
+// is dropped so the opcode falls through to its remaining steps (see
+// rewriteGasReturns).
+func (g *generator) generateStaticGas(amount any) string {
+ fn := g.gasHelpers["ChargeRegularOnly"]
+ if fn == nil {
+ fatalf("no ChargeRegularOnly gas helper to inline")
+ }
+ names := paramNames(fn)
+ if len(names) != 1 {
+ fatalf("ChargeRegularOnly takes %d params, want 1", len(names))
+ }
+ src := g.renderAst(fn.Body.List)
+ src = regexp.MustCompile(`\b`+recvName(fn)+`\b`).ReplaceAllString(src, "contract.Gas")
+ src = regexp.MustCompile(`\b`+names[0]+`\b`).ReplaceAllString(src, fmt.Sprint(amount))
+ return g.rewriteGasReturns(src)
+}
+
+// closureSegRe matches the anonymous trailing segments of a closure's
+// FuncForPC name, "func31" or a nested "2".
+var closureSegRe = regexp.MustCompile(`^(func\d+|\d+)$`)
+
+// factoryName returns the factory a closure-built handler was created by
+// (e.g. "makeDup" for "newFrontierInstructionSet.makeDup.func37"), or "" for
+// a plain top-level handler name.
+func factoryName(fn string) string {
+ segs := strings.Split(fn, ".")
+ n := len(segs)
+ for n > 0 && closureSegRe.MatchString(segs[n-1]) {
+ n--
+ }
+ if n == len(segs) || n == 0 {
+ return ""
+ }
+ return segs[n-1]
+}
+
+// emitInlineOp emits an inlined opcode case: the stack and gas guards followed by
+// the spliced opcode body. A fork-introduced opcode wraps that body in a fork gate
+// so it runs only when the opcode is active for the current fork, otherwise the
+// case mirrors the legacy loop's undefined-opcode handling.
+func (g *generator) emitInlineOp(code byte) {
+ spec := g.specs[code]
+ g.p("case %s:\n", spec.name)
+ if spec.fork != "" {
+ g.p("if rules.%s {\n", spec.fork)
+ }
+
+ // stack bounds check
+ g.p("%s", g.generateStackChecks(spec.minStack, spec.maxStack, spec.minStack > 0, spec.maxStack < int(params.StackLimit)))
+
+ // static gas
+ if spec.constGas != 0 {
+ g.p("%s", g.generateStaticGas(spec.constGas))
+ }
+
+ // 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 constant static gas and stack guard above already match.
+ if code >= 0x60 && code <= 0x7f {
+ g.p(`
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ `)
+ }
+
+ // opcode body
+ switch factory := factoryName(spec.execFn); factory {
+ case "makePush": // PUSH3-PUSH32: splice makePush(size, size)
+ n := int(code) - 0x5f
+ g.p("%s", g.spliceOpcodeFactoryBody("makePush", n, n))
+ case "makeDup": // DUP1-DUP16: splice makeDup(n)
+ g.p("%s", g.spliceOpcodeFactoryBody("makeDup", int(code)-0x7f))
+ case "": // the rest: splice the opXxx handler body
+ g.p("%s", g.spliceOpcodeBody(spec.execFn))
+ default:
+ fatalf("opcode %#x (%s) is built by factory %q, which the generator cannot inline", code, spec.name, factory)
+ }
+
+ // If opcode is inactive for this fork, then close the gate
+ // and fall back to the legacy loop's undefined-opcode handling.
+ if spec.fork != "" {
+ g.p(`
+ }
+ res, err = opUndefined(&pc, evm, scope)
+ break mainLoop
+ `)
+ }
+}
+
+// emitDirectCallOp emits an 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 directCallOps).
+func (g *generator) emitDirectCallOp(code byte) {
+ spec := g.specs[code]
+ g.p("case %s:\n", spec.name)
+
+ // stack bounds check
+ g.p("%s", g.generateStackChecks(spec.minStack, spec.maxStack, spec.minStack > 0, spec.maxStack < int(params.StackLimit)))
+
+ // static gas
+ if spec.constGas != 0 {
+ g.p("%s", g.generateStaticGas(spec.constGas))
+ }
+
+ // dynamic gas
+ g.p("\nvar memorySize uint64\n")
+
+ // Splice computeMemorySize's body, rewriting its operation.memorySize lookup to
+ // the opcode's memory-size function and its returns for the dispatch loop.
+ memSizeFn := g.gasHelpers["computeMemorySize"]
+ if memSizeFn == nil {
+ fatalf("no computeMemorySize gas helper to inline")
+ }
+ memSizeSrc := g.renderAst(memSizeFn.Body.List)
+ memSizeSrc = strings.ReplaceAll(memSizeSrc, "operation.memorySize", spec.memFn)
+ g.p("%s", g.rewriteStepReturns(memSizeSrc, "memorySize"))
+
+ // Splice chargeDynamicGas's body the same way, rewriting operation.dynamicGas to
+ // the opcode's gas function.
+ dynGasFn := g.gasHelpers["chargeDynamicGas"]
+ if dynGasFn == nil {
+ fatalf("no chargeDynamicGas gas helper to inline")
+ }
+ dynGasSrc := g.renderAst(dynGasFn.Body.List)
+ dynGasSrc = strings.ReplaceAll(dynGasSrc, "operation.dynamicGas", spec.dynFn)
+ g.p("%s", g.rewriteStepReturns(dynGasSrc, ""))
+
+ // resize memory
+ g.p(`
+ if memorySize > 0 {
+ mem.Resize(memorySize)
+ }
+ `)
+
+ // call the opcode handler
+ g.p(`
+ res, err = %s(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ `, spec.execFn)
+
+ // advance to the next opcode
+ g.p(`
+ pc++
+ continue mainLoop
+ `)
+}
+
+// emitDefault emits the switch's default case: every opcode not inlined or
+// direct-called (the fork-varying ops such as CALL, CREATE, SSTORE, SLOAD, LOG
+// and the COPY family) is dispatched through the active per-fork table, exactly
+// as the legacy loop did, so their volatile gas and opcode logic stays shared
+// rather than restated here.
+func (g *generator) emitDefault() {
+ g.p(`
+ default:
+ operation := table[op]
+ `)
+ // stack bounds check
+ g.p("%s", g.generateStackChecks("operation.minStack", "operation.maxStack", true, true))
+
+ // static gas
+ g.p("%s", g.generateStaticGas("operation.constantGas"))
+
+ // dynamic gas
+ g.p(`
+ var memorySize uint64
+ if memorySize, _, err = contract.meterDynamicGas(operation, evm, stack, mem); err != nil {
+ return nil, err
+ }
+ `)
+
+ // resize memory
+ g.p(`
+ if memorySize > 0 {
+ mem.Resize(memorySize)
+ }
+ `)
+
+ // call the opcode handler
+ g.p(`
+ res, err = operation.execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ `)
+
+ // advance to the next opcode
+ g.p(`
+ pc++
+ continue mainLoop
+ `)
+}
+
+// createFile emits the whole generated file into g.buf: the header, package and
+// imports, then the execUntraced function (its locals and dispatch loop, the
+// verkle code-chunk gas, and a switch with one case per opcode built by the
+// emit* helpers). main formats the buffer and writes it to interpreter_gen.go.
+//
+// The switch has three tiers:
+//
+// - the hot, fork-stable opcodes (arithmetic / comparison / bitwise / PUSH /
+// DUP / SWAP / POP / JUMP / JUMPI / PC / MSIZE / JUMPDEST) are inlined by
+// splicing the existing opXxx handler bodies from instructions.go and
+// eips.go, with their static gas and stack bounds emitted as constants
+// derived from the per-fork instruction tables via vm.GenForks.
+//
+// - the fork-invariant ops (KECCAK256 / MLOAD / MSTORE / MSTORE8, see
+// directCallOps) are called directly by name, skipping the table's function
+// pointers, which Go cannot inline through.
+//
+// - everything fork-varying (CALL / CREATE / SSTORE / SLOAD / LOG / the COPY
+// family and so on) is dispatched 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.
+func (g *generator) createFile() {
+ // file header, package clause, and imports
+ g.p(`
+ // 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: doc comment, loop-local declarations, and the dispatch loop
+ g.p(`
+ // execUntraced is the generated, tracing-free interpreter fast path. Hot,
+ // fork-stable opcodes are inlined with their static gas and stack bounds emitted
+ // as constants. Fork-invariant 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 {
+ `)
+
+ // verkle code-chunk gas, spliced from chargeVerkleCodeChunkGas
+ ccgFn := g.gasHelpers["chargeVerkleCodeChunkGas"]
+ if ccgFn == nil {
+ fatalf("no chargeVerkleCodeChunkGas gas helper to inline")
+ }
+ g.p("%s", g.rewriteGasReturns(g.renderAst(ccgFn.Body.List)))
+
+ // fetch the opcode and open the dispatch switch
+ g.p(`
+ op := contract.GetOp(pc)
+ switch op {
+ `)
+
+ // one case per inlined or direct-call opcode, in opcode order
+ for code := range 256 {
+ b := byte(code)
+ if inlineOps[b] {
+ g.emitInlineOp(b)
+ } else if directCallOps[b] {
+ g.emitDirectCallOp(b)
+ }
+ }
+
+ // the default case: fork-varying ops via the per-fork table
+ g.emitDefault()
+
+ // close the switch and loop, clear the stop token, and return
+ g.p(`
+ }
+ }
+ if err == errStopToken {
+ err = nil
+ }
+ return res, err
+ }
+ `)
+}
+
+func fatalf(format string, args ...any) {
+ fmt.Fprintf(os.Stderr, "gen: "+format+"\n", args...)
+ os.Exit(1)
+}
+
+// vmDir returns the core/vm directory, the parent of this generator package. It
+// is resolved from this source file's own path so it does not depend on the
+// directory the generator or the test happens to run from.
+func vmDir() string {
+ _, self, _, ok := runtime.Caller(0)
+ if !ok {
+ fatalf("cannot resolve generator source path")
+ }
+ return filepath.Dir(filepath.Dir(self)) // .../core/vm/gen -> .../core/vm
+}
+
+// generate parses the opcode, gas and fork definitions under core/vm and returns
+// the formatted contents of interpreter_gen.go. It is the shared core of the
+// generator: main writes the result to disk, and the up-to-date test in
+// gen_test.go compares it against the committed file.
+func generate() ([]byte, error) {
+ fset, opcodeHandlers, stackHelpers, gasHelpers := parseHandlers(vmDir())
+ g := &generator{fset: fset, opcodeHandlers: opcodeHandlers, stackHelpers: stackHelpers, gasHelpers: gasHelpers, buf: new(bytes.Buffer)}
+ g.deriveSpecs(vm.GenForks())
+ g.createFile()
+
+ formatted, err := format.Source(g.buf.Bytes())
+ if err != nil {
+ dbg := filepath.Join(vmDir(), "interpreter_gen.go.broken")
+ os.WriteFile(dbg, g.buf.Bytes(), 0644)
+ return nil, fmt.Errorf("gofmt failed (%v); wrote unformatted output to %s", err, dbg)
+ }
+ return formatted, nil
+}
diff --git a/core/vm/gen/gen_test.go b/core/vm/gen/gen_test.go
new file mode 100644
index 0000000000..f22c3c9db0
--- /dev/null
+++ b/core/vm/gen/gen_test.go
@@ -0,0 +1,42 @@
+// 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 main
+
+import (
+ "bytes"
+ "os"
+ "path/filepath"
+ "testing"
+)
+
+// TestGeneratedDispatchUpToDate asserts that the committed interpreter_gen.go
+// matches what the generator produces from the current opcode, gas and 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) {
+ got, err := generate()
+ if err != nil {
+ t.Fatalf("running generator: %v", err)
+ }
+ want, err := os.ReadFile(filepath.Join(vmDir(), "interpreter_gen.go"))
+ if err != nil {
+ t.Fatalf("reading committed interpreter_gen.go: %v", err)
+ }
+ if !bytes.Equal(got, want) {
+ t.Fatal("interpreter_gen.go is out of date; run `go generate ./core/vm/...` and commit the result")
+ }
+}
diff --git a/core/vm/gen/main.go b/core/vm/gen/main.go
new file mode 100644
index 0000000000..2c31a8c9d7
--- /dev/null
+++ b/core/vm/gen/main.go
@@ -0,0 +1,41 @@
+// 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/interpreter_gen.go, the EVM interpreter's untraced
+// fast-path dispatch, a switch over the opcode byte. The generated file is
+// committed and a CI test asserts it matches `go generate` output. Do not
+// hand-edit interpreter_gen.go.
+//
+// Usage: go generate ./core/vm/...
+package main
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+)
+
+func main() {
+ formatted, err := generate()
+ if err != nil {
+ fatalf("%v", err)
+ }
+ out := filepath.Join(vmDir(), "interpreter_gen.go")
+ 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..f4dba5569d
--- /dev/null
+++ b/core/vm/genspec.go
@@ -0,0 +1,162 @@
+// 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
+
+import (
+ "reflect"
+ "runtime"
+ "strings"
+
+ "github.com/ethereum/go-ethereum/params"
+)
+
+// 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 the FuncForPC names of its handler/gas/memory functions) from
+// the existing per-fork instruction sets, rather than restating that metadata.
+//
+// The function names supply the generator's opcode-to-handler mapping and its
+// fork-invariance checks. The fork-varying gas/execute functions themselves are
+// still reached through the active per-fork JumpTable at runtime (see
+// interpreter_gen.go), not emitted by name: several are closures (gasCall, the
+// memoryCopierGas family, makeGasLog) that have no callable name.
+
+// 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
+ ExecuteFn string // FuncForPC name of op.execute
+ DynamicGasFn string // FuncForPC name of op.dynamicGas, "" if nil
+ MemorySizeFn string // FuncForPC name of op.memorySize, "" if nil
+}
+
+// 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
+}
+
+// codegenSkippedForks are forks in geth's fork schedule that the generator does
+// not give a lane. Verkle/UBT is the only one: over its Shanghai base it adds no
+// opcodes, it only swaps gas and execute functions on existing ones (enable4762),
+// which the generated switch picks up from the active table at runtime. Emitting
+// a lane for it would trip the generator's fork-stability check (an inlined op's
+// execute function is not allowed to vary by fork).
+var codegenSkippedForks = map[string]bool{"IsUBT": true}
+
+// genFnName returns the FuncForPC name of a jump-table function value with the
+// package path stripped (e.g. "gasKeccak256"), or "" if nil. An aliased var
+// resolves to the underlying function (gasMLoad reports "pureMemoryGascost").
+// A closure keeps its enclosing chain (DUP7's handler reports
+// "newFrontierInstructionSet.makeDup.func37"), so the generator can tell which
+// factory built it and unrelated closures cannot collide on a bare "funcN".
+func genFnName(fn any) string {
+ v := reflect.ValueOf(fn)
+ if !v.IsValid() || v.IsNil() {
+ return ""
+ }
+ full := runtime.FuncForPC(v.Pointer()).Name()
+ if i := strings.LastIndex(full, "/"); i >= 0 {
+ full = full[i+1:] // strip the package path, leaving "vm."
+ }
+ if i := strings.Index(full, "."); i >= 0 {
+ full = full[i+1:] // strip the package name
+ }
+ return full
+}
+
+// GenForks returns per-fork opcode metadata for the interpreter code generator
+// (core/vm/gen), one entry per fork that changes the opcode table, oldest to
+// newest. It derives the progression from params.Rules and LookupInstructionSet
+// (in params.Rules declaration order, which is chronological) so new forks are
+// picked up without restating a list here.
+func GenForks() []GenFork {
+ // Frontier is always active and carries no rule gate.
+ frontier, _ := LookupInstructionSet(params.Rules{})
+ out := []GenFork{genFork("Frontier", "", &frontier)}
+
+ rt := reflect.TypeOf(params.Rules{})
+ for i := range rt.NumField() {
+ field := rt.Field(i)
+ if field.Type.Kind() != reflect.Bool || codegenSkippedForks[field.Name] {
+ continue
+ }
+ // Activate only this field so the fork resolves to the table it gates.
+ var rules params.Rules
+ reflect.ValueOf(&rules).Elem().Field(i).SetBool(true)
+ set, _ := LookupInstructionSet(rules)
+ if sameOps(&set, &frontier) {
+ continue // this rule does not change the opcode table
+ }
+ out = append(out, genFork(strings.TrimPrefix(field.Name, "Is"), field.Name, &set))
+ }
+ return out
+}
+
+// genFork extracts the generator-facing per-opcode metadata from one fork's
+// instruction set.
+func genFork(name, rule string, set *JumpTable) GenFork {
+ gf := GenFork{Name: name, RuleField: rule}
+ for code := range 256 {
+ op := 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,
+ ExecuteFn: genFnName(op.execute),
+ DynamicGasFn: genFnName(op.dynamicGas),
+ MemorySizeFn: genFnName(op.memorySize),
+ }
+ }
+ return gf
+}
+
+// sameOps reports whether two instruction sets carry identical per-opcode static
+// metadata: which slots are defined, and their static gas and stack bounds. It
+// is used to tell whether a fork actually changes the opcode table. Handler,
+// dynamic-gas and memory-size functions are ignored (they cannot be compared for
+// equality and are reached through the table at runtime).
+func sameOps(a, b *JumpTable) bool {
+ for code := range 256 {
+ oa, ob := a[code], b[code]
+ undefA := oa == nil || oa.undefined
+ undefB := ob == nil || ob.undefined
+ if undefA != undefB {
+ return false
+ }
+ if undefA {
+ continue
+ }
+ if oa.constantGas != ob.constantGas || oa.minStack != ob.minStack || oa.maxStack != ob.maxStack {
+ return false
+ }
+ }
+ return true
+}
diff --git a/core/vm/instructions.go b/core/vm/instructions.go
index 2cc8defb84..7b71a06a1d 100644
--- a/core/vm/instructions.go
+++ b/core/vm/instructions.go
@@ -536,12 +536,14 @@ func opJumpdest(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
}
func opPc(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
- scope.Stack.get().SetUint64(*pc)
+ elem := scope.Stack.get()
+ elem.SetUint64(*pc)
return nil, nil
}
func opMsize(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
- scope.Stack.get().SetUint64(uint64(scope.Memory.Len()))
+ elem := scope.Stack.get()
+ elem.SetUint64(uint64(scope.Memory.Len()))
return nil, nil
}
@@ -1131,10 +1133,8 @@ func makeLog(size int) executionFunc {
// opPush1 is a specialized version of pushN
func opPush1(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
- var (
- codeLen = uint64(len(scope.Contract.Code))
- elem = scope.Stack.get()
- )
+ codeLen := uint64(len(scope.Contract.Code))
+ elem := scope.Stack.get()
*pc += 1
if *pc < codeLen {
elem.SetUint64(uint64(scope.Contract.Code[*pc]))
@@ -1146,10 +1146,8 @@ func opPush1(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
// opPush2 is a specialized version of pushN
func opPush2(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
- var (
- codeLen = uint64(len(scope.Contract.Code))
- elem = scope.Stack.get()
- )
+ 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 {
diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go
index c2dfe3769c..c8039f605d 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 resolves 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 {
@@ -168,16 +193,8 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte
// Capture pre-execution values for tracing.
logged, pcCopy, gasCopy = false, pc, contract.Gas.RegularGas
}
-
- if isEIP4762 && !contract.IsDeployment && !contract.IsSystemCall {
- // if the PC ends up in a new "chunk" of verkleized code, charge the
- // associated costs.
- contractAddr := contract.Address()
- consumed, wanted := evm.TxContext.AccessEvents.CodeChunksRangeGas(contractAddr, pc, 1, uint64(len(contract.Code)), false, contract.Gas.RegularGas)
- contract.chargeRegular(consumed, evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk)
- if consumed < wanted {
- return nil, ErrOutOfGas
- }
+ if err = contract.chargeVerkleCodeChunkGas(evm, pc, isEIP4762); err != nil {
+ return nil, err
}
// Get the operation from the jump table and validate the stack to ensure there are
@@ -192,43 +209,19 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte
return nil, &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack}
}
// for tracing: this gas consumption event is emitted below in the debug section.
- if !contract.Gas.ChargeRegularOnly(cost) {
- return nil, ErrOutOfGas
+ if err := contract.Gas.ChargeRegularOnly(cost); err != nil {
+ return nil, err
}
- // All ops with a dynamic memory usage also has a dynamic gas cost.
+ // All ops with a dynamic memory usage also has a dynamic gas cost. Size the
+ // memory and charge dynamic gas here.
var memorySize uint64
- if operation.dynamicGas != nil {
- // calculate the new memory size and expand the memory to fit
- // the operation
- // Memory check needs to be done prior to evaluating the dynamic gas portion,
- // to detect calculation overflows
- if operation.memorySize != nil {
- memSize, overflow := operation.memorySize(stack)
- if overflow {
- return nil, ErrGasUintOverflow
- }
- // memory is expanded in words of 32 bytes. Gas
- // is also calculated in words.
- if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow {
- return nil, ErrGasUintOverflow
- }
- }
- // Consume the gas and return an error if not enough gas is available.
- // cost is explicitly set so that the capture state defer method can get the proper cost
- var dynamicCost GasCosts
- dynamicCost, err = operation.dynamicGas(evm, contract, stack, mem, memorySize)
- cost += dynamicCost.RegularGas // for tracing
- if err != nil {
- return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err)
- }
- if dynamicCost.StateGas == 0 {
- if !contract.Gas.ChargeRegularOnly(dynamicCost.RegularGas) {
- return nil, ErrOutOfGas
- }
- } else if !contract.Gas.charge(dynamicCost) {
- return nil, ErrOutOfGas
- }
+ var dynamicCost GasCosts
+ memorySize, dynamicCost, err = contract.meterDynamicGas(operation, evm, stack, mem)
+ // cost is explicitly set so that the capture state defer method can get the proper cost
+ cost += dynamicCost.RegularGas // for tracing
+ if err != nil {
+ return nil, err
}
// Do tracing before potential memory expansion
@@ -263,3 +256,79 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte
return res, err
}
+
+// computeMemorySize runs an operation's memorySize function and word-aligns the
+// result, guarding overflow. The traced loop and the default case call it, and the
+// direct-call ops splice it, substituting the opcode's memory-size function for
+// operation.memorySize.
+func computeMemorySize(operation *operation, stack *Stack) (uint64, error) {
+ memSize, overflow := operation.memorySize(stack)
+ if overflow {
+ return 0, ErrGasUintOverflow
+ }
+ // memory is expanded in words of 32 bytes. Gas is also calculated in words.
+ size, overflow := math.SafeMul(toWordSize(memSize), 32)
+ if overflow {
+ return 0, ErrGasUintOverflow
+ }
+ return size, nil
+}
+
+// chargeDynamicGas runs an operation's dynamicGas function, treats a computation
+// failure as out of gas, and charges the cost. It returns the cost so the traced
+// loop can report it. The traced loop and the default case call it, and the
+// direct-call ops splice it, substituting the opcode's gas function for
+// operation.dynamicGas.
+func (contract *Contract) chargeDynamicGas(operation *operation, evm *EVM, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
+ dynamicCost, gerr := operation.dynamicGas(evm, contract, stack, mem, memorySize)
+ if gerr != nil {
+ return dynamicCost, fmt.Errorf("%w: %v", ErrOutOfGas, gerr)
+ }
+ // A regular-only deduction when there is no state gas, otherwise the full
+ // multidimensional charge through the reservoir.
+ if dynamicCost.StateGas == 0 {
+ if cerr := contract.Gas.ChargeRegularOnly(dynamicCost.RegularGas); cerr != nil {
+ return dynamicCost, cerr
+ }
+ } else if !contract.Gas.charge(dynamicCost) {
+ return dynamicCost, ErrOutOfGas
+ }
+ return dynamicCost, nil
+}
+
+// meterDynamicGas sizes the memory an operation needs and charges its dynamic
+// gas, returning the size to expand to and the charged cost (which the traced
+// loop reports). The caller expands the memory afterward. computeMemorySize and
+// chargeDynamicGas do the work, guarded here for the generic table paths. The
+// direct-call ops splice those two by name and skip the guards.
+func (contract *Contract) meterDynamicGas(operation *operation, evm *EVM, stack *Stack, mem *Memory) (memorySize uint64, dynamicCost GasCosts, err error) {
+ if operation.dynamicGas != nil {
+ if operation.memorySize != nil {
+ if memorySize, err = computeMemorySize(operation, stack); err != nil {
+ return memorySize, dynamicCost, err
+ }
+ }
+ if dynamicCost, err = contract.chargeDynamicGas(operation, evm, stack, mem, memorySize); err != nil {
+ return memorySize, dynamicCost, err
+ }
+ }
+ return memorySize, dynamicCost, nil
+}
+
+// chargeVerkleCodeChunkGas charges EIP-4762 (verkle) witness gas for the code chunk the
+// pc sits in. It is a no-op outside verkle and for deployment or system calls.
+// isEIP4762 is passed in so the caller can hoist evm.chainRules.IsEIP4762 out of
+// the loop. The traced loop calls it, the generated loop splices its body.
+func (contract *Contract) chargeVerkleCodeChunkGas(evm *EVM, pc uint64, isEIP4762 bool) error {
+ // if the PC ends up in a new "chunk" of verkleized code, charge the
+ // associated costs.
+ 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.chargeRegular(consumed, evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk)
+ if consumed < wanted {
+ return ErrOutOfGas
+ }
+ }
+ return nil
+}
diff --git a/core/vm/interpreter_gen.go b/core/vm/interpreter_gen.go
new file mode 100644
index 0000000000..a8d075a5b8
--- /dev/null
+++ b/core/vm/interpreter_gen.go
@@ -0,0 +1,2596 @@
+// 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 emitted
+// as constants. Fork-invariant 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.chargeRegular(consumed, evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk)
+ if consumed < wanted {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ }
+
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ stack.inner.top--
+ stack.size--
+ x := &stack.inner.data[stack.inner.top]
+ y := &stack.inner.data[stack.inner.top-1]
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 5
+ contract.Gas.UsedRegularGas += 5
+
+ stack.inner.top--
+ stack.size--
+ x := &stack.inner.data[stack.inner.top]
+ y := &stack.inner.data[stack.inner.top-1]
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ stack.inner.top--
+ stack.size--
+ x := &stack.inner.data[stack.inner.top]
+ y := &stack.inner.data[stack.inner.top-1]
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 5
+ contract.Gas.UsedRegularGas += 5
+
+ stack.inner.top--
+ stack.size--
+ x := &stack.inner.data[stack.inner.top]
+ y := &stack.inner.data[stack.inner.top-1]
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 5
+ contract.Gas.UsedRegularGas += 5
+
+ stack.inner.top--
+ stack.size--
+ x := &stack.inner.data[stack.inner.top]
+ y := &stack.inner.data[stack.inner.top-1]
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 5
+ contract.Gas.UsedRegularGas += 5
+
+ stack.inner.top--
+ stack.size--
+ x := &stack.inner.data[stack.inner.top]
+ y := &stack.inner.data[stack.inner.top-1]
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 5
+ contract.Gas.UsedRegularGas += 5
+
+ stack.inner.top--
+ stack.size--
+ x := &stack.inner.data[stack.inner.top]
+ y := &stack.inner.data[stack.inner.top-1]
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 8
+ contract.Gas.UsedRegularGas += 8
+
+ stack.inner.top -= 2
+ stack.size -= 2
+ x := &stack.inner.data[stack.inner.top+1]
+ y := &stack.inner.data[stack.inner.top]
+ z := &stack.inner.data[stack.inner.top-1]
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 8
+ contract.Gas.UsedRegularGas += 8
+
+ stack.inner.top -= 2
+ stack.size -= 2
+ x := &stack.inner.data[stack.inner.top+1]
+ y := &stack.inner.data[stack.inner.top]
+ z := &stack.inner.data[stack.inner.top-1]
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 5
+ contract.Gas.UsedRegularGas += 5
+
+ stack.inner.top--
+ stack.size--
+ back := &stack.inner.data[stack.inner.top]
+ num := &stack.inner.data[stack.inner.top-1]
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ stack.inner.top--
+ stack.size--
+ x := &stack.inner.data[stack.inner.top]
+ y := &stack.inner.data[stack.inner.top-1]
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ stack.inner.top--
+ stack.size--
+ x := &stack.inner.data[stack.inner.top]
+ y := &stack.inner.data[stack.inner.top-1]
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ stack.inner.top--
+ stack.size--
+ x := &stack.inner.data[stack.inner.top]
+ y := &stack.inner.data[stack.inner.top-1]
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ stack.inner.top--
+ stack.size--
+ x := &stack.inner.data[stack.inner.top]
+ y := &stack.inner.data[stack.inner.top-1]
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ stack.inner.top--
+ stack.size--
+ x := &stack.inner.data[stack.inner.top]
+ y := &stack.inner.data[stack.inner.top-1]
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ stack.inner.top--
+ stack.size--
+ x := &stack.inner.data[stack.inner.top]
+ y := &stack.inner.data[stack.inner.top-1]
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ stack.inner.top--
+ stack.size--
+ x := &stack.inner.data[stack.inner.top]
+ y := &stack.inner.data[stack.inner.top-1]
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ stack.inner.top--
+ stack.size--
+ x := &stack.inner.data[stack.inner.top]
+ y := &stack.inner.data[stack.inner.top-1]
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ stack.inner.top--
+ stack.size--
+ th := &stack.inner.data[stack.inner.top]
+ val := &stack.inner.data[stack.inner.top-1]
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ stack.inner.top--
+ stack.size--
+ shift := &stack.inner.data[stack.inner.top]
+ value := &stack.inner.data[stack.inner.top-1]
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ stack.inner.top--
+ stack.size--
+ shift := &stack.inner.data[stack.inner.top]
+ value := &stack.inner.data[stack.inner.top-1]
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ stack.inner.top--
+ stack.size--
+ shift := &stack.inner.data[stack.inner.top]
+ value := &stack.inner.data[stack.inner.top-1]
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 5
+ contract.Gas.UsedRegularGas += 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 30
+ contract.Gas.UsedRegularGas += 30
+
+ var memorySize uint64
+ memSize, overflow := memoryKeccak256(stack)
+ if overflow {
+ res, err = nil, ErrGasUintOverflow
+ break mainLoop
+ }
+ size, overflow := math.SafeMul(toWordSize(memSize), 32)
+ if overflow {
+ res, err = nil, ErrGasUintOverflow
+ break mainLoop
+ }
+ memorySize = size
+
+ dynamicCost, gerr := gasKeccak256(evm, contract, stack, mem, memorySize)
+ if gerr != nil {
+ res, err = nil, fmt.Errorf("%w: %v", ErrOutOfGas, gerr)
+ break mainLoop
+ }
+ if dynamicCost.StateGas == 0 {
+ if cerr := contract.Gas.ChargeRegularOnly(dynamicCost.RegularGas); cerr != nil {
+ res, err = nil, cerr
+ break mainLoop
+ }
+ } else if !contract.Gas.charge(dynamicCost) {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 2
+ contract.Gas.UsedRegularGas += 2
+
+ scope.Stack.drop()
+ pc++
+ continue mainLoop
+
+ case MLOAD:
+ if sLen := stack.len(); sLen < 1 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 1}
+ }
+ if contract.Gas.RegularGas < 3 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ var memorySize uint64
+ memSize, overflow := memoryMLoad(stack)
+ if overflow {
+ res, err = nil, ErrGasUintOverflow
+ break mainLoop
+ }
+ size, overflow := math.SafeMul(toWordSize(memSize), 32)
+ if overflow {
+ res, err = nil, ErrGasUintOverflow
+ break mainLoop
+ }
+ memorySize = size
+
+ dynamicCost, gerr := pureMemoryGascost(evm, contract, stack, mem, memorySize)
+ if gerr != nil {
+ res, err = nil, fmt.Errorf("%w: %v", ErrOutOfGas, gerr)
+ break mainLoop
+ }
+ if dynamicCost.StateGas == 0 {
+ if cerr := contract.Gas.ChargeRegularOnly(dynamicCost.RegularGas); cerr != nil {
+ res, err = nil, cerr
+ break mainLoop
+ }
+ } else if !contract.Gas.charge(dynamicCost) {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ var memorySize uint64
+ memSize, overflow := memoryMStore(stack)
+ if overflow {
+ res, err = nil, ErrGasUintOverflow
+ break mainLoop
+ }
+ size, overflow := math.SafeMul(toWordSize(memSize), 32)
+ if overflow {
+ res, err = nil, ErrGasUintOverflow
+ break mainLoop
+ }
+ memorySize = size
+
+ dynamicCost, gerr := pureMemoryGascost(evm, contract, stack, mem, memorySize)
+ if gerr != nil {
+ res, err = nil, fmt.Errorf("%w: %v", ErrOutOfGas, gerr)
+ break mainLoop
+ }
+ if dynamicCost.StateGas == 0 {
+ if cerr := contract.Gas.ChargeRegularOnly(dynamicCost.RegularGas); cerr != nil {
+ res, err = nil, cerr
+ break mainLoop
+ }
+ } else if !contract.Gas.charge(dynamicCost) {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ var memorySize uint64
+ memSize, overflow := memoryMStore8(stack)
+ if overflow {
+ res, err = nil, ErrGasUintOverflow
+ break mainLoop
+ }
+ size, overflow := math.SafeMul(toWordSize(memSize), 32)
+ if overflow {
+ res, err = nil, ErrGasUintOverflow
+ break mainLoop
+ }
+ memorySize = size
+
+ dynamicCost, gerr := pureMemoryGascost(evm, contract, stack, mem, memorySize)
+ if gerr != nil {
+ res, err = nil, fmt.Errorf("%w: %v", ErrOutOfGas, gerr)
+ break mainLoop
+ }
+ if dynamicCost.StateGas == 0 {
+ if cerr := contract.Gas.ChargeRegularOnly(dynamicCost.RegularGas); cerr != nil {
+ res, err = nil, cerr
+ break mainLoop
+ }
+ } else if !contract.Gas.charge(dynamicCost) {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 8
+ contract.Gas.UsedRegularGas += 8
+
+ if evm.abort.Load() {
+ res, err = nil, errStopToken
+ break mainLoop
+ }
+ pos := scope.Stack.pop1()
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 10
+ contract.Gas.UsedRegularGas += 10
+
+ if evm.abort.Load() {
+ res, err = nil, errStopToken
+ break mainLoop
+ }
+ stack.inner.top -= 2
+ stack.size -= 2
+ pos := &stack.inner.data[stack.inner.top+1]
+ cond := &stack.inner.data[stack.inner.top]
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 2
+ contract.Gas.UsedRegularGas += 2
+
+ stack.inner.top++
+ stack.size++
+ elem := &stack.inner.data[stack.inner.top-1]
+ elem.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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 2
+ contract.Gas.UsedRegularGas += 2
+
+ stack.inner.top++
+ stack.size++
+ elem := &stack.inner.data[stack.inner.top-1]
+ elem.SetUint64(uint64(scope.Memory.Len()))
+ pc++
+ continue mainLoop
+
+ case JUMPDEST:
+ if contract.Gas.RegularGas < 1 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 1
+ contract.Gas.UsedRegularGas += 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 2
+ contract.Gas.UsedRegularGas += 2
+
+ stack.inner.top++
+ stack.size++
+ elem := &stack.inner.data[stack.inner.top-1]
+ elem.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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ codeLen := uint64(len(scope.Contract.Code))
+ stack.inner.top++
+ stack.size++
+ elem := &stack.inner.data[stack.inner.top-1]
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ codeLen := uint64(len(scope.Contract.Code))
+ stack.inner.top++
+ stack.size++
+ elem := &stack.inner.data[stack.inner.top-1]
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ var (
+ codeLen = len(scope.Contract.Code)
+ start = min(codeLen, int(pc+1))
+ end = min(codeLen, start+3)
+ )
+ stack.inner.top++
+ stack.size++
+ a := &stack.inner.data[stack.inner.top-1]
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ var (
+ codeLen = len(scope.Contract.Code)
+ start = min(codeLen, int(pc+1))
+ end = min(codeLen, start+4)
+ )
+ stack.inner.top++
+ stack.size++
+ a := &stack.inner.data[stack.inner.top-1]
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ var (
+ codeLen = len(scope.Contract.Code)
+ start = min(codeLen, int(pc+1))
+ end = min(codeLen, start+5)
+ )
+ stack.inner.top++
+ stack.size++
+ a := &stack.inner.data[stack.inner.top-1]
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ var (
+ codeLen = len(scope.Contract.Code)
+ start = min(codeLen, int(pc+1))
+ end = min(codeLen, start+6)
+ )
+ stack.inner.top++
+ stack.size++
+ a := &stack.inner.data[stack.inner.top-1]
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ var (
+ codeLen = len(scope.Contract.Code)
+ start = min(codeLen, int(pc+1))
+ end = min(codeLen, start+7)
+ )
+ stack.inner.top++
+ stack.size++
+ a := &stack.inner.data[stack.inner.top-1]
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ var (
+ codeLen = len(scope.Contract.Code)
+ start = min(codeLen, int(pc+1))
+ end = min(codeLen, start+8)
+ )
+ stack.inner.top++
+ stack.size++
+ a := &stack.inner.data[stack.inner.top-1]
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ var (
+ codeLen = len(scope.Contract.Code)
+ start = min(codeLen, int(pc+1))
+ end = min(codeLen, start+9)
+ )
+ stack.inner.top++
+ stack.size++
+ a := &stack.inner.data[stack.inner.top-1]
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ var (
+ codeLen = len(scope.Contract.Code)
+ start = min(codeLen, int(pc+1))
+ end = min(codeLen, start+10)
+ )
+ stack.inner.top++
+ stack.size++
+ a := &stack.inner.data[stack.inner.top-1]
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ var (
+ codeLen = len(scope.Contract.Code)
+ start = min(codeLen, int(pc+1))
+ end = min(codeLen, start+11)
+ )
+ stack.inner.top++
+ stack.size++
+ a := &stack.inner.data[stack.inner.top-1]
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ var (
+ codeLen = len(scope.Contract.Code)
+ start = min(codeLen, int(pc+1))
+ end = min(codeLen, start+12)
+ )
+ stack.inner.top++
+ stack.size++
+ a := &stack.inner.data[stack.inner.top-1]
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ var (
+ codeLen = len(scope.Contract.Code)
+ start = min(codeLen, int(pc+1))
+ end = min(codeLen, start+13)
+ )
+ stack.inner.top++
+ stack.size++
+ a := &stack.inner.data[stack.inner.top-1]
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ var (
+ codeLen = len(scope.Contract.Code)
+ start = min(codeLen, int(pc+1))
+ end = min(codeLen, start+14)
+ )
+ stack.inner.top++
+ stack.size++
+ a := &stack.inner.data[stack.inner.top-1]
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ var (
+ codeLen = len(scope.Contract.Code)
+ start = min(codeLen, int(pc+1))
+ end = min(codeLen, start+15)
+ )
+ stack.inner.top++
+ stack.size++
+ a := &stack.inner.data[stack.inner.top-1]
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ var (
+ codeLen = len(scope.Contract.Code)
+ start = min(codeLen, int(pc+1))
+ end = min(codeLen, start+16)
+ )
+ stack.inner.top++
+ stack.size++
+ a := &stack.inner.data[stack.inner.top-1]
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ var (
+ codeLen = len(scope.Contract.Code)
+ start = min(codeLen, int(pc+1))
+ end = min(codeLen, start+17)
+ )
+ stack.inner.top++
+ stack.size++
+ a := &stack.inner.data[stack.inner.top-1]
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ var (
+ codeLen = len(scope.Contract.Code)
+ start = min(codeLen, int(pc+1))
+ end = min(codeLen, start+18)
+ )
+ stack.inner.top++
+ stack.size++
+ a := &stack.inner.data[stack.inner.top-1]
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ var (
+ codeLen = len(scope.Contract.Code)
+ start = min(codeLen, int(pc+1))
+ end = min(codeLen, start+19)
+ )
+ stack.inner.top++
+ stack.size++
+ a := &stack.inner.data[stack.inner.top-1]
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ var (
+ codeLen = len(scope.Contract.Code)
+ start = min(codeLen, int(pc+1))
+ end = min(codeLen, start+20)
+ )
+ stack.inner.top++
+ stack.size++
+ a := &stack.inner.data[stack.inner.top-1]
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ var (
+ codeLen = len(scope.Contract.Code)
+ start = min(codeLen, int(pc+1))
+ end = min(codeLen, start+21)
+ )
+ stack.inner.top++
+ stack.size++
+ a := &stack.inner.data[stack.inner.top-1]
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ var (
+ codeLen = len(scope.Contract.Code)
+ start = min(codeLen, int(pc+1))
+ end = min(codeLen, start+22)
+ )
+ stack.inner.top++
+ stack.size++
+ a := &stack.inner.data[stack.inner.top-1]
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ var (
+ codeLen = len(scope.Contract.Code)
+ start = min(codeLen, int(pc+1))
+ end = min(codeLen, start+23)
+ )
+ stack.inner.top++
+ stack.size++
+ a := &stack.inner.data[stack.inner.top-1]
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ var (
+ codeLen = len(scope.Contract.Code)
+ start = min(codeLen, int(pc+1))
+ end = min(codeLen, start+24)
+ )
+ stack.inner.top++
+ stack.size++
+ a := &stack.inner.data[stack.inner.top-1]
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ var (
+ codeLen = len(scope.Contract.Code)
+ start = min(codeLen, int(pc+1))
+ end = min(codeLen, start+25)
+ )
+ stack.inner.top++
+ stack.size++
+ a := &stack.inner.data[stack.inner.top-1]
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ var (
+ codeLen = len(scope.Contract.Code)
+ start = min(codeLen, int(pc+1))
+ end = min(codeLen, start+26)
+ )
+ stack.inner.top++
+ stack.size++
+ a := &stack.inner.data[stack.inner.top-1]
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ var (
+ codeLen = len(scope.Contract.Code)
+ start = min(codeLen, int(pc+1))
+ end = min(codeLen, start+27)
+ )
+ stack.inner.top++
+ stack.size++
+ a := &stack.inner.data[stack.inner.top-1]
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ var (
+ codeLen = len(scope.Contract.Code)
+ start = min(codeLen, int(pc+1))
+ end = min(codeLen, start+28)
+ )
+ stack.inner.top++
+ stack.size++
+ a := &stack.inner.data[stack.inner.top-1]
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ var (
+ codeLen = len(scope.Contract.Code)
+ start = min(codeLen, int(pc+1))
+ end = min(codeLen, start+29)
+ )
+ stack.inner.top++
+ stack.size++
+ a := &stack.inner.data[stack.inner.top-1]
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ var (
+ codeLen = len(scope.Contract.Code)
+ start = min(codeLen, int(pc+1))
+ end = min(codeLen, start+30)
+ )
+ stack.inner.top++
+ stack.size++
+ a := &stack.inner.data[stack.inner.top-1]
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ var (
+ codeLen = len(scope.Contract.Code)
+ start = min(codeLen, int(pc+1))
+ end = min(codeLen, start+31)
+ )
+ stack.inner.top++
+ stack.size++
+ a := &stack.inner.data[stack.inner.top-1]
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ if isEIP4762 {
+ res, err = table[op].execute(&pc, evm, scope)
+ if err != nil {
+ break mainLoop
+ }
+ pc++
+ continue mainLoop
+ }
+ var (
+ codeLen = len(scope.Contract.Code)
+ start = min(codeLen, int(pc+1))
+ end = min(codeLen, start+32)
+ )
+ stack.inner.top++
+ stack.size++
+ a := &stack.inner.data[stack.inner.top-1]
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ stack.inner.data[stack.bottom+stack.size] = stack.inner.data[stack.bottom+stack.size-1]
+ stack.size++
+ stack.inner.top++
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ stack.inner.data[stack.bottom+stack.size] = stack.inner.data[stack.bottom+stack.size-2]
+ stack.size++
+ stack.inner.top++
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ stack.inner.data[stack.bottom+stack.size] = stack.inner.data[stack.bottom+stack.size-3]
+ stack.size++
+ stack.inner.top++
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ stack.inner.data[stack.bottom+stack.size] = stack.inner.data[stack.bottom+stack.size-4]
+ stack.size++
+ stack.inner.top++
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ stack.inner.data[stack.bottom+stack.size] = stack.inner.data[stack.bottom+stack.size-5]
+ stack.size++
+ stack.inner.top++
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ stack.inner.data[stack.bottom+stack.size] = stack.inner.data[stack.bottom+stack.size-6]
+ stack.size++
+ stack.inner.top++
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ stack.inner.data[stack.bottom+stack.size] = stack.inner.data[stack.bottom+stack.size-7]
+ stack.size++
+ stack.inner.top++
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ stack.inner.data[stack.bottom+stack.size] = stack.inner.data[stack.bottom+stack.size-8]
+ stack.size++
+ stack.inner.top++
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ stack.inner.data[stack.bottom+stack.size] = stack.inner.data[stack.bottom+stack.size-9]
+ stack.size++
+ stack.inner.top++
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ stack.inner.data[stack.bottom+stack.size] = stack.inner.data[stack.bottom+stack.size-10]
+ stack.size++
+ stack.inner.top++
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ stack.inner.data[stack.bottom+stack.size] = stack.inner.data[stack.bottom+stack.size-11]
+ stack.size++
+ stack.inner.top++
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ stack.inner.data[stack.bottom+stack.size] = stack.inner.data[stack.bottom+stack.size-12]
+ stack.size++
+ stack.inner.top++
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ stack.inner.data[stack.bottom+stack.size] = stack.inner.data[stack.bottom+stack.size-13]
+ stack.size++
+ stack.inner.top++
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ stack.inner.data[stack.bottom+stack.size] = stack.inner.data[stack.bottom+stack.size-14]
+ stack.size++
+ stack.inner.top++
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ stack.inner.data[stack.bottom+stack.size] = stack.inner.data[stack.bottom+stack.size-15]
+ stack.size++
+ stack.inner.top++
+ 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 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ stack.inner.data[stack.bottom+stack.size] = stack.inner.data[stack.bottom+stack.size-16]
+ stack.size++
+ stack.inner.top++
+ pc++
+ continue mainLoop
+
+ case SWAP1:
+ if sLen := stack.len(); sLen < 2 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 2}
+ }
+ if contract.Gas.RegularGas < 3 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ stack.inner.data[stack.bottom+stack.size-2], stack.inner.data[stack.bottom+stack.size-1] = stack.inner.data[stack.bottom+stack.size-1], stack.inner.data[stack.bottom+stack.size-2]
+ pc++
+ continue mainLoop
+
+ case SWAP2:
+ if sLen := stack.len(); sLen < 3 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 3}
+ }
+ if contract.Gas.RegularGas < 3 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ stack.inner.data[stack.bottom+stack.size-3], stack.inner.data[stack.bottom+stack.size-1] = stack.inner.data[stack.bottom+stack.size-1], stack.inner.data[stack.bottom+stack.size-3]
+ pc++
+ continue mainLoop
+
+ case SWAP3:
+ if sLen := stack.len(); sLen < 4 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 4}
+ }
+ if contract.Gas.RegularGas < 3 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ stack.inner.data[stack.bottom+stack.size-4], stack.inner.data[stack.bottom+stack.size-1] = stack.inner.data[stack.bottom+stack.size-1], stack.inner.data[stack.bottom+stack.size-4]
+ pc++
+ continue mainLoop
+
+ case SWAP4:
+ if sLen := stack.len(); sLen < 5 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 5}
+ }
+ if contract.Gas.RegularGas < 3 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ stack.inner.data[stack.bottom+stack.size-5], stack.inner.data[stack.bottom+stack.size-1] = stack.inner.data[stack.bottom+stack.size-1], stack.inner.data[stack.bottom+stack.size-5]
+ pc++
+ continue mainLoop
+
+ case SWAP5:
+ if sLen := stack.len(); sLen < 6 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 6}
+ }
+ if contract.Gas.RegularGas < 3 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ stack.inner.data[stack.bottom+stack.size-6], stack.inner.data[stack.bottom+stack.size-1] = stack.inner.data[stack.bottom+stack.size-1], stack.inner.data[stack.bottom+stack.size-6]
+ pc++
+ continue mainLoop
+
+ case SWAP6:
+ if sLen := stack.len(); sLen < 7 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 7}
+ }
+ if contract.Gas.RegularGas < 3 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ stack.inner.data[stack.bottom+stack.size-7], stack.inner.data[stack.bottom+stack.size-1] = stack.inner.data[stack.bottom+stack.size-1], stack.inner.data[stack.bottom+stack.size-7]
+ pc++
+ continue mainLoop
+
+ case SWAP7:
+ if sLen := stack.len(); sLen < 8 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 8}
+ }
+ if contract.Gas.RegularGas < 3 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ stack.inner.data[stack.bottom+stack.size-8], stack.inner.data[stack.bottom+stack.size-1] = stack.inner.data[stack.bottom+stack.size-1], stack.inner.data[stack.bottom+stack.size-8]
+ pc++
+ continue mainLoop
+
+ case SWAP8:
+ if sLen := stack.len(); sLen < 9 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 9}
+ }
+ if contract.Gas.RegularGas < 3 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ stack.inner.data[stack.bottom+stack.size-9], stack.inner.data[stack.bottom+stack.size-1] = stack.inner.data[stack.bottom+stack.size-1], stack.inner.data[stack.bottom+stack.size-9]
+ pc++
+ continue mainLoop
+
+ case SWAP9:
+ if sLen := stack.len(); sLen < 10 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 10}
+ }
+ if contract.Gas.RegularGas < 3 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ stack.inner.data[stack.bottom+stack.size-10], stack.inner.data[stack.bottom+stack.size-1] = stack.inner.data[stack.bottom+stack.size-1], stack.inner.data[stack.bottom+stack.size-10]
+ pc++
+ continue mainLoop
+
+ case SWAP10:
+ if sLen := stack.len(); sLen < 11 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 11}
+ }
+ if contract.Gas.RegularGas < 3 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ stack.inner.data[stack.bottom+stack.size-11], stack.inner.data[stack.bottom+stack.size-1] = stack.inner.data[stack.bottom+stack.size-1], stack.inner.data[stack.bottom+stack.size-11]
+ pc++
+ continue mainLoop
+
+ case SWAP11:
+ if sLen := stack.len(); sLen < 12 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 12}
+ }
+ if contract.Gas.RegularGas < 3 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ stack.inner.data[stack.bottom+stack.size-12], stack.inner.data[stack.bottom+stack.size-1] = stack.inner.data[stack.bottom+stack.size-1], stack.inner.data[stack.bottom+stack.size-12]
+ pc++
+ continue mainLoop
+
+ case SWAP12:
+ if sLen := stack.len(); sLen < 13 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 13}
+ }
+ if contract.Gas.RegularGas < 3 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ stack.inner.data[stack.bottom+stack.size-13], stack.inner.data[stack.bottom+stack.size-1] = stack.inner.data[stack.bottom+stack.size-1], stack.inner.data[stack.bottom+stack.size-13]
+ pc++
+ continue mainLoop
+
+ case SWAP13:
+ if sLen := stack.len(); sLen < 14 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 14}
+ }
+ if contract.Gas.RegularGas < 3 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ stack.inner.data[stack.bottom+stack.size-14], stack.inner.data[stack.bottom+stack.size-1] = stack.inner.data[stack.bottom+stack.size-1], stack.inner.data[stack.bottom+stack.size-14]
+ pc++
+ continue mainLoop
+
+ case SWAP14:
+ if sLen := stack.len(); sLen < 15 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 15}
+ }
+ if contract.Gas.RegularGas < 3 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ stack.inner.data[stack.bottom+stack.size-15], stack.inner.data[stack.bottom+stack.size-1] = stack.inner.data[stack.bottom+stack.size-1], stack.inner.data[stack.bottom+stack.size-15]
+ pc++
+ continue mainLoop
+
+ case SWAP15:
+ if sLen := stack.len(); sLen < 16 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 16}
+ }
+ if contract.Gas.RegularGas < 3 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ stack.inner.data[stack.bottom+stack.size-16], stack.inner.data[stack.bottom+stack.size-1] = stack.inner.data[stack.bottom+stack.size-1], stack.inner.data[stack.bottom+stack.size-16]
+ pc++
+ continue mainLoop
+
+ case SWAP16:
+ if sLen := stack.len(); sLen < 17 {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: 17}
+ }
+ if contract.Gas.RegularGas < 3 {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= 3
+ contract.Gas.UsedRegularGas += 3
+
+ stack.inner.data[stack.bottom+stack.size-17], stack.inner.data[stack.bottom+stack.size-1] = stack.inner.data[stack.bottom+stack.size-1], stack.inner.data[stack.bottom+stack.size-17]
+ 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}
+ }
+ if contract.Gas.RegularGas < operation.constantGas {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
+ }
+ contract.Gas.RegularGas -= operation.constantGas
+ contract.Gas.UsedRegularGas += operation.constantGas
+
+ var memorySize uint64
+ if memorySize, _, err = contract.meterDynamicGas(operation, evm, stack, mem); err != nil {
+ return nil, err
+ }
+ 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/interpreter_gen_test.go b/core/vm/interpreter_gen_test.go
new file mode 100644
index 0000000000..b01bba1cd1
--- /dev/null
+++ b/core/vm/interpreter_gen_test.go
@@ -0,0 +1,515 @@
+// 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
+
+// Tests for the generated interpreter dispatch (interpreter_gen.go): that it
+// behaves identically to the table loop, and that the fast path keeps its cheap
+// stack helpers inlined. The check that the committed file matches the generator
+// output lives with the generator, in core/vm/gen.
+
+import (
+ "bytes"
+ "go/ast"
+ "go/parser"
+ "go/token"
+ "math/big"
+ "os/exec"
+ "regexp"
+ "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/ethereum/go-ethereum/params"
+ "github.com/holiman/uint256"
+)
+
+// Differential test comparing the table loop against the generated dispatch.
+//
+// These tests prove 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.
+
+// diffForks is the set of fork configurations the diff test runs every program
+// under. Spanning forks exercises the generated 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
+
+ // amsterdam: Merged plus the Amsterdam (EIP-8037) timestamp, so the diff test
+ // exercises the multidimensional gas accounting (regular + state gas). Without
+ // this lane a state-gas charging divergence between the two interpreters would
+ // go unnoticed.
+ ams := *params.MergedTestChainConfig
+ amsTime := uint64(0)
+ ams.AmsterdamTime = &amsTime
+
+ return []struct {
+ name string
+ cfg *params.ChainConfig
+ merged bool
+ }{
+ {"Frontier", params.NonActivatedConfig, false},
+ {"Byzantium", &preCon, false},
+ {"London", params.TestChainConfig, false},
+ {"Merged", params.MergedTestChainConfig, true},
+ {"Amsterdam", &ams, 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),
+ // Price state gas as mainnet does (see core.NewEVMBlockContext), so the
+ // Amsterdam diff lane exercises the EIP-8037 state-gas charging path.
+ CostPerStateByte: params.CostPerStateByte,
+ }
+ 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 generated 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, 0), 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, 0), 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))
+ }
+ }
+ })
+}
+
+// markedHelpers parses stack.go and returns the *Stack helpers tagged
+// //gen:inline. That tag is the single source of truth, shared with the
+// generator (core/vm/gen), for which helpers are spliced into the dispatch.
+func markedHelpers(t *testing.T) map[string]bool {
+ t.Helper()
+ fset := token.NewFileSet()
+ f, err := parser.ParseFile(fset, "stack.go", nil, parser.ParseComments)
+ if err != nil {
+ t.Fatalf("parsing stack.go: %v", err)
+ }
+ marked := map[string]bool{}
+ for _, decl := range f.Decls {
+ fn, ok := decl.(*ast.FuncDecl)
+ if !ok || fn.Doc == nil {
+ continue
+ }
+ for _, c := range fn.Doc.List {
+ if c.Text == "//gen:inline" {
+ marked[fn.Name.Name] = true
+ }
+ }
+ }
+ if len(marked) == 0 {
+ t.Fatal("found no //gen:inline helpers in stack.go")
+ }
+ return marked
+}
+
+// TestGeneratedFastPathHelpersExpanded asserts the generator spliced every
+// //gen:inline helper inline, so none survives as a real call in interpreter_gen.go.
+// Those helpers exceed the compiler's inline budget for a function as large as
+// execUntraced, so a missed splice would silently drop the inlining the fast
+// path exists for. It is the expand-side counterpart to
+// TestGeneratedFastPathHelpersInlined: together they hold the one invariant that
+// the fast path makes no real stack-helper call, the costly ones by splicing,
+// the cheap ones by compiler inlining.
+func TestGeneratedFastPathHelpersExpanded(t *testing.T) {
+ calls := countStackCalls(t, "interpreter_gen.go")
+ for h := range markedHelpers(t) {
+ if n := calls[h]; n != 0 {
+ t.Errorf("(*Stack).%s is //gen:inline but has %d residual call(s) in interpreter_gen.go, expected 0.\n"+
+ "The generator did not splice it. Check it is still in inlinable shape (core/vm/gen).", h, n)
+ }
+ }
+}
+
+// TestGeneratedFastPathHelpersInlined recompiles this package with the
+// compiler's inlining diagnostics on and fails if any *Stack helper call that
+// survives into interpreter_gen.go was not inlined. Every survivor must be a cheap
+// helper (len, pop1, peek, drop) the compiler inlines into execUntraced; the
+// //gen:inline helpers are spliced away and owned by the Expanded test. The
+// cheap ones inline today with margin except pop1, at cost 18 against Go's
+// big-function budget of 20. A toolchain that re-scores inline cost, or an extra
+// branch in one of these bodies, could silently stop the inlining and slow the
+// interpreter, so this turns that into a red build.
+func TestGeneratedFastPathHelpersInlined(t *testing.T) {
+ if testing.Short() {
+ t.Skip("skipping inlining check (recompiles the package) in -short mode")
+ }
+
+ // go build -gcflags=-m prints every inlining decision. The build cache
+ // replays the diagnostics on a hit, so repeated runs are deterministic. The
+ // flag applies only to this package, cached dependencies stay quiet.
+ out, err := exec.Command("go", "build", "-gcflags=-m", ".").CombinedOutput()
+ if err != nil {
+ t.Fatalf("compiling with inlining diagnostics: %v\n%s", err, out)
+ }
+ diag := string(out)
+ if !strings.Contains(diag, "interpreter_gen.go") {
+ t.Fatalf("captured no interpreter_gen.go diagnostics, the -m build produced nothing to check:\n%s", diag)
+ }
+
+ // Every surviving stack-helper call (i.e. not a //gen:inline target) must be
+ // inlined by the compiler.
+ marked := markedHelpers(t)
+ for h, n := range countStackCalls(t, "interpreter_gen.go") {
+ if marked[h] {
+ continue // spliced away, owned by TestGeneratedFastPathHelpersExpanded
+ }
+ inlinedRe := regexp.MustCompile(`interpreter_gen\.go.*inlining call to \(\*Stack\)\.` + regexp.QuoteMeta(h) + `\b`)
+ inlined := len(inlinedRe.FindAllString(diag, -1))
+ if inlined != n {
+ t.Errorf("(*Stack).%s: %d call site(s) in interpreter_gen.go, %d inlined into execUntraced.\n"+
+ "The compiler stopped inlining it, so the fast path now pays a real call. Shrink the\n"+
+ "body to fit the inline budget, or tag it //gen:inline in stack.go to splice it instead.", h, n, inlined)
+ continue
+ }
+ t.Logf("(*Stack).%s: %d/%d call sites inlined", h, inlined, n)
+ }
+}
+
+// countStackCalls parses a generated source file and counts calls to each
+// *Stack helper method, keyed by method name. It matches the fast path's stack
+// local and scope.Stack receivers. Parsing rather than grepping keeps comments
+// and strings from inflating the count.
+func countStackCalls(t *testing.T, file string) map[string]int {
+ t.Helper()
+ fset := token.NewFileSet()
+ f, err := parser.ParseFile(fset, file, nil, 0)
+ if err != nil {
+ t.Fatalf("parsing %s: %v", file, err)
+ }
+ counts := map[string]int{}
+ ast.Inspect(f, func(n ast.Node) bool {
+ call, ok := n.(*ast.CallExpr)
+ if !ok {
+ return true
+ }
+ if sel, ok := call.Fun.(*ast.SelectorExpr); ok && isStackReceiver(sel.X) {
+ counts[sel.Sel.Name]++
+ }
+ return true
+ })
+ return counts
+}
+
+// isStackReceiver reports whether x is the fast path's stack local or scope.Stack.
+func isStackReceiver(x ast.Expr) bool {
+ switch r := x.(type) {
+ case *ast.Ident:
+ return r.Name == "stack"
+ case *ast.SelectorExpr:
+ return r.Sel.Name == "Stack"
+ }
+ return false
+}
diff --git a/core/vm/stack.go b/core/vm/stack.go
index 564345ccd8..f76aa9713f 100644
--- a/core/vm/stack.go
+++ b/core/vm/stack.go
@@ -104,11 +104,12 @@ func (s *Stack) push(d *uint256.Int) {
// get returns a pointer to a newly created element
// on top of the stack
+//
+//gen:inline
func (s *Stack) get() *uint256.Int {
- elem := &s.inner.data[s.inner.top]
s.inner.top++
s.size++
- return elem
+ return &s.inner.data[s.inner.top-1]
}
func (s *Stack) pop() uint256.Int {
@@ -137,6 +138,8 @@ func (s *Stack) pop1() *uint256.Int {
// pop2 removes the top two elements and returns pointers to them. The
// pointers stay valid only until the next push or sub call.
+//
+//gen:inline
func (s *Stack) pop2() (top, second *uint256.Int) {
s.inner.top -= 2
s.size -= 2
@@ -162,6 +165,8 @@ func (s *Stack) pop4() (top, second, third, fourth *uint256.Int) {
// pop1Peek1 removes the top element and returns pointers to it and to the new
// top, the usual operand and write target of a binary operation. The first
// pointer stays valid only until the next push or sub call.
+//
+//gen:inline
func (s *Stack) pop1Peek1() (top, rest *uint256.Int) {
s.inner.top--
s.size--
@@ -171,61 +176,95 @@ func (s *Stack) pop1Peek1() (top, rest *uint256.Int) {
// pop2Peek1 removes the top two elements and returns pointers to them and to
// the new top, for three operand operations. The first two pointers stay
// valid only until the next push or sub call.
+//
+//gen:inline
func (s *Stack) pop2Peek1() (top, second, rest *uint256.Int) {
s.inner.top -= 2
s.size -= 2
return &s.inner.data[s.inner.top+1], &s.inner.data[s.inner.top], &s.inner.data[s.inner.top-1]
}
+//gen:inline
func (s *Stack) swap1() {
s.inner.data[s.bottom+s.size-2], s.inner.data[s.bottom+s.size-1] = s.inner.data[s.bottom+s.size-1], s.inner.data[s.bottom+s.size-2]
}
+
+//gen:inline
func (s *Stack) swap2() {
s.inner.data[s.bottom+s.size-3], s.inner.data[s.bottom+s.size-1] = s.inner.data[s.bottom+s.size-1], s.inner.data[s.bottom+s.size-3]
}
+
+//gen:inline
func (s *Stack) swap3() {
s.inner.data[s.bottom+s.size-4], s.inner.data[s.bottom+s.size-1] = s.inner.data[s.bottom+s.size-1], s.inner.data[s.bottom+s.size-4]
}
+
+//gen:inline
func (s *Stack) swap4() {
s.inner.data[s.bottom+s.size-5], s.inner.data[s.bottom+s.size-1] = s.inner.data[s.bottom+s.size-1], s.inner.data[s.bottom+s.size-5]
}
+
+//gen:inline
func (s *Stack) swap5() {
s.inner.data[s.bottom+s.size-6], s.inner.data[s.bottom+s.size-1] = s.inner.data[s.bottom+s.size-1], s.inner.data[s.bottom+s.size-6]
}
+
+//gen:inline
func (s *Stack) swap6() {
s.inner.data[s.bottom+s.size-7], s.inner.data[s.bottom+s.size-1] = s.inner.data[s.bottom+s.size-1], s.inner.data[s.bottom+s.size-7]
}
+
+//gen:inline
func (s *Stack) swap7() {
s.inner.data[s.bottom+s.size-8], s.inner.data[s.bottom+s.size-1] = s.inner.data[s.bottom+s.size-1], s.inner.data[s.bottom+s.size-8]
}
+
+//gen:inline
func (s *Stack) swap8() {
s.inner.data[s.bottom+s.size-9], s.inner.data[s.bottom+s.size-1] = s.inner.data[s.bottom+s.size-1], s.inner.data[s.bottom+s.size-9]
}
+
+//gen:inline
func (s *Stack) swap9() {
s.inner.data[s.bottom+s.size-10], s.inner.data[s.bottom+s.size-1] = s.inner.data[s.bottom+s.size-1], s.inner.data[s.bottom+s.size-10]
}
+
+//gen:inline
func (s *Stack) swap10() {
s.inner.data[s.bottom+s.size-11], s.inner.data[s.bottom+s.size-1] = s.inner.data[s.bottom+s.size-1], s.inner.data[s.bottom+s.size-11]
}
+
+//gen:inline
func (s *Stack) swap11() {
s.inner.data[s.bottom+s.size-12], s.inner.data[s.bottom+s.size-1] = s.inner.data[s.bottom+s.size-1], s.inner.data[s.bottom+s.size-12]
}
+
+//gen:inline
func (s *Stack) swap12() {
s.inner.data[s.bottom+s.size-13], s.inner.data[s.bottom+s.size-1] = s.inner.data[s.bottom+s.size-1], s.inner.data[s.bottom+s.size-13]
}
+
+//gen:inline
func (s *Stack) swap13() {
s.inner.data[s.bottom+s.size-14], s.inner.data[s.bottom+s.size-1] = s.inner.data[s.bottom+s.size-1], s.inner.data[s.bottom+s.size-14]
}
+
+//gen:inline
func (s *Stack) swap14() {
s.inner.data[s.bottom+s.size-15], s.inner.data[s.bottom+s.size-1] = s.inner.data[s.bottom+s.size-1], s.inner.data[s.bottom+s.size-15]
}
+
+//gen:inline
func (s *Stack) swap15() {
s.inner.data[s.bottom+s.size-16], s.inner.data[s.bottom+s.size-1] = s.inner.data[s.bottom+s.size-1], s.inner.data[s.bottom+s.size-16]
}
+
+//gen:inline
func (s *Stack) swap16() {
s.inner.data[s.bottom+s.size-17], s.inner.data[s.bottom+s.size-1] = s.inner.data[s.bottom+s.size-1], s.inner.data[s.bottom+s.size-17]
}
+//gen:inline
func (s *Stack) dup(n int) {
s.inner.data[s.bottom+s.size] = s.inner.data[s.bottom+s.size-n]
s.size++