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) {
// Seed with corpus from execution-spec-tests
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)
if err != nil {
break
@ -66,11 +66,11 @@ func FuzzEofParsing(f *testing.F) {
}
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) {
testEofParse(t, false, "testdata/results.regular.txt")
testEofParse(t, false, "testdata/eof/results.regular.txt")
}
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++ {
fname := fmt.Sprintf("testdata/eof_corpus_%d.txt", i)
fname := fmt.Sprintf("testdata/eof/eof_corpus_%d.txt", i)
corpus, err := os.Open(fname)
if err != nil {
break
@ -126,7 +126,7 @@ func testEofParse(t *testing.T, isInitCode bool, wantFile string) {
if len(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)
}
}
@ -137,7 +137,7 @@ func testEofParse(t *testing.T, isInitCode bool, wantFile string) {
}
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 {
b.Fatal(err)
}

View file

@ -138,9 +138,18 @@ var (
Usage: "enable return data output",
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",
Aliases: []string{"t8n"},
Usage: "Executes a full state transition",
@ -166,7 +175,7 @@ var stateTransitionCommand = &cli.Command{
},
}
var transactionCommand = &cli.Command{
transactionCommand = &cli.Command{
Name: "transaction",
Aliases: []string{"t9n"},
Usage: "Performs transaction validation",
@ -178,7 +187,7 @@ var transactionCommand = &cli.Command{
},
}
var blockBuilderCommand = &cli.Command{
blockBuilderCommand = &cli.Command{
Name: "block-builder",
Aliases: []string{"b11r"},
Usage: "Builds a block",
@ -193,6 +202,26 @@ var blockBuilderCommand = &cli.Command{
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.
var vmFlags = []cli.Flag{
@ -235,6 +264,8 @@ func init() {
stateTransitionCommand,
transactionCommand,
blockBuilderCommand,
eofParseCommand,
eofDumpCommand,
}
app.Before = func(ctx *cli.Context) error {
flags.MigrateGlobalFlags(ctx)

File diff suppressed because one or more lines are too long

View file

@ -19,10 +19,10 @@ package vm
import (
"bytes"
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"io"
"strings"
"github.com/ethereum/go-ethereum/params"
)
@ -81,6 +81,30 @@ type functionMetadata struct {
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.
func (c *Container) MarshalBinary() []byte {
// 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 {
if !hasEOFMagic(b) {
return fmt.Errorf("%w: want %x", ErrInvalidMagic, eofMagic)
return fmt.Errorf("%w: want %x", errInvalidMagic, eofMagic)
}
if len(b) < 14 {
return io.ErrUnexpectedEOF
@ -146,7 +170,7 @@ func (c *Container) unmarshalContainer(b []byte, isInitcode bool, topLevel bool)
return ErrMaxInitCodeSizeExceeded
}
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 (
@ -161,13 +185,13 @@ func (c *Container) unmarshalContainer(b []byte, isInitcode bool, topLevel bool)
return err
}
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 {
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 {
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.
@ -176,10 +200,10 @@ func (c *Container) unmarshalContainer(b []byte, isInitcode bool, topLevel bool)
return err
}
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 {
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.
@ -194,7 +218,7 @@ func (c *Container) unmarshalContainer(b []byte, isInitcode bool, topLevel bool)
panic("somethings wrong")
}
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
}
@ -205,7 +229,7 @@ func (c *Container) unmarshalContainer(b []byte, isInitcode bool, topLevel bool)
return err
}
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
@ -215,7 +239,7 @@ func (c *Container) unmarshalContainer(b []byte, isInitcode bool, topLevel bool)
return fmt.Errorf("%w: invalid offset terminator", io.ErrUnexpectedEOF)
}
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.
@ -224,11 +248,11 @@ func (c *Container) unmarshalContainer(b []byte, isInitcode bool, topLevel bool)
expectedSize += sum(containerSizes)
}
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
if !isInitcode && len(b) > expectedSize {
return fmt.Errorf("%w: have %d, want %d", ErrInvalidContainerSize, len(b), expectedSize)
if (!topLevel || !isInitcode) && len(b) > expectedSize {
return fmt.Errorf("%w: have %d, want %d", errInvalidContainerSize, len(b), expectedSize)
}
// 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:]),
}
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 {
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 {
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)
}
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
@ -261,7 +285,7 @@ func (c *Container) unmarshalContainer(b []byte, isInitcode bool, topLevel bool)
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)
return fmt.Errorf("%w for section %d: size must not be 0", errInvalidCodeSize, i)
}
codeSections[i] = b[idx : idx+size]
idx += size
@ -270,13 +294,13 @@ func (c *Container) unmarshalContainer(b []byte, isInitcode bool, topLevel bool)
// 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))
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)
return fmt.Errorf("%w for section %d: size must not be 0", errInvalidContainerSectionSize, i)
}
subC := new(Container)
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))
}
if topLevel && len(b) != idx+dataSize {
return ErrTruncatedTopLevelContainer
return errTruncatedTopLevelContainer
}
c.data = b[idx:end]
@ -354,22 +378,22 @@ func (c *Container) validateSubContainer(jt *JumpTable, refBy int) error {
subContainerVisited[idx] = reference
}
if refBy == refByReturnContract && res.isInitCode {
return ErrIncompatibleContainerKind
return errIncompatibleContainerKind
}
if refBy == refByEOFCreate && res.isRuntime {
return ErrIncompatibleContainerKind
return errIncompatibleContainerKind
}
}
toVisit = toVisit[1:]
}
// Make sure every code section is visited at least once.
if len(visited) != len(c.codeSections) {
return ErrUnreachableCode
return errUnreachableCode
}
for idx, container := range c.subContainers {
reference, ok := subContainerVisited[idx]
if !ok {
return ErrOrphanedSubcontainer
return errOrphanedSubcontainer
}
if err := container.validateSubContainer(jt, reference); err != nil {
return err
@ -440,40 +464,38 @@ func sum(list []int) (s int) {
}
func (c *Container) String() string {
var result string
result += "Header\n"
result += "-----------\n"
result += fmt.Sprintf("EOFMagic: %02x\n", eofMagic)
result += fmt.Sprintf("EOFVersion: %02x\n", eof1Version)
result += fmt.Sprintf("KindType: %02x\n", kindTypes)
result += fmt.Sprintf("TypesSize: %04x\n", len(c.types)*4)
result += fmt.Sprintf("KindCode: %02x\n", kindCode)
result += fmt.Sprintf("CodeSize: %04x\n", len(c.codeSections))
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 {
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)
result += fmt.Sprintf("ContainerSize: %04x\n", len(c.subContainers))
output = append(output, fmt.Sprintf(" - Number of subcontainers: %d", len(c.subContainers)))
if len(c.subContainers) > 0 {
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)
result += fmt.Sprintf("DataSize: %04x\n", len(c.data))
result += fmt.Sprintf("Terminator: %02x\n", 0x0)
result += "-----------\n"
result += "Body\n"
result += "-----------\n"
output = append(output, "Body")
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 {
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 {
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))
return result
output = append(output, fmt.Sprintf(" - Data: %#x", c.data))
return strings.Join(output, "\n")
}

View file

@ -9,7 +9,6 @@ import (
func validateControlFlow(code []byte, section int, metadata []*functionMetadata, jt *JumpTable) (int, error) {
var (
maxStackHeight = int(metadata[section].inputs)
debugging = !true
visitCount = 0
next = make([]int, 0, 1)
)
@ -46,56 +45,53 @@ func validateControlFlow(code []byte, section int, metadata []*functionMetadata,
op := OpCode(code[pos])
ok, currentStackMin, currentStackMax := getStackMaxMin(pos)
if !ok {
if debugging {
fmt.Printf("Stack bounds not set: %v at %v \n", op, pos)
}
return 0, ErrUnreachableCode
}
if debugging {
fmt.Println(pos, op, maxStackHeight, currentStackMin, currentStackMax)
return 0, errUnreachableCode
}
switch op {
case CALLF:
arg, _ := parseUint16(code[pos+1:])
newSection := metadata[arg]
if want, have := int(newSection.inputs), currentStackMin; want > have {
return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos)
if err := newSection.checkInputs(currentStackMin); err != nil {
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 {
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)
}
change := int(newSection.outputs) - int(newSection.inputs)
currentStackMax += change
currentStackMin += change
delta := newSection.stackDelta()
currentStackMax += delta
currentStackMin += delta
case RETF:
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)
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 {
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
case JUMPF:
arg, _ := parseUint16(code[pos+1:])
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 want, have := int(newSection.inputs), currentStackMin; want > have {
return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos)
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)
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 {
return 0, fmt.Errorf("%w: at pos %d", ErrInvalidOutputs, 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
@ -136,10 +132,10 @@ func validateControlFlow(code []byte, section int, metadata []*functionMetadata,
if nextPos+1 < pos {
ok, nextMin, nextMax := getStackMaxMin(nextPos + 1)
if !ok {
return 0, ErrInvalidBackwardJump
return 0, errInvalidBackwardJump
}
if nextMax != currentStackMax || nextMin != currentStackMin {
return 0, ErrInvalidMaxStackHeight
return 0, errInvalidMaxStackHeight
}
} else {
ok, nextMin, nextMax := getStackMaxMin(nextPos + 1)
@ -168,15 +164,12 @@ func validateControlFlow(code []byte, section int, metadata []*functionMetadata,
next = append(next, pos)
}
}
if debugging {
fmt.Println(next)
}
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)
return 0, fmt.Errorf("%w: end with %s, pos %d", errInvalidCodeTermination, op, pos)
}
if nextPC > pos {
// 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
ok, nextMin, nextMax := getStackMaxMin(nextPC)
if !ok {
return 0, ErrInvalidBackwardJump
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)
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)
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) {
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) {
return 0, ErrStackOverflow{maxStackHeight, int(params.StackLimit)}
}
if maxStackHeight != int(metadata[section].maxStackHeight) {
if debugging {
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 0, fmt.Errorf("%w in code section %d: have %d, want %d", errInvalidMaxStackHeight, section, maxStackHeight, metadata[section].maxStackHeight)
}
return visitCount, nil
}

View file

@ -23,40 +23,38 @@ import (
// 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")
//ErrConflictingStack = errors.New("conflicting stack height")
//ErrInvalidBranchCount = errors.New("invalid number of branches in jump table")
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")
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 (
@ -97,11 +95,11 @@ func validateCode(code []byte, section int, container *Container, jt *JumpTable,
count++
op = OpCode(code[i])
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])
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 {
case RJUMP, RJUMPI:
@ -112,7 +110,7 @@ func validateCode(code []byte, section int, container *Container, jt *JumpTable,
max_size := int(code[i+1])
length := max_size + 1
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
for j := 0; j < length; j++ {
@ -124,10 +122,10 @@ func validateCode(code []byte, section int, container *Container, jt *JumpTable,
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)
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)
return nil, fmt.Errorf("%w: section %v", errInvalidCallArgument, arg)
}
if visitedCode == nil {
visitedCode = make(map[int]struct{})
@ -136,10 +134,10 @@ func validateCode(code []byte, section int, container *Container, jt *JumpTable,
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)
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)
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{})
@ -149,15 +147,15 @@ func validateCode(code []byte, section int, container *Container, jt *JumpTable,
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)
return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", errInvalidDataloadNArgument, arg, len(container.data), i)
}
case RETURNCONTRACT:
if !isInitCode {
return nil, ErrIncompatibleContainerKind
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)
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)
@ -167,17 +165,17 @@ func validateCode(code []byte, section int, container *Container, jt *JumpTable,
return nil, fmt.Errorf("section already referenced, arg :%d", arg)
}
if hasStop {
return nil, ErrStopAndReturnContract
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)
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)
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)
@ -189,10 +187,10 @@ func validateCode(code []byte, section int, container *Container, jt *JumpTable,
visitedSubcontainers[arg] = refByEOFCreate
case STOP, RETURN:
if isInitCode {
return nil, ErrStopInInitCode
return nil, errStopInInitCode
}
if hasReturnContract {
return nil, ErrStopAndReturnContract
return nil, errStopAndReturnContract
}
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.
// 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)
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 nil, errUnreachableCode
}
return &validationResult{
visitedCode: visitedCode,
@ -228,10 +226,10 @@ func checkDest(code []byte, analysis *bitvec, imm, from, length int) error {
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)
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 fmt.Errorf("%w: offset into immediate: offset %d, dest %d, pos %d", errInvalidJumpDest, offset, dest, imm)
}
return nil
}

View file

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