mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-19 02:12:23 +00:00
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:
parent
f05c07b8fe
commit
03d36ac908
14 changed files with 1509 additions and 1064 deletions
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
200
cmd/evm/eofparse.go
Normal 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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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",
|
||||
|
|
@ -164,9 +173,9 @@ var stateTransitionCommand = &cli.Command{
|
|||
t8ntool.ChainIDFlag,
|
||||
t8ntool.RewardFlag,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
var transactionCommand = &cli.Command{
|
||||
transactionCommand = &cli.Command{
|
||||
Name: "transaction",
|
||||
Aliases: []string{"t9n"},
|
||||
Usage: "Performs transaction validation",
|
||||
|
|
@ -176,9 +185,9 @@ var transactionCommand = &cli.Command{
|
|||
t8ntool.ChainIDFlag,
|
||||
t8ntool.ForknameFlag,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
var blockBuilderCommand = &cli.Command{
|
||||
blockBuilderCommand = &cli.Command{
|
||||
Name: "block-builder",
|
||||
Aliases: []string{"b11r"},
|
||||
Usage: "Builds a block",
|
||||
|
|
@ -192,7 +201,27 @@ var blockBuilderCommand = &cli.Command{
|
|||
t8ntool.InputTxsRlpFlag,
|
||||
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
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
124
core/vm/eof.go
124
core/vm/eof.go
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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{
|
||||
Loading…
Reference in a new issue