core/vm, cmd/evm: unexport errors, remove err-to-code mapping, new tests, last fix

Changes include:
- unexporting errors, removing error-to-code mapping
- new test vectors from execution-spec-tests v eip7692@v1.1.1
- remaining fix from @shemnon in https://github.com/MariusVanDerWijden/go-ethereum/pull/56
- core/vm: address review-comments - simplify code
- cmd/evm: move eofdump/eofparse into `evm` binary
- Also makes eofdump read from stdin if hex is not provided, and makes the output of eofdump a bit more eye-friendly.
- core/vm: refactor check in eof control-flow validation
- core/vm: refactor some control flow checks for readability
This commit is contained in:
Martin Holst Swende 2024-09-25 12:50:58 +02:00
parent f05c07b8fe
commit 03d36ac908
No known key found for this signature in database
GPG key ID: 683B438C05A5DDF0
14 changed files with 1509 additions and 1064 deletions

View file

@ -1,247 +0,0 @@
// Copyright 2023 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 <http://www.gnu.org/licenses/>.
package main
import (
"bufio"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
"strings"
"sync/atomic"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/urfave/cli/v2"
)
func init() {
jt = vm.NewPragueEOFInstructionSetForTesting()
}
var (
jt vm.JumpTable
errorMap = map[string]int{
io.ErrUnexpectedEOF.Error(): 1,
vm.ErrInvalidMagic.Error(): 2,
vm.ErrInvalidVersion.Error(): 3,
vm.ErrMissingTypeHeader.Error(): 4,
vm.ErrInvalidTypeSize.Error(): 5,
vm.ErrMissingCodeHeader.Error(): 6,
//vm.ErrInvalidCodeHeader.Error(): 7,
vm.ErrMissingDataHeader.Error(): 8,
vm.ErrMissingTerminator.Error(): 9,
vm.ErrTooManyInputs.Error(): 10,
vm.ErrTooManyOutputs.Error(): 11,
vm.ErrTooLargeMaxStackHeight.Error(): 12,
vm.ErrInvalidCodeSize.Error(): 13,
vm.ErrInvalidContainerSize.Error(): 14,
vm.ErrUndefinedInstruction.Error(): 15,
vm.ErrTruncatedImmediate.Error(): 16,
vm.ErrInvalidSectionArgument.Error(): 17,
vm.ErrInvalidJumpDest.Error(): 18,
//vm.ErrConflictingStack.Error(): 19,
//vm.ErrInvalidBranchCount.Error(): 20,
vm.ErrInvalidOutputs.Error(): 21,
vm.ErrInvalidMaxStackHeight.Error(): 22,
vm.ErrInvalidCodeTermination.Error(): 23,
vm.ErrUnreachableCode.Error(): 24,
}
initcode = "INITCODE"
)
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 eofParser(ctx *cli.Context) error {
// 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 {
if err2 := errors.Unwrap(err); err2 != nil {
err = err2
}
return fmt.Errorf("err(%d): %w", errorMap[err.Error()], err)
}
fmt.Println("OK")
return nil
}
// 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 atomic.Int32
passedTests atomic.Int32
)
if info, err := os.Stat(file); err != nil {
return err
} else if !info.IsDir() {
src, err := os.ReadFile(file)
if err != nil {
return err
}
_, _, err = ExecuteTest(src)
return err
} else {
err = filepath.Walk(file, func(path string, info fs.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
return nil
}
fmt.Printf("Executing Tests: %v\n", info.Name())
src, err := os.ReadFile(path)
if err != nil {
return err
}
passed, total, err := ExecuteTest(src)
passedTests.Add(int32(passed))
executedTests.Add(int32(total))
return err
})
if err != nil {
return err
}
fmt.Printf("Passed %v tests out of %v\n", passedTests.Load(), executedTests.Load())
return nil
}
}
// If neither are passed in, read input from stdin.
reader := bufio.NewReaderSize(os.Stdin, 1024*1024)
t, err := reader.ReadString('\n')
for err == nil {
l := len(t)
if l == 0 || t[0] == '#' {
continue
}
if t[l-1] == '\n' {
t = t[:l-1] // remove newline
}
if _, err := parseAndValidate(t, false); err != nil {
if err2 := errors.Unwrap(err); err2 != nil {
err = err2
}
fmt.Printf("err(%d): %v\n", errorMap[err.Error()], err)
} else {
fmt.Println("OK")
}
t, err = reader.ReadString('\n')
}
println(err.Error())
return nil
}
func ExecuteTest(src []byte) (int, int, error) {
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++
// TODO(matt): all tests currently run against
// shanghai EOF, add support for custom forks.
_, err := parseAndValidate(tt.Code, tt.ContainerKind == initcode)
if err2 := errors.Unwrap(err); err2 != nil {
err = err2
}
if r.Result && err != nil {
fmt.Fprintf(os.Stderr, "%s %s, %s: expected success, got %v\n", testsName, name, fork, err)
continue
}
if !r.Result && err == nil {
fmt.Fprintf(os.Stderr, "%s %s, %s: expected error %s, got %v\n", testsName, name, fork, r.Exception, err)
continue
}
/*
// TODO (MariusVanDerWijden) reenable once tests have a decent error format
if !r.Result && err != nil && r.Exception != err.Error() {
fmt.Fprintf(os.Stderr, "%s, %s: expected error %d, got: err(%d): %v\n", name, fork, r.Exception, errorMap[err.Error()], err)
continue
}
*/
passed++
}
}
}
fmt.Printf("%d/%d tests passed.\n", passed, total)
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 eofDump(ctx *cli.Context) error {
// If `--hex` is set, parse and validate the hex string argument.
if ctx.IsSet(HexFlag.Name) {
s := ctx.String(HexFlag.Name)
if len(s) >= 2 && strings.HasPrefix(s, "0x") {
s = s[2:]
}
b, err := hex.DecodeString(s)
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.Print(c.String())
return nil
}
return nil
}

View file

@ -1,65 +0,0 @@
package main
import (
"fmt"
"os"
"github.com/ethereum/go-ethereum/internal/debug"
"github.com/ethereum/go-ethereum/internal/flags"
"github.com/urfave/cli/v2"
)
var app = flags.NewApp("the evm command line interface")
var (
RefTestFlag = &cli.StringFlag{
Name: "test",
Usage: "Path to EOF validation reference test.",
}
HexFlag = &cli.StringFlag{
Name: "hex",
Usage: "single container data parse and validation",
}
)
var eofParserCommand = &cli.Command{
Name: "eofparser",
Aliases: []string{"eof"},
Usage: "parses hex eof container and returns validation errors (if any)",
Action: eofParser,
Flags: []cli.Flag{
HexFlag,
RefTestFlag,
},
}
var eofDumpCommand = &cli.Command{
Name: "eofdump",
Usage: "parses hex eof container",
Action: eofDump,
Flags: []cli.Flag{
HexFlag,
},
}
func init() {
app.Commands = []*cli.Command{
eofParserCommand,
eofDumpCommand,
}
app.Before = func(ctx *cli.Context) error {
flags.MigrateGlobalFlags(ctx)
return debug.Setup(ctx)
}
app.After = func(ctx *cli.Context) error {
debug.Exit()
return nil
}
}
func main() {
if err := app.Run(os.Args); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}

200
cmd/evm/eofparse.go Normal file
View file

@ -0,0 +1,200 @@
// Copyright 2023 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 <http://www.gnu.org/licenses/>.
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"
)
func init() {
jt = vm.NewPragueEOFInstructionSetForTesting()
}
var (
jt vm.JumpTable
initcode = "INITCODE"
)
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
}

View file

@ -16,7 +16,7 @@ import (
func FuzzEofParsing(f *testing.F) { func FuzzEofParsing(f *testing.F) {
// Seed with corpus from execution-spec-tests // Seed with corpus from execution-spec-tests
for i := 0; ; i++ { for i := 0; ; i++ {
fname := fmt.Sprintf("testdata/eof_corpus_%d.txt", i) fname := fmt.Sprintf("testdata/eof/eof_corpus_%d.txt", i)
corpus, err := os.Open(fname) corpus, err := os.Open(fname)
if err != nil { if err != nil {
break break
@ -66,11 +66,11 @@ func FuzzEofParsing(f *testing.F) {
} }
func TestEofParseInitcode(t *testing.T) { func TestEofParseInitcode(t *testing.T) {
testEofParse(t, true, "testdata/results.initcode.txt") testEofParse(t, true, "testdata/eof/results.initcode.txt")
} }
func TestEofParseRegular(t *testing.T) { func TestEofParseRegular(t *testing.T) {
testEofParse(t, false, "testdata/results.regular.txt") testEofParse(t, false, "testdata/eof/results.regular.txt")
} }
func testEofParse(t *testing.T, isInitCode bool, wantFile string) { func testEofParse(t *testing.T, isInitCode bool, wantFile string) {
@ -93,7 +93,7 @@ func testEofParse(t *testing.T, isInitCode bool, wantFile string) {
} }
for i := 0; ; i++ { for i := 0; ; i++ {
fname := fmt.Sprintf("testdata/eof_corpus_%d.txt", i) fname := fmt.Sprintf("testdata/eof/eof_corpus_%d.txt", i)
corpus, err := os.Open(fname) corpus, err := os.Open(fname)
if err != nil { if err != nil {
break break
@ -126,7 +126,7 @@ func testEofParse(t *testing.T, isInitCode bool, wantFile string) {
if len(b) > 100 { if len(b) > 100 {
b = b[:100] b = b[:100]
} }
t.Fatalf("%v:%d\n%v\ninput %x\nisInit: %v\nhave: %q\nwant: %q\n", 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) fname, line, fmt.Sprintf("%v:%d", wantFile, wantLoc), b, isInitCode, have, want)
} }
} }
@ -137,7 +137,7 @@ func testEofParse(t *testing.T, isInitCode bool, wantFile string) {
} }
func BenchmarkEofParse(b *testing.B) { func BenchmarkEofParse(b *testing.B) {
corpus, err := os.Open("testdata/eof_benches.txt") corpus, err := os.Open("testdata/eof/eof_benches.txt")
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }

View file

@ -138,9 +138,18 @@ var (
Usage: "enable return data output", Usage: "enable return data output",
Category: flags.VMCategory, Category: flags.VMCategory,
} }
refTestFlag = &cli.StringFlag{
Name: "test",
Usage: "Path to EOF validation reference test.",
}
hexFlag = &cli.StringFlag{
Name: "hex",
Usage: "single container data parse and validation",
}
) )
var stateTransitionCommand = &cli.Command{ var (
stateTransitionCommand = &cli.Command{
Name: "transition", Name: "transition",
Aliases: []string{"t8n"}, Aliases: []string{"t8n"},
Usage: "Executes a full state transition", Usage: "Executes a full state transition",
@ -164,9 +173,9 @@ var stateTransitionCommand = &cli.Command{
t8ntool.ChainIDFlag, t8ntool.ChainIDFlag,
t8ntool.RewardFlag, t8ntool.RewardFlag,
}, },
} }
var transactionCommand = &cli.Command{ transactionCommand = &cli.Command{
Name: "transaction", Name: "transaction",
Aliases: []string{"t9n"}, Aliases: []string{"t9n"},
Usage: "Performs transaction validation", Usage: "Performs transaction validation",
@ -176,9 +185,9 @@ var transactionCommand = &cli.Command{
t8ntool.ChainIDFlag, t8ntool.ChainIDFlag,
t8ntool.ForknameFlag, t8ntool.ForknameFlag,
}, },
} }
var blockBuilderCommand = &cli.Command{ blockBuilderCommand = &cli.Command{
Name: "block-builder", Name: "block-builder",
Aliases: []string{"b11r"}, Aliases: []string{"b11r"},
Usage: "Builds a block", Usage: "Builds a block",
@ -192,7 +201,27 @@ var blockBuilderCommand = &cli.Command{
t8ntool.InputTxsRlpFlag, t8ntool.InputTxsRlpFlag,
t8ntool.SealCliqueFlag, t8ntool.SealCliqueFlag,
}, },
} }
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,
},
}
)
// vmFlags contains flags related to running the EVM. // vmFlags contains flags related to running the EVM.
var vmFlags = []cli.Flag{ var vmFlags = []cli.Flag{
@ -235,6 +264,8 @@ func init() {
stateTransitionCommand, stateTransitionCommand,
transactionCommand, transactionCommand,
blockBuilderCommand, blockBuilderCommand,
eofParseCommand,
eofDumpCommand,
} }
app.Before = func(ctx *cli.Context) error { app.Before = func(ctx *cli.Context) error {
flags.MigrateGlobalFlags(ctx) flags.MigrateGlobalFlags(ctx)

File diff suppressed because one or more lines are too long

View file

@ -19,10 +19,10 @@ package vm
import ( import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"encoding/hex"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"strings"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
) )
@ -81,6 +81,30 @@ type functionMetadata struct {
maxStackHeight uint16 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
// functin 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. // MarshalBinary encodes an EOF container into binary format.
func (c *Container) MarshalBinary() []byte { func (c *Container) MarshalBinary() []byte {
// Build EOF prefix. // Build EOF prefix.
@ -137,7 +161,7 @@ func (c *Container) UnmarshalSubContainer(b []byte, isInitcode bool) error {
func (c *Container) unmarshalContainer(b []byte, isInitcode bool, topLevel bool) error { func (c *Container) unmarshalContainer(b []byte, isInitcode bool, topLevel bool) error {
if !hasEOFMagic(b) { if !hasEOFMagic(b) {
return fmt.Errorf("%w: want %x", ErrInvalidMagic, eofMagic) return fmt.Errorf("%w: want %x", errInvalidMagic, eofMagic)
} }
if len(b) < 14 { if len(b) < 14 {
return io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
@ -146,7 +170,7 @@ func (c *Container) unmarshalContainer(b []byte, isInitcode bool, topLevel bool)
return ErrMaxInitCodeSizeExceeded return ErrMaxInitCodeSizeExceeded
} }
if !isEOFVersion1(b) { if !isEOFVersion1(b) {
return fmt.Errorf("%w: have %d, want %d", ErrInvalidVersion, b[2], eof1Version) return fmt.Errorf("%w: have %d, want %d", errInvalidVersion, b[2], eof1Version)
} }
var ( var (
@ -161,13 +185,13 @@ func (c *Container) unmarshalContainer(b []byte, isInitcode bool, topLevel bool)
return err return err
} }
if kind != kindTypes { if kind != kindTypes {
return fmt.Errorf("%w: found section kind %x instead", ErrMissingTypeHeader, kind) return fmt.Errorf("%w: found section kind %x instead", errMissingTypeHeader, kind)
} }
if typesSize < 4 || typesSize%4 != 0 { if typesSize < 4 || typesSize%4 != 0 {
return fmt.Errorf("%w: type section size must be divisible by 4, have %d", ErrInvalidTypeSize, typesSize) return fmt.Errorf("%w: type section size must be divisible by 4, have %d", errInvalidTypeSize, typesSize)
} }
if typesSize/4 > 1024 { if typesSize/4 > 1024 {
return fmt.Errorf("%w: type section must not exceed 4*1024, have %d", ErrInvalidTypeSize, typesSize) return fmt.Errorf("%w: type section must not exceed 4*1024, have %d", errInvalidTypeSize, typesSize)
} }
// Parse code section header. // Parse code section header.
@ -176,10 +200,10 @@ func (c *Container) unmarshalContainer(b []byte, isInitcode bool, topLevel bool)
return err return err
} }
if kind != kindCode { if kind != kindCode {
return fmt.Errorf("%w: found section kind %x instead", ErrMissingCodeHeader, kind) return fmt.Errorf("%w: found section kind %x instead", errMissingCodeHeader, kind)
} }
if len(codeSizes) != typesSize/4 { 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)) 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. // Parse (optional) container section header.
@ -194,7 +218,7 @@ func (c *Container) unmarshalContainer(b []byte, isInitcode bool, topLevel bool)
panic("somethings wrong") panic("somethings wrong")
} }
if len(containerSizes) == 0 { if len(containerSizes) == 0 {
return fmt.Errorf("%w: total container count must not be zero", ErrInvalidContainerSectionSize) return fmt.Errorf("%w: total container count must not be zero", errInvalidContainerSectionSize)
} }
offset = offset + 2 + 2*len(containerSizes) + 1 offset = offset + 2 + 2*len(containerSizes) + 1
} }
@ -205,7 +229,7 @@ func (c *Container) unmarshalContainer(b []byte, isInitcode bool, topLevel bool)
return err return err
} }
if kind != kindData { if kind != kindData {
return fmt.Errorf("%w: found section %x instead", ErrMissingDataHeader, kind) return fmt.Errorf("%w: found section %x instead", errMissingDataHeader, kind)
} }
c.dataSize = dataSize c.dataSize = dataSize
@ -215,7 +239,7 @@ func (c *Container) unmarshalContainer(b []byte, isInitcode bool, topLevel bool)
return fmt.Errorf("%w: invalid offset terminator", io.ErrUnexpectedEOF) return fmt.Errorf("%w: invalid offset terminator", io.ErrUnexpectedEOF)
} }
if b[offsetTerminator] != 0 { if b[offsetTerminator] != 0 {
return fmt.Errorf("%w: have %x", ErrMissingTerminator, b[offsetTerminator]) return fmt.Errorf("%w: have %x", errMissingTerminator, b[offsetTerminator])
} }
// Verify overall container size. // Verify overall container size.
@ -224,11 +248,11 @@ func (c *Container) unmarshalContainer(b []byte, isInitcode bool, topLevel bool)
expectedSize += sum(containerSizes) expectedSize += sum(containerSizes)
} }
if len(b) < expectedSize-dataSize { if len(b) < expectedSize-dataSize {
return fmt.Errorf("%w: have %d, want %d", ErrInvalidContainerSize, len(b), expectedSize) return fmt.Errorf("%w: have %d, want %d", errInvalidContainerSize, len(b), expectedSize)
} }
// Only check that the expected size is not exceed on non-initcode // Only check that the expected size is not exceed on non-initcode
if !isInitcode && len(b) > expectedSize { if (!topLevel || !isInitcode) && len(b) > expectedSize {
return fmt.Errorf("%w: have %d, want %d", ErrInvalidContainerSize, len(b), expectedSize) return fmt.Errorf("%w: have %d, want %d", errInvalidContainerSize, len(b), expectedSize)
} }
// Parse types section. // Parse types section.
@ -241,18 +265,18 @@ func (c *Container) unmarshalContainer(b []byte, isInitcode bool, topLevel bool)
maxStackHeight: binary.BigEndian.Uint16(b[idx+i*4+2:]), maxStackHeight: binary.BigEndian.Uint16(b[idx+i*4+2:]),
} }
if sig.inputs > maxInputItems { if sig.inputs > maxInputItems {
return fmt.Errorf("%w for section %d: have %d", ErrTooManyInputs, i, sig.inputs) return fmt.Errorf("%w for section %d: have %d", errTooManyInputs, i, sig.inputs)
} }
if sig.outputs > maxOutputItems { if sig.outputs > maxOutputItems {
return fmt.Errorf("%w for section %d: have %d", ErrTooManyOutputs, i, sig.outputs) return fmt.Errorf("%w for section %d: have %d", errTooManyOutputs, i, sig.outputs)
} }
if sig.maxStackHeight > maxStackHeight { if sig.maxStackHeight > maxStackHeight {
return fmt.Errorf("%w for section %d: have %d", ErrTooLargeMaxStackHeight, i, sig.maxStackHeight) return fmt.Errorf("%w for section %d: have %d", errTooLargeMaxStackHeight, i, sig.maxStackHeight)
} }
types = append(types, sig) types = append(types, sig)
} }
if types[0].inputs != 0 || types[0].outputs != 0x80 { if types[0].inputs != 0 || types[0].outputs != 0x80 {
return fmt.Errorf("%w: have %d, %d", ErrInvalidSection0Type, types[0].inputs, types[0].outputs) return fmt.Errorf("%w: have %d, %d", errInvalidSection0Type, types[0].inputs, types[0].outputs)
} }
c.types = types c.types = types
@ -261,7 +285,7 @@ func (c *Container) unmarshalContainer(b []byte, isInitcode bool, topLevel bool)
codeSections := make([][]byte, len(codeSizes)) codeSections := make([][]byte, len(codeSizes))
for i, size := range codeSizes { for i, size := range codeSizes {
if size == 0 { if size == 0 {
return fmt.Errorf("%w for section %d: size must not be 0", ErrInvalidCodeSize, i) return fmt.Errorf("%w for section %d: size must not be 0", errInvalidCodeSize, i)
} }
codeSections[i] = b[idx : idx+size] codeSections[i] = b[idx : idx+size]
idx += size idx += size
@ -270,13 +294,13 @@ func (c *Container) unmarshalContainer(b []byte, isInitcode bool, topLevel bool)
// Parse the optional container sizes. // Parse the optional container sizes.
if len(containerSizes) != 0 { if len(containerSizes) != 0 {
if len(containerSizes) > maxContainerSections { if len(containerSizes) > maxContainerSections {
return fmt.Errorf("%w number of container section exceed: %v: have %v", ErrInvalidContainerSectionSize, maxContainerSections, len(containerSizes)) return fmt.Errorf("%w number of container section exceed: %v: have %v", errInvalidContainerSectionSize, maxContainerSections, len(containerSizes))
} }
subContainerCodes := make([][]byte, 0, len(containerSizes)) subContainerCodes := make([][]byte, 0, len(containerSizes))
subContainers := make([]*Container, 0, len(containerSizes)) subContainers := make([]*Container, 0, len(containerSizes))
for i, size := range containerSizes { for i, size := range containerSizes {
if size == 0 || idx+size > len(b) { if size == 0 || idx+size > len(b) {
return fmt.Errorf("%w for section %d: size must not be 0", ErrInvalidContainerSectionSize, i) return fmt.Errorf("%w for section %d: size must not be 0", errInvalidContainerSectionSize, i)
} }
subC := new(Container) subC := new(Container)
end := min(idx+size, len(b)) end := min(idx+size, len(b))
@ -301,7 +325,7 @@ func (c *Container) unmarshalContainer(b []byte, isInitcode bool, topLevel bool)
end = min(idx+dataSize, len(b)) end = min(idx+dataSize, len(b))
} }
if topLevel && len(b) != idx+dataSize { if topLevel && len(b) != idx+dataSize {
return ErrTruncatedTopLevelContainer return errTruncatedTopLevelContainer
} }
c.data = b[idx:end] c.data = b[idx:end]
@ -354,22 +378,22 @@ func (c *Container) validateSubContainer(jt *JumpTable, refBy int) error {
subContainerVisited[idx] = reference subContainerVisited[idx] = reference
} }
if refBy == refByReturnContract && res.isInitCode { if refBy == refByReturnContract && res.isInitCode {
return ErrIncompatibleContainerKind return errIncompatibleContainerKind
} }
if refBy == refByEOFCreate && res.isRuntime { if refBy == refByEOFCreate && res.isRuntime {
return ErrIncompatibleContainerKind return errIncompatibleContainerKind
} }
} }
toVisit = toVisit[1:] toVisit = toVisit[1:]
} }
// Make sure every code section is visited at least once. // Make sure every code section is visited at least once.
if len(visited) != len(c.codeSections) { if len(visited) != len(c.codeSections) {
return ErrUnreachableCode return errUnreachableCode
} }
for idx, container := range c.subContainers { for idx, container := range c.subContainers {
reference, ok := subContainerVisited[idx] reference, ok := subContainerVisited[idx]
if !ok { if !ok {
return ErrOrphanedSubcontainer return errOrphanedSubcontainer
} }
if err := container.validateSubContainer(jt, reference); err != nil { if err := container.validateSubContainer(jt, reference); err != nil {
return err return err
@ -440,40 +464,38 @@ func sum(list []int) (s int) {
} }
func (c *Container) String() string { func (c *Container) String() string {
var result string var output = []string{
result += "Header\n" "Header",
result += "-----------\n" fmt.Sprintf(" - EOFMagic: %02x", eofMagic),
result += fmt.Sprintf("EOFMagic: %02x\n", eofMagic) fmt.Sprintf(" - EOFVersion: %02x", eof1Version),
result += fmt.Sprintf("EOFVersion: %02x\n", eof1Version) fmt.Sprintf(" - KindType: %02x", kindTypes),
result += fmt.Sprintf("KindType: %02x\n", kindTypes) fmt.Sprintf(" - TypesSize: %04x", len(c.types)*4),
result += fmt.Sprintf("TypesSize: %04x\n", len(c.types)*4) fmt.Sprintf(" - KindCode: %02x", kindCode),
result += fmt.Sprintf("KindCode: %02x\n", kindCode) fmt.Sprintf(" - KindData: %02x", kindData),
result += fmt.Sprintf("CodeSize: %04x\n", len(c.codeSections)) fmt.Sprintf(" - DataSize: %04x", len(c.data)),
fmt.Sprintf(" - Number of code sections: %d", len(c.codeSections)),
}
for i, code := range c.codeSections { for i, code := range c.codeSections {
result += fmt.Sprintf("Code %v length: %04x\n", i, len(code)) output = append(output, fmt.Sprintf(" - Code section %d length: %04x", i, len(code)))
} }
if len(c.subContainers) != 0 {
result += fmt.Sprintf("KindContainer: %02x\n", kindContainer) output = append(output, fmt.Sprintf(" - Number of subcontainers: %d", len(c.subContainers)))
result += fmt.Sprintf("ContainerSize: %04x\n", len(c.subContainers)) if len(c.subContainers) > 0 {
for i, section := range c.subContainers { for i, section := range c.subContainers {
result += fmt.Sprintf("Container %v length: %04x\n", i, len(section.MarshalBinary())) output = append(output, fmt.Sprintf(" - subcontainer %d length: %04x\n", i, len(section.MarshalBinary())))
} }
} }
result += fmt.Sprintf("KindData: %02x\n", kindData) output = append(output, "Body")
result += fmt.Sprintf("DataSize: %04x\n", len(c.data))
result += fmt.Sprintf("Terminator: %02x\n", 0x0)
result += "-----------\n"
result += "Body\n"
result += "-----------\n"
for i, typ := range c.types { for i, typ := range c.types {
result += fmt.Sprintf("Type %v: %v\n", i, hex.EncodeToString([]byte{typ.inputs, typ.outputs, byte(typ.maxStackHeight >> 8), byte(typ.maxStackHeight & 0x00ff)})) 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 { for i, code := range c.codeSections {
result += fmt.Sprintf("Code %v: %v\n", i, hex.EncodeToString(code)) output = append(output, fmt.Sprintf(" - Code section %d: %#x", i, code))
} }
for i, section := range c.subContainers { for i, section := range c.subContainers {
result += fmt.Sprintf("Section %v: %v\n", i, hex.EncodeToString(section.MarshalBinary())) output = append(output, fmt.Sprintf(" - Subcontainer %d: %x", i, section.MarshalBinary()))
} }
result += fmt.Sprintf("Data: %v\n", hex.EncodeToString(c.data)) output = append(output, fmt.Sprintf(" - Data: %#x", c.data))
return result return strings.Join(output, "\n")
} }

View file

@ -9,7 +9,6 @@ import (
func validateControlFlow(code []byte, section int, metadata []*functionMetadata, jt *JumpTable) (int, error) { func validateControlFlow(code []byte, section int, metadata []*functionMetadata, jt *JumpTable) (int, error) {
var ( var (
maxStackHeight = int(metadata[section].inputs) maxStackHeight = int(metadata[section].inputs)
debugging = !true
visitCount = 0 visitCount = 0
next = make([]int, 0, 1) next = make([]int, 0, 1)
) )
@ -46,56 +45,53 @@ func validateControlFlow(code []byte, section int, metadata []*functionMetadata,
op := OpCode(code[pos]) op := OpCode(code[pos])
ok, currentStackMin, currentStackMax := getStackMaxMin(pos) ok, currentStackMin, currentStackMax := getStackMaxMin(pos)
if !ok { if !ok {
if debugging { return 0, errUnreachableCode
fmt.Printf("Stack bounds not set: %v at %v \n", op, pos)
}
return 0, ErrUnreachableCode
}
if debugging {
fmt.Println(pos, op, maxStackHeight, currentStackMin, currentStackMax)
} }
switch op { switch op {
case CALLF: case CALLF:
arg, _ := parseUint16(code[pos+1:]) arg, _ := parseUint16(code[pos+1:])
newSection := metadata[arg] newSection := metadata[arg]
if want, have := int(newSection.inputs), currentStackMin; want > have { if err := newSection.checkInputs(currentStackMin); err != nil {
return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos) return 0, fmt.Errorf("%w: at pos %d", err, pos)
} }
if have, limit := currentStackMax+int(newSection.maxStackHeight)-int(newSection.inputs), int(params.StackLimit); have > limit { if err := newSection.checkStackMax(currentStackMax); err != nil {
return 0, fmt.Errorf("%w: at pos %d", ErrStackOverflow{stackLen: have, limit: limit}, pos) return 0, fmt.Errorf("%w: at pos %d", err, pos)
} }
change := int(newSection.outputs) - int(newSection.inputs) delta := newSection.stackDelta()
currentStackMax += change currentStackMax += delta
currentStackMin += change currentStackMin += delta
case RETF: case RETF:
if currentStackMax != currentStackMin { if currentStackMax != currentStackMin {
return 0, fmt.Errorf("%w: max %d, min %d, at pos %d", ErrInvalidOutputs, currentStackMax, currentStackMin, pos) return 0, fmt.Errorf("%w: max %d, min %d, at pos %d", errInvalidOutputs, currentStackMax, currentStackMin, pos)
} }
have := int(metadata[section].outputs) have := int(metadata[section].outputs)
if have >= maxOutputItems { if have >= maxOutputItems {
return 0, fmt.Errorf("%w: at pos %d", ErrInvalidNonReturningFlag, pos) return 0, fmt.Errorf("%w: at pos %d", errInvalidNonReturningFlag, pos)
} }
if want := currentStackMin; have != want { if want := currentStackMin; have != want {
return 0, fmt.Errorf("%w: have %d, want %d, at pos %d", ErrInvalidOutputs, have, want, pos) return 0, fmt.Errorf("%w: have %d, want %d, at pos %d", errInvalidOutputs, have, want, pos)
} }
qualifiedExit = true qualifiedExit = true
case JUMPF: case JUMPF:
arg, _ := parseUint16(code[pos+1:]) arg, _ := parseUint16(code[pos+1:])
newSection := metadata[arg] newSection := metadata[arg]
if have, limit := currentStackMax+int(newSection.maxStackHeight)-int(newSection.inputs), int(params.StackLimit); have > limit {
return 0, fmt.Errorf("%w: at pos %d", ErrStackOverflow{stackLen: have, limit: limit}, pos) if err := newSection.checkStackMax(currentStackMax); err != nil {
return 0, fmt.Errorf("%w: at pos %d", err, pos)
} }
if newSection.outputs == 0x80 { if newSection.outputs == 0x80 {
if want, have := int(newSection.inputs), currentStackMin; want > have { if err := newSection.checkInputs(currentStackMin); err != nil {
return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos) return 0, fmt.Errorf("%w: at pos %d", err, pos)
} }
} else { } else {
if currentStackMax != currentStackMin { if currentStackMax != currentStackMin {
return 0, fmt.Errorf("%w: max %d, min %d, at pos %d", ErrInvalidOutputs, currentStackMax, currentStackMin, pos) return 0, fmt.Errorf("%w: max %d, min %d, at pos %d", errInvalidOutputs, currentStackMax, currentStackMin, pos)
} }
if have, want := currentStackMax, int(metadata[section].outputs)+int(newSection.inputs)-int(newSection.outputs); have != want { wantStack := int(metadata[section].outputs) - newSection.stackDelta()
return 0, fmt.Errorf("%w: at pos %d", ErrInvalidOutputs, pos) if currentStackMax != wantStack {
return 0, fmt.Errorf("%w: at pos %d", errInvalidOutputs, pos)
} }
} }
qualifiedExit = qualifiedExit || newSection.outputs < maxOutputItems qualifiedExit = qualifiedExit || newSection.outputs < maxOutputItems
@ -136,10 +132,10 @@ func validateControlFlow(code []byte, section int, metadata []*functionMetadata,
if nextPos+1 < pos { if nextPos+1 < pos {
ok, nextMin, nextMax := getStackMaxMin(nextPos + 1) ok, nextMin, nextMax := getStackMaxMin(nextPos + 1)
if !ok { if !ok {
return 0, ErrInvalidBackwardJump return 0, errInvalidBackwardJump
} }
if nextMax != currentStackMax || nextMin != currentStackMin { if nextMax != currentStackMax || nextMin != currentStackMin {
return 0, ErrInvalidMaxStackHeight return 0, errInvalidMaxStackHeight
} }
} else { } else {
ok, nextMin, nextMax := getStackMaxMin(nextPos + 1) ok, nextMin, nextMax := getStackMaxMin(nextPos + 1)
@ -168,15 +164,12 @@ func validateControlFlow(code []byte, section int, metadata []*functionMetadata,
next = append(next, pos) next = append(next, pos)
} }
} }
if debugging {
fmt.Println(next)
}
if op != RJUMP && !terminals[op] { if op != RJUMP && !terminals[op] {
for _, instr := range next { for _, instr := range next {
nextPC := instr + 1 nextPC := instr + 1
if nextPC >= len(code) { if nextPC >= len(code) {
return 0, fmt.Errorf("%w: end with %s, pos %d", ErrInvalidCodeTermination, op, pos) return 0, fmt.Errorf("%w: end with %s, pos %d", errInvalidCodeTermination, op, pos)
} }
if nextPC > pos { if nextPC > pos {
// target reached via forward jump or seq flow // target reached via forward jump or seq flow
@ -190,13 +183,13 @@ func validateControlFlow(code []byte, section int, metadata []*functionMetadata,
// target reached via backwards jump // target reached via backwards jump
ok, nextMin, nextMax := getStackMaxMin(nextPC) ok, nextMin, nextMax := getStackMaxMin(nextPC)
if !ok { if !ok {
return 0, ErrInvalidBackwardJump return 0, errInvalidBackwardJump
} }
if currentStackMax != nextMax { if currentStackMax != nextMax {
return 0, fmt.Errorf("%w want %d as current max got %d at pos %d,", ErrInvalidBackwardJump, currentStackMax, nextMax, pos) return 0, fmt.Errorf("%w want %d as current max got %d at pos %d,", errInvalidBackwardJump, currentStackMax, nextMax, pos)
} }
if currentStackMin != nextMin { if currentStackMin != nextMin {
return 0, fmt.Errorf("%w want %d as current min got %d at pos %d,", ErrInvalidBackwardJump, currentStackMin, nextMin, pos) return 0, fmt.Errorf("%w want %d as current min got %d at pos %d,", errInvalidBackwardJump, currentStackMin, nextMin, pos)
} }
} }
} }
@ -209,16 +202,13 @@ func validateControlFlow(code []byte, section int, metadata []*functionMetadata,
} }
} }
if qualifiedExit != (metadata[section].outputs < maxOutputItems) { if qualifiedExit != (metadata[section].outputs < maxOutputItems) {
return 0, fmt.Errorf("%w no RETF or qualified JUMPF", ErrInvalidNonReturningFlag) return 0, fmt.Errorf("%w no RETF or qualified JUMPF", errInvalidNonReturningFlag)
} }
if maxStackHeight >= int(params.StackLimit) { if maxStackHeight >= int(params.StackLimit) {
return 0, ErrStackOverflow{maxStackHeight, int(params.StackLimit)} return 0, ErrStackOverflow{maxStackHeight, int(params.StackLimit)}
} }
if maxStackHeight != int(metadata[section].maxStackHeight) { if maxStackHeight != int(metadata[section].maxStackHeight) {
if debugging { return 0, fmt.Errorf("%w in code section %d: have %d, want %d", errInvalidMaxStackHeight, section, maxStackHeight, metadata[section].maxStackHeight)
fmt.Print(maxStackHeight, 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 return visitCount, nil
} }

View file

@ -23,40 +23,38 @@ import (
// Below are all possible errors that can occur during validation of // Below are all possible errors that can occur during validation of
// EOF containers. // EOF containers.
var ( var (
ErrInvalidMagic = errors.New("invalid magic") errInvalidMagic = errors.New("invalid magic")
ErrUndefinedInstruction = errors.New("undefined instruction") errUndefinedInstruction = errors.New("undefined instruction")
ErrTruncatedImmediate = errors.New("truncated immediate") errTruncatedImmediate = errors.New("truncated immediate")
ErrInvalidSectionArgument = errors.New("invalid section argument") errInvalidSectionArgument = errors.New("invalid section argument")
ErrInvalidCallArgument = errors.New("callf into non-returning section") errInvalidCallArgument = errors.New("callf into non-returning section")
ErrInvalidDataloadNArgument = errors.New("invalid dataloadN argument") errInvalidDataloadNArgument = errors.New("invalid dataloadN argument")
ErrInvalidJumpDest = errors.New("invalid jump destination") errInvalidJumpDest = errors.New("invalid jump destination")
ErrInvalidBackwardJump = errors.New("invalid backward jump") errInvalidBackwardJump = errors.New("invalid backward jump")
//ErrConflictingStack = errors.New("conflicting stack height") errInvalidOutputs = errors.New("invalid number of outputs")
//ErrInvalidBranchCount = errors.New("invalid number of branches in jump table") errInvalidMaxStackHeight = errors.New("invalid max stack height")
ErrInvalidOutputs = errors.New("invalid number of outputs") errInvalidCodeTermination = errors.New("invalid code termination")
ErrInvalidMaxStackHeight = errors.New("invalid max stack height") errEOFCreateWithTruncatedSection = errors.New("eofcreate with truncated section")
ErrInvalidCodeTermination = errors.New("invalid code termination") errOrphanedSubcontainer = errors.New("subcontainer not referenced at all")
ErrEOFCreateWithTruncatedSection = errors.New("eofcreate with truncated section") errIncompatibleContainerKind = errors.New("incompatible container kind")
ErrOrphanedSubcontainer = errors.New("subcontainer not referenced at all") errStopAndReturnContract = errors.New("Stop/Return and Returncontract in the same code section")
ErrIncompatibleContainerKind = errors.New("incompatible container kind") errStopInInitCode = errors.New("initcode contains a RETURN or STOP opcode")
ErrStopAndReturnContract = errors.New("Stop/Return and Returncontract in the same code section") errTruncatedTopLevelContainer = errors.New("truncated top level container")
ErrStopInInitCode = errors.New("initcode contains a RETURN or STOP opcode") errUnreachableCode = errors.New("unreachable code")
ErrTruncatedTopLevelContainer = errors.New("truncated top level container") errInvalidNonReturningFlag = errors.New("invalid non-returning flag, bad RETF")
ErrUnreachableCode = errors.New("unreachable code") errInvalidVersion = errors.New("invalid version")
ErrInvalidNonReturningFlag = errors.New("invalid non-returning flag, bad RETF") errMissingTypeHeader = errors.New("missing type header")
ErrInvalidVersion = errors.New("invalid version") errInvalidTypeSize = errors.New("invalid type section size")
ErrMissingTypeHeader = errors.New("missing type header") errMissingCodeHeader = errors.New("missing code header")
ErrInvalidTypeSize = errors.New("invalid type section size") errInvalidCodeSize = errors.New("invalid code size")
ErrMissingCodeHeader = errors.New("missing code header") errInvalidContainerSectionSize = errors.New("invalid container section size")
ErrInvalidCodeSize = errors.New("invalid code size") errMissingDataHeader = errors.New("missing data header")
ErrInvalidContainerSectionSize = errors.New("invalid container section size") errMissingTerminator = errors.New("missing header terminator")
ErrMissingDataHeader = errors.New("missing data header") errTooManyInputs = errors.New("invalid type content, too many inputs")
ErrMissingTerminator = errors.New("missing header terminator") errTooManyOutputs = errors.New("invalid type content, too many outputs")
ErrTooManyInputs = errors.New("invalid type content, too many inputs") errInvalidSection0Type = errors.New("invalid section 0 type, input and output should be zero and non-returning (0x80)")
ErrTooManyOutputs = errors.New("invalid type content, too many outputs") errTooLargeMaxStackHeight = errors.New("invalid type content, max stack height exceeds limit")
ErrInvalidSection0Type = errors.New("invalid section 0 type, input and output should be zero and non-returning (0x80)") errInvalidContainerSize = errors.New("invalid container size")
ErrTooLargeMaxStackHeight = errors.New("invalid type content, max stack height exceeds limit")
ErrInvalidContainerSize = errors.New("invalid container size")
) )
const ( const (
@ -97,11 +95,11 @@ func validateCode(code []byte, section int, container *Container, jt *JumpTable,
count++ count++
op = OpCode(code[i]) op = OpCode(code[i])
if jt[op].undefined { if jt[op].undefined {
return nil, fmt.Errorf("%w: op %s, pos %d", ErrUndefinedInstruction, op, i) return nil, fmt.Errorf("%w: op %s, pos %d", errUndefinedInstruction, op, i)
} }
size := int(immediates[op]) size := int(immediates[op])
if size != 0 && len(code) <= i+size { if size != 0 && len(code) <= i+size {
return nil, fmt.Errorf("%w: op %s, pos %d", ErrTruncatedImmediate, op, i) return nil, fmt.Errorf("%w: op %s, pos %d", errTruncatedImmediate, op, i)
} }
switch op { switch op {
case RJUMP, RJUMPI: case RJUMP, RJUMPI:
@ -112,7 +110,7 @@ func validateCode(code []byte, section int, container *Container, jt *JumpTable,
max_size := int(code[i+1]) max_size := int(code[i+1])
length := max_size + 1 length := max_size + 1
if len(code) <= i+length { if len(code) <= i+length {
return nil, fmt.Errorf("%w: jump table truncated, op %s, pos %d", ErrTruncatedImmediate, op, i) return nil, fmt.Errorf("%w: jump table truncated, op %s, pos %d", errTruncatedImmediate, op, i)
} }
offset := i + 2 offset := i + 2
for j := 0; j < length; j++ { for j := 0; j < length; j++ {
@ -124,10 +122,10 @@ func validateCode(code []byte, section int, container *Container, jt *JumpTable,
case CALLF: case CALLF:
arg, _ := parseUint16(code[i+1:]) arg, _ := parseUint16(code[i+1:])
if arg >= len(container.types) { if arg >= len(container.types) {
return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", ErrInvalidSectionArgument, arg, len(container.types), i) return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", errInvalidSectionArgument, arg, len(container.types), i)
} }
if container.types[arg].outputs == 0x80 { if container.types[arg].outputs == 0x80 {
return nil, fmt.Errorf("%w: section %v", ErrInvalidCallArgument, arg) return nil, fmt.Errorf("%w: section %v", errInvalidCallArgument, arg)
} }
if visitedCode == nil { if visitedCode == nil {
visitedCode = make(map[int]struct{}) visitedCode = make(map[int]struct{})
@ -136,10 +134,10 @@ func validateCode(code []byte, section int, container *Container, jt *JumpTable,
case JUMPF: case JUMPF:
arg, _ := parseUint16(code[i+1:]) arg, _ := parseUint16(code[i+1:])
if arg >= len(container.types) { if arg >= len(container.types) {
return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", ErrInvalidSectionArgument, arg, len(container.types), i) 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 { 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) return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", errInvalidOutputs, arg, len(container.types), i)
} }
if visitedCode == nil { if visitedCode == nil {
visitedCode = make(map[int]struct{}) visitedCode = make(map[int]struct{})
@ -149,15 +147,15 @@ func validateCode(code []byte, section int, container *Container, jt *JumpTable,
arg, _ := parseUint16(code[i+1:]) arg, _ := parseUint16(code[i+1:])
// TODO why are we checking this? We should just pad // TODO why are we checking this? We should just pad
if arg+32 > len(container.data) { if arg+32 > len(container.data) {
return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", ErrInvalidDataloadNArgument, arg, len(container.data), i) return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", errInvalidDataloadNArgument, arg, len(container.data), i)
} }
case RETURNCONTRACT: case RETURNCONTRACT:
if !isInitCode { if !isInitCode {
return nil, ErrIncompatibleContainerKind return nil, errIncompatibleContainerKind
} }
arg := int(code[i+1]) arg := int(code[i+1])
if arg >= len(container.subContainers) { if arg >= len(container.subContainers) {
return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", ErrUnreachableCode, arg, len(container.subContainers), i) return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", errUnreachableCode, arg, len(container.subContainers), i)
} }
if visitedSubcontainers == nil { if visitedSubcontainers == nil {
visitedSubcontainers = make(map[int]int) visitedSubcontainers = make(map[int]int)
@ -167,17 +165,17 @@ func validateCode(code []byte, section int, container *Container, jt *JumpTable,
return nil, fmt.Errorf("section already referenced, arg :%d", arg) return nil, fmt.Errorf("section already referenced, arg :%d", arg)
} }
if hasStop { if hasStop {
return nil, ErrStopAndReturnContract return nil, errStopAndReturnContract
} }
hasReturnContract = true hasReturnContract = true
visitedSubcontainers[arg] = refByReturnContract visitedSubcontainers[arg] = refByReturnContract
case EOFCREATE: case EOFCREATE:
arg := int(code[i+1]) arg := int(code[i+1])
if arg >= len(container.subContainers) { if arg >= len(container.subContainers) {
return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", ErrUnreachableCode, arg, len(container.subContainers), i) 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 { 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) return nil, fmt.Errorf("%w: container %d, have %d, claimed %d, pos %d", errEOFCreateWithTruncatedSection, arg, len(ct.data), ct.dataSize, i)
} }
if visitedSubcontainers == nil { if visitedSubcontainers == nil {
visitedSubcontainers = make(map[int]int) visitedSubcontainers = make(map[int]int)
@ -189,10 +187,10 @@ func validateCode(code []byte, section int, container *Container, jt *JumpTable,
visitedSubcontainers[arg] = refByEOFCreate visitedSubcontainers[arg] = refByEOFCreate
case STOP, RETURN: case STOP, RETURN:
if isInitCode { if isInitCode {
return nil, ErrStopInInitCode return nil, errStopInInitCode
} }
if hasReturnContract { if hasReturnContract {
return nil, ErrStopAndReturnContract return nil, errStopAndReturnContract
} }
hasStop = true hasStop = true
} }
@ -201,13 +199,13 @@ func validateCode(code []byte, section int, container *Container, jt *JumpTable,
// Code sections may not "fall through" and require proper termination. // Code sections may not "fall through" and require proper termination.
// Therefore, the last instruction must be considered terminal or RJUMP. // Therefore, the last instruction must be considered terminal or RJUMP.
if !terminals[op] && op != RJUMP { if !terminals[op] && op != RJUMP {
return nil, fmt.Errorf("%w: end with %s, pos %d", ErrInvalidCodeTermination, op, i) return nil, fmt.Errorf("%w: end with %s, pos %d", errInvalidCodeTermination, op, i)
} }
if paths, err := validateControlFlow(code, section, container.types, jt); err != nil { if paths, err := validateControlFlow(code, section, container.types, jt); err != nil {
return nil, err return nil, err
} else if paths != count { } else if paths != count {
// TODO(matt): return actual position of unreachable code // TODO(matt): return actual position of unreachable code
return nil, ErrUnreachableCode return nil, errUnreachableCode
} }
return &validationResult{ return &validationResult{
visitedCode: visitedCode, visitedCode: visitedCode,
@ -228,10 +226,10 @@ func checkDest(code []byte, analysis *bitvec, imm, from, length int) error {
offset := parseInt16(code[imm:]) offset := parseInt16(code[imm:])
dest := from + offset dest := from + offset
if dest < 0 || dest >= length { if dest < 0 || dest >= length {
return fmt.Errorf("%w: out-of-bounds offset: offset %d, dest %d, pos %d", ErrInvalidJumpDest, offset, dest, imm) return fmt.Errorf("%w: out-of-bounds offset: offset %d, dest %d, pos %d", errInvalidJumpDest, offset, dest, imm)
} }
if !analysis.codeSegment(uint64(dest)) { if !analysis.codeSegment(uint64(dest)) {
return fmt.Errorf("%w: offset into immediate: offset %d, dest %d, pos %d", ErrInvalidJumpDest, offset, dest, imm) return fmt.Errorf("%w: offset into immediate: offset %d, dest %d, pos %d", errInvalidJumpDest, offset, dest, imm)
} }
return nil return nil
} }

View file

@ -66,7 +66,7 @@ func TestValidateCode(t *testing.T) {
}, },
section: 0, section: 0,
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}}, metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
err: ErrInvalidCodeTermination, err: errInvalidCodeTermination,
}, },
{ {
code: []byte{ code: []byte{
@ -78,7 +78,7 @@ func TestValidateCode(t *testing.T) {
}, },
section: 0, section: 0,
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 0}}, metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 0}},
err: ErrUnreachableCode, err: errUnreachableCode,
}, },
{ {
code: []byte{ code: []byte{
@ -100,7 +100,7 @@ func TestValidateCode(t *testing.T) {
}, },
section: 0, section: 0,
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 2}}, metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 2}},
err: ErrInvalidMaxStackHeight, err: errInvalidMaxStackHeight,
}, },
{ {
code: []byte{ code: []byte{
@ -115,7 +115,7 @@ func TestValidateCode(t *testing.T) {
}, },
section: 0, section: 0,
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}}, metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
err: ErrInvalidJumpDest, err: errInvalidJumpDest,
}, },
{ {
code: []byte{ code: []byte{
@ -133,7 +133,7 @@ func TestValidateCode(t *testing.T) {
}, },
section: 0, section: 0,
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}}, metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
err: ErrInvalidJumpDest, err: errInvalidJumpDest,
}, },
{ {
code: []byte{ code: []byte{
@ -144,7 +144,7 @@ func TestValidateCode(t *testing.T) {
}, },
section: 0, section: 0,
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}}, metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
err: ErrTruncatedImmediate, err: errTruncatedImmediate,
}, },
{ {
code: []byte{ code: []byte{
@ -162,7 +162,7 @@ func TestValidateCode(t *testing.T) {
}, },
section: 0, section: 0,
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 3}}, metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 3}},
err: ErrUnreachableCode, err: errUnreachableCode,
}, },
{ {
code: []byte{ code: []byte{
@ -208,7 +208,7 @@ func TestValidateCode(t *testing.T) {
}, },
section: 0, section: 0,
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 0}}, metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 0}},
err: ErrUnreachableCode, err: errUnreachableCode,
}, },
{ {
code: []byte{ code: []byte{
@ -216,7 +216,7 @@ func TestValidateCode(t *testing.T) {
}, },
section: 0, section: 0,
metadata: []*functionMetadata{{inputs: 0, outputs: 1, maxStackHeight: 0}}, metadata: []*functionMetadata{{inputs: 0, outputs: 1, maxStackHeight: 0}},
err: ErrInvalidOutputs, err: errInvalidOutputs,
}, },
{ {
code: []byte{ code: []byte{