diff --git a/cmd/evm/eofparse.go b/cmd/evm/eofparse.go
deleted file mode 100644
index 9710735576..0000000000
--- a/cmd/evm/eofparse.go
+++ /dev/null
@@ -1,228 +0,0 @@
-// Copyright 2024 The go-ethereum Authors
-// This file is part of go-ethereum.
-//
-// go-ethereum is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// go-ethereum 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 General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with go-ethereum. If not, see .
-
-package main
-
-import (
- "bufio"
- "encoding/hex"
- "encoding/json"
- "fmt"
- "io/fs"
- "os"
- "path/filepath"
- "strings"
-
- "github.com/ethereum/go-ethereum/core/vm"
- "github.com/ethereum/go-ethereum/log"
- "github.com/urfave/cli/v2"
-)
-
-var jt vm.JumpTable
-
-const initcode = "INITCODE"
-
-func init() {
- jt = vm.NewEOFInstructionSetForTesting()
-}
-
-var (
- hexFlag = &cli.StringFlag{
- Name: "hex",
- Usage: "Single container data parse and validation",
- }
- refTestFlag = &cli.StringFlag{
- Name: "test",
- Usage: "Path to EOF validation reference test.",
- }
- eofParseCommand = &cli.Command{
- Name: "eofparse",
- Aliases: []string{"eof"},
- Usage: "Parses hex eof container and returns validation errors (if any)",
- Action: eofParseAction,
- Flags: []cli.Flag{
- hexFlag,
- refTestFlag,
- },
- }
- eofDumpCommand = &cli.Command{
- Name: "eofdump",
- Usage: "Parses hex eof container and prints out human-readable representation of the container.",
- Action: eofDumpAction,
- Flags: []cli.Flag{
- hexFlag,
- },
- }
-)
-
-func eofParseAction(ctx *cli.Context) error {
- // If `--test` is set, parse and validate the reference test at the provided path.
- if ctx.IsSet(refTestFlag.Name) {
- var (
- file = ctx.String(refTestFlag.Name)
- executedTests int
- passedTests int
- )
- err := filepath.Walk(file, func(path string, info fs.FileInfo, err error) error {
- if err != nil {
- return err
- }
- if info.IsDir() {
- return nil
- }
- log.Debug("Executing test", "name", info.Name())
- passed, tot, err := executeTest(path)
- passedTests += passed
- executedTests += tot
- return err
- })
- if err != nil {
- return err
- }
- log.Info("Executed tests", "passed", passedTests, "total executed", executedTests)
- return nil
- }
- // If `--hex` is set, parse and validate the hex string argument.
- if ctx.IsSet(hexFlag.Name) {
- if _, err := parseAndValidate(ctx.String(hexFlag.Name), false); err != nil {
- return fmt.Errorf("err: %w", err)
- }
- fmt.Println("OK")
- return nil
- }
- // If neither are passed in, read input from stdin.
- scanner := bufio.NewScanner(os.Stdin)
- scanner.Buffer(make([]byte, 1024*1024), 10*1024*1024)
- for scanner.Scan() {
- l := strings.TrimSpace(scanner.Text())
- if strings.HasPrefix(l, "#") || l == "" {
- continue
- }
- if _, err := parseAndValidate(l, false); err != nil {
- fmt.Printf("err: %v\n", err)
- } else {
- fmt.Println("OK")
- }
- }
- if err := scanner.Err(); err != nil {
- fmt.Println(err.Error())
- }
- return nil
-}
-
-type refTests struct {
- Vectors map[string]eOFTest `json:"vectors"`
-}
-
-type eOFTest struct {
- Code string `json:"code"`
- Results map[string]etResult `json:"results"`
- ContainerKind string `json:"containerKind"`
-}
-
-type etResult struct {
- Result bool `json:"result"`
- Exception string `json:"exception,omitempty"`
-}
-
-func executeTest(path string) (int, int, error) {
- src, err := os.ReadFile(path)
- if err != nil {
- return 0, 0, err
- }
- var testsByName map[string]refTests
- if err := json.Unmarshal(src, &testsByName); err != nil {
- return 0, 0, err
- }
- passed, total := 0, 0
- for testsName, tests := range testsByName {
- for name, tt := range tests.Vectors {
- for fork, r := range tt.Results {
- total++
- _, err := parseAndValidate(tt.Code, tt.ContainerKind == initcode)
- if r.Result && err != nil {
- log.Error("Test failure, expected validation success", "name", testsName, "idx", name, "fork", fork, "err", err)
- continue
- }
- if !r.Result && err == nil {
- log.Error("Test failure, expected validation error", "name", testsName, "idx", name, "fork", fork, "have err", r.Exception, "err", err)
- continue
- }
- passed++
- }
- }
- }
- return passed, total, nil
-}
-
-func parseAndValidate(s string, isInitCode bool) (*vm.Container, error) {
- if len(s) >= 2 && strings.HasPrefix(s, "0x") {
- s = s[2:]
- }
- b, err := hex.DecodeString(s)
- if err != nil {
- return nil, fmt.Errorf("unable to decode data: %w", err)
- }
- return parse(b, isInitCode)
-}
-
-func parse(b []byte, isInitCode bool) (*vm.Container, error) {
- var c vm.Container
- if err := c.UnmarshalBinary(b, isInitCode); err != nil {
- return nil, err
- }
- if err := c.ValidateCode(&jt, isInitCode); err != nil {
- return nil, err
- }
- return &c, nil
-}
-
-func eofDumpAction(ctx *cli.Context) error {
- // If `--hex` is set, parse and validate the hex string argument.
- if ctx.IsSet(hexFlag.Name) {
- return eofDump(ctx.String(hexFlag.Name))
- }
- // Otherwise read from stdin
- scanner := bufio.NewScanner(os.Stdin)
- scanner.Buffer(make([]byte, 1024*1024), 10*1024*1024)
- for scanner.Scan() {
- l := strings.TrimSpace(scanner.Text())
- if strings.HasPrefix(l, "#") || l == "" {
- continue
- }
- if err := eofDump(l); err != nil {
- return err
- }
- fmt.Println("")
- }
- return scanner.Err()
-}
-
-func eofDump(hexdata string) error {
- if len(hexdata) >= 2 && strings.HasPrefix(hexdata, "0x") {
- hexdata = hexdata[2:]
- }
- b, err := hex.DecodeString(hexdata)
- if err != nil {
- return fmt.Errorf("unable to decode data: %w", err)
- }
- var c vm.Container
- if err := c.UnmarshalBinary(b, false); err != nil {
- return err
- }
- fmt.Println(c.String())
- return nil
-}
diff --git a/cmd/evm/eofparse_test.go b/cmd/evm/eofparse_test.go
deleted file mode 100644
index a9119916a5..0000000000
--- a/cmd/evm/eofparse_test.go
+++ /dev/null
@@ -1,182 +0,0 @@
-// Copyright 2024 The go-ethereum Authors
-// This file is part of go-ethereum.
-//
-// go-ethereum is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// go-ethereum 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 General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with go-ethereum. If not, see .
-
-package main
-
-import (
- "bufio"
- "bytes"
- "encoding/hex"
- "fmt"
- "os"
- "strings"
- "testing"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/vm"
-)
-
-func FuzzEofParsing(f *testing.F) {
- // Seed with corpus from execution-spec-tests
- for i := 0; ; i++ {
- fname := fmt.Sprintf("testdata/eof/eof_corpus_%d.txt", i)
- corpus, err := os.Open(fname)
- if err != nil {
- break
- }
- f.Logf("Reading seed data from %v", fname)
- scanner := bufio.NewScanner(corpus)
- scanner.Buffer(make([]byte, 1024), 10*1024*1024)
- for scanner.Scan() {
- s := scanner.Text()
- if len(s) >= 2 && strings.HasPrefix(s, "0x") {
- s = s[2:]
- }
- b, err := hex.DecodeString(s)
- if err != nil {
- panic(err) // rotten corpus
- }
- f.Add(b)
- }
- corpus.Close()
- if err := scanner.Err(); err != nil {
- panic(err) // rotten corpus
- }
- }
- // And do the fuzzing
- f.Fuzz(func(t *testing.T, data []byte) {
- var (
- jt = vm.NewEOFInstructionSetForTesting()
- c vm.Container
- )
- cpy := common.CopyBytes(data)
- if err := c.UnmarshalBinary(data, true); err == nil {
- c.ValidateCode(&jt, true)
- if have := c.MarshalBinary(); !bytes.Equal(have, data) {
- t.Fatal("Unmarshal-> Marshal failure!")
- }
- }
- if err := c.UnmarshalBinary(data, false); err == nil {
- c.ValidateCode(&jt, false)
- if have := c.MarshalBinary(); !bytes.Equal(have, data) {
- t.Fatal("Unmarshal-> Marshal failure!")
- }
- }
- if !bytes.Equal(cpy, data) {
- panic("data modified during unmarshalling")
- }
- })
-}
-
-func TestEofParseInitcode(t *testing.T) {
- testEofParse(t, true, "testdata/eof/results.initcode.txt")
-}
-
-func TestEofParseRegular(t *testing.T) {
- testEofParse(t, false, "testdata/eof/results.regular.txt")
-}
-
-func testEofParse(t *testing.T, isInitCode bool, wantFile string) {
- var wantFn func() string
- var wantLoc = 0
- { // Configure the want-reader
- wants, err := os.Open(wantFile)
- if err != nil {
- t.Fatal(err)
- }
- scanner := bufio.NewScanner(wants)
- scanner.Buffer(make([]byte, 1024), 10*1024*1024)
- wantFn = func() string {
- if scanner.Scan() {
- wantLoc++
- return scanner.Text()
- }
- return "end of file reached"
- }
- }
-
- for i := 0; ; i++ {
- fname := fmt.Sprintf("testdata/eof/eof_corpus_%d.txt", i)
- corpus, err := os.Open(fname)
- if err != nil {
- break
- }
- t.Logf("# Reading seed data from %v", fname)
- scanner := bufio.NewScanner(corpus)
- scanner.Buffer(make([]byte, 1024), 10*1024*1024)
- line := 1
- for scanner.Scan() {
- s := scanner.Text()
- if len(s) >= 2 && strings.HasPrefix(s, "0x") {
- s = s[2:]
- }
- b, err := hex.DecodeString(s)
- if err != nil {
- panic(err) // rotten corpus
- }
- have := "OK"
- if _, err := parse(b, isInitCode); err != nil {
- have = fmt.Sprintf("ERR: %v", err)
- }
- if false { // Change this to generate the want-output
- fmt.Printf("%v\n", have)
- } else {
- want := wantFn()
- if have != want {
- if len(want) > 100 {
- want = want[:100]
- }
- if len(b) > 100 {
- b = b[:100]
- }
- t.Errorf("%v:%d\n%v\ninput %x\nisInit: %v\nhave: %q\nwant: %q\n",
- fname, line, fmt.Sprintf("%v:%d", wantFile, wantLoc), b, isInitCode, have, want)
- }
- }
- line++
- }
- corpus.Close()
- }
-}
-
-func BenchmarkEofParse(b *testing.B) {
- corpus, err := os.Open("testdata/eof/eof_benches.txt")
- if err != nil {
- b.Fatal(err)
- }
- defer corpus.Close()
- scanner := bufio.NewScanner(corpus)
- scanner.Buffer(make([]byte, 1024), 10*1024*1024)
- line := 1
- for scanner.Scan() {
- s := scanner.Text()
- if len(s) >= 2 && strings.HasPrefix(s, "0x") {
- s = s[2:]
- }
- data, err := hex.DecodeString(s)
- if err != nil {
- b.Fatal(err) // rotten corpus
- }
- b.Run(fmt.Sprintf("test-%d", line), func(b *testing.B) {
- b.ReportAllocs()
- b.SetBytes(int64(len(data)))
- for i := 0; i < b.N; i++ {
- _, _ = parse(data, false)
- }
- })
- line++
- }
-}
diff --git a/cmd/evm/main.go b/cmd/evm/main.go
index 61e46aa50e..bf5be9a359 100644
--- a/cmd/evm/main.go
+++ b/cmd/evm/main.go
@@ -210,8 +210,6 @@ func init() {
stateTransitionCommand,
transactionCommand,
blockBuilderCommand,
- eofParseCommand,
- eofDumpCommand,
}
app.Before = func(ctx *cli.Context) error {
flags.MigrateGlobalFlags(ctx)
diff --git a/core/vm/analysis_eof.go b/core/vm/analysis_eof.go
deleted file mode 100644
index eb78904cfd..0000000000
--- a/core/vm/analysis_eof.go
+++ /dev/null
@@ -1,98 +0,0 @@
-// Copyright 2024 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
-
-// eofCodeBitmap collects data locations in code.
-func eofCodeBitmap(code []byte) bitvec {
- // The bitmap is 4 bytes longer than necessary, in case the code
- // ends with a PUSH32, the algorithm will push zeroes onto the
- // bitvector outside the bounds of the actual code.
- bits := make(bitvec, len(code)/8+1+4)
- return eofCodeBitmapInternal(code, bits)
-}
-
-// eofCodeBitmapInternal is the internal implementation of codeBitmap for EOF
-// code validation.
-func eofCodeBitmapInternal(code, bits bitvec) bitvec {
- for pc := uint64(0); pc < uint64(len(code)); {
- var (
- op = OpCode(code[pc])
- numbits uint16
- )
- pc++
-
- if op == RJUMPV {
- // RJUMPV is unique as it has a variable sized operand.
- // The total size is determined by the count byte which
- // immediate follows RJUMPV. Truncation will be caught
- // in other validation steps -- for now, just return a
- // valid bitmap for as much of the code as is
- // available.
- end := uint64(len(code))
- if pc >= end {
- // Count missing, no more bits to mark.
- return bits
- }
- numbits = uint16(code[pc])*2 + 3
- if pc+uint64(numbits) > end {
- // Jump table is truncated, mark as many bits
- // as possible.
- numbits = uint16(end - pc)
- }
- } else {
- numbits = uint16(Immediates(op))
- if numbits == 0 {
- continue
- }
- }
-
- if numbits >= 8 {
- for ; numbits >= 16; numbits -= 16 {
- bits.set16(pc)
- pc += 16
- }
- for ; numbits >= 8; numbits -= 8 {
- bits.set8(pc)
- pc += 8
- }
- }
- switch numbits {
- case 1:
- bits.set1(pc)
- pc += 1
- case 2:
- bits.setN(set2BitsMask, pc)
- pc += 2
- case 3:
- bits.setN(set3BitsMask, pc)
- pc += 3
- case 4:
- bits.setN(set4BitsMask, pc)
- pc += 4
- case 5:
- bits.setN(set5BitsMask, pc)
- pc += 5
- case 6:
- bits.setN(set6BitsMask, pc)
- pc += 6
- case 7:
- bits.setN(set7BitsMask, pc)
- pc += 7
- }
- }
- return bits
-}
diff --git a/core/vm/analysis_legacy_test.go b/core/vm/analysis_legacy_test.go
index 7f5de225e2..471d2b4ffb 100644
--- a/core/vm/analysis_legacy_test.go
+++ b/core/vm/analysis_legacy_test.go
@@ -105,31 +105,3 @@ func BenchmarkJumpdestOpAnalysis(bench *testing.B) {
op = STOP
bench.Run(op.String(), bencher)
}
-
-func BenchmarkJumpdestOpEOFAnalysis(bench *testing.B) {
- var op OpCode
- bencher := func(b *testing.B) {
- code := make([]byte, analysisCodeSize)
- b.SetBytes(analysisCodeSize)
- for i := range code {
- code[i] = byte(op)
- }
- bits := make(bitvec, len(code)/8+1+4)
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- clear(bits)
- eofCodeBitmapInternal(code, bits)
- }
- }
- for op = PUSH1; op <= PUSH32; op++ {
- bench.Run(op.String(), bencher)
- }
- op = JUMPDEST
- bench.Run(op.String(), bencher)
- op = STOP
- bench.Run(op.String(), bencher)
- op = RJUMPV
- bench.Run(op.String(), bencher)
- op = EOFCREATE
- bench.Run(op.String(), bencher)
-}
diff --git a/core/vm/eips.go b/core/vm/eips.go
index d95fa51284..79fd24d13e 100644
--- a/core/vm/eips.go
+++ b/core/vm/eips.go
@@ -531,176 +531,6 @@ func enable4762(jt *JumpTable) {
}
}
-// enableEOF applies the EOF changes.
-// OBS! For EOF, there are two changes:
-// 1. Two separate jumptables are required. One, EOF-jumptable, is used by
-// eof contracts. This one contains things like RJUMP.
-// 2. The regular non-eof jumptable also needs to be modified, specifically to
-// modify how EXTCODECOPY works under the hood.
-//
-// This method _only_ deals with case 1.
-func enableEOF(jt *JumpTable) {
- // Deprecate opcodes
- undefined := &operation{
- execute: opUndefined,
- constantGas: 0,
- minStack: minStack(0, 0),
- maxStack: maxStack(0, 0),
- undefined: true,
- }
- jt[CALL] = undefined
- jt[CALLCODE] = undefined
- jt[DELEGATECALL] = undefined
- jt[STATICCALL] = undefined
- jt[SELFDESTRUCT] = undefined
- jt[JUMP] = undefined
- jt[JUMPI] = undefined
- jt[PC] = undefined
- jt[CREATE] = undefined
- jt[CREATE2] = undefined
- jt[CODESIZE] = undefined
- jt[CODECOPY] = undefined
- jt[EXTCODESIZE] = undefined
- jt[EXTCODECOPY] = undefined
- jt[EXTCODEHASH] = undefined
- jt[GAS] = undefined
- // Allow 0xFE to terminate sections
- jt[INVALID] = &operation{
- execute: opUndefined,
- constantGas: 0,
- minStack: minStack(0, 0),
- maxStack: maxStack(0, 0),
- }
-
- // New opcodes
- jt[RJUMP] = &operation{
- execute: opRjump,
- constantGas: GasQuickStep,
- minStack: minStack(0, 0),
- maxStack: maxStack(0, 0),
- }
- jt[RJUMPI] = &operation{
- execute: opRjumpi,
- constantGas: GasFastishStep,
- minStack: minStack(1, 0),
- maxStack: maxStack(1, 0),
- }
- jt[RJUMPV] = &operation{
- execute: opRjumpv,
- constantGas: GasFastishStep,
- minStack: minStack(1, 0),
- maxStack: maxStack(1, 0),
- }
- jt[CALLF] = &operation{
- execute: opCallf,
- constantGas: GasFastStep,
- minStack: minStack(0, 0),
- maxStack: maxStack(0, 0),
- }
- jt[RETF] = &operation{
- execute: opRetf,
- constantGas: GasFastestStep,
- minStack: minStack(0, 0),
- maxStack: maxStack(0, 0),
- }
- jt[JUMPF] = &operation{
- execute: opJumpf,
- constantGas: GasFastStep,
- minStack: minStack(0, 0),
- maxStack: maxStack(0, 0),
- }
- jt[EOFCREATE] = &operation{
- execute: opEOFCreate,
- constantGas: params.Create2Gas,
- dynamicGas: gasEOFCreate,
- minStack: minStack(4, 1),
- maxStack: maxStack(4, 1),
- memorySize: memoryEOFCreate,
- }
- jt[RETURNCONTRACT] = &operation{
- execute: opReturnContract,
- // returncontract has zero constant gas cost
- dynamicGas: pureMemoryGascost,
- minStack: minStack(2, 0),
- maxStack: maxStack(2, 0),
- memorySize: memoryReturnContract,
- }
- jt[DATALOAD] = &operation{
- execute: opDataLoad,
- constantGas: GasFastishStep,
- minStack: minStack(1, 1),
- maxStack: maxStack(1, 1),
- }
- jt[DATALOADN] = &operation{
- execute: opDataLoadN,
- constantGas: GasFastestStep,
- minStack: minStack(0, 1),
- maxStack: maxStack(0, 1),
- }
- jt[DATASIZE] = &operation{
- execute: opDataSize,
- constantGas: GasQuickStep,
- minStack: minStack(0, 1),
- maxStack: maxStack(0, 1),
- }
- jt[DATACOPY] = &operation{
- execute: opDataCopy,
- constantGas: GasFastestStep,
- dynamicGas: memoryCopierGas(2),
- minStack: minStack(3, 0),
- maxStack: maxStack(3, 0),
- memorySize: memoryDataCopy,
- }
- jt[DUPN] = &operation{
- execute: opDupN,
- constantGas: GasFastestStep,
- minStack: minStack(0, 1),
- maxStack: maxStack(0, 1),
- }
- jt[SWAPN] = &operation{
- execute: opSwapN,
- constantGas: GasFastestStep,
- minStack: minStack(0, 0),
- maxStack: maxStack(0, 0),
- }
- jt[EXCHANGE] = &operation{
- execute: opExchange,
- constantGas: GasFastestStep,
- minStack: minStack(0, 0),
- maxStack: maxStack(0, 0),
- }
- jt[RETURNDATALOAD] = &operation{
- execute: opReturnDataLoad,
- constantGas: GasFastestStep,
- minStack: minStack(1, 1),
- maxStack: maxStack(1, 1),
- }
- jt[EXTCALL] = &operation{
- execute: opExtCall,
- constantGas: params.WarmStorageReadCostEIP2929,
- dynamicGas: makeCallVariantGasCallEIP2929(gasExtCall, 0),
- minStack: minStack(4, 1),
- maxStack: maxStack(4, 1),
- memorySize: memoryExtCall,
- }
- jt[EXTDELEGATECALL] = &operation{
- execute: opExtDelegateCall,
- dynamicGas: makeCallVariantGasCallEIP2929(gasExtDelegateCall, 0),
- constantGas: params.WarmStorageReadCostEIP2929,
- minStack: minStack(3, 1),
- maxStack: maxStack(3, 1),
- memorySize: memoryExtCall,
- }
- jt[EXTSTATICCALL] = &operation{
- execute: opExtStaticCall,
- constantGas: params.WarmStorageReadCostEIP2929,
- dynamicGas: makeCallVariantGasCallEIP2929(gasExtStaticCall, 0),
- minStack: minStack(3, 1),
- maxStack: maxStack(3, 1),
- memorySize: memoryExtCall,
- }
-}
-
// enable7702 the EIP-7702 changes to support delegation designators.
func enable7702(jt *JumpTable) {
jt[CALL].dynamicGas = gasCallEIP7702
diff --git a/core/vm/eof.go b/core/vm/eof.go
deleted file mode 100644
index a5406283d5..0000000000
--- a/core/vm/eof.go
+++ /dev/null
@@ -1,501 +0,0 @@
-// Copyright 2024 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package vm
-
-import (
- "bytes"
- "encoding/binary"
- "errors"
- "fmt"
- "io"
- "strings"
-
- "github.com/ethereum/go-ethereum/params"
-)
-
-const (
- offsetVersion = 2
- offsetTypesKind = 3
- offsetCodeKind = 6
-
- kindTypes = 1
- kindCode = 2
- kindContainer = 3
- kindData = 4
-
- eofFormatByte = 0xef
- eof1Version = 1
-
- maxInputItems = 127
- maxOutputItems = 128
- maxStackHeight = 1023
- maxContainerSections = 256
-)
-
-var eofMagic = []byte{0xef, 0x00}
-
-// HasEOFByte returns true if code starts with 0xEF byte
-func HasEOFByte(code []byte) bool {
- return len(code) != 0 && code[0] == eofFormatByte
-}
-
-// hasEOFMagic returns true if code starts with magic defined by EIP-3540
-func hasEOFMagic(code []byte) bool {
- return len(eofMagic) <= len(code) && bytes.Equal(eofMagic, code[0:len(eofMagic)])
-}
-
-// isEOFVersion1 returns true if the code's version byte equals eof1Version. It
-// does not verify the EOF magic is valid.
-func isEOFVersion1(code []byte) bool {
- return 2 < len(code) && code[2] == byte(eof1Version)
-}
-
-// Container is an EOF container object.
-type Container struct {
- types []*functionMetadata
- codeSections [][]byte
- subContainers []*Container
- subContainerCodes [][]byte
- data []byte
- dataSize int // might be more than len(data)
-}
-
-// functionMetadata is an EOF function signature.
-type functionMetadata struct {
- inputs uint8
- outputs uint8
- maxStackHeight uint16
-}
-
-// stackDelta returns the #outputs - #inputs
-func (meta *functionMetadata) stackDelta() int {
- return int(meta.outputs) - int(meta.inputs)
-}
-
-// checkInputs checks the current minimum stack (stackMin) against the required inputs
-// of the metadata, and returns an error if the stack is too shallow.
-func (meta *functionMetadata) checkInputs(stackMin int) error {
- if int(meta.inputs) > stackMin {
- return ErrStackUnderflow{stackLen: stackMin, required: int(meta.inputs)}
- }
- return nil
-}
-
-// checkStackMax checks the if current maximum stack combined with the
-// function max stack will result in a stack overflow, and if so returns an error.
-func (meta *functionMetadata) checkStackMax(stackMax int) error {
- newMaxStack := stackMax + int(meta.maxStackHeight) - int(meta.inputs)
- if newMaxStack > int(params.StackLimit) {
- return ErrStackOverflow{stackLen: newMaxStack, limit: int(params.StackLimit)}
- }
- return nil
-}
-
-// MarshalBinary encodes an EOF container into binary format.
-func (c *Container) MarshalBinary() []byte {
- // Build EOF prefix.
- b := make([]byte, 2)
- copy(b, eofMagic)
- b = append(b, eof1Version)
-
- // Write section headers.
- b = append(b, kindTypes)
- b = binary.BigEndian.AppendUint16(b, uint16(len(c.types)*4))
- b = append(b, kindCode)
- b = binary.BigEndian.AppendUint16(b, uint16(len(c.codeSections)))
- for _, codeSection := range c.codeSections {
- b = binary.BigEndian.AppendUint16(b, uint16(len(codeSection)))
- }
- var encodedContainer [][]byte
- if len(c.subContainers) != 0 {
- b = append(b, kindContainer)
- b = binary.BigEndian.AppendUint16(b, uint16(len(c.subContainers)))
- for _, section := range c.subContainers {
- encoded := section.MarshalBinary()
- b = binary.BigEndian.AppendUint16(b, uint16(len(encoded)))
- encodedContainer = append(encodedContainer, encoded)
- }
- }
- b = append(b, kindData)
- b = binary.BigEndian.AppendUint16(b, uint16(c.dataSize))
- b = append(b, 0) // terminator
-
- // Write section contents.
- for _, ty := range c.types {
- b = append(b, []byte{ty.inputs, ty.outputs, byte(ty.maxStackHeight >> 8), byte(ty.maxStackHeight & 0x00ff)}...)
- }
- for _, code := range c.codeSections {
- b = append(b, code...)
- }
- for _, section := range encodedContainer {
- b = append(b, section...)
- }
- b = append(b, c.data...)
-
- return b
-}
-
-// UnmarshalBinary decodes an EOF container.
-func (c *Container) UnmarshalBinary(b []byte, isInitcode bool) error {
- return c.unmarshalContainer(b, isInitcode, true)
-}
-
-// UnmarshalSubContainer decodes an EOF container that is inside another container.
-func (c *Container) UnmarshalSubContainer(b []byte, isInitcode bool) error {
- return c.unmarshalContainer(b, isInitcode, false)
-}
-
-func (c *Container) unmarshalContainer(b []byte, isInitcode bool, topLevel bool) error {
- if !hasEOFMagic(b) {
- return fmt.Errorf("%w: want %x", errInvalidMagic, eofMagic)
- }
- if len(b) < 14 {
- return io.ErrUnexpectedEOF
- }
- if len(b) > params.MaxInitCodeSize {
- return ErrMaxInitCodeSizeExceeded
- }
- if !isEOFVersion1(b) {
- return fmt.Errorf("%w: have %d, want %d", errInvalidVersion, b[2], eof1Version)
- }
-
- var (
- kind, typesSize, dataSize int
- codeSizes []int
- err error
- )
-
- // Parse type section header.
- kind, typesSize, err = parseSection(b, offsetTypesKind)
- if err != nil {
- return err
- }
- if kind != kindTypes {
- return fmt.Errorf("%w: found section kind %x instead", errMissingTypeHeader, kind)
- }
- if typesSize < 4 || typesSize%4 != 0 {
- return fmt.Errorf("%w: type section size must be divisible by 4, have %d", errInvalidTypeSize, typesSize)
- }
- if typesSize/4 > 1024 {
- return fmt.Errorf("%w: type section must not exceed 4*1024, have %d", errInvalidTypeSize, typesSize)
- }
-
- // Parse code section header.
- kind, codeSizes, err = parseSectionList(b, offsetCodeKind)
- if err != nil {
- return err
- }
- if kind != kindCode {
- return fmt.Errorf("%w: found section kind %x instead", errMissingCodeHeader, kind)
- }
- if len(codeSizes) != typesSize/4 {
- return fmt.Errorf("%w: mismatch of code sections found and type signatures, types %d, code %d", errInvalidCodeSize, typesSize/4, len(codeSizes))
- }
-
- // Parse (optional) container section header.
- var containerSizes []int
- offset := offsetCodeKind + 2 + 2*len(codeSizes) + 1
- if offset < len(b) && b[offset] == kindContainer {
- kind, containerSizes, err = parseSectionList(b, offset)
- if err != nil {
- return err
- }
- if kind != kindContainer {
- panic("somethings wrong")
- }
- if len(containerSizes) == 0 {
- return fmt.Errorf("%w: total container count must not be zero", errInvalidContainerSectionSize)
- }
- offset = offset + 2 + 2*len(containerSizes) + 1
- }
-
- // Parse data section header.
- kind, dataSize, err = parseSection(b, offset)
- if err != nil {
- return err
- }
- if kind != kindData {
- return fmt.Errorf("%w: found section %x instead", errMissingDataHeader, kind)
- }
- c.dataSize = dataSize
-
- // Check for terminator.
- offsetTerminator := offset + 3
- if len(b) < offsetTerminator {
- return fmt.Errorf("%w: invalid offset terminator", io.ErrUnexpectedEOF)
- }
- if b[offsetTerminator] != 0 {
- return fmt.Errorf("%w: have %x", errMissingTerminator, b[offsetTerminator])
- }
-
- // Verify overall container size.
- expectedSize := offsetTerminator + typesSize + sum(codeSizes) + dataSize + 1
- if len(containerSizes) != 0 {
- expectedSize += sum(containerSizes)
- }
- if len(b) < expectedSize-dataSize {
- return fmt.Errorf("%w: have %d, want %d", errInvalidContainerSize, len(b), expectedSize)
- }
- // Only check that the expected size is not exceed on non-initcode
- if (!topLevel || !isInitcode) && len(b) > expectedSize {
- return fmt.Errorf("%w: have %d, want %d", errInvalidContainerSize, len(b), expectedSize)
- }
-
- // Parse types section.
- idx := offsetTerminator + 1
- var types = make([]*functionMetadata, 0, typesSize/4)
- for i := 0; i < typesSize/4; i++ {
- sig := &functionMetadata{
- inputs: b[idx+i*4],
- outputs: b[idx+i*4+1],
- maxStackHeight: binary.BigEndian.Uint16(b[idx+i*4+2:]),
- }
- if sig.inputs > maxInputItems {
- return fmt.Errorf("%w for section %d: have %d", errTooManyInputs, i, sig.inputs)
- }
- if sig.outputs > maxOutputItems {
- return fmt.Errorf("%w for section %d: have %d", errTooManyOutputs, i, sig.outputs)
- }
- if sig.maxStackHeight > maxStackHeight {
- return fmt.Errorf("%w for section %d: have %d", errTooLargeMaxStackHeight, i, sig.maxStackHeight)
- }
- types = append(types, sig)
- }
- if types[0].inputs != 0 || types[0].outputs != 0x80 {
- return fmt.Errorf("%w: have %d, %d", errInvalidSection0Type, types[0].inputs, types[0].outputs)
- }
- c.types = types
-
- // Parse code sections.
- idx += typesSize
- codeSections := make([][]byte, len(codeSizes))
- for i, size := range codeSizes {
- if size == 0 {
- return fmt.Errorf("%w for section %d: size must not be 0", errInvalidCodeSize, i)
- }
- codeSections[i] = b[idx : idx+size]
- idx += size
- }
- c.codeSections = codeSections
- // Parse the optional container sizes.
- if len(containerSizes) != 0 {
- if len(containerSizes) > maxContainerSections {
- return fmt.Errorf("%w number of container section exceed: %v: have %v", errInvalidContainerSectionSize, maxContainerSections, len(containerSizes))
- }
- subContainerCodes := make([][]byte, 0, len(containerSizes))
- subContainers := make([]*Container, 0, len(containerSizes))
- for i, size := range containerSizes {
- if size == 0 || idx+size > len(b) {
- return fmt.Errorf("%w for section %d: size must not be 0", errInvalidContainerSectionSize, i)
- }
- subC := new(Container)
- end := min(idx+size, len(b))
- if err := subC.unmarshalContainer(b[idx:end], isInitcode, false); err != nil {
- if topLevel {
- return fmt.Errorf("%w in sub container %d", err, i)
- }
- return err
- }
- subContainers = append(subContainers, subC)
- subContainerCodes = append(subContainerCodes, b[idx:end])
-
- idx += size
- }
- c.subContainers = subContainers
- c.subContainerCodes = subContainerCodes
- }
-
- //Parse data section.
- end := len(b)
- if !isInitcode {
- end = min(idx+dataSize, len(b))
- }
- if topLevel && len(b) != idx+dataSize {
- return errTruncatedTopLevelContainer
- }
- c.data = b[idx:end]
-
- return nil
-}
-
-// ValidateCode validates each code section of the container against the EOF v1
-// rule set.
-func (c *Container) ValidateCode(jt *JumpTable, isInitCode bool) error {
- refBy := notRefByEither
- if isInitCode {
- refBy = refByEOFCreate
- }
- return c.validateSubContainer(jt, refBy)
-}
-
-func (c *Container) validateSubContainer(jt *JumpTable, refBy int) error {
- visited := make(map[int]struct{})
- subContainerVisited := make(map[int]int)
- toVisit := []int{0}
- for len(toVisit) > 0 {
- // TODO check if this can be used as a DOS
- // Theres and edge case here where we mark something as visited that we visit before,
- // This should not trigger a re-visit
- // e.g. 0 -> 1, 2, 3
- // 1 -> 2, 3
- // should not mean 2 and 3 should be visited twice
- var (
- index = toVisit[0]
- code = c.codeSections[index]
- )
- if _, ok := visited[index]; !ok {
- res, err := validateCode(code, index, c, jt, refBy == refByEOFCreate)
- if err != nil {
- return err
- }
- visited[index] = struct{}{}
- // Mark all sections that can be visited from here.
- for idx := range res.visitedCode {
- if _, ok := visited[idx]; !ok {
- toVisit = append(toVisit, idx)
- }
- }
- // Mark all subcontainer that can be visited from here.
- for idx, reference := range res.visitedSubContainers {
- // Make sure subcontainers are only ever referenced by either EOFCreate or ReturnContract
- if ref, ok := subContainerVisited[idx]; ok && ref != reference {
- return errors.New("section referenced by both EOFCreate and ReturnContract")
- }
- subContainerVisited[idx] = reference
- }
- if refBy == refByReturnContract && res.isInitCode {
- return errIncompatibleContainerKind
- }
- if refBy == refByEOFCreate && res.isRuntime {
- return errIncompatibleContainerKind
- }
- }
- toVisit = toVisit[1:]
- }
- // Make sure every code section is visited at least once.
- if len(visited) != len(c.codeSections) {
- return errUnreachableCode
- }
- for idx, container := range c.subContainers {
- reference, ok := subContainerVisited[idx]
- if !ok {
- return errOrphanedSubcontainer
- }
- if err := container.validateSubContainer(jt, reference); err != nil {
- return err
- }
- }
- return nil
-}
-
-// parseSection decodes a (kind, size) pair from an EOF header.
-func parseSection(b []byte, idx int) (kind, size int, err error) {
- if idx+3 >= len(b) {
- return 0, 0, io.ErrUnexpectedEOF
- }
- kind = int(b[idx])
- size = int(binary.BigEndian.Uint16(b[idx+1:]))
- return kind, size, nil
-}
-
-// parseSectionList decodes a (kind, len, []codeSize) section list from an EOF
-// header.
-func parseSectionList(b []byte, idx int) (kind int, list []int, err error) {
- if idx >= len(b) {
- return 0, nil, io.ErrUnexpectedEOF
- }
- kind = int(b[idx])
- list, err = parseList(b, idx+1)
- if err != nil {
- return 0, nil, err
- }
- return kind, list, nil
-}
-
-// parseList decodes a list of uint16..
-func parseList(b []byte, idx int) ([]int, error) {
- if len(b) < idx+2 {
- return nil, io.ErrUnexpectedEOF
- }
- count := binary.BigEndian.Uint16(b[idx:])
- if len(b) <= idx+2+int(count)*2 {
- return nil, io.ErrUnexpectedEOF
- }
- list := make([]int, count)
- for i := 0; i < int(count); i++ {
- list[i] = int(binary.BigEndian.Uint16(b[idx+2+2*i:]))
- }
- return list, nil
-}
-
-// parseUint16 parses a 16 bit unsigned integer.
-func parseUint16(b []byte) (int, error) {
- if len(b) < 2 {
- return 0, io.ErrUnexpectedEOF
- }
- return int(binary.BigEndian.Uint16(b)), nil
-}
-
-// parseInt16 parses a 16 bit signed integer.
-func parseInt16(b []byte) int {
- return int(int16(b[1]) | int16(b[0])<<8)
-}
-
-// sum computes the sum of a slice.
-func sum(list []int) (s int) {
- for _, n := range list {
- s += n
- }
- return
-}
-
-func (c *Container) String() string {
- var output = []string{
- "Header",
- fmt.Sprintf(" - EOFMagic: %02x", eofMagic),
- fmt.Sprintf(" - EOFVersion: %02x", eof1Version),
- fmt.Sprintf(" - KindType: %02x", kindTypes),
- fmt.Sprintf(" - TypesSize: %04x", len(c.types)*4),
- fmt.Sprintf(" - KindCode: %02x", kindCode),
- fmt.Sprintf(" - KindData: %02x", kindData),
- fmt.Sprintf(" - DataSize: %04x", len(c.data)),
- fmt.Sprintf(" - Number of code sections: %d", len(c.codeSections)),
- }
- for i, code := range c.codeSections {
- output = append(output, fmt.Sprintf(" - Code section %d length: %04x", i, len(code)))
- }
-
- output = append(output, fmt.Sprintf(" - Number of subcontainers: %d", len(c.subContainers)))
- if len(c.subContainers) > 0 {
- for i, section := range c.subContainers {
- output = append(output, fmt.Sprintf(" - subcontainer %d length: %04x\n", i, len(section.MarshalBinary())))
- }
- }
- output = append(output, "Body")
- for i, typ := range c.types {
- output = append(output, fmt.Sprintf(" - Type %v: %x", i,
- []byte{typ.inputs, typ.outputs, byte(typ.maxStackHeight >> 8), byte(typ.maxStackHeight & 0x00ff)}))
- }
- for i, code := range c.codeSections {
- output = append(output, fmt.Sprintf(" - Code section %d: %#x", i, code))
- }
- for i, section := range c.subContainers {
- output = append(output, fmt.Sprintf(" - Subcontainer %d: %x", i, section.MarshalBinary()))
- }
- output = append(output, fmt.Sprintf(" - Data: %#x", c.data))
- return strings.Join(output, "\n")
-}
diff --git a/core/vm/eof_control_flow.go b/core/vm/eof_control_flow.go
deleted file mode 100644
index c0a4459906..0000000000
--- a/core/vm/eof_control_flow.go
+++ /dev/null
@@ -1,235 +0,0 @@
-// Copyright 2024 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package vm
-
-import (
- "fmt"
-
- "github.com/ethereum/go-ethereum/params"
-)
-
-func validateControlFlow(code []byte, section int, metadata []*functionMetadata, jt *JumpTable) (int, error) {
- var (
- maxStackHeight = int(metadata[section].inputs)
- visitCount = 0
- next = make([]int, 0, 1)
- )
- var (
- stackBoundsMax = make([]uint16, len(code))
- stackBoundsMin = make([]uint16, len(code))
- )
- setBounds := func(pos, min, maxi int) {
- // The stackboundMax slice is a bit peculiar. We use `0` to denote
- // not set. Therefore, we use `1` to represent the value `0`, and so on.
- // So if the caller wants to store `1` as max bound, we internally store it as
- // `2`.
- if stackBoundsMax[pos] == 0 { // Not yet set
- visitCount++
- }
- if maxi < 65535 {
- stackBoundsMax[pos] = uint16(maxi + 1)
- }
- stackBoundsMin[pos] = uint16(min)
- maxStackHeight = max(maxStackHeight, maxi)
- }
- getStackMaxMin := func(pos int) (ok bool, min, max int) {
- maxi := stackBoundsMax[pos]
- if maxi == 0 { // Not yet set
- return false, 0, 0
- }
- return true, int(stackBoundsMin[pos]), int(maxi - 1)
- }
- // set the initial stack bounds
- setBounds(0, int(metadata[section].inputs), int(metadata[section].inputs))
-
- qualifiedExit := false
- for pos := 0; pos < len(code); pos++ {
- op := OpCode(code[pos])
- ok, currentStackMin, currentStackMax := getStackMaxMin(pos)
- if !ok {
- return 0, errUnreachableCode
- }
-
- switch op {
- case CALLF:
- arg, _ := parseUint16(code[pos+1:])
- newSection := metadata[arg]
- if err := newSection.checkInputs(currentStackMin); err != nil {
- return 0, fmt.Errorf("%w: at pos %d", err, pos)
- }
- if err := newSection.checkStackMax(currentStackMax); err != nil {
- return 0, fmt.Errorf("%w: at pos %d", err, pos)
- }
- delta := newSection.stackDelta()
- currentStackMax += delta
- currentStackMin += delta
- case RETF:
- /* From the spec:
- > for RETF the following must hold: stack_height_max == stack_height_min == types[current_code_index].outputs,
-
- In other words: RETF must unambiguously return all items remaining on the stack.
- */
- if currentStackMax != currentStackMin {
- return 0, fmt.Errorf("%w: max %d, min %d, at pos %d", errInvalidOutputs, currentStackMax, currentStackMin, pos)
- }
- numOutputs := int(metadata[section].outputs)
- if numOutputs >= maxOutputItems {
- return 0, fmt.Errorf("%w: at pos %d", errInvalidNonReturningFlag, pos)
- }
- if numOutputs != currentStackMin {
- return 0, fmt.Errorf("%w: have %d, want %d, at pos %d", errInvalidOutputs, numOutputs, currentStackMin, pos)
- }
- qualifiedExit = true
- case JUMPF:
- arg, _ := parseUint16(code[pos+1:])
- newSection := metadata[arg]
-
- if err := newSection.checkStackMax(currentStackMax); err != nil {
- return 0, fmt.Errorf("%w: at pos %d", err, pos)
- }
-
- if newSection.outputs == 0x80 {
- if err := newSection.checkInputs(currentStackMin); err != nil {
- return 0, fmt.Errorf("%w: at pos %d", err, pos)
- }
- } else {
- if currentStackMax != currentStackMin {
- return 0, fmt.Errorf("%w: max %d, min %d, at pos %d", errInvalidOutputs, currentStackMax, currentStackMin, pos)
- }
- wantStack := int(metadata[section].outputs) - newSection.stackDelta()
- if currentStackMax != wantStack {
- return 0, fmt.Errorf("%w: at pos %d", errInvalidOutputs, pos)
- }
- }
- qualifiedExit = qualifiedExit || newSection.outputs < maxOutputItems
- case DUPN:
- arg := int(code[pos+1]) + 1
- if want, have := arg, currentStackMin; want > have {
- return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos)
- }
- case SWAPN:
- arg := int(code[pos+1]) + 1
- if want, have := arg+1, currentStackMin; want > have {
- return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos)
- }
- case EXCHANGE:
- arg := int(code[pos+1])
- n := arg>>4 + 1
- m := arg&0x0f + 1
- if want, have := n+m+1, currentStackMin; want > have {
- return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos)
- }
- default:
- if want, have := jt[op].minStack, currentStackMin; want > have {
- return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos)
- }
- }
- if !terminals[op] && op != CALLF {
- change := int(params.StackLimit) - jt[op].maxStack
- currentStackMax += change
- currentStackMin += change
- }
- next = next[:0]
- switch op {
- case RJUMP:
- nextPos := pos + 2 + parseInt16(code[pos+1:])
- next = append(next, nextPos)
- // We set the stack bounds of the destination
- // and skip the argument, only for RJUMP, all other opcodes are handled later
- if nextPos+1 < pos {
- ok, nextMin, nextMax := getStackMaxMin(nextPos + 1)
- if !ok {
- return 0, errInvalidBackwardJump
- }
- if nextMax != currentStackMax || nextMin != currentStackMin {
- return 0, errInvalidMaxStackHeight
- }
- } else {
- ok, nextMin, nextMax := getStackMaxMin(nextPos + 1)
- if !ok {
- setBounds(nextPos+1, currentStackMin, currentStackMax)
- } else {
- setBounds(nextPos+1, min(nextMin, currentStackMin), max(nextMax, currentStackMax))
- }
- }
- case RJUMPI:
- arg := parseInt16(code[pos+1:])
- next = append(next, pos+2)
- next = append(next, pos+2+arg)
- case RJUMPV:
- count := int(code[pos+1]) + 1
- next = append(next, pos+1+2*count)
- for i := 0; i < count; i++ {
- arg := parseInt16(code[pos+2+2*i:])
- next = append(next, pos+1+2*count+arg)
- }
- default:
- if imm := int(immediates[op]); imm != 0 {
- next = append(next, pos+imm)
- } else {
- // Simple op, no operand.
- next = append(next, pos)
- }
- }
-
- if op != RJUMP && !terminals[op] {
- for _, instr := range next {
- nextPC := instr + 1
- if nextPC >= len(code) {
- return 0, fmt.Errorf("%w: end with %s, pos %d", errInvalidCodeTermination, op, pos)
- }
- if nextPC > pos {
- // target reached via forward jump or seq flow
- ok, nextMin, nextMax := getStackMaxMin(nextPC)
- if !ok {
- setBounds(nextPC, currentStackMin, currentStackMax)
- } else {
- setBounds(nextPC, min(nextMin, currentStackMin), max(nextMax, currentStackMax))
- }
- } else {
- // target reached via backwards jump
- ok, nextMin, nextMax := getStackMaxMin(nextPC)
- if !ok {
- return 0, errInvalidBackwardJump
- }
- if currentStackMax != nextMax {
- return 0, fmt.Errorf("%w want %d as current max got %d at pos %d,", errInvalidBackwardJump, currentStackMax, nextMax, pos)
- }
- if currentStackMin != nextMin {
- return 0, fmt.Errorf("%w want %d as current min got %d at pos %d,", errInvalidBackwardJump, currentStackMin, nextMin, pos)
- }
- }
- }
- }
-
- if op == RJUMP {
- pos += 2 // skip the immediate
- } else {
- pos = next[0]
- }
- }
- if qualifiedExit != (metadata[section].outputs < maxOutputItems) {
- return 0, fmt.Errorf("%w no RETF or qualified JUMPF", errInvalidNonReturningFlag)
- }
- if maxStackHeight >= int(params.StackLimit) {
- return 0, ErrStackOverflow{maxStackHeight, int(params.StackLimit)}
- }
- if maxStackHeight != int(metadata[section].maxStackHeight) {
- return 0, fmt.Errorf("%w in code section %d: have %d, want %d", errInvalidMaxStackHeight, section, maxStackHeight, metadata[section].maxStackHeight)
- }
- return visitCount, nil
-}
diff --git a/core/vm/eof_immediates.go b/core/vm/eof_immediates.go
deleted file mode 100644
index 9cb7d999a2..0000000000
--- a/core/vm/eof_immediates.go
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright 2024 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
-
-// immediate denotes how many immediate bytes an operation uses. This information
-// is not required during runtime, only during EOF-validation, so is not
-// places into the op-struct in the instruction table.
-// Note: the immediates is fork-agnostic, and assumes that validity of opcodes at
-// the given time is performed elsewhere.
-var immediates [256]uint8
-
-// terminals denotes whether instructions can be the final opcode in a code section.
-// Note: the terminals is fork-agnostic, and assumes that validity of opcodes at
-// the given time is performed elsewhere.
-var terminals [256]bool
-
-func init() {
- // The legacy pushes
- for i := uint8(1); i < 33; i++ {
- immediates[int(PUSH0)+int(i)] = i
- }
- // And new eof opcodes.
- immediates[DATALOADN] = 2
- immediates[RJUMP] = 2
- immediates[RJUMPI] = 2
- immediates[RJUMPV] = 3
- immediates[CALLF] = 2
- immediates[JUMPF] = 2
- immediates[DUPN] = 1
- immediates[SWAPN] = 1
- immediates[EXCHANGE] = 1
- immediates[EOFCREATE] = 1
- immediates[RETURNCONTRACT] = 1
-
- // Define the terminals.
- terminals[STOP] = true
- terminals[RETF] = true
- terminals[JUMPF] = true
- terminals[RETURNCONTRACT] = true
- terminals[RETURN] = true
- terminals[REVERT] = true
- terminals[INVALID] = true
-}
-
-// Immediates returns the number bytes of immediates (argument not from
-// stack but from code) a given opcode has.
-// OBS:
-// - This function assumes EOF instruction-set. It cannot be upon in
-// a. pre-EOF code
-// b. post-EOF but legacy code
-// - RJUMPV is unique as it has a variable sized operand. The total size is
-// determined by the count byte which immediately follows RJUMPV. This method
-// will return '3' for RJUMPV, which is the minimum.
-func Immediates(op OpCode) int {
- return int(immediates[op])
-}
diff --git a/core/vm/eof_instructions.go b/core/vm/eof_instructions.go
deleted file mode 100644
index 800d14d7b8..0000000000
--- a/core/vm/eof_instructions.go
+++ /dev/null
@@ -1,112 +0,0 @@
-// Copyright 2024 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
-
-// opRjump implements the RJUMP opcode.
-func opRjump(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
- panic("not implemented")
-}
-
-// opRjumpi implements the RJUMPI opcode
-func opRjumpi(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
- panic("not implemented")
-}
-
-// opRjumpv implements the RJUMPV opcode
-func opRjumpv(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
- panic("not implemented")
-}
-
-// opCallf implements the CALLF opcode
-func opCallf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
- panic("not implemented")
-}
-
-// opRetf implements the RETF opcode
-func opRetf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
- panic("not implemented")
-}
-
-// opJumpf implements the JUMPF opcode
-func opJumpf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
- panic("not implemented")
-}
-
-// opEOFCreate implements the EOFCREATE opcode
-func opEOFCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
- panic("not implemented")
-}
-
-// opReturnContract implements the RETURNCONTRACT opcode
-func opReturnContract(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
- panic("not implemented")
-}
-
-// opDataLoad implements the DATALOAD opcode
-func opDataLoad(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
- panic("not implemented")
-}
-
-// opDataLoadN implements the DATALOADN opcode
-func opDataLoadN(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
- panic("not implemented")
-}
-
-// opDataSize implements the DATASIZE opcode
-func opDataSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
- panic("not implemented")
-}
-
-// opDataCopy implements the DATACOPY opcode
-func opDataCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
- panic("not implemented")
-}
-
-// opDupN implements the DUPN opcode
-func opDupN(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
- panic("not implemented")
-}
-
-// opSwapN implements the SWAPN opcode
-func opSwapN(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
- panic("not implemented")
-}
-
-// opExchange implements the EXCHANGE opcode
-func opExchange(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
- panic("not implemented")
-}
-
-// opReturnDataLoad implements the RETURNDATALOAD opcode
-func opReturnDataLoad(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
- panic("not implemented")
-}
-
-// opExtCall implements the EOFCREATE opcode
-func opExtCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
- panic("not implemented")
-}
-
-// opExtDelegateCall implements the EXTDELEGATECALL opcode
-func opExtDelegateCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
- panic("not implemented")
-}
-
-// opExtStaticCall implements the EXTSTATICCALL opcode
-func opExtStaticCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
- panic("not implemented")
-}
diff --git a/core/vm/eof_test.go b/core/vm/eof_test.go
deleted file mode 100644
index 0a9cf638ce..0000000000
--- a/core/vm/eof_test.go
+++ /dev/null
@@ -1,117 +0,0 @@
-// Copyright 2024 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package vm
-
-import (
- "encoding/hex"
- "reflect"
- "testing"
-
- "github.com/ethereum/go-ethereum/common"
-)
-
-func TestEOFMarshaling(t *testing.T) {
- for i, test := range []struct {
- want Container
- err error
- }{
- {
- want: Container{
- types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
- codeSections: [][]byte{common.Hex2Bytes("604200")},
- data: []byte{0x01, 0x02, 0x03},
- dataSize: 3,
- },
- },
- {
- want: Container{
- types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
- codeSections: [][]byte{common.Hex2Bytes("604200")},
- data: []byte{0x01, 0x02, 0x03},
- dataSize: 3,
- },
- },
- {
- want: Container{
- types: []*functionMetadata{
- {inputs: 0, outputs: 0x80, maxStackHeight: 1},
- {inputs: 2, outputs: 3, maxStackHeight: 4},
- {inputs: 1, outputs: 1, maxStackHeight: 1},
- },
- codeSections: [][]byte{
- common.Hex2Bytes("604200"),
- common.Hex2Bytes("6042604200"),
- common.Hex2Bytes("00"),
- },
- data: []byte{},
- },
- },
- } {
- var (
- b = test.want.MarshalBinary()
- got Container
- )
- t.Logf("b: %#x", b)
- if err := got.UnmarshalBinary(b, true); err != nil && err != test.err {
- t.Fatalf("test %d: got error \"%v\", want \"%v\"", i, err, test.err)
- }
- if !reflect.DeepEqual(got, test.want) {
- t.Fatalf("test %d: got %+v, want %+v", i, got, test.want)
- }
- }
-}
-
-func TestEOFSubcontainer(t *testing.T) {
- var subcontainer = new(Container)
- if err := subcontainer.UnmarshalBinary(common.Hex2Bytes("ef000101000402000100010400000000800000fe"), true); err != nil {
- t.Fatal(err)
- }
- container := Container{
- types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
- codeSections: [][]byte{common.Hex2Bytes("604200")},
- subContainers: []*Container{subcontainer},
- data: []byte{0x01, 0x02, 0x03},
- dataSize: 3,
- }
- var (
- b = container.MarshalBinary()
- got Container
- )
- if err := got.UnmarshalBinary(b, true); err != nil {
- t.Fatal(err)
- }
- if res := got.MarshalBinary(); !reflect.DeepEqual(res, b) {
- t.Fatalf("invalid marshalling, want %v got %v", b, res)
- }
-}
-
-func TestMarshaling(t *testing.T) {
- tests := []string{
- "EF000101000402000100040400000000800000E0000000",
- "ef0001010004020001000d04000000008000025fe100055f5fe000035f600100",
- }
- for i, test := range tests {
- s, err := hex.DecodeString(test)
- if err != nil {
- t.Fatalf("test %d: error decoding: %v", i, err)
- }
- var got Container
- if err := got.UnmarshalBinary(s, true); err != nil {
- t.Fatalf("test %d: got error %v", i, err)
- }
- }
-}
diff --git a/core/vm/eof_validation.go b/core/vm/eof_validation.go
deleted file mode 100644
index 514f9fb58c..0000000000
--- a/core/vm/eof_validation.go
+++ /dev/null
@@ -1,255 +0,0 @@
-// Copyright 2024 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package vm
-
-import (
- "errors"
- "fmt"
- "io"
-)
-
-// Below are all possible errors that can occur during validation of
-// EOF containers.
-var (
- errInvalidMagic = errors.New("invalid magic")
- errUndefinedInstruction = errors.New("undefined instruction")
- errTruncatedImmediate = errors.New("truncated immediate")
- errInvalidSectionArgument = errors.New("invalid section argument")
- errInvalidCallArgument = errors.New("callf into non-returning section")
- errInvalidDataloadNArgument = errors.New("invalid dataloadN argument")
- errInvalidJumpDest = errors.New("invalid jump destination")
- errInvalidBackwardJump = errors.New("invalid backward jump")
- errInvalidOutputs = errors.New("invalid number of outputs")
- errInvalidMaxStackHeight = errors.New("invalid max stack height")
- errInvalidCodeTermination = errors.New("invalid code termination")
- errEOFCreateWithTruncatedSection = errors.New("eofcreate with truncated section")
- errOrphanedSubcontainer = errors.New("subcontainer not referenced at all")
- errIncompatibleContainerKind = errors.New("incompatible container kind")
- errStopAndReturnContract = errors.New("Stop/Return and Returncontract in the same code section")
- errStopInInitCode = errors.New("initcode contains a RETURN or STOP opcode")
- errTruncatedTopLevelContainer = errors.New("truncated top level container")
- errUnreachableCode = errors.New("unreachable code")
- errInvalidNonReturningFlag = errors.New("invalid non-returning flag, bad RETF")
- errInvalidVersion = errors.New("invalid version")
- errMissingTypeHeader = errors.New("missing type header")
- errInvalidTypeSize = errors.New("invalid type section size")
- errMissingCodeHeader = errors.New("missing code header")
- errInvalidCodeSize = errors.New("invalid code size")
- errInvalidContainerSectionSize = errors.New("invalid container section size")
- errMissingDataHeader = errors.New("missing data header")
- errMissingTerminator = errors.New("missing header terminator")
- errTooManyInputs = errors.New("invalid type content, too many inputs")
- errTooManyOutputs = errors.New("invalid type content, too many outputs")
- errInvalidSection0Type = errors.New("invalid section 0 type, input and output should be zero and non-returning (0x80)")
- errTooLargeMaxStackHeight = errors.New("invalid type content, max stack height exceeds limit")
- errInvalidContainerSize = errors.New("invalid container size")
-)
-
-const (
- notRefByEither = iota
- refByReturnContract
- refByEOFCreate
-)
-
-type validationResult struct {
- visitedCode map[int]struct{}
- visitedSubContainers map[int]int
- isInitCode bool
- isRuntime bool
-}
-
-// validateCode validates the code parameter against the EOF v1 validity requirements.
-func validateCode(code []byte, section int, container *Container, jt *JumpTable, isInitCode bool) (*validationResult, error) {
- var (
- i = 0
- // Tracks the number of actual instructions in the code (e.g.
- // non-immediate values). This is used at the end to determine
- // if each instruction is reachable.
- count = 0
- op OpCode
- analysis bitvec
- visitedCode map[int]struct{}
- visitedSubcontainers map[int]int
- hasReturnContract bool
- hasStop bool
- )
- // This loop visits every single instruction and verifies:
- // * if the instruction is valid for the given jump table.
- // * if the instruction has an immediate value, it is not truncated.
- // * if performing a relative jump, all jump destinations are valid.
- // * if changing code sections, the new code section index is valid and
- // will not cause a stack overflow.
- for i < len(code) {
- count++
- op = OpCode(code[i])
- if jt[op].undefined {
- return nil, fmt.Errorf("%w: op %s, pos %d", errUndefinedInstruction, op, i)
- }
- size := int(immediates[op])
- if size != 0 && len(code) <= i+size {
- return nil, fmt.Errorf("%w: op %s, pos %d", errTruncatedImmediate, op, i)
- }
- switch op {
- case RJUMP, RJUMPI:
- if err := checkDest(code, &analysis, i+1, i+3, len(code)); err != nil {
- return nil, err
- }
- case RJUMPV:
- maxSize := int(code[i+1])
- length := maxSize + 1
- if len(code) <= i+length {
- return nil, fmt.Errorf("%w: jump table truncated, op %s, pos %d", errTruncatedImmediate, op, i)
- }
- offset := i + 2
- for j := 0; j < length; j++ {
- if err := checkDest(code, &analysis, offset+j*2, offset+(length*2), len(code)); err != nil {
- return nil, err
- }
- }
- i += 2 * maxSize
- case CALLF:
- arg, _ := parseUint16(code[i+1:])
- if arg >= len(container.types) {
- return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", errInvalidSectionArgument, arg, len(container.types), i)
- }
- if container.types[arg].outputs == 0x80 {
- return nil, fmt.Errorf("%w: section %v", errInvalidCallArgument, arg)
- }
- if visitedCode == nil {
- visitedCode = make(map[int]struct{})
- }
- visitedCode[arg] = struct{}{}
- case JUMPF:
- arg, _ := parseUint16(code[i+1:])
- if arg >= len(container.types) {
- return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", errInvalidSectionArgument, arg, len(container.types), i)
- }
- if container.types[arg].outputs != 0x80 && container.types[arg].outputs > container.types[section].outputs {
- return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", errInvalidOutputs, arg, len(container.types), i)
- }
- if visitedCode == nil {
- visitedCode = make(map[int]struct{})
- }
- visitedCode[arg] = struct{}{}
- case DATALOADN:
- arg, _ := parseUint16(code[i+1:])
- // TODO why are we checking this? We should just pad
- if arg+32 > len(container.data) {
- return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", errInvalidDataloadNArgument, arg, len(container.data), i)
- }
- case RETURNCONTRACT:
- if !isInitCode {
- return nil, errIncompatibleContainerKind
- }
- arg := int(code[i+1])
- if arg >= len(container.subContainers) {
- return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", errUnreachableCode, arg, len(container.subContainers), i)
- }
- if visitedSubcontainers == nil {
- visitedSubcontainers = make(map[int]int)
- }
- // We need to store per subcontainer how it was referenced
- if v, ok := visitedSubcontainers[arg]; ok && v != refByReturnContract {
- return nil, fmt.Errorf("section already referenced, arg :%d", arg)
- }
- if hasStop {
- return nil, errStopAndReturnContract
- }
- hasReturnContract = true
- visitedSubcontainers[arg] = refByReturnContract
- case EOFCREATE:
- arg := int(code[i+1])
- if arg >= len(container.subContainers) {
- return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", errUnreachableCode, arg, len(container.subContainers), i)
- }
- if ct := container.subContainers[arg]; len(ct.data) != ct.dataSize {
- return nil, fmt.Errorf("%w: container %d, have %d, claimed %d, pos %d", errEOFCreateWithTruncatedSection, arg, len(ct.data), ct.dataSize, i)
- }
- if visitedSubcontainers == nil {
- visitedSubcontainers = make(map[int]int)
- }
- // We need to store per subcontainer how it was referenced
- if v, ok := visitedSubcontainers[arg]; ok && v != refByEOFCreate {
- return nil, fmt.Errorf("section already referenced, arg :%d", arg)
- }
- visitedSubcontainers[arg] = refByEOFCreate
- case STOP, RETURN:
- if isInitCode {
- return nil, errStopInInitCode
- }
- if hasReturnContract {
- return nil, errStopAndReturnContract
- }
- hasStop = true
- }
- i += size + 1
- }
- // Code sections may not "fall through" and require proper termination.
- // Therefore, the last instruction must be considered terminal or RJUMP.
- if !terminals[op] && op != RJUMP {
- return nil, fmt.Errorf("%w: end with %s, pos %d", errInvalidCodeTermination, op, i)
- }
- if paths, err := validateControlFlow(code, section, container.types, jt); err != nil {
- return nil, err
- } else if paths != count {
- // TODO(matt): return actual position of unreachable code
- return nil, errUnreachableCode
- }
- return &validationResult{
- visitedCode: visitedCode,
- visitedSubContainers: visitedSubcontainers,
- isInitCode: hasReturnContract,
- isRuntime: hasStop,
- }, nil
-}
-
-// checkDest parses a relative offset at code[0:2] and checks if it is a valid jump destination.
-func checkDest(code []byte, analysis *bitvec, imm, from, length int) error {
- if len(code) < imm+2 {
- return io.ErrUnexpectedEOF
- }
- if analysis != nil && *analysis == nil {
- *analysis = eofCodeBitmap(code)
- }
- offset := parseInt16(code[imm:])
- dest := from + offset
- if dest < 0 || dest >= length {
- return fmt.Errorf("%w: out-of-bounds offset: offset %d, dest %d, pos %d", errInvalidJumpDest, offset, dest, imm)
- }
- if !analysis.codeSegment(uint64(dest)) {
- return fmt.Errorf("%w: offset into immediate: offset %d, dest %d, pos %d", errInvalidJumpDest, offset, dest, imm)
- }
- return nil
-}
-
-//// disasm is a helper utility to show a sequence of comma-separated operations,
-//// with immediates shown inline,
-//// e.g: PUSH1(0x00),EOFCREATE(0x00),
-//func disasm(code []byte) string {
-// var ops []string
-// for i := 0; i < len(code); i++ {
-// var op string
-// if args := immediates[code[i]]; args > 0 {
-// op = fmt.Sprintf("%v(%#x)", OpCode(code[i]).String(), code[i+1:i+1+int(args)])
-// i += int(args)
-// } else {
-// op = OpCode(code[i]).String()
-// }
-// ops = append(ops, op)
-// }
-// return strings.Join(ops, ",")
-//}
diff --git a/core/vm/eof_validation_test.go b/core/vm/eof_validation_test.go
deleted file mode 100644
index f7b0e78f26..0000000000
--- a/core/vm/eof_validation_test.go
+++ /dev/null
@@ -1,517 +0,0 @@
-// Copyright 2024 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package vm
-
-import (
- "encoding/binary"
- "errors"
- "testing"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/params"
-)
-
-func TestValidateCode(t *testing.T) {
- for i, test := range []struct {
- code []byte
- section int
- metadata []*functionMetadata
- err error
- }{
- {
- code: []byte{
- byte(CALLER),
- byte(POP),
- byte(STOP),
- },
- section: 0,
- metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
- },
- {
- code: []byte{
- byte(CALLF), 0x00, 0x00,
- byte(RETF),
- },
- section: 0,
- metadata: []*functionMetadata{{inputs: 0, outputs: 0, maxStackHeight: 0}},
- },
- {
- code: []byte{
- byte(ADDRESS),
- byte(CALLF), 0x00, 0x00,
- byte(POP),
- byte(RETF),
- },
- section: 0,
- metadata: []*functionMetadata{{inputs: 0, outputs: 0, maxStackHeight: 1}},
- },
- {
- code: []byte{
- byte(CALLER),
- byte(POP),
- },
- section: 0,
- metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
- err: errInvalidCodeTermination,
- },
- {
- code: []byte{
- byte(RJUMP),
- byte(0x00),
- byte(0x01),
- byte(CALLER),
- byte(STOP),
- },
- section: 0,
- metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 0}},
- err: errUnreachableCode,
- },
- {
- code: []byte{
- byte(PUSH1),
- byte(0x42),
- byte(ADD),
- byte(STOP),
- },
- section: 0,
- metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
- err: ErrStackUnderflow{stackLen: 1, required: 2},
- },
- {
- code: []byte{
- byte(PUSH1),
- byte(0x42),
- byte(POP),
- byte(STOP),
- },
- section: 0,
- metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 2}},
- err: errInvalidMaxStackHeight,
- },
- {
- code: []byte{
- byte(PUSH0),
- byte(RJUMPI),
- byte(0x00),
- byte(0x01),
- byte(PUSH1),
- byte(0x42), // jumps to here
- byte(POP),
- byte(STOP),
- },
- section: 0,
- metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
- err: errInvalidJumpDest,
- },
- {
- code: []byte{
- byte(PUSH0),
- byte(RJUMPV),
- byte(0x01),
- byte(0x00),
- byte(0x01),
- byte(0x00),
- byte(0x02),
- byte(PUSH1),
- byte(0x42), // jumps to here
- byte(POP), // and here
- byte(STOP),
- },
- section: 0,
- metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
- err: errInvalidJumpDest,
- },
- {
- code: []byte{
- byte(PUSH0),
- byte(RJUMPV),
- byte(0x00),
- byte(STOP),
- },
- section: 0,
- metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
- err: errTruncatedImmediate,
- },
- {
- code: []byte{
- byte(RJUMP), 0x00, 0x03,
- byte(JUMPDEST), // this code is unreachable to forward jumps alone
- byte(JUMPDEST),
- byte(RETURN),
- byte(PUSH1), 20,
- byte(PUSH1), 39,
- byte(PUSH1), 0x00,
- byte(DATACOPY),
- byte(PUSH1), 20,
- byte(PUSH1), 0x00,
- byte(RJUMP), 0xff, 0xef,
- },
- section: 0,
- metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 3}},
- err: errUnreachableCode,
- },
- {
- code: []byte{
- byte(PUSH1), 1,
- byte(RJUMPI), 0x00, 0x03,
- byte(JUMPDEST),
- byte(JUMPDEST),
- byte(STOP),
- byte(PUSH1), 20,
- byte(PUSH1), 39,
- byte(PUSH1), 0x00,
- byte(DATACOPY),
- byte(PUSH1), 20,
- byte(PUSH1), 0x00,
- byte(RETURN),
- },
- section: 0,
- metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 3}},
- },
- {
- code: []byte{
- byte(PUSH1), 1,
- byte(RJUMPV), 0x01, 0x00, 0x03, 0xff, 0xf8,
- byte(JUMPDEST),
- byte(JUMPDEST),
- byte(STOP),
- byte(PUSH1), 20,
- byte(PUSH1), 39,
- byte(PUSH1), 0x00,
- byte(DATACOPY),
- byte(PUSH1), 20,
- byte(PUSH1), 0x00,
- byte(RETURN),
- },
- section: 0,
- metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 3}},
- },
- {
- code: []byte{
- byte(STOP),
- byte(STOP),
- byte(INVALID),
- },
- section: 0,
- metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 0}},
- err: errUnreachableCode,
- },
- {
- code: []byte{
- byte(RETF),
- },
- section: 0,
- metadata: []*functionMetadata{{inputs: 0, outputs: 1, maxStackHeight: 0}},
- err: errInvalidOutputs,
- },
- {
- code: []byte{
- byte(RETF),
- },
- section: 0,
- metadata: []*functionMetadata{{inputs: 3, outputs: 3, maxStackHeight: 3}},
- },
- {
- code: []byte{
- byte(CALLF), 0x00, 0x01,
- byte(POP),
- byte(STOP),
- },
- section: 0,
- metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}, {inputs: 0, outputs: 1, maxStackHeight: 0}},
- },
- {
- code: []byte{
- byte(ORIGIN),
- byte(ORIGIN),
- byte(CALLF), 0x00, 0x01,
- byte(POP),
- byte(RETF),
- },
- section: 0,
- metadata: []*functionMetadata{{inputs: 0, outputs: 0, maxStackHeight: 2}, {inputs: 2, outputs: 1, maxStackHeight: 2}},
- },
- } {
- container := &Container{
- types: test.metadata,
- data: make([]byte, 0),
- subContainers: make([]*Container, 0),
- }
- _, err := validateCode(test.code, test.section, container, &eofInstructionSet, false)
- if !errors.Is(err, test.err) {
- t.Errorf("test %d (%s): unexpected error (want: %v, got: %v)", i, common.Bytes2Hex(test.code), test.err, err)
- }
- }
-}
-
-// BenchmarkRJUMPI tries to benchmark the RJUMPI opcode validation
-// For this we do a bunch of RJUMPIs that jump backwards (in a potential infinite loop).
-func BenchmarkRJUMPI(b *testing.B) {
- snippet := []byte{
- byte(PUSH0),
- byte(RJUMPI), 0xFF, 0xFC,
- }
- code := []byte{}
- for i := 0; i < params.MaxCodeSize/len(snippet)-1; i++ {
- code = append(code, snippet...)
- }
- code = append(code, byte(STOP))
- container := &Container{
- types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
- data: make([]byte, 0),
- subContainers: make([]*Container, 0),
- }
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- _, err := validateCode(code, 0, container, &eofInstructionSet, false)
- if err != nil {
- b.Fatal(err)
- }
- }
-}
-
-// BenchmarkRJUMPV tries to benchmark the validation of the RJUMPV opcode
-// for this we set up as many RJUMPV opcodes with a full jumptable (containing 0s) as possible.
-func BenchmarkRJUMPV(b *testing.B) {
- snippet := []byte{
- byte(PUSH0),
- byte(RJUMPV),
- 0xff, // count
- 0x00, 0x00,
- }
- for i := 0; i < 255; i++ {
- snippet = append(snippet, []byte{0x00, 0x00}...)
- }
- code := []byte{}
- for i := 0; i < 24576/len(snippet)-1; i++ {
- code = append(code, snippet...)
- }
- code = append(code, byte(PUSH0))
- code = append(code, byte(STOP))
- container := &Container{
- types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
- data: make([]byte, 0),
- subContainers: make([]*Container, 0),
- }
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- _, err := validateCode(code, 0, container, &pragueInstructionSet, false)
- if err != nil {
- b.Fatal(err)
- }
- }
-}
-
-// BenchmarkEOFValidation tries to benchmark the code validation for the CALLF/RETF operation.
-// For this we set up code that calls into 1024 code sections which can either
-// - just contain a RETF opcode
-// - or code to again call into 1024 code sections.
-// We can't have all code sections calling each other, otherwise we would exceed 48KB.
-func BenchmarkEOFValidation(b *testing.B) {
- var container Container
- var code []byte
- maxSections := 1024
- for i := 0; i < maxSections; i++ {
- code = append(code, byte(CALLF))
- code = binary.BigEndian.AppendUint16(code, uint16(i%(maxSections-1))+1)
- }
- // First container
- container.codeSections = append(container.codeSections, append(code, byte(STOP)))
- container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: 0})
-
- inner := []byte{
- byte(RETF),
- }
-
- for i := 0; i < 1023; i++ {
- container.codeSections = append(container.codeSections, inner)
- container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0, maxStackHeight: 0})
- }
-
- for i := 0; i < 12; i++ {
- container.codeSections[i+1] = append(code, byte(RETF))
- }
-
- bin := container.MarshalBinary()
- if len(bin) > 48*1024 {
- b.Fatal("Exceeds 48Kb")
- }
-
- var container2 Container
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- if err := container2.UnmarshalBinary(bin, true); err != nil {
- b.Fatal(err)
- }
- if err := container2.ValidateCode(&pragueInstructionSet, false); err != nil {
- b.Fatal(err)
- }
- }
-}
-
-// BenchmarkEOFValidation2 tries to benchmark the code validation for the CALLF/RETF operation.
-// For this we set up code that calls into 1024 code sections which
-// - contain calls to some other code sections.
-// We can't have all code sections calling each other, otherwise we would exceed 48KB.
-func BenchmarkEOFValidation2(b *testing.B) {
- var container Container
- var code []byte
- maxSections := 1024
- for i := 0; i < maxSections; i++ {
- code = append(code, byte(CALLF))
- code = binary.BigEndian.AppendUint16(code, uint16(i%(maxSections-1))+1)
- }
- code = append(code, byte(STOP))
- // First container
- container.codeSections = append(container.codeSections, code)
- container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: 0})
-
- inner := []byte{
- byte(CALLF), 0x03, 0xE8,
- byte(CALLF), 0x03, 0xE9,
- byte(CALLF), 0x03, 0xF0,
- byte(CALLF), 0x03, 0xF1,
- byte(CALLF), 0x03, 0xF2,
- byte(CALLF), 0x03, 0xF3,
- byte(CALLF), 0x03, 0xF4,
- byte(CALLF), 0x03, 0xF5,
- byte(CALLF), 0x03, 0xF6,
- byte(CALLF), 0x03, 0xF7,
- byte(CALLF), 0x03, 0xF8,
- byte(CALLF), 0x03, 0xF,
- byte(RETF),
- }
-
- for i := 0; i < 1023; i++ {
- container.codeSections = append(container.codeSections, inner)
- container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0, maxStackHeight: 0})
- }
-
- bin := container.MarshalBinary()
- if len(bin) > 48*1024 {
- b.Fatal("Exceeds 48Kb")
- }
-
- var container2 Container
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- if err := container2.UnmarshalBinary(bin, true); err != nil {
- b.Fatal(err)
- }
- if err := container2.ValidateCode(&pragueInstructionSet, false); err != nil {
- b.Fatal(err)
- }
- }
-}
-
-// BenchmarkEOFValidation3 tries to benchmark the code validation for the CALLF/RETF and RJUMPI/V operations.
-// For this we set up code that calls into 1024 code sections which either
-// - contain an RJUMP opcode
-// - contain calls to other code sections
-// We can't have all code sections calling each other, otherwise we would exceed 48KB.
-func BenchmarkEOFValidation3(b *testing.B) {
- var container Container
- var code []byte
- snippet := []byte{
- byte(PUSH0),
- byte(RJUMPV),
- 0xff, // count
- 0x00, 0x00,
- }
- for i := 0; i < 255; i++ {
- snippet = append(snippet, []byte{0x00, 0x00}...)
- }
- code = append(code, snippet...)
- // First container, calls into all other containers
- maxSections := 1024
- for i := 0; i < maxSections; i++ {
- code = append(code, byte(CALLF))
- code = binary.BigEndian.AppendUint16(code, uint16(i%(maxSections-1))+1)
- }
- code = append(code, byte(STOP))
- container.codeSections = append(container.codeSections, code)
- container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: 1})
-
- // Other containers
- for i := 0; i < 1023; i++ {
- container.codeSections = append(container.codeSections, []byte{byte(RJUMP), 0x00, 0x00, byte(RETF)})
- container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0, maxStackHeight: 0})
- }
- // Other containers
- for i := 0; i < 68; i++ {
- container.codeSections[i+1] = append(snippet, byte(RETF))
- container.types[i+1] = &functionMetadata{inputs: 0, outputs: 0, maxStackHeight: 1}
- }
- bin := container.MarshalBinary()
- if len(bin) > 48*1024 {
- b.Fatal("Exceeds 48Kb")
- }
- b.ResetTimer()
- b.ReportMetric(float64(len(bin)), "bytes")
- for i := 0; i < b.N; i++ {
- for k := 0; k < 40; k++ {
- var container2 Container
- if err := container2.UnmarshalBinary(bin, true); err != nil {
- b.Fatal(err)
- }
- if err := container2.ValidateCode(&pragueInstructionSet, false); err != nil {
- b.Fatal(err)
- }
- }
- }
-}
-
-func BenchmarkRJUMPI_2(b *testing.B) {
- code := []byte{
- byte(PUSH0),
- byte(RJUMPI), 0xFF, 0xFC,
- }
- for i := 0; i < params.MaxCodeSize/4-1; i++ {
- code = append(code, byte(PUSH0))
- x := -4 * i
- code = append(code, byte(RJUMPI))
- code = binary.BigEndian.AppendUint16(code, uint16(x))
- }
- code = append(code, byte(STOP))
- container := &Container{
- types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
- data: make([]byte, 0),
- subContainers: make([]*Container, 0),
- }
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- _, err := validateCode(code, 0, container, &pragueInstructionSet, false)
- if err != nil {
- b.Fatal(err)
- }
- }
-}
-
-func FuzzUnmarshalBinary(f *testing.F) {
- f.Fuzz(func(_ *testing.T, input []byte) {
- var container Container
- container.UnmarshalBinary(input, true)
- })
-}
-
-func FuzzValidate(f *testing.F) {
- f.Fuzz(func(_ *testing.T, code []byte, maxStack uint16) {
- var container Container
- container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: maxStack})
- validateCode(code, 0, &container, &pragueInstructionSet, true)
- })
-}
diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go
index f711aa4a18..58f039df9f 100644
--- a/core/vm/gas_table.go
+++ b/core/vm/gas_table.go
@@ -485,20 +485,3 @@ func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me
}
return gas, nil
}
-
-func gasExtCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
- panic("not implemented")
-}
-
-func gasExtDelegateCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
- panic("not implemented")
-}
-func gasExtStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
- panic("not implemented")
-}
-
-// gasEOFCreate returns the gas-cost for EOF-Create. Hashing charge needs to be
-// deducted in the opcode itself, since it depends on the immediate
-func gasEOFCreate(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
- panic("not implemented")
-}
diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go
index 26b9473fe8..17ac738c98 100644
--- a/core/vm/jump_table.go
+++ b/core/vm/jump_table.go
@@ -62,7 +62,6 @@ var (
cancunInstructionSet = newCancunInstructionSet()
verkleInstructionSet = newVerkleInstructionSet()
pragueInstructionSet = newPragueInstructionSet()
- eofInstructionSet = newEOFInstructionSetForTesting()
)
// JumpTable contains the EVM opcodes supported at a given fork.
@@ -92,16 +91,6 @@ func newVerkleInstructionSet() JumpTable {
return validate(instructionSet)
}
-func NewEOFInstructionSetForTesting() JumpTable {
- return newEOFInstructionSetForTesting()
-}
-
-func newEOFInstructionSetForTesting() JumpTable {
- instructionSet := newPragueInstructionSet()
- enableEOF(&instructionSet)
- return validate(instructionSet)
-}
-
func newPragueInstructionSet() JumpTable {
instructionSet := newCancunInstructionSet()
enable7702(&instructionSet) // EIP-7702 Setcode transaction type
diff --git a/core/vm/memory_table.go b/core/vm/memory_table.go
index 28746042cf..63ad967850 100644
--- a/core/vm/memory_table.go
+++ b/core/vm/memory_table.go
@@ -120,19 +120,3 @@ func memoryRevert(stack *Stack) (uint64, bool) {
func memoryLog(stack *Stack) (uint64, bool) {
return calcMemSize64(stack.Back(0), stack.Back(1))
}
-
-func memoryExtCall(stack *Stack) (uint64, bool) {
- return calcMemSize64(stack.Back(1), stack.Back(2))
-}
-
-func memoryDataCopy(stack *Stack) (uint64, bool) {
- return calcMemSize64(stack.Back(0), stack.Back(2))
-}
-
-func memoryEOFCreate(stack *Stack) (uint64, bool) {
- return calcMemSize64(stack.Back(2), stack.Back(3))
-}
-
-func memoryReturnContract(stack *Stack) (uint64, bool) {
- return calcMemSize64(stack.Back(0), stack.Back(1))
-}