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)) -}