mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-19 10:22:23 +00:00
core/vm, cmd/eofdump: implement eof validation
cmd/eofdump: benchmarks of eof validation speeds core/vm: move eof instructions to separate file core/vm: unexport fields Co-authored-by: lightclient <lightclient@protonmail.com> Co-authored-by: Marius van der Wijden <m.vanderwijden@live.de> Co-authored-by: Danno Ferrin <danno.ferrin@shemnon.com>
This commit is contained in:
parent
db6ae7fa12
commit
77484b4cf6
26 changed files with 9303 additions and 166 deletions
248
cmd/eofdump/eofparser.go
Normal file
248
cmd/eofdump/eofparser.go
Normal file
|
|
@ -0,0 +1,248 @@
|
||||||
|
// 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
|
||||||
|
}
|
||||||
65
cmd/eofdump/main.go
Normal file
65
cmd/eofdump/main.go
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
167
cmd/eofdump/parse_test.go
Normal file
167
cmd/eofdump/parse_test.go
Normal file
|
|
@ -0,0 +1,167 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func FuzzEofParsing(f *testing.F) {
|
||||||
|
// Seed with corpus from execution-spec-tests
|
||||||
|
for i := 0; ; i++ {
|
||||||
|
fname := fmt.Sprintf("testdata/eof_corpus_%d.txt", i)
|
||||||
|
corpus, err := os.Open(fname)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
f.Logf("Reading seed data from %v", fname)
|
||||||
|
scanner := bufio.NewScanner(corpus)
|
||||||
|
scanner.Buffer(make([]byte, 1024), 10*1024*1024)
|
||||||
|
for scanner.Scan() {
|
||||||
|
s := scanner.Text()
|
||||||
|
if len(s) >= 2 && strings.HasPrefix(s, "0x") {
|
||||||
|
s = s[2:]
|
||||||
|
}
|
||||||
|
b, err := hex.DecodeString(s)
|
||||||
|
if err != nil {
|
||||||
|
panic(err) // rotten corpus
|
||||||
|
}
|
||||||
|
f.Add(b)
|
||||||
|
}
|
||||||
|
corpus.Close()
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
panic(err) // rotten corpus
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// And do the fuzzing
|
||||||
|
f.Fuzz(func(t *testing.T, data []byte) {
|
||||||
|
var (
|
||||||
|
jt = vm.NewPragueEOFInstructionSetForTesting()
|
||||||
|
c vm.Container
|
||||||
|
)
|
||||||
|
cpy := common.CopyBytes(data)
|
||||||
|
if err := c.UnmarshalBinary(data, true); err == nil {
|
||||||
|
c.ValidateCode(&jt, true)
|
||||||
|
if have := c.MarshalBinary(); !bytes.Equal(have, data) {
|
||||||
|
f.Fatal("Unmarshal-> Marshal failure!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := c.UnmarshalBinary(data, false); err == nil {
|
||||||
|
c.ValidateCode(&jt, false)
|
||||||
|
if have := c.MarshalBinary(); !bytes.Equal(have, data) {
|
||||||
|
f.Fatal("Unmarshal-> Marshal failure!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !bytes.Equal(cpy, data) {
|
||||||
|
panic("data modified during unmarshalling")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEofParseInitcode(t *testing.T) {
|
||||||
|
testEofParse(t, true, "testdata/results.initcode.txt")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEofParseRegular(t *testing.T) {
|
||||||
|
testEofParse(t, false, "testdata/results.regular.txt")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testEofParse(t *testing.T, isInitCode bool, wantFile string) {
|
||||||
|
var wantFn func() string
|
||||||
|
var wantLoc = 0
|
||||||
|
{ // Configure the want-reader
|
||||||
|
wants, err := os.Open(wantFile)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
scanner := bufio.NewScanner(wants)
|
||||||
|
scanner.Buffer(make([]byte, 1024), 10*1024*1024)
|
||||||
|
wantFn = func() string {
|
||||||
|
if scanner.Scan() {
|
||||||
|
wantLoc++
|
||||||
|
return scanner.Text()
|
||||||
|
}
|
||||||
|
return "end of file reached"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; ; i++ {
|
||||||
|
fname := fmt.Sprintf("testdata/eof_corpus_%d.txt", i)
|
||||||
|
corpus, err := os.Open(fname)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
t.Logf("# Reading seed data from %v", fname)
|
||||||
|
scanner := bufio.NewScanner(corpus)
|
||||||
|
scanner.Buffer(make([]byte, 1024), 10*1024*1024)
|
||||||
|
line := 1
|
||||||
|
for scanner.Scan() {
|
||||||
|
s := scanner.Text()
|
||||||
|
if len(s) >= 2 && strings.HasPrefix(s, "0x") {
|
||||||
|
s = s[2:]
|
||||||
|
}
|
||||||
|
b, err := hex.DecodeString(s)
|
||||||
|
if err != nil {
|
||||||
|
panic(err) // rotten corpus
|
||||||
|
}
|
||||||
|
have := "OK"
|
||||||
|
if _, err := parse(b, isInitCode); err != nil {
|
||||||
|
have = fmt.Sprintf("ERR: %v", err)
|
||||||
|
}
|
||||||
|
if false { // Change this to generate the want-output
|
||||||
|
fmt.Printf("%v\n", have)
|
||||||
|
} else {
|
||||||
|
want := wantFn()
|
||||||
|
if have != want {
|
||||||
|
if len(want) > 100 {
|
||||||
|
want = want[:100]
|
||||||
|
}
|
||||||
|
if len(b) > 100 {
|
||||||
|
b = b[:100]
|
||||||
|
}
|
||||||
|
t.Fatalf("%v:%d\n%v\ninput %x\nisInit: %v\nhave: %q\nwant: %q\n",
|
||||||
|
fname, line, fmt.Sprintf("%v:%d", wantFile, wantLoc), b, isInitCode, have, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
line++
|
||||||
|
|
||||||
|
}
|
||||||
|
corpus.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkEofParse(b *testing.B) {
|
||||||
|
corpus, err := os.Open("testdata/eof_benches.txt")
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
defer corpus.Close()
|
||||||
|
scanner := bufio.NewScanner(corpus)
|
||||||
|
scanner.Buffer(make([]byte, 1024), 10*1024*1024)
|
||||||
|
line := 1
|
||||||
|
for scanner.Scan() {
|
||||||
|
s := scanner.Text()
|
||||||
|
if len(s) >= 2 && strings.HasPrefix(s, "0x") {
|
||||||
|
s = s[2:]
|
||||||
|
}
|
||||||
|
data, err := hex.DecodeString(s)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err) // rotten corpus
|
||||||
|
}
|
||||||
|
b.Run(fmt.Sprintf("test-%d", line), func(b *testing.B) {
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.SetBytes(int64(len(data)))
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, _ = parse(data, false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
line++
|
||||||
|
}
|
||||||
|
}
|
||||||
19
cmd/eofdump/testdata/eof_benches.txt
vendored
Normal file
19
cmd/eofdump/testdata/eof_benches.txt
vendored
Normal file
File diff suppressed because one or more lines are too long
1814
cmd/eofdump/testdata/eof_corpus_0.txt
vendored
Normal file
1814
cmd/eofdump/testdata/eof_corpus_0.txt
vendored
Normal file
File diff suppressed because one or more lines are too long
350
cmd/eofdump/testdata/eof_corpus_1.txt
vendored
Normal file
350
cmd/eofdump/testdata/eof_corpus_1.txt
vendored
Normal file
File diff suppressed because one or more lines are too long
2164
cmd/eofdump/testdata/results.initcode.txt
vendored
Normal file
2164
cmd/eofdump/testdata/results.initcode.txt
vendored
Normal file
File diff suppressed because it is too large
Load diff
2164
cmd/eofdump/testdata/results.regular.txt
vendored
Normal file
2164
cmd/eofdump/testdata/results.regular.txt
vendored
Normal file
File diff suppressed because it is too large
Load diff
103
core/vm/analysis_eof.go
Normal file
103
core/vm/analysis_eof.go
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
// Copyright 2024 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package vm
|
||||||
|
|
||||||
|
// eofCodeBitmap collects data locations in code.
|
||||||
|
func eofCodeBitmap(code []byte) bitvec {
|
||||||
|
// The bitmap is 4 bytes longer than necessary, in case the code
|
||||||
|
// ends with a PUSH32, the algorithm will push zeroes onto the
|
||||||
|
// bitvector outside the bounds of the actual code.
|
||||||
|
bits := make(bitvec, len(code)/8+1+4)
|
||||||
|
return eofCodeBitmapInternal(code, bits)
|
||||||
|
}
|
||||||
|
|
||||||
|
// eofCodeBitmapInternal is the internal implementation of codeBitmap for EOF
|
||||||
|
// code validation.
|
||||||
|
func eofCodeBitmapInternal(code, bits bitvec) bitvec {
|
||||||
|
for pc := uint64(0); pc < uint64(len(code)); {
|
||||||
|
var (
|
||||||
|
op = OpCode(code[pc])
|
||||||
|
numbits uint16
|
||||||
|
)
|
||||||
|
pc++
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case op >= PUSH1 && op <= PUSH32:
|
||||||
|
numbits = uint16(op - PUSH1 + 1)
|
||||||
|
case op == RJUMP || op == RJUMPI || op == CALLF || op == JUMPF || op == DATALOADN:
|
||||||
|
numbits = 2
|
||||||
|
case op == RJUMPV:
|
||||||
|
// RJUMPV is unique as it has a variable sized operand.
|
||||||
|
// The total size is determined by the count byte which
|
||||||
|
// immediate proceeds RJUMPV. Truncation will be caught
|
||||||
|
// in other validation steps -- for now, just return a
|
||||||
|
// valid bitmap for as much of the code as is
|
||||||
|
// available.
|
||||||
|
end := uint64(len(code))
|
||||||
|
if pc >= end {
|
||||||
|
// Count missing, no more bits to mark.
|
||||||
|
return bits
|
||||||
|
}
|
||||||
|
numbits = uint16(code[pc])*2 + 3
|
||||||
|
if pc+uint64(numbits) > end {
|
||||||
|
// Jump table is truncated, mark as many bits
|
||||||
|
// as possible.
|
||||||
|
numbits = uint16(end - pc)
|
||||||
|
}
|
||||||
|
case op == DUPN || op == SWAPN || op == EXCHANGE || op == EOFCREATE || op == RETURNCONTRACT:
|
||||||
|
numbits = 1
|
||||||
|
default:
|
||||||
|
// Op had no immediate operand, continue.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if numbits >= 8 {
|
||||||
|
for ; numbits >= 16; numbits -= 16 {
|
||||||
|
bits.set16(pc)
|
||||||
|
pc += 16
|
||||||
|
}
|
||||||
|
for ; numbits >= 8; numbits -= 8 {
|
||||||
|
bits.set8(pc)
|
||||||
|
pc += 8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch numbits {
|
||||||
|
case 1:
|
||||||
|
bits.set1(pc)
|
||||||
|
pc += 1
|
||||||
|
case 2:
|
||||||
|
bits.setN(set2BitsMask, pc)
|
||||||
|
pc += 2
|
||||||
|
case 3:
|
||||||
|
bits.setN(set3BitsMask, pc)
|
||||||
|
pc += 3
|
||||||
|
case 4:
|
||||||
|
bits.setN(set4BitsMask, pc)
|
||||||
|
pc += 4
|
||||||
|
case 5:
|
||||||
|
bits.setN(set5BitsMask, pc)
|
||||||
|
pc += 5
|
||||||
|
case 6:
|
||||||
|
bits.setN(set6BitsMask, pc)
|
||||||
|
pc += 6
|
||||||
|
case 7:
|
||||||
|
bits.setN(set7BitsMask, pc)
|
||||||
|
pc += 7
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bits
|
||||||
|
}
|
||||||
163
core/vm/eips.go
163
core/vm/eips.go
|
|
@ -533,3 +533,166 @@ func enable4762(jt *JumpTable) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// enableEOF applies the EOF changes.
|
||||||
|
func enableEOF(jt *JumpTable) {
|
||||||
|
// Deprecate opcodes
|
||||||
|
undefined := &operation{
|
||||||
|
execute: opUndefined,
|
||||||
|
constantGas: 0,
|
||||||
|
minStack: minStack(0, 0),
|
||||||
|
maxStack: maxStack(0, 0),
|
||||||
|
undefined: true,
|
||||||
|
}
|
||||||
|
jt[CALL] = undefined
|
||||||
|
jt[CALLCODE] = undefined
|
||||||
|
jt[DELEGATECALL] = undefined
|
||||||
|
jt[STATICCALL] = undefined
|
||||||
|
jt[SELFDESTRUCT] = undefined
|
||||||
|
jt[JUMP] = undefined
|
||||||
|
jt[JUMPI] = undefined
|
||||||
|
jt[PC] = undefined
|
||||||
|
jt[CREATE] = undefined
|
||||||
|
jt[CREATE2] = undefined
|
||||||
|
jt[CODESIZE] = undefined
|
||||||
|
jt[CODECOPY] = undefined
|
||||||
|
jt[EXTCODESIZE] = undefined
|
||||||
|
jt[EXTCODECOPY] = undefined
|
||||||
|
jt[EXTCODEHASH] = undefined
|
||||||
|
jt[GAS] = undefined
|
||||||
|
// Allow 0xFE to terminate sections
|
||||||
|
jt[INVALID] = &operation{
|
||||||
|
execute: opUndefined,
|
||||||
|
constantGas: 0,
|
||||||
|
minStack: minStack(0, 0),
|
||||||
|
maxStack: maxStack(0, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
// New opcodes
|
||||||
|
jt[RJUMP] = &operation{
|
||||||
|
execute: opRjump,
|
||||||
|
constantGas: GasQuickStep,
|
||||||
|
minStack: minStack(0, 0),
|
||||||
|
maxStack: maxStack(0, 0),
|
||||||
|
}
|
||||||
|
jt[RJUMPI] = &operation{
|
||||||
|
execute: opRjumpi,
|
||||||
|
constantGas: GasFastishStep,
|
||||||
|
minStack: minStack(1, 0),
|
||||||
|
maxStack: maxStack(1, 0),
|
||||||
|
}
|
||||||
|
jt[RJUMPV] = &operation{
|
||||||
|
execute: opRjumpv,
|
||||||
|
constantGas: GasFastishStep,
|
||||||
|
minStack: minStack(1, 0),
|
||||||
|
maxStack: maxStack(1, 0),
|
||||||
|
}
|
||||||
|
jt[CALLF] = &operation{
|
||||||
|
execute: opCallf,
|
||||||
|
constantGas: GasFastStep,
|
||||||
|
minStack: minStack(0, 0),
|
||||||
|
maxStack: maxStack(0, 0),
|
||||||
|
}
|
||||||
|
jt[RETF] = &operation{
|
||||||
|
execute: opRetf,
|
||||||
|
constantGas: GasFastestStep,
|
||||||
|
minStack: minStack(0, 0),
|
||||||
|
maxStack: maxStack(0, 0),
|
||||||
|
}
|
||||||
|
jt[JUMPF] = &operation{
|
||||||
|
execute: opJumpf,
|
||||||
|
constantGas: GasFastStep,
|
||||||
|
minStack: minStack(0, 0),
|
||||||
|
maxStack: maxStack(0, 0),
|
||||||
|
}
|
||||||
|
jt[EOFCREATE] = &operation{
|
||||||
|
execute: opEOFCreate,
|
||||||
|
constantGas: params.Create2Gas,
|
||||||
|
dynamicGas: gasEOFCreate,
|
||||||
|
minStack: minStack(4, 1),
|
||||||
|
maxStack: maxStack(4, 1),
|
||||||
|
memorySize: memoryEOFCreate,
|
||||||
|
}
|
||||||
|
jt[RETURNCONTRACT] = &operation{
|
||||||
|
execute: opReturnContract,
|
||||||
|
// returncontract has zero constant gas cost
|
||||||
|
dynamicGas: pureMemoryGascost,
|
||||||
|
minStack: minStack(2, 0),
|
||||||
|
maxStack: maxStack(2, 0),
|
||||||
|
memorySize: memoryReturnContract,
|
||||||
|
}
|
||||||
|
jt[DATALOAD] = &operation{
|
||||||
|
execute: opDataLoad,
|
||||||
|
constantGas: GasFastishStep,
|
||||||
|
minStack: minStack(1, 1),
|
||||||
|
maxStack: maxStack(1, 1),
|
||||||
|
}
|
||||||
|
jt[DATALOADN] = &operation{
|
||||||
|
execute: opDataLoadN,
|
||||||
|
constantGas: GasFastestStep,
|
||||||
|
minStack: minStack(0, 1),
|
||||||
|
maxStack: maxStack(0, 1),
|
||||||
|
}
|
||||||
|
jt[DATASIZE] = &operation{
|
||||||
|
execute: opDataSize,
|
||||||
|
constantGas: GasQuickStep,
|
||||||
|
minStack: minStack(0, 1),
|
||||||
|
maxStack: maxStack(0, 1),
|
||||||
|
}
|
||||||
|
jt[DATACOPY] = &operation{
|
||||||
|
execute: opDataCopy,
|
||||||
|
constantGas: GasFastestStep,
|
||||||
|
dynamicGas: memoryCopierGas(2),
|
||||||
|
minStack: minStack(3, 0),
|
||||||
|
maxStack: maxStack(3, 0),
|
||||||
|
memorySize: memoryDataCopy,
|
||||||
|
}
|
||||||
|
jt[DUPN] = &operation{
|
||||||
|
execute: opDupN,
|
||||||
|
constantGas: GasFastestStep,
|
||||||
|
minStack: minStack(0, 1),
|
||||||
|
maxStack: maxStack(0, 1),
|
||||||
|
}
|
||||||
|
jt[SWAPN] = &operation{
|
||||||
|
execute: opSwapN,
|
||||||
|
constantGas: GasFastestStep,
|
||||||
|
minStack: minStack(0, 0),
|
||||||
|
maxStack: maxStack(0, 0),
|
||||||
|
}
|
||||||
|
jt[EXCHANGE] = &operation{
|
||||||
|
execute: opExchange,
|
||||||
|
constantGas: GasFastestStep,
|
||||||
|
minStack: minStack(0, 0),
|
||||||
|
maxStack: maxStack(0, 0),
|
||||||
|
}
|
||||||
|
jt[RETURNDATALOAD] = &operation{
|
||||||
|
execute: opReturnDataLoad,
|
||||||
|
constantGas: GasFastestStep,
|
||||||
|
minStack: minStack(1, 1),
|
||||||
|
maxStack: maxStack(1, 1),
|
||||||
|
}
|
||||||
|
jt[EXTCALL] = &operation{
|
||||||
|
execute: opExtCall,
|
||||||
|
constantGas: params.WarmStorageReadCostEIP2929,
|
||||||
|
dynamicGas: makeCallVariantGasCallEIP2929(gasExtCall, 0),
|
||||||
|
minStack: minStack(4, 1),
|
||||||
|
maxStack: maxStack(4, 1),
|
||||||
|
memorySize: memoryExtCall,
|
||||||
|
}
|
||||||
|
jt[EXTDELEGATECALL] = &operation{
|
||||||
|
execute: opExtDelegateCall,
|
||||||
|
dynamicGas: makeCallVariantGasCallEIP2929(gasExtDelegateCall, 0),
|
||||||
|
constantGas: params.WarmStorageReadCostEIP2929,
|
||||||
|
minStack: minStack(3, 1),
|
||||||
|
maxStack: maxStack(3, 1),
|
||||||
|
memorySize: memoryExtCall,
|
||||||
|
}
|
||||||
|
jt[EXTSTATICCALL] = &operation{
|
||||||
|
execute: opExtStaticCall,
|
||||||
|
constantGas: params.WarmStorageReadCostEIP2929,
|
||||||
|
dynamicGas: makeCallVariantGasCallEIP2929(gasExtStaticCall, 0),
|
||||||
|
minStack: minStack(3, 1),
|
||||||
|
maxStack: maxStack(3, 1),
|
||||||
|
memorySize: memoryExtCall,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
475
core/vm/eof.go
Normal file
475
core/vm/eof.go
Normal file
|
|
@ -0,0 +1,475 @@
|
||||||
|
// Copyright 2022 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package vm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
offsetVersion = 2
|
||||||
|
offsetTypesKind = 3
|
||||||
|
offsetCodeKind = 6
|
||||||
|
|
||||||
|
kindTypes = 1
|
||||||
|
kindCode = 2
|
||||||
|
kindContainer = 3
|
||||||
|
kindData = 4
|
||||||
|
|
||||||
|
eofFormatByte = 0xef
|
||||||
|
eof1Version = 1
|
||||||
|
|
||||||
|
maxInputItems = 127
|
||||||
|
maxOutputItems = 128
|
||||||
|
maxStackHeight = 1023
|
||||||
|
maxContainerSections = 256
|
||||||
|
)
|
||||||
|
|
||||||
|
var eofMagic = []byte{0xef, 0x00}
|
||||||
|
|
||||||
|
// HasEOFByte returns true if code starts with 0xEF byte
|
||||||
|
func HasEOFByte(code []byte) bool {
|
||||||
|
return len(code) != 0 && code[0] == eofFormatByte
|
||||||
|
}
|
||||||
|
|
||||||
|
// hasEOFMagic returns true if code starts with magic defined by EIP-3540
|
||||||
|
func hasEOFMagic(code []byte) bool {
|
||||||
|
return len(eofMagic) <= len(code) && bytes.Equal(eofMagic, code[0:len(eofMagic)])
|
||||||
|
}
|
||||||
|
|
||||||
|
// isEOFVersion1 returns true if the code's version byte equals eof1Version. It
|
||||||
|
// does not verify the EOF magic is valid.
|
||||||
|
func isEOFVersion1(code []byte) bool {
|
||||||
|
return 2 < len(code) && code[2] == byte(eof1Version)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Container is an EOF container object.
|
||||||
|
type Container struct {
|
||||||
|
types []*functionMetadata
|
||||||
|
code [][]byte
|
||||||
|
sections []*Container
|
||||||
|
containerCode [][]byte
|
||||||
|
data []byte
|
||||||
|
dataSize int // might be more than len(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// functionMetadata is an EOF function signature.
|
||||||
|
type functionMetadata struct {
|
||||||
|
inputs uint8
|
||||||
|
outputs uint8
|
||||||
|
maxStackHeight uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalBinary encodes an EOF container into binary format.
|
||||||
|
func (c *Container) MarshalBinary() []byte {
|
||||||
|
// Build EOF prefix.
|
||||||
|
b := make([]byte, 2)
|
||||||
|
copy(b, eofMagic)
|
||||||
|
b = append(b, eof1Version)
|
||||||
|
|
||||||
|
// Write section headers.
|
||||||
|
b = append(b, kindTypes)
|
||||||
|
b = binary.BigEndian.AppendUint16(b, uint16(len(c.types)*4))
|
||||||
|
b = append(b, kindCode)
|
||||||
|
b = binary.BigEndian.AppendUint16(b, uint16(len(c.code)))
|
||||||
|
for _, code := range c.code {
|
||||||
|
b = binary.BigEndian.AppendUint16(b, uint16(len(code)))
|
||||||
|
}
|
||||||
|
var encodedContainer [][]byte
|
||||||
|
if len(c.sections) != 0 {
|
||||||
|
b = append(b, kindContainer)
|
||||||
|
b = binary.BigEndian.AppendUint16(b, uint16(len(c.sections)))
|
||||||
|
for _, section := range c.sections {
|
||||||
|
encoded := section.MarshalBinary()
|
||||||
|
b = binary.BigEndian.AppendUint16(b, uint16(len(encoded)))
|
||||||
|
encodedContainer = append(encodedContainer, encoded)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b = append(b, kindData)
|
||||||
|
b = binary.BigEndian.AppendUint16(b, uint16(c.dataSize))
|
||||||
|
b = append(b, 0) // terminator
|
||||||
|
|
||||||
|
// Write section contents.
|
||||||
|
for _, ty := range c.types {
|
||||||
|
b = append(b, []byte{ty.inputs, ty.outputs, byte(ty.maxStackHeight >> 8), byte(ty.maxStackHeight & 0x00ff)}...)
|
||||||
|
}
|
||||||
|
for _, code := range c.code {
|
||||||
|
b = append(b, code...)
|
||||||
|
}
|
||||||
|
for _, section := range encodedContainer {
|
||||||
|
b = append(b, section...)
|
||||||
|
}
|
||||||
|
b = append(b, c.data...)
|
||||||
|
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalBinary decodes an EOF container.
|
||||||
|
func (c *Container) UnmarshalBinary(b []byte, isInitcode bool) error {
|
||||||
|
return c.unmarshalSubContainer(b, isInitcode, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Container) unmarshalSubContainer(b []byte, isInitcode bool, topLevel bool) error {
|
||||||
|
if !hasEOFMagic(b) {
|
||||||
|
return fmt.Errorf("%w: want %x", ErrInvalidMagic, eofMagic)
|
||||||
|
}
|
||||||
|
if len(b) < 14 {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
if len(b) > params.MaxInitCodeSize {
|
||||||
|
return ErrMaxInitCodeSizeExceeded
|
||||||
|
}
|
||||||
|
if !isEOFVersion1(b) {
|
||||||
|
return fmt.Errorf("%w: have %d, want %d", ErrInvalidVersion, b[2], eof1Version)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
kind, typesSize, dataSize int
|
||||||
|
codeSizes []int
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
// Parse type section header.
|
||||||
|
kind, typesSize, err = parseSection(b, offsetTypesKind)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if kind != kindTypes {
|
||||||
|
return fmt.Errorf("%w: found section kind %x instead", ErrMissingTypeHeader, kind)
|
||||||
|
}
|
||||||
|
if typesSize < 4 || typesSize%4 != 0 {
|
||||||
|
return fmt.Errorf("%w: type section size must be divisible by 4, have %d", ErrInvalidTypeSize, typesSize)
|
||||||
|
}
|
||||||
|
if typesSize/4 > 1024 {
|
||||||
|
return fmt.Errorf("%w: type section must not exceed 4*1024, have %d", ErrInvalidTypeSize, typesSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse code section header.
|
||||||
|
kind, codeSizes, err = parseSectionList(b, offsetCodeKind)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if kind != kindCode {
|
||||||
|
return fmt.Errorf("%w: found section kind %x instead", ErrMissingCodeHeader, kind)
|
||||||
|
}
|
||||||
|
if len(codeSizes) != typesSize/4 {
|
||||||
|
return fmt.Errorf("%w: mismatch of code sections found and type signatures, types %d, code %d", ErrInvalidCodeSize, typesSize/4, len(codeSizes))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse (optional) container section header.
|
||||||
|
var containerSizes []int
|
||||||
|
offset := offsetCodeKind + 2 + 2*len(codeSizes) + 1
|
||||||
|
if offset < len(b) && b[offset] == kindContainer {
|
||||||
|
kind, containerSizes, err = parseSectionList(b, offset)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if kind != kindContainer {
|
||||||
|
panic("somethings wrong")
|
||||||
|
}
|
||||||
|
if len(containerSizes) == 0 {
|
||||||
|
return fmt.Errorf("%w: total container count must not be zero", ErrInvalidContainerSectionSize)
|
||||||
|
}
|
||||||
|
offset = offset + 2 + 2*len(containerSizes) + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse data section header.
|
||||||
|
kind, dataSize, err = parseSection(b, offset)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if kind != kindData {
|
||||||
|
return fmt.Errorf("%w: found section %x instead", ErrMissingDataHeader, kind)
|
||||||
|
}
|
||||||
|
c.dataSize = dataSize
|
||||||
|
|
||||||
|
// Check for terminator.
|
||||||
|
offsetTerminator := offset + 3
|
||||||
|
if len(b) < offsetTerminator {
|
||||||
|
return fmt.Errorf("%w: invalid offset terminator", io.ErrUnexpectedEOF)
|
||||||
|
}
|
||||||
|
if b[offsetTerminator] != 0 {
|
||||||
|
return fmt.Errorf("%w: have %x", ErrMissingTerminator, b[offsetTerminator])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify overall container size.
|
||||||
|
expectedSize := offsetTerminator + typesSize + sum(codeSizes) + dataSize + 1
|
||||||
|
if len(containerSizes) != 0 {
|
||||||
|
expectedSize += sum(containerSizes)
|
||||||
|
}
|
||||||
|
if len(b) < expectedSize-dataSize {
|
||||||
|
return fmt.Errorf("%w: have %d, want %d", ErrInvalidContainerSize, len(b), expectedSize)
|
||||||
|
}
|
||||||
|
// Only check that the expected size is not exceed on non-initcode
|
||||||
|
if !isInitcode && len(b) > expectedSize {
|
||||||
|
return fmt.Errorf("%w: have %d, want %d", ErrInvalidContainerSize, len(b), expectedSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse types section.
|
||||||
|
idx := offsetTerminator + 1
|
||||||
|
var types []*functionMetadata
|
||||||
|
for i := 0; i < typesSize/4; i++ {
|
||||||
|
sig := &functionMetadata{
|
||||||
|
inputs: b[idx+i*4],
|
||||||
|
outputs: b[idx+i*4+1],
|
||||||
|
maxStackHeight: binary.BigEndian.Uint16(b[idx+i*4+2:]),
|
||||||
|
}
|
||||||
|
if sig.inputs > maxInputItems {
|
||||||
|
return fmt.Errorf("%w for section %d: have %d", ErrTooManyInputs, i, sig.inputs)
|
||||||
|
}
|
||||||
|
if sig.outputs > maxOutputItems {
|
||||||
|
return fmt.Errorf("%w for section %d: have %d", ErrTooManyOutputs, i, sig.outputs)
|
||||||
|
}
|
||||||
|
if sig.maxStackHeight > maxStackHeight {
|
||||||
|
return fmt.Errorf("%w for section %d: have %d", ErrTooLargeMaxStackHeight, i, sig.maxStackHeight)
|
||||||
|
}
|
||||||
|
types = append(types, sig)
|
||||||
|
}
|
||||||
|
if types[0].inputs != 0 || types[0].outputs != 0x80 {
|
||||||
|
return fmt.Errorf("%w: have %d, %d", ErrInvalidSection0Type, types[0].inputs, types[0].outputs)
|
||||||
|
}
|
||||||
|
c.types = types
|
||||||
|
|
||||||
|
// Parse code sections.
|
||||||
|
idx += typesSize
|
||||||
|
code := 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)
|
||||||
|
}
|
||||||
|
code[i] = b[idx : idx+size]
|
||||||
|
idx += size
|
||||||
|
}
|
||||||
|
c.code = code
|
||||||
|
|
||||||
|
// 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))
|
||||||
|
}
|
||||||
|
containerCode := make([][]byte, 0, len(containerSizes))
|
||||||
|
container := 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)
|
||||||
|
}
|
||||||
|
c := new(Container)
|
||||||
|
end := min(idx+size, len(b))
|
||||||
|
if err := c.unmarshalSubContainer(b[idx:end], isInitcode, false); err != nil {
|
||||||
|
if topLevel {
|
||||||
|
return fmt.Errorf("%w in sub container %d", err, i)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
container = append(container, c)
|
||||||
|
containerCode = append(containerCode, b[idx:end])
|
||||||
|
|
||||||
|
idx += size
|
||||||
|
}
|
||||||
|
c.sections = container
|
||||||
|
c.containerCode = containerCode
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse data section.
|
||||||
|
end := len(b)
|
||||||
|
if !isInitcode {
|
||||||
|
end = min(idx+dataSize, len(b))
|
||||||
|
}
|
||||||
|
if topLevel && len(b) != idx+dataSize {
|
||||||
|
return ErrTruncatedTopLevelContainer
|
||||||
|
}
|
||||||
|
c.data = b[idx:end]
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateCode validates each code section of the container against the EOF v1
|
||||||
|
// rule set.
|
||||||
|
func (c *Container) ValidateCode(jt *JumpTable, isInitCode bool) error {
|
||||||
|
refBy := notRefByEither
|
||||||
|
if isInitCode {
|
||||||
|
refBy = refByEOFCreate
|
||||||
|
}
|
||||||
|
return c.validateSubContainer(jt, refBy)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Container) validateSubContainer(jt *JumpTable, refBy int) error {
|
||||||
|
visited := make(map[int]struct{})
|
||||||
|
subContainerVisited := make(map[int]int)
|
||||||
|
toVisit := []int{0}
|
||||||
|
for len(toVisit) > 0 {
|
||||||
|
// TODO check if this can be used as a DOS
|
||||||
|
// Theres and edge case here where we mark something as visited that we visit before,
|
||||||
|
// This should not trigger a re-visit
|
||||||
|
// e.g. 0 -> 1, 2, 3
|
||||||
|
// 1 -> 2, 3
|
||||||
|
// should not mean 2 and 3 should be visited twice
|
||||||
|
var (
|
||||||
|
index = toVisit[0]
|
||||||
|
code = c.code[index]
|
||||||
|
)
|
||||||
|
if _, ok := visited[index]; !ok {
|
||||||
|
res, err := validateCode(code, index, c, jt, refBy == refByEOFCreate)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
visited[index] = struct{}{}
|
||||||
|
// Mark all sections that can be visited from here.
|
||||||
|
for idx := range res.visitedCode {
|
||||||
|
if _, ok := visited[idx]; !ok {
|
||||||
|
toVisit = append(toVisit, idx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Mark all subcontainer that can be visited from here.
|
||||||
|
for idx, reference := range res.visitedSubContainers {
|
||||||
|
// Make sure subcontainers are only ever referenced by either EOFCreate or ReturnContract
|
||||||
|
if ref, ok := subContainerVisited[idx]; ok && ref != reference {
|
||||||
|
return errors.New("section referenced by both EOFCreate and ReturnContract")
|
||||||
|
}
|
||||||
|
subContainerVisited[idx] = reference
|
||||||
|
}
|
||||||
|
if refBy == refByReturnContract && res.isInitCode {
|
||||||
|
return ErrIncompatibleContainerKind
|
||||||
|
}
|
||||||
|
if refBy == refByEOFCreate && res.isRuntime {
|
||||||
|
return ErrIncompatibleContainerKind
|
||||||
|
}
|
||||||
|
}
|
||||||
|
toVisit = toVisit[1:]
|
||||||
|
}
|
||||||
|
// Make sure every code section is visited at least once.
|
||||||
|
if len(visited) != len(c.code) {
|
||||||
|
return ErrUnreachableCode
|
||||||
|
}
|
||||||
|
for idx, container := range c.sections {
|
||||||
|
reference, ok := subContainerVisited[idx]
|
||||||
|
if !ok {
|
||||||
|
return ErrOrphanedSubcontainer
|
||||||
|
}
|
||||||
|
if err := container.validateSubContainer(jt, reference); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseSection decodes a (kind, size) pair from an EOF header.
|
||||||
|
func parseSection(b []byte, idx int) (kind, size int, err error) {
|
||||||
|
if idx+3 >= len(b) {
|
||||||
|
return 0, 0, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
kind = int(b[idx])
|
||||||
|
size = int(binary.BigEndian.Uint16(b[idx+1:]))
|
||||||
|
return kind, size, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseSectionList decodes a (kind, len, []codeSize) section list from an EOF
|
||||||
|
// header.
|
||||||
|
func parseSectionList(b []byte, idx int) (kind int, list []int, err error) {
|
||||||
|
if idx >= len(b) {
|
||||||
|
return 0, nil, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
kind = int(b[idx])
|
||||||
|
list, err = parseList(b, idx+1)
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil, err
|
||||||
|
}
|
||||||
|
return kind, list, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseList decodes a list of uint16..
|
||||||
|
func parseList(b []byte, idx int) ([]int, error) {
|
||||||
|
if len(b) < idx+2 {
|
||||||
|
return nil, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
count := binary.BigEndian.Uint16(b[idx:])
|
||||||
|
if len(b) <= idx+2+int(count)*2 {
|
||||||
|
return nil, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
list := make([]int, count)
|
||||||
|
for i := 0; i < int(count); i++ {
|
||||||
|
list[i] = int(binary.BigEndian.Uint16(b[idx+2+2*i:]))
|
||||||
|
}
|
||||||
|
return list, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseUint16 parses a 16 bit unsigned integer.
|
||||||
|
func parseUint16(b []byte) (int, error) {
|
||||||
|
if len(b) < 2 {
|
||||||
|
return 0, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
return int(binary.BigEndian.Uint16(b)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseInt16 parses a 16 bit signed integer.
|
||||||
|
func parseInt16(b []byte) int {
|
||||||
|
return int(int16(b[1]) | int16(b[0])<<8)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sum computes the sum of a slice.
|
||||||
|
func sum(list []int) (s int) {
|
||||||
|
for _, n := range list {
|
||||||
|
s += n
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Container) String() string {
|
||||||
|
var 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.code))
|
||||||
|
for i, code := range c.code {
|
||||||
|
result += fmt.Sprintf("Code %v length: %04x\n", i, len(code))
|
||||||
|
}
|
||||||
|
if len(c.sections) != 0 {
|
||||||
|
result += fmt.Sprintf("KindContainer: %02x\n", kindContainer)
|
||||||
|
result += fmt.Sprintf("ContainerSize: %04x\n", len(c.sections))
|
||||||
|
for i, section := range c.sections {
|
||||||
|
result += fmt.Sprintf("Container %v 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"
|
||||||
|
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)}))
|
||||||
|
}
|
||||||
|
for i, code := range c.code {
|
||||||
|
result += fmt.Sprintf("Code %v: %v\n", i, hex.EncodeToString(code))
|
||||||
|
}
|
||||||
|
for i, section := range c.sections {
|
||||||
|
result += fmt.Sprintf("Section %v: %v\n", i, hex.EncodeToString(section.MarshalBinary()))
|
||||||
|
}
|
||||||
|
result += fmt.Sprintf("Data: %v\n", hex.EncodeToString(c.data))
|
||||||
|
return result
|
||||||
|
}
|
||||||
57
core/vm/eof_immediates.go
Normal file
57
core/vm/eof_immediates.go
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
// Copyright 2024 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package vm
|
||||||
|
|
||||||
|
// immediate denotes how many immediate bytes an operation uses. This information
|
||||||
|
// is not required during runtime, only during EOF-validation, so is not
|
||||||
|
// places into the op-struct in the instruction table.
|
||||||
|
// Note: the immediates is fork-agnostic, and assumes that validity of opcodes at
|
||||||
|
// the given time is performed elsewhere.
|
||||||
|
var immediates [256]uint8
|
||||||
|
|
||||||
|
// terminals denotes whether instructions can be the final opcode in a code section.
|
||||||
|
// Note: the terminals is fork-agnostic, and assumes that validity of opcodes at
|
||||||
|
// the given time is performed elsewhere.
|
||||||
|
var terminals [256]bool
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// The legacy pushes
|
||||||
|
for i := uint8(1); i < 33; i++ {
|
||||||
|
immediates[int(PUSH0)+int(i)] = i
|
||||||
|
}
|
||||||
|
// And new eof opcodes.
|
||||||
|
immediates[DATALOADN] = 2
|
||||||
|
immediates[RJUMP] = 2
|
||||||
|
immediates[RJUMPI] = 2
|
||||||
|
immediates[RJUMPV] = 3
|
||||||
|
immediates[CALLF] = 2
|
||||||
|
immediates[JUMPF] = 2
|
||||||
|
immediates[DUPN] = 1
|
||||||
|
immediates[SWAPN] = 1
|
||||||
|
immediates[EXCHANGE] = 1
|
||||||
|
immediates[EOFCREATE] = 1
|
||||||
|
immediates[RETURNCONTRACT] = 1
|
||||||
|
|
||||||
|
// Define the terminals.
|
||||||
|
terminals[STOP] = true
|
||||||
|
terminals[RETF] = true
|
||||||
|
terminals[JUMPF] = true
|
||||||
|
terminals[RETURNCONTRACT] = true
|
||||||
|
terminals[RETURN] = true
|
||||||
|
terminals[REVERT] = true
|
||||||
|
terminals[INVALID] = true
|
||||||
|
}
|
||||||
117
core/vm/eof_instructions.go
Normal file
117
core/vm/eof_instructions.go
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
// Copyright 2024 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package vm
|
||||||
|
|
||||||
|
// opExtCodeCopyEOF implements the EXTCODECOPY opcode for EOF-enabled forks.
|
||||||
|
func opExtCodeCopyEOF(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// opRjump implements the RJUMP opcode.
|
||||||
|
func opRjump(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// opRjumpi implements the RJUMPI opcode
|
||||||
|
func opRjumpi(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// opRjumpv implements the RJUMPV opcode
|
||||||
|
func opRjumpv(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// opCallf implements the CALLF opcode
|
||||||
|
func opCallf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// opRetf implements the RETF opcode
|
||||||
|
func opRetf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// opJumpf implements the JUMPF opcode
|
||||||
|
func opJumpf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// opEOFCreate implements the EOFCREATE opcode
|
||||||
|
func opEOFCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// opReturnContract implements the RETURNCONTRACT opcode
|
||||||
|
func opReturnContract(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// opDataLoad implements the DATALOAD opcode
|
||||||
|
func opDataLoad(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// opDataLoadN implements the DATALOADN opcode
|
||||||
|
func opDataLoadN(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// opDataSize implements the DATASIZE opcode
|
||||||
|
func opDataSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// opDataCopy implements the DATACOPY opcode
|
||||||
|
func opDataCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// opDupN implements the DUPN opcode
|
||||||
|
func opDupN(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// opSwapN implements the SWAPN opcode
|
||||||
|
func opSwapN(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// opExchange implements the EXCHANGE opcode
|
||||||
|
func opExchange(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// opReturnDataLoad implements the RETURNDATALOAD opcode
|
||||||
|
func opReturnDataLoad(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// opExtCall implements the EOFCREATE opcode
|
||||||
|
func opExtCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// opExtDelegateCall implements the EXTDELEGATECALL opcode
|
||||||
|
func opExtDelegateCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// opExtStaticCall implements the EXTSTATICCALL opcode
|
||||||
|
func opExtStaticCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
119
core/vm/eof_test.go
Normal file
119
core/vm/eof_test.go
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
// Copyright 2022 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package vm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEOFMarshaling(t *testing.T) {
|
||||||
|
for i, test := range []struct {
|
||||||
|
want Container
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
want: Container{
|
||||||
|
types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
|
||||||
|
code: [][]byte{common.Hex2Bytes("604200")},
|
||||||
|
data: []byte{0x01, 0x02, 0x03},
|
||||||
|
dataSize: 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
want: Container{
|
||||||
|
types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
|
||||||
|
code: [][]byte{common.Hex2Bytes("604200")},
|
||||||
|
data: []byte{0x01, 0x02, 0x03},
|
||||||
|
dataSize: 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
want: Container{
|
||||||
|
types: []*functionMetadata{
|
||||||
|
{inputs: 0, outputs: 0x80, maxStackHeight: 1},
|
||||||
|
{inputs: 2, outputs: 3, maxStackHeight: 4},
|
||||||
|
{inputs: 1, outputs: 1, maxStackHeight: 1},
|
||||||
|
},
|
||||||
|
code: [][]byte{
|
||||||
|
common.Hex2Bytes("604200"),
|
||||||
|
common.Hex2Bytes("6042604200"),
|
||||||
|
common.Hex2Bytes("00"),
|
||||||
|
},
|
||||||
|
data: []byte{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
var (
|
||||||
|
b = test.want.MarshalBinary()
|
||||||
|
got Container
|
||||||
|
)
|
||||||
|
t.Logf("b: %#x", b)
|
||||||
|
if err := got.UnmarshalBinary(b, true); err != nil && err != test.err {
|
||||||
|
t.Fatalf("test %d: got error \"%v\", want \"%v\"", i, err, test.err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, test.want) {
|
||||||
|
t.Fatalf("test %d: got %+v, want %+v", i, got, test.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEOFSubcontainer(t *testing.T) {
|
||||||
|
var subcontainer = new(Container)
|
||||||
|
if err := subcontainer.UnmarshalBinary(common.Hex2Bytes("ef000101000402000100010400000000800000fe"), true); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
container := Container{
|
||||||
|
types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
|
||||||
|
code: [][]byte{common.Hex2Bytes("604200")},
|
||||||
|
sections: []*Container{subcontainer},
|
||||||
|
data: []byte{0x01, 0x02, 0x03},
|
||||||
|
dataSize: 3,
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
b = container.MarshalBinary()
|
||||||
|
got Container
|
||||||
|
)
|
||||||
|
if err := got.UnmarshalBinary(b, true); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
fmt.Print(got)
|
||||||
|
if res := got.MarshalBinary(); !reflect.DeepEqual(res, b) {
|
||||||
|
t.Fatalf("invalid marshalling, want %v got %v", b, res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshaling(t *testing.T) {
|
||||||
|
tests := []string{
|
||||||
|
"EF000101000402000100040400000000800000E0000000",
|
||||||
|
"ef0001010004020001000d04000000008000025fe100055f5fe000035f600100",
|
||||||
|
}
|
||||||
|
for i, test := range tests {
|
||||||
|
s, err := hex.DecodeString(test)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("test %d: error decoding: %v", i, err)
|
||||||
|
}
|
||||||
|
var got Container
|
||||||
|
if err := got.UnmarshalBinary(s, true); err != nil {
|
||||||
|
t.Fatalf("test %d: got error %v", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -51,10 +51,14 @@ type ErrStackUnderflow struct {
|
||||||
required int
|
required int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ErrStackUnderflow) Error() string {
|
func (e ErrStackUnderflow) Error() string {
|
||||||
return fmt.Sprintf("stack underflow (%d <=> %d)", e.stackLen, e.required)
|
return fmt.Sprintf("stack underflow (%d <=> %d)", e.stackLen, e.required)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e ErrStackUnderflow) Unwrap() error {
|
||||||
|
return fmt.Errorf("stack underflow")
|
||||||
|
}
|
||||||
|
|
||||||
// ErrStackOverflow wraps an evm error when the items on the stack exceeds
|
// ErrStackOverflow wraps an evm error when the items on the stack exceeds
|
||||||
// the maximum allowance.
|
// the maximum allowance.
|
||||||
type ErrStackOverflow struct {
|
type ErrStackOverflow struct {
|
||||||
|
|
@ -62,10 +66,14 @@ type ErrStackOverflow struct {
|
||||||
limit int
|
limit int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ErrStackOverflow) Error() string {
|
func (e ErrStackOverflow) Error() string {
|
||||||
return fmt.Sprintf("stack limit reached %d (%d)", e.stackLen, e.limit)
|
return fmt.Sprintf("stack limit reached %d (%d)", e.stackLen, e.limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e ErrStackOverflow) Unwrap() error {
|
||||||
|
return fmt.Errorf("stack overflow")
|
||||||
|
}
|
||||||
|
|
||||||
// ErrInvalidOpCode wraps an evm error when an invalid opcode is encountered.
|
// ErrInvalidOpCode wraps an evm error when an invalid opcode is encountered.
|
||||||
type ErrInvalidOpCode struct {
|
type ErrInvalidOpCode struct {
|
||||||
opcode OpCode
|
opcode OpCode
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import (
|
||||||
const (
|
const (
|
||||||
GasQuickStep uint64 = 2
|
GasQuickStep uint64 = 2
|
||||||
GasFastestStep uint64 = 3
|
GasFastestStep uint64 = 3
|
||||||
|
GasFastishStep uint64 = 4
|
||||||
GasFastStep uint64 = 5
|
GasFastStep uint64 = 5
|
||||||
GasMidStep uint64 = 8
|
GasMidStep uint64 = 8
|
||||||
GasSlowStep uint64 = 10
|
GasSlowStep uint64 = 10
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/math"
|
"github.com/ethereum/go-ethereum/common/math"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
"github.com/holiman/uint256"
|
||||||
)
|
)
|
||||||
|
|
||||||
// memoryGasCost calculates the quadratic gas for memory expansion. It does so
|
// memoryGasCost calculates the quadratic gas for memory expansion. It does so
|
||||||
|
|
@ -480,6 +481,52 @@ func gasStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memo
|
||||||
return gas, nil
|
return gas, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func gasExtCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||||
|
var (
|
||||||
|
gas uint64
|
||||||
|
transfersValue = !stack.Back(2).IsZero()
|
||||||
|
address = common.Address(stack.Back(1).Bytes20())
|
||||||
|
)
|
||||||
|
if transfersValue && evm.StateDB.Empty(address) {
|
||||||
|
gas += params.CallNewAccountGas
|
||||||
|
}
|
||||||
|
if transfersValue && !evm.chainRules.IsEIP4762 { // Non-verkle
|
||||||
|
gas += params.CallValueTransferGas
|
||||||
|
} else if transfersValue && evm.chainRules.IsEIP4762 { // Verkle
|
||||||
|
gas += evm.AccessEvents.ValueTransferGas(contract.Address(), address)
|
||||||
|
}
|
||||||
|
memoryGas, err := gasEOFMemCost(evm, contract, stack, mem, memorySize)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
var overflow bool
|
||||||
|
if gas, overflow = math.SafeAdd(gas, memoryGas); overflow {
|
||||||
|
return 0, ErrGasUintOverflow
|
||||||
|
}
|
||||||
|
return gas, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// gasEOFMemCost calculates the cost of an external call in EOF, but does not
|
||||||
|
// take value-transfer-related costs into account: only mem expansion costs.
|
||||||
|
func gasEOFMemCost(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||||
|
gas, err := memoryGasCost(mem, memorySize)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
evm.callGasTemp, err = callGas(true, contract.Gas, gas, new(uint256.Int).SetUint64(contract.Gas))
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
var overflow bool
|
||||||
|
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
|
||||||
|
return 0, ErrGasUintOverflow
|
||||||
|
}
|
||||||
|
return gas, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var gasExtDelegateCall = gasEOFMemCost
|
||||||
|
var gasExtStaticCall = gasEOFMemCost
|
||||||
|
|
||||||
func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||||
var gas uint64
|
var gas uint64
|
||||||
// EIP150 homestead gas reprice fork:
|
// EIP150 homestead gas reprice fork:
|
||||||
|
|
@ -502,3 +549,9 @@ func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me
|
||||||
}
|
}
|
||||||
return gas, nil
|
return gas, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// gasEOFCreate returns the gas-cost for EOF-Create. Hashing charge needs to be
|
||||||
|
// deducted in the opcode itself, since it depends on the immediate
|
||||||
|
func gasEOFCreate(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||||
|
return memoryGasCost(mem, memorySize)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,9 @@ type operation struct {
|
||||||
|
|
||||||
// memorySize returns the memory size required for the operation
|
// memorySize returns the memory size required for the operation
|
||||||
memorySize memorySizeFunc
|
memorySize memorySizeFunc
|
||||||
|
|
||||||
|
// undefined denotes if the instruction is not officially defined in the jump table
|
||||||
|
undefined bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -58,6 +61,7 @@ var (
|
||||||
shanghaiInstructionSet = newShanghaiInstructionSet()
|
shanghaiInstructionSet = newShanghaiInstructionSet()
|
||||||
cancunInstructionSet = newCancunInstructionSet()
|
cancunInstructionSet = newCancunInstructionSet()
|
||||||
verkleInstructionSet = newVerkleInstructionSet()
|
verkleInstructionSet = newVerkleInstructionSet()
|
||||||
|
pragueEOFInstructionSet = newPragueEOFInstructionSet()
|
||||||
)
|
)
|
||||||
|
|
||||||
// JumpTable contains the EVM opcodes supported at a given fork.
|
// JumpTable contains the EVM opcodes supported at a given fork.
|
||||||
|
|
@ -87,6 +91,16 @@ func newVerkleInstructionSet() JumpTable {
|
||||||
return validate(instructionSet)
|
return validate(instructionSet)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewPragueEOFInstructionSetForTesting() JumpTable {
|
||||||
|
return newPragueEOFInstructionSet()
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPragueEOFInstructionSet() JumpTable {
|
||||||
|
instructionSet := newCancunInstructionSet()
|
||||||
|
enableEOF(&instructionSet)
|
||||||
|
return validate(instructionSet)
|
||||||
|
}
|
||||||
|
|
||||||
func newCancunInstructionSet() JumpTable {
|
func newCancunInstructionSet() JumpTable {
|
||||||
instructionSet := newShanghaiInstructionSet()
|
instructionSet := newShanghaiInstructionSet()
|
||||||
enable4844(&instructionSet) // EIP-4844 (BLOBHASH opcode)
|
enable4844(&instructionSet) // EIP-4844 (BLOBHASH opcode)
|
||||||
|
|
@ -1059,12 +1073,17 @@ func newFrontierInstructionSet() JumpTable {
|
||||||
minStack: minStack(1, 0),
|
minStack: minStack(1, 0),
|
||||||
maxStack: maxStack(1, 0),
|
maxStack: maxStack(1, 0),
|
||||||
},
|
},
|
||||||
|
INVALID: {
|
||||||
|
execute: opUndefined,
|
||||||
|
minStack: minStack(0, 0),
|
||||||
|
maxStack: maxStack(0, 0),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fill all unassigned slots with opUndefined.
|
// Fill all unassigned slots with opUndefined.
|
||||||
for i, entry := range tbl {
|
for i, entry := range tbl {
|
||||||
if entry == nil {
|
if entry == nil {
|
||||||
tbl[i] = &operation{execute: opUndefined, maxStack: maxStack(0, 0)}
|
tbl[i] = &operation{execute: opUndefined, maxStack: maxStack(0, 0), undefined: true}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,7 @@ func memoryCall(stack *Stack) (uint64, bool) {
|
||||||
}
|
}
|
||||||
return y, false
|
return y, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func memoryDelegateCall(stack *Stack) (uint64, bool) {
|
func memoryDelegateCall(stack *Stack) (uint64, bool) {
|
||||||
x, overflow := calcMemSize64(stack.Back(4), stack.Back(5))
|
x, overflow := calcMemSize64(stack.Back(4), stack.Back(5))
|
||||||
if overflow {
|
if overflow {
|
||||||
|
|
@ -119,3 +120,19 @@ func memoryRevert(stack *Stack) (uint64, bool) {
|
||||||
func memoryLog(stack *Stack) (uint64, bool) {
|
func memoryLog(stack *Stack) (uint64, bool) {
|
||||||
return calcMemSize64(stack.Back(0), stack.Back(1))
|
return calcMemSize64(stack.Back(0), stack.Back(1))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func memoryExtCall(stack *Stack) (uint64, bool) {
|
||||||
|
return calcMemSize64(stack.Back(1), stack.Back(2))
|
||||||
|
}
|
||||||
|
|
||||||
|
func memoryDataCopy(stack *Stack) (uint64, bool) {
|
||||||
|
return calcMemSize64(stack.Back(0), stack.Back(2))
|
||||||
|
}
|
||||||
|
|
||||||
|
func memoryEOFCreate(stack *Stack) (uint64, bool) {
|
||||||
|
return calcMemSize64(stack.Back(2), stack.Back(3))
|
||||||
|
}
|
||||||
|
|
||||||
|
func memoryReturnContract(stack *Stack) (uint64, bool) {
|
||||||
|
return calcMemSize64(stack.Back(0), stack.Back(1))
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -209,6 +209,29 @@ const (
|
||||||
LOG4
|
LOG4
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 0xd0 range - eof operations.
|
||||||
|
const (
|
||||||
|
DATALOAD OpCode = 0xd0
|
||||||
|
DATALOADN OpCode = 0xd1
|
||||||
|
DATASIZE OpCode = 0xd2
|
||||||
|
DATACOPY OpCode = 0xd3
|
||||||
|
)
|
||||||
|
|
||||||
|
// 0xe0 range - eof operations.
|
||||||
|
const (
|
||||||
|
RJUMP OpCode = 0xe0
|
||||||
|
RJUMPI OpCode = 0xe1
|
||||||
|
RJUMPV OpCode = 0xe2
|
||||||
|
CALLF OpCode = 0xe3
|
||||||
|
RETF OpCode = 0xe4
|
||||||
|
JUMPF OpCode = 0xe5
|
||||||
|
DUPN OpCode = 0xe6
|
||||||
|
SWAPN OpCode = 0xe7
|
||||||
|
EXCHANGE OpCode = 0xe8
|
||||||
|
EOFCREATE OpCode = 0xec
|
||||||
|
RETURNCONTRACT OpCode = 0xee
|
||||||
|
)
|
||||||
|
|
||||||
// 0xf0 range - closures.
|
// 0xf0 range - closures.
|
||||||
const (
|
const (
|
||||||
CREATE OpCode = 0xf0
|
CREATE OpCode = 0xf0
|
||||||
|
|
@ -218,10 +241,15 @@ const (
|
||||||
DELEGATECALL OpCode = 0xf4
|
DELEGATECALL OpCode = 0xf4
|
||||||
CREATE2 OpCode = 0xf5
|
CREATE2 OpCode = 0xf5
|
||||||
|
|
||||||
STATICCALL OpCode = 0xfa
|
RETURNDATALOAD OpCode = 0xf7
|
||||||
REVERT OpCode = 0xfd
|
EXTCALL OpCode = 0xf8
|
||||||
INVALID OpCode = 0xfe
|
EXTDELEGATECALL OpCode = 0xf9
|
||||||
SELFDESTRUCT OpCode = 0xff
|
|
||||||
|
STATICCALL OpCode = 0xfa
|
||||||
|
EXTSTATICCALL OpCode = 0xfb
|
||||||
|
REVERT OpCode = 0xfd
|
||||||
|
INVALID OpCode = 0xfe
|
||||||
|
SELFDESTRUCT OpCode = 0xff
|
||||||
)
|
)
|
||||||
|
|
||||||
var opCodeToString = [256]string{
|
var opCodeToString = [256]string{
|
||||||
|
|
@ -384,6 +412,25 @@ var opCodeToString = [256]string{
|
||||||
LOG3: "LOG3",
|
LOG3: "LOG3",
|
||||||
LOG4: "LOG4",
|
LOG4: "LOG4",
|
||||||
|
|
||||||
|
// 0xd range - eof ops.
|
||||||
|
DATALOAD: "DATALOAD",
|
||||||
|
DATALOADN: "DATALOADN",
|
||||||
|
DATASIZE: "DATASIZE",
|
||||||
|
DATACOPY: "DATACOPY",
|
||||||
|
|
||||||
|
// 0xe0 range.
|
||||||
|
RJUMP: "RJUMP",
|
||||||
|
RJUMPI: "RJUMPI",
|
||||||
|
RJUMPV: "RJUMPV",
|
||||||
|
CALLF: "CALLF",
|
||||||
|
RETF: "RETF",
|
||||||
|
JUMPF: "JUMPF",
|
||||||
|
DUPN: "DUPN",
|
||||||
|
SWAPN: "SWAPN",
|
||||||
|
EXCHANGE: "EXCHANGE",
|
||||||
|
EOFCREATE: "EOFCREATE",
|
||||||
|
RETURNCONTRACT: "RETURNCONTRACT",
|
||||||
|
|
||||||
// 0xf0 range - closures.
|
// 0xf0 range - closures.
|
||||||
CREATE: "CREATE",
|
CREATE: "CREATE",
|
||||||
CALL: "CALL",
|
CALL: "CALL",
|
||||||
|
|
@ -391,10 +438,16 @@ var opCodeToString = [256]string{
|
||||||
CALLCODE: "CALLCODE",
|
CALLCODE: "CALLCODE",
|
||||||
DELEGATECALL: "DELEGATECALL",
|
DELEGATECALL: "DELEGATECALL",
|
||||||
CREATE2: "CREATE2",
|
CREATE2: "CREATE2",
|
||||||
STATICCALL: "STATICCALL",
|
|
||||||
REVERT: "REVERT",
|
RETURNDATALOAD: "RETURNDATALOAD",
|
||||||
INVALID: "INVALID",
|
EXTCALL: "EXTCALL",
|
||||||
SELFDESTRUCT: "SELFDESTRUCT",
|
EXTDELEGATECALL: "EXTDELEGATECALL",
|
||||||
|
|
||||||
|
STATICCALL: "STATICCALL",
|
||||||
|
EXTSTATICCALL: "EXTSTATICCALL",
|
||||||
|
REVERT: "REVERT",
|
||||||
|
INVALID: "INVALID",
|
||||||
|
SELFDESTRUCT: "SELFDESTRUCT",
|
||||||
}
|
}
|
||||||
|
|
||||||
func (op OpCode) String() string {
|
func (op OpCode) String() string {
|
||||||
|
|
@ -405,155 +458,174 @@ func (op OpCode) String() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
var stringToOp = map[string]OpCode{
|
var stringToOp = map[string]OpCode{
|
||||||
"STOP": STOP,
|
"STOP": STOP,
|
||||||
"ADD": ADD,
|
"ADD": ADD,
|
||||||
"MUL": MUL,
|
"MUL": MUL,
|
||||||
"SUB": SUB,
|
"SUB": SUB,
|
||||||
"DIV": DIV,
|
"DIV": DIV,
|
||||||
"SDIV": SDIV,
|
"SDIV": SDIV,
|
||||||
"MOD": MOD,
|
"MOD": MOD,
|
||||||
"SMOD": SMOD,
|
"SMOD": SMOD,
|
||||||
"EXP": EXP,
|
"EXP": EXP,
|
||||||
"NOT": NOT,
|
"NOT": NOT,
|
||||||
"LT": LT,
|
"LT": LT,
|
||||||
"GT": GT,
|
"GT": GT,
|
||||||
"SLT": SLT,
|
"SLT": SLT,
|
||||||
"SGT": SGT,
|
"SGT": SGT,
|
||||||
"EQ": EQ,
|
"EQ": EQ,
|
||||||
"ISZERO": ISZERO,
|
"ISZERO": ISZERO,
|
||||||
"SIGNEXTEND": SIGNEXTEND,
|
"SIGNEXTEND": SIGNEXTEND,
|
||||||
"AND": AND,
|
"AND": AND,
|
||||||
"OR": OR,
|
"OR": OR,
|
||||||
"XOR": XOR,
|
"XOR": XOR,
|
||||||
"BYTE": BYTE,
|
"BYTE": BYTE,
|
||||||
"SHL": SHL,
|
"SHL": SHL,
|
||||||
"SHR": SHR,
|
"SHR": SHR,
|
||||||
"SAR": SAR,
|
"SAR": SAR,
|
||||||
"ADDMOD": ADDMOD,
|
"ADDMOD": ADDMOD,
|
||||||
"MULMOD": MULMOD,
|
"MULMOD": MULMOD,
|
||||||
"KECCAK256": KECCAK256,
|
"KECCAK256": KECCAK256,
|
||||||
"ADDRESS": ADDRESS,
|
"ADDRESS": ADDRESS,
|
||||||
"BALANCE": BALANCE,
|
"BALANCE": BALANCE,
|
||||||
"ORIGIN": ORIGIN,
|
"ORIGIN": ORIGIN,
|
||||||
"CALLER": CALLER,
|
"CALLER": CALLER,
|
||||||
"CALLVALUE": CALLVALUE,
|
"CALLVALUE": CALLVALUE,
|
||||||
"CALLDATALOAD": CALLDATALOAD,
|
"CALLDATALOAD": CALLDATALOAD,
|
||||||
"CALLDATASIZE": CALLDATASIZE,
|
"CALLDATASIZE": CALLDATASIZE,
|
||||||
"CALLDATACOPY": CALLDATACOPY,
|
"CALLDATACOPY": CALLDATACOPY,
|
||||||
"CHAINID": CHAINID,
|
"CHAINID": CHAINID,
|
||||||
"BASEFEE": BASEFEE,
|
"BASEFEE": BASEFEE,
|
||||||
"BLOBHASH": BLOBHASH,
|
"BLOBHASH": BLOBHASH,
|
||||||
"BLOBBASEFEE": BLOBBASEFEE,
|
"BLOBBASEFEE": BLOBBASEFEE,
|
||||||
"DELEGATECALL": DELEGATECALL,
|
"DELEGATECALL": DELEGATECALL,
|
||||||
"STATICCALL": STATICCALL,
|
"STATICCALL": STATICCALL,
|
||||||
"CODESIZE": CODESIZE,
|
"CODESIZE": CODESIZE,
|
||||||
"CODECOPY": CODECOPY,
|
"CODECOPY": CODECOPY,
|
||||||
"GASPRICE": GASPRICE,
|
"GASPRICE": GASPRICE,
|
||||||
"EXTCODESIZE": EXTCODESIZE,
|
"EXTCODESIZE": EXTCODESIZE,
|
||||||
"EXTCODECOPY": EXTCODECOPY,
|
"EXTCODECOPY": EXTCODECOPY,
|
||||||
"RETURNDATASIZE": RETURNDATASIZE,
|
"RETURNDATASIZE": RETURNDATASIZE,
|
||||||
"RETURNDATACOPY": RETURNDATACOPY,
|
"RETURNDATACOPY": RETURNDATACOPY,
|
||||||
"EXTCODEHASH": EXTCODEHASH,
|
"EXTCODEHASH": EXTCODEHASH,
|
||||||
"BLOCKHASH": BLOCKHASH,
|
"BLOCKHASH": BLOCKHASH,
|
||||||
"COINBASE": COINBASE,
|
"COINBASE": COINBASE,
|
||||||
"TIMESTAMP": TIMESTAMP,
|
"TIMESTAMP": TIMESTAMP,
|
||||||
"NUMBER": NUMBER,
|
"NUMBER": NUMBER,
|
||||||
"DIFFICULTY": DIFFICULTY,
|
"DIFFICULTY": DIFFICULTY,
|
||||||
"GASLIMIT": GASLIMIT,
|
"GASLIMIT": GASLIMIT,
|
||||||
"SELFBALANCE": SELFBALANCE,
|
"SELFBALANCE": SELFBALANCE,
|
||||||
"POP": POP,
|
"POP": POP,
|
||||||
"MLOAD": MLOAD,
|
"MLOAD": MLOAD,
|
||||||
"MSTORE": MSTORE,
|
"MSTORE": MSTORE,
|
||||||
"MSTORE8": MSTORE8,
|
"MSTORE8": MSTORE8,
|
||||||
"SLOAD": SLOAD,
|
"SLOAD": SLOAD,
|
||||||
"SSTORE": SSTORE,
|
"SSTORE": SSTORE,
|
||||||
"JUMP": JUMP,
|
"JUMP": JUMP,
|
||||||
"JUMPI": JUMPI,
|
"JUMPI": JUMPI,
|
||||||
"PC": PC,
|
"PC": PC,
|
||||||
"MSIZE": MSIZE,
|
"MSIZE": MSIZE,
|
||||||
"GAS": GAS,
|
"GAS": GAS,
|
||||||
"JUMPDEST": JUMPDEST,
|
"JUMPDEST": JUMPDEST,
|
||||||
"TLOAD": TLOAD,
|
"TLOAD": TLOAD,
|
||||||
"TSTORE": TSTORE,
|
"TSTORE": TSTORE,
|
||||||
"MCOPY": MCOPY,
|
"MCOPY": MCOPY,
|
||||||
"PUSH0": PUSH0,
|
"PUSH0": PUSH0,
|
||||||
"PUSH1": PUSH1,
|
"PUSH1": PUSH1,
|
||||||
"PUSH2": PUSH2,
|
"PUSH2": PUSH2,
|
||||||
"PUSH3": PUSH3,
|
"PUSH3": PUSH3,
|
||||||
"PUSH4": PUSH4,
|
"PUSH4": PUSH4,
|
||||||
"PUSH5": PUSH5,
|
"PUSH5": PUSH5,
|
||||||
"PUSH6": PUSH6,
|
"PUSH6": PUSH6,
|
||||||
"PUSH7": PUSH7,
|
"PUSH7": PUSH7,
|
||||||
"PUSH8": PUSH8,
|
"PUSH8": PUSH8,
|
||||||
"PUSH9": PUSH9,
|
"PUSH9": PUSH9,
|
||||||
"PUSH10": PUSH10,
|
"PUSH10": PUSH10,
|
||||||
"PUSH11": PUSH11,
|
"PUSH11": PUSH11,
|
||||||
"PUSH12": PUSH12,
|
"PUSH12": PUSH12,
|
||||||
"PUSH13": PUSH13,
|
"PUSH13": PUSH13,
|
||||||
"PUSH14": PUSH14,
|
"PUSH14": PUSH14,
|
||||||
"PUSH15": PUSH15,
|
"PUSH15": PUSH15,
|
||||||
"PUSH16": PUSH16,
|
"PUSH16": PUSH16,
|
||||||
"PUSH17": PUSH17,
|
"PUSH17": PUSH17,
|
||||||
"PUSH18": PUSH18,
|
"PUSH18": PUSH18,
|
||||||
"PUSH19": PUSH19,
|
"PUSH19": PUSH19,
|
||||||
"PUSH20": PUSH20,
|
"PUSH20": PUSH20,
|
||||||
"PUSH21": PUSH21,
|
"PUSH21": PUSH21,
|
||||||
"PUSH22": PUSH22,
|
"PUSH22": PUSH22,
|
||||||
"PUSH23": PUSH23,
|
"PUSH23": PUSH23,
|
||||||
"PUSH24": PUSH24,
|
"PUSH24": PUSH24,
|
||||||
"PUSH25": PUSH25,
|
"PUSH25": PUSH25,
|
||||||
"PUSH26": PUSH26,
|
"PUSH26": PUSH26,
|
||||||
"PUSH27": PUSH27,
|
"PUSH27": PUSH27,
|
||||||
"PUSH28": PUSH28,
|
"PUSH28": PUSH28,
|
||||||
"PUSH29": PUSH29,
|
"PUSH29": PUSH29,
|
||||||
"PUSH30": PUSH30,
|
"PUSH30": PUSH30,
|
||||||
"PUSH31": PUSH31,
|
"PUSH31": PUSH31,
|
||||||
"PUSH32": PUSH32,
|
"PUSH32": PUSH32,
|
||||||
"DUP1": DUP1,
|
"DUP1": DUP1,
|
||||||
"DUP2": DUP2,
|
"DUP2": DUP2,
|
||||||
"DUP3": DUP3,
|
"DUP3": DUP3,
|
||||||
"DUP4": DUP4,
|
"DUP4": DUP4,
|
||||||
"DUP5": DUP5,
|
"DUP5": DUP5,
|
||||||
"DUP6": DUP6,
|
"DUP6": DUP6,
|
||||||
"DUP7": DUP7,
|
"DUP7": DUP7,
|
||||||
"DUP8": DUP8,
|
"DUP8": DUP8,
|
||||||
"DUP9": DUP9,
|
"DUP9": DUP9,
|
||||||
"DUP10": DUP10,
|
"DUP10": DUP10,
|
||||||
"DUP11": DUP11,
|
"DUP11": DUP11,
|
||||||
"DUP12": DUP12,
|
"DUP12": DUP12,
|
||||||
"DUP13": DUP13,
|
"DUP13": DUP13,
|
||||||
"DUP14": DUP14,
|
"DUP14": DUP14,
|
||||||
"DUP15": DUP15,
|
"DUP15": DUP15,
|
||||||
"DUP16": DUP16,
|
"DUP16": DUP16,
|
||||||
"SWAP1": SWAP1,
|
"SWAP1": SWAP1,
|
||||||
"SWAP2": SWAP2,
|
"SWAP2": SWAP2,
|
||||||
"SWAP3": SWAP3,
|
"SWAP3": SWAP3,
|
||||||
"SWAP4": SWAP4,
|
"SWAP4": SWAP4,
|
||||||
"SWAP5": SWAP5,
|
"SWAP5": SWAP5,
|
||||||
"SWAP6": SWAP6,
|
"SWAP6": SWAP6,
|
||||||
"SWAP7": SWAP7,
|
"SWAP7": SWAP7,
|
||||||
"SWAP8": SWAP8,
|
"SWAP8": SWAP8,
|
||||||
"SWAP9": SWAP9,
|
"SWAP9": SWAP9,
|
||||||
"SWAP10": SWAP10,
|
"SWAP10": SWAP10,
|
||||||
"SWAP11": SWAP11,
|
"SWAP11": SWAP11,
|
||||||
"SWAP12": SWAP12,
|
"SWAP12": SWAP12,
|
||||||
"SWAP13": SWAP13,
|
"SWAP13": SWAP13,
|
||||||
"SWAP14": SWAP14,
|
"SWAP14": SWAP14,
|
||||||
"SWAP15": SWAP15,
|
"SWAP15": SWAP15,
|
||||||
"SWAP16": SWAP16,
|
"SWAP16": SWAP16,
|
||||||
"LOG0": LOG0,
|
"LOG0": LOG0,
|
||||||
"LOG1": LOG1,
|
"LOG1": LOG1,
|
||||||
"LOG2": LOG2,
|
"LOG2": LOG2,
|
||||||
"LOG3": LOG3,
|
"LOG3": LOG3,
|
||||||
"LOG4": LOG4,
|
"LOG4": LOG4,
|
||||||
"CREATE": CREATE,
|
"DATALOAD": DATALOAD,
|
||||||
"CREATE2": CREATE2,
|
"DATALOADN": DATALOADN,
|
||||||
"CALL": CALL,
|
"DATASIZE": DATASIZE,
|
||||||
"RETURN": RETURN,
|
"DATACOPY": DATACOPY,
|
||||||
"CALLCODE": CALLCODE,
|
"RJUMP": RJUMP,
|
||||||
"REVERT": REVERT,
|
"RJUMPI": RJUMPI,
|
||||||
"INVALID": INVALID,
|
"RJUMPV": RJUMPV,
|
||||||
"SELFDESTRUCT": SELFDESTRUCT,
|
"CALLF": CALLF,
|
||||||
|
"RETF": RETF,
|
||||||
|
"JUMPF": JUMPF,
|
||||||
|
"DUPN": DUPN,
|
||||||
|
"SWAPN": SWAPN,
|
||||||
|
"EXCHANGE": EXCHANGE,
|
||||||
|
"EOFCREATE": EOFCREATE,
|
||||||
|
"RETURNCONTRACT": RETURNCONTRACT,
|
||||||
|
"CREATE": CREATE,
|
||||||
|
"CREATE2": CREATE2,
|
||||||
|
"RETURNDATALOAD": RETURNDATALOAD,
|
||||||
|
"EXTCALL": EXTCALL,
|
||||||
|
"EXTDELEGATECALL": EXTDELEGATECALL,
|
||||||
|
"EXTSTATICCALL": EXTSTATICCALL,
|
||||||
|
"CALL": CALL,
|
||||||
|
"RETURN": RETURN,
|
||||||
|
"CALLCODE": CALLCODE,
|
||||||
|
"REVERT": REVERT,
|
||||||
|
"INVALID": INVALID,
|
||||||
|
"SELFDESTRUCT": SELFDESTRUCT,
|
||||||
}
|
}
|
||||||
|
|
||||||
// StringToOp finds the opcode whose name is stored in `str`.
|
// StringToOp finds the opcode whose name is stored in `str`.
|
||||||
|
|
|
||||||
|
|
@ -152,9 +152,9 @@ func gasEip2929AccountCheck(evm *EVM, contract *Contract, stack *Stack, mem *Mem
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeCallVariantGasCallEIP2929(oldCalculator gasFunc) gasFunc {
|
func makeCallVariantGasCallEIP2929(oldCalculator gasFunc, addressPosition int) gasFunc {
|
||||||
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||||
addr := common.Address(stack.Back(1).Bytes20())
|
addr := common.Address(stack.Back(addressPosition).Bytes20())
|
||||||
// Check slot presence in the access list
|
// Check slot presence in the access list
|
||||||
warmAccess := evm.StateDB.AddressInAccessList(addr)
|
warmAccess := evm.StateDB.AddressInAccessList(addr)
|
||||||
// The WarmStorageReadCostEIP2929 (100) is already deducted in the form of a constant cost, so
|
// The WarmStorageReadCostEIP2929 (100) is already deducted in the form of a constant cost, so
|
||||||
|
|
@ -192,10 +192,10 @@ func makeCallVariantGasCallEIP2929(oldCalculator gasFunc) gasFunc {
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
gasCallEIP2929 = makeCallVariantGasCallEIP2929(gasCall)
|
gasCallEIP2929 = makeCallVariantGasCallEIP2929(gasCall, 1)
|
||||||
gasDelegateCallEIP2929 = makeCallVariantGasCallEIP2929(gasDelegateCall)
|
gasDelegateCallEIP2929 = makeCallVariantGasCallEIP2929(gasDelegateCall, 1)
|
||||||
gasStaticCallEIP2929 = makeCallVariantGasCallEIP2929(gasStaticCall)
|
gasStaticCallEIP2929 = makeCallVariantGasCallEIP2929(gasStaticCall, 1)
|
||||||
gasCallCodeEIP2929 = makeCallVariantGasCallEIP2929(gasCallCode)
|
gasCallCodeEIP2929 = makeCallVariantGasCallEIP2929(gasCallCode, 1)
|
||||||
gasSelfdestructEIP2929 = makeSelfdestructGasFn(true)
|
gasSelfdestructEIP2929 = makeSelfdestructGasFn(true)
|
||||||
// gasSelfdestructEIP3529 implements the changes in EIP-3529 (no refunds)
|
// gasSelfdestructEIP3529 implements the changes in EIP-3529 (no refunds)
|
||||||
gasSelfdestructEIP3529 = makeSelfdestructGasFn(false)
|
gasSelfdestructEIP3529 = makeSelfdestructGasFn(false)
|
||||||
|
|
|
||||||
228
core/vm/validate.go
Normal file
228
core/vm/validate.go
Normal file
|
|
@ -0,0 +1,228 @@
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package vm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Below are all possible errors that can occur during validation of
|
||||||
|
// EOF containers.
|
||||||
|
var (
|
||||||
|
ErrInvalidMagic = errors.New("invalid magic")
|
||||||
|
ErrUndefinedInstruction = errors.New("undefined instruction")
|
||||||
|
ErrTruncatedImmediate = errors.New("truncated immediate")
|
||||||
|
ErrInvalidSectionArgument = errors.New("invalid section argument")
|
||||||
|
ErrInvalidCallArgument = errors.New("callf into non-returning section")
|
||||||
|
ErrInvalidDataloadNArgument = errors.New("invalid dataloadN argument")
|
||||||
|
ErrInvalidJumpDest = errors.New("invalid jump destination")
|
||||||
|
ErrInvalidBackwardJump = errors.New("invalid backward jump")
|
||||||
|
//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")
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
notRefByEither = iota
|
||||||
|
refByReturnContract
|
||||||
|
refByEOFCreate
|
||||||
|
)
|
||||||
|
|
||||||
|
type validationResult struct {
|
||||||
|
visitedCode map[int]struct{}
|
||||||
|
visitedSubContainers map[int]int
|
||||||
|
isInitCode bool
|
||||||
|
isRuntime bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateCode validates the code parameter against the EOF v1 validity requirements.
|
||||||
|
func validateCode(code []byte, section int, container *Container, jt *JumpTable, isInitCode bool) (*validationResult, error) {
|
||||||
|
var (
|
||||||
|
i = 0
|
||||||
|
// Tracks the number of actual instructions in the code (e.g.
|
||||||
|
// non-immediate values). This is used at the end to determine
|
||||||
|
// if each instruction is reachable.
|
||||||
|
count = 0
|
||||||
|
op OpCode
|
||||||
|
analysis bitvec
|
||||||
|
visitedCode = make(map[int]struct{})
|
||||||
|
visitedSubcontainers = make(map[int]int)
|
||||||
|
hasReturnContract bool
|
||||||
|
hasStop bool
|
||||||
|
)
|
||||||
|
// This loop visits every single instruction and verifies:
|
||||||
|
// * if the instruction is valid for the given jump table.
|
||||||
|
// * if the instruction has an immediate value, it is not truncated.
|
||||||
|
// * if performing a relative jump, all jump destinations are valid.
|
||||||
|
// * if changing code sections, the new code section index is valid and
|
||||||
|
// will not cause a stack overflow.
|
||||||
|
for i < len(code) {
|
||||||
|
count++
|
||||||
|
op = OpCode(code[i])
|
||||||
|
if jt[op].undefined {
|
||||||
|
return nil, fmt.Errorf("%w: op %s, pos %d", ErrUndefinedInstruction, op, i)
|
||||||
|
}
|
||||||
|
size := int(immediates[op])
|
||||||
|
if size != 0 && len(code) <= i+size {
|
||||||
|
return nil, fmt.Errorf("%w: op %s, pos %d", ErrTruncatedImmediate, op, i)
|
||||||
|
}
|
||||||
|
switch op {
|
||||||
|
case RJUMP, RJUMPI:
|
||||||
|
if err := checkDest(code, &analysis, i+1, i+3, len(code)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case RJUMPV:
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
offset := i + 2
|
||||||
|
for j := 0; j < length; j++ {
|
||||||
|
if err := checkDest(code, &analysis, offset+j*2, offset+(length*2), len(code)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i += 2 * max_size
|
||||||
|
case CALLF:
|
||||||
|
arg, _ := parseUint16(code[i+1:])
|
||||||
|
if arg >= len(container.types) {
|
||||||
|
return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", ErrInvalidSectionArgument, arg, len(container.types), i)
|
||||||
|
}
|
||||||
|
if container.types[arg].outputs == 0x80 {
|
||||||
|
return nil, fmt.Errorf("%w: section %v", ErrInvalidCallArgument, arg)
|
||||||
|
}
|
||||||
|
visitedCode[arg] = struct{}{}
|
||||||
|
case JUMPF:
|
||||||
|
arg, _ := parseUint16(code[i+1:])
|
||||||
|
if arg >= len(container.types) {
|
||||||
|
return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", ErrInvalidSectionArgument, arg, len(container.types), i)
|
||||||
|
}
|
||||||
|
if container.types[arg].outputs != 0x80 && container.types[arg].outputs > container.types[section].outputs {
|
||||||
|
return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", ErrInvalidOutputs, arg, len(container.types), i)
|
||||||
|
}
|
||||||
|
visitedCode[arg] = struct{}{}
|
||||||
|
case DATALOADN:
|
||||||
|
arg, _ := parseUint16(code[i+1:])
|
||||||
|
// TODO why are we checking this? We should just pad
|
||||||
|
if arg+32 > len(container.data) {
|
||||||
|
return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", ErrInvalidDataloadNArgument, arg, len(container.data), i)
|
||||||
|
}
|
||||||
|
case RETURNCONTRACT:
|
||||||
|
if !isInitCode {
|
||||||
|
return nil, ErrIncompatibleContainerKind
|
||||||
|
}
|
||||||
|
arg := int(code[i+1])
|
||||||
|
if arg >= len(container.sections) {
|
||||||
|
return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", ErrUnreachableCode, arg, len(container.sections), i)
|
||||||
|
}
|
||||||
|
// We need to store per subcontainer how it was referenced
|
||||||
|
if v, ok := visitedSubcontainers[arg]; ok && v != refByReturnContract {
|
||||||
|
return nil, fmt.Errorf("section already referenced, arg :%d", arg)
|
||||||
|
}
|
||||||
|
if hasStop {
|
||||||
|
return nil, ErrStopAndReturnContract
|
||||||
|
}
|
||||||
|
hasReturnContract = true
|
||||||
|
visitedSubcontainers[arg] = refByReturnContract
|
||||||
|
case EOFCREATE:
|
||||||
|
arg := int(code[i+1])
|
||||||
|
if arg >= len(container.sections) {
|
||||||
|
return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", ErrUnreachableCode, arg, len(container.sections), i)
|
||||||
|
}
|
||||||
|
if ct := container.sections[arg]; len(ct.data) != ct.dataSize {
|
||||||
|
return nil, fmt.Errorf("%w: container %d, have %d, claimed %d, pos %d", ErrEOFCreateWithTruncatedSection, arg, len(ct.data), ct.dataSize, i)
|
||||||
|
}
|
||||||
|
if _, ok := visitedSubcontainers[arg]; ok {
|
||||||
|
return nil, fmt.Errorf("section already referenced, arg :%d", arg)
|
||||||
|
}
|
||||||
|
// We need to store per subcontainer how it was referenced
|
||||||
|
if v, ok := visitedSubcontainers[arg]; ok && v != refByEOFCreate {
|
||||||
|
return nil, fmt.Errorf("section already referenced, arg :%d", arg)
|
||||||
|
}
|
||||||
|
visitedSubcontainers[arg] = refByEOFCreate
|
||||||
|
case STOP, RETURN:
|
||||||
|
if isInitCode {
|
||||||
|
return nil, ErrStopInInitCode
|
||||||
|
}
|
||||||
|
if hasReturnContract {
|
||||||
|
return nil, ErrStopAndReturnContract
|
||||||
|
}
|
||||||
|
hasStop = true
|
||||||
|
}
|
||||||
|
i += size + 1
|
||||||
|
}
|
||||||
|
// Code sections may not "fall through" and require proper termination.
|
||||||
|
// Therefore, the last instruction must be considered terminal or RJUMP.
|
||||||
|
if !terminals[op] && op != RJUMP {
|
||||||
|
return nil, fmt.Errorf("%w: end with %s, pos %d", ErrInvalidCodeTermination, op, i)
|
||||||
|
}
|
||||||
|
if paths, err := validateControlFlow(code, section, container.types, jt); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if paths != count {
|
||||||
|
// TODO(matt): return actual position of unreachable code
|
||||||
|
return nil, ErrUnreachableCode
|
||||||
|
}
|
||||||
|
return &validationResult{
|
||||||
|
visitedCode: visitedCode,
|
||||||
|
visitedSubContainers: visitedSubcontainers,
|
||||||
|
isInitCode: hasReturnContract,
|
||||||
|
isRuntime: hasStop,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkDest parses a relative offset at code[0:2] and checks if it is a valid jump destination.
|
||||||
|
func checkDest(code []byte, analysis *bitvec, imm, from, length int) error {
|
||||||
|
if len(code) < imm+2 {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
if analysis != nil && *analysis == nil {
|
||||||
|
*analysis = eofCodeBitmap(code)
|
||||||
|
}
|
||||||
|
offset := parseInt16(code[imm:])
|
||||||
|
dest := from + offset
|
||||||
|
if dest < 0 || dest >= length {
|
||||||
|
return fmt.Errorf("%w: out-of-bounds offset: offset %d, dest %d, pos %d", ErrInvalidJumpDest, offset, dest, imm)
|
||||||
|
}
|
||||||
|
if !analysis.codeSegment(uint64(dest)) {
|
||||||
|
return fmt.Errorf("%w: offset into immediate: offset %d, dest %d, pos %d", ErrInvalidJumpDest, offset, dest, imm)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
217
core/vm/validate_linear.go
Normal file
217
core/vm/validate_linear.go
Normal file
|
|
@ -0,0 +1,217 @@
|
||||||
|
package vm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
)
|
||||||
|
|
||||||
|
type bounds struct {
|
||||||
|
min int
|
||||||
|
max int
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateControlFlow(code []byte, section int, metadata []*functionMetadata, jt *JumpTable) (int, error) {
|
||||||
|
var (
|
||||||
|
stackBounds = make(map[int]*bounds)
|
||||||
|
maxStackHeight = int(metadata[section].inputs)
|
||||||
|
debugging = !true
|
||||||
|
)
|
||||||
|
|
||||||
|
setBounds := func(pos, min, maxi int) *bounds {
|
||||||
|
stackBounds[pos] = &bounds{min, maxi}
|
||||||
|
maxStackHeight = max(maxStackHeight, maxi)
|
||||||
|
return stackBounds[pos]
|
||||||
|
}
|
||||||
|
// set the initial stack bounds
|
||||||
|
setBounds(0, int(metadata[section].inputs), int(metadata[section].inputs))
|
||||||
|
|
||||||
|
qualifiedExit := false
|
||||||
|
for pos := 0; pos < len(code); pos++ {
|
||||||
|
op := OpCode(code[pos])
|
||||||
|
currentBounds := stackBounds[pos]
|
||||||
|
if currentBounds == nil {
|
||||||
|
if debugging {
|
||||||
|
fmt.Printf("Stack bounds not set: %v at %v \n", op, pos)
|
||||||
|
}
|
||||||
|
return 0, ErrUnreachableCode
|
||||||
|
}
|
||||||
|
|
||||||
|
if debugging {
|
||||||
|
fmt.Println(pos, op, maxStackHeight, currentBounds)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
currentStackMax = currentBounds.max
|
||||||
|
currentStackMin = currentBounds.min
|
||||||
|
)
|
||||||
|
|
||||||
|
switch op {
|
||||||
|
case CALLF:
|
||||||
|
arg, _ := parseUint16(code[pos+1:])
|
||||||
|
newSection := metadata[arg]
|
||||||
|
if want, have := int(newSection.inputs), currentBounds.min; want > have {
|
||||||
|
return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos)
|
||||||
|
}
|
||||||
|
if have, limit := currentBounds.max+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)
|
||||||
|
}
|
||||||
|
change := int(newSection.outputs) - int(newSection.inputs)
|
||||||
|
currentStackMax += change
|
||||||
|
currentStackMin += change
|
||||||
|
case RETF:
|
||||||
|
if currentBounds.max != currentBounds.min {
|
||||||
|
return 0, fmt.Errorf("%w: max %d, min %d, at pos %d", ErrInvalidOutputs, currentBounds.max, currentBounds.min, pos)
|
||||||
|
}
|
||||||
|
have := int(metadata[section].outputs)
|
||||||
|
if have >= maxOutputItems {
|
||||||
|
return 0, fmt.Errorf("%w: at pos %d", ErrInvalidNonReturningFlag, pos)
|
||||||
|
}
|
||||||
|
if want := currentBounds.min; have != want {
|
||||||
|
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 := currentBounds.max+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 newSection.outputs == 0x80 {
|
||||||
|
if want, have := int(newSection.inputs), currentBounds.min; want > have {
|
||||||
|
return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if currentBounds.max != currentBounds.min {
|
||||||
|
return 0, fmt.Errorf("%w: max %d, min %d, at pos %d", ErrInvalidOutputs, currentBounds.max, currentBounds.min, pos)
|
||||||
|
}
|
||||||
|
if have, want := currentBounds.max, int(metadata[section].outputs)+int(newSection.inputs)-int(newSection.outputs); have != want {
|
||||||
|
return 0, fmt.Errorf("%w: at pos %d", ErrInvalidOutputs, pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
qualifiedExit = qualifiedExit || newSection.outputs < maxOutputItems
|
||||||
|
case DUPN:
|
||||||
|
arg := int(code[pos+1]) + 1
|
||||||
|
if want, have := arg, currentBounds.min; want > have {
|
||||||
|
return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos)
|
||||||
|
}
|
||||||
|
case SWAPN:
|
||||||
|
arg := int(code[pos+1]) + 1
|
||||||
|
if want, have := arg+1, currentBounds.min; want > have {
|
||||||
|
return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos)
|
||||||
|
}
|
||||||
|
case EXCHANGE:
|
||||||
|
arg := int(code[pos+1])
|
||||||
|
n := arg>>4 + 1
|
||||||
|
m := arg&0x0f + 1
|
||||||
|
if want, have := n+m+1, currentBounds.min; want > have {
|
||||||
|
return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if want, have := jt[op].minStack, currentBounds.min; want > have {
|
||||||
|
return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !terminals[op] && op != CALLF {
|
||||||
|
change := int(params.StackLimit) - jt[op].maxStack
|
||||||
|
currentStackMax += change
|
||||||
|
currentStackMin += change
|
||||||
|
}
|
||||||
|
|
||||||
|
var next []int
|
||||||
|
switch op {
|
||||||
|
case RJUMP:
|
||||||
|
nextPos := pos + 2 + parseInt16(code[pos+1:])
|
||||||
|
next = append(next, nextPos)
|
||||||
|
// We set the stack bounds of the destination
|
||||||
|
// and skip the argument, only for RJUMP, all other opcodes are handled later
|
||||||
|
if nextPos+1 < pos {
|
||||||
|
nextBounds, ok := stackBounds[nextPos+1]
|
||||||
|
if !ok {
|
||||||
|
return 0, ErrInvalidBackwardJump
|
||||||
|
}
|
||||||
|
if nextBounds.max != currentStackMax || nextBounds.min != currentStackMin {
|
||||||
|
return 0, ErrInvalidMaxStackHeight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nextBounds, ok := stackBounds[nextPos+1]
|
||||||
|
if !ok {
|
||||||
|
setBounds(nextPos+1, currentStackMin, currentStackMax)
|
||||||
|
} else {
|
||||||
|
setBounds(nextPos+1, min(nextBounds.min, currentStackMin), max(nextBounds.max, currentStackMax))
|
||||||
|
}
|
||||||
|
case RJUMPI:
|
||||||
|
arg := parseInt16(code[pos+1:])
|
||||||
|
next = append(next, pos+2)
|
||||||
|
next = append(next, pos+2+arg)
|
||||||
|
case RJUMPV:
|
||||||
|
count := int(code[pos+1]) + 1
|
||||||
|
next = append(next, pos+1+2*count)
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
arg := parseInt16(code[pos+2+2*i:])
|
||||||
|
next = append(next, pos+1+2*count+arg)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if imm := int(immediates[op]); imm != 0 {
|
||||||
|
next = append(next, pos+imm)
|
||||||
|
} else {
|
||||||
|
// Simple op, no operand.
|
||||||
|
next = append(next, pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if 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)
|
||||||
|
}
|
||||||
|
if nextPC > pos {
|
||||||
|
// target reached via forward jump or seq flow
|
||||||
|
nextBounds, ok := stackBounds[nextPC]
|
||||||
|
if !ok {
|
||||||
|
setBounds(nextPC, currentStackMin, currentStackMax)
|
||||||
|
} else {
|
||||||
|
setBounds(nextPC, min(nextBounds.min, currentStackMin), max(nextBounds.max, currentStackMax))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// target reached via backwards jump
|
||||||
|
nextBounds, ok := stackBounds[nextPC]
|
||||||
|
if !ok {
|
||||||
|
return 0, ErrInvalidBackwardJump
|
||||||
|
}
|
||||||
|
if currentStackMax != nextBounds.max {
|
||||||
|
return 0, fmt.Errorf("%w want %d as current max got %d at pos %d,", ErrInvalidBackwardJump, currentStackMax, nextBounds.max, pos)
|
||||||
|
}
|
||||||
|
if currentStackMin != nextBounds.min {
|
||||||
|
return 0, fmt.Errorf("%w want %d as current min got %d at pos %d,", ErrInvalidBackwardJump, currentStackMin, nextBounds.min, pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if op == RJUMP {
|
||||||
|
pos += 2 // skip the immediate
|
||||||
|
} else {
|
||||||
|
pos = next[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
if qualifiedExit != (metadata[section].outputs < maxOutputItems) {
|
||||||
|
return 0, fmt.Errorf("%w no RETF or qualified JUMPF", ErrInvalidNonReturningFlag)
|
||||||
|
}
|
||||||
|
if maxStackHeight >= int(params.StackLimit) {
|
||||||
|
return 0, ErrStackOverflow{maxStackHeight, int(params.StackLimit)}
|
||||||
|
}
|
||||||
|
if maxStackHeight != int(metadata[section].maxStackHeight) {
|
||||||
|
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 len(stackBounds), nil
|
||||||
|
}
|
||||||
497
core/vm/validate_test.go
Normal file
497
core/vm/validate_test.go
Normal file
|
|
@ -0,0 +1,497 @@
|
||||||
|
// Copyright 2022 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package vm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestValidateCode(t *testing.T) {
|
||||||
|
for i, test := range []struct {
|
||||||
|
code []byte
|
||||||
|
section int
|
||||||
|
metadata []*functionMetadata
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
code: []byte{
|
||||||
|
byte(CALLER),
|
||||||
|
byte(POP),
|
||||||
|
byte(STOP),
|
||||||
|
},
|
||||||
|
section: 0,
|
||||||
|
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: []byte{
|
||||||
|
byte(CALLF), 0x00, 0x00,
|
||||||
|
byte(RETF),
|
||||||
|
},
|
||||||
|
section: 0,
|
||||||
|
metadata: []*functionMetadata{{inputs: 0, outputs: 0, maxStackHeight: 0}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: []byte{
|
||||||
|
byte(ADDRESS),
|
||||||
|
byte(CALLF), 0x00, 0x00,
|
||||||
|
byte(POP),
|
||||||
|
byte(RETF),
|
||||||
|
},
|
||||||
|
section: 0,
|
||||||
|
metadata: []*functionMetadata{{inputs: 0, outputs: 0, maxStackHeight: 1}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: []byte{
|
||||||
|
byte(CALLER),
|
||||||
|
byte(POP),
|
||||||
|
},
|
||||||
|
section: 0,
|
||||||
|
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
|
||||||
|
err: ErrInvalidCodeTermination,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: []byte{
|
||||||
|
byte(RJUMP),
|
||||||
|
byte(0x00),
|
||||||
|
byte(0x01),
|
||||||
|
byte(CALLER),
|
||||||
|
byte(STOP),
|
||||||
|
},
|
||||||
|
section: 0,
|
||||||
|
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 0}},
|
||||||
|
err: ErrUnreachableCode,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: []byte{
|
||||||
|
byte(PUSH1),
|
||||||
|
byte(0x42),
|
||||||
|
byte(ADD),
|
||||||
|
byte(STOP),
|
||||||
|
},
|
||||||
|
section: 0,
|
||||||
|
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
|
||||||
|
err: ErrStackUnderflow{stackLen: 1, required: 2},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: []byte{
|
||||||
|
byte(PUSH1),
|
||||||
|
byte(0x42),
|
||||||
|
byte(POP),
|
||||||
|
byte(STOP),
|
||||||
|
},
|
||||||
|
section: 0,
|
||||||
|
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 2}},
|
||||||
|
err: ErrInvalidMaxStackHeight,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: []byte{
|
||||||
|
byte(PUSH0),
|
||||||
|
byte(RJUMPI),
|
||||||
|
byte(0x00),
|
||||||
|
byte(0x01),
|
||||||
|
byte(PUSH1),
|
||||||
|
byte(0x42), // jumps to here
|
||||||
|
byte(POP),
|
||||||
|
byte(STOP),
|
||||||
|
},
|
||||||
|
section: 0,
|
||||||
|
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
|
||||||
|
err: ErrInvalidJumpDest,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: []byte{
|
||||||
|
byte(PUSH0),
|
||||||
|
byte(RJUMPV),
|
||||||
|
byte(0x01),
|
||||||
|
byte(0x00),
|
||||||
|
byte(0x01),
|
||||||
|
byte(0x00),
|
||||||
|
byte(0x02),
|
||||||
|
byte(PUSH1),
|
||||||
|
byte(0x42), // jumps to here
|
||||||
|
byte(POP), // and here
|
||||||
|
byte(STOP),
|
||||||
|
},
|
||||||
|
section: 0,
|
||||||
|
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
|
||||||
|
err: ErrInvalidJumpDest,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: []byte{
|
||||||
|
byte(PUSH0),
|
||||||
|
byte(RJUMPV),
|
||||||
|
byte(0x00),
|
||||||
|
byte(STOP),
|
||||||
|
},
|
||||||
|
section: 0,
|
||||||
|
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
|
||||||
|
err: ErrTruncatedImmediate,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: []byte{
|
||||||
|
byte(RJUMP), 0x00, 0x03,
|
||||||
|
byte(JUMPDEST), // this code is unreachable to forward jumps alone
|
||||||
|
byte(JUMPDEST),
|
||||||
|
byte(RETURN),
|
||||||
|
byte(PUSH1), 20,
|
||||||
|
byte(PUSH1), 39,
|
||||||
|
byte(PUSH1), 0x00,
|
||||||
|
byte(DATACOPY),
|
||||||
|
byte(PUSH1), 20,
|
||||||
|
byte(PUSH1), 0x00,
|
||||||
|
byte(RJUMP), 0xff, 0xef,
|
||||||
|
},
|
||||||
|
section: 0,
|
||||||
|
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 3}},
|
||||||
|
err: ErrUnreachableCode,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: []byte{
|
||||||
|
byte(PUSH1), 1,
|
||||||
|
byte(RJUMPI), 0x00, 0x03,
|
||||||
|
byte(JUMPDEST),
|
||||||
|
byte(JUMPDEST),
|
||||||
|
byte(STOP),
|
||||||
|
byte(PUSH1), 20,
|
||||||
|
byte(PUSH1), 39,
|
||||||
|
byte(PUSH1), 0x00,
|
||||||
|
byte(DATACOPY),
|
||||||
|
byte(PUSH1), 20,
|
||||||
|
byte(PUSH1), 0x00,
|
||||||
|
byte(RETURN),
|
||||||
|
},
|
||||||
|
section: 0,
|
||||||
|
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 3}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: []byte{
|
||||||
|
byte(PUSH1), 1,
|
||||||
|
byte(RJUMPV), 0x01, 0x00, 0x03, 0xff, 0xf8,
|
||||||
|
byte(JUMPDEST),
|
||||||
|
byte(JUMPDEST),
|
||||||
|
byte(STOP),
|
||||||
|
byte(PUSH1), 20,
|
||||||
|
byte(PUSH1), 39,
|
||||||
|
byte(PUSH1), 0x00,
|
||||||
|
byte(DATACOPY),
|
||||||
|
byte(PUSH1), 20,
|
||||||
|
byte(PUSH1), 0x00,
|
||||||
|
byte(RETURN),
|
||||||
|
},
|
||||||
|
section: 0,
|
||||||
|
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 3}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: []byte{
|
||||||
|
byte(STOP),
|
||||||
|
byte(STOP),
|
||||||
|
byte(INVALID),
|
||||||
|
},
|
||||||
|
section: 0,
|
||||||
|
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 0}},
|
||||||
|
err: ErrUnreachableCode,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: []byte{
|
||||||
|
byte(RETF),
|
||||||
|
},
|
||||||
|
section: 0,
|
||||||
|
metadata: []*functionMetadata{{inputs: 0, outputs: 1, maxStackHeight: 0}},
|
||||||
|
err: ErrInvalidOutputs,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: []byte{
|
||||||
|
byte(RETF),
|
||||||
|
},
|
||||||
|
section: 0,
|
||||||
|
metadata: []*functionMetadata{{inputs: 3, outputs: 3, maxStackHeight: 3}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: []byte{
|
||||||
|
byte(CALLF), 0x00, 0x01,
|
||||||
|
byte(POP),
|
||||||
|
byte(STOP),
|
||||||
|
},
|
||||||
|
section: 0,
|
||||||
|
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}, {inputs: 0, outputs: 1, maxStackHeight: 0}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: []byte{
|
||||||
|
byte(ORIGIN),
|
||||||
|
byte(ORIGIN),
|
||||||
|
byte(CALLF), 0x00, 0x01,
|
||||||
|
byte(POP),
|
||||||
|
byte(RETF),
|
||||||
|
},
|
||||||
|
section: 0,
|
||||||
|
metadata: []*functionMetadata{{inputs: 0, outputs: 0, maxStackHeight: 2}, {inputs: 2, outputs: 1, maxStackHeight: 2}},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
container := &Container{
|
||||||
|
types: test.metadata,
|
||||||
|
data: make([]byte, 0),
|
||||||
|
sections: make([]*Container, 0),
|
||||||
|
}
|
||||||
|
_, err := validateCode(test.code, test.section, container, &pragueEOFInstructionSet, false)
|
||||||
|
if !errors.Is(err, test.err) {
|
||||||
|
t.Errorf("test %d (%s): unexpected error (want: %v, got: %v)", i, common.Bytes2Hex(test.code), test.err, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkRJUMPI(b *testing.B) {
|
||||||
|
snippet := []byte{
|
||||||
|
byte(PUSH0),
|
||||||
|
byte(RJUMPI), 0xFF, 0xFC,
|
||||||
|
}
|
||||||
|
code := []byte{}
|
||||||
|
for i := 0; i < params.MaxCodeSize/len(snippet)-1; i++ {
|
||||||
|
code = append(code, snippet...)
|
||||||
|
}
|
||||||
|
code = append(code, byte(STOP))
|
||||||
|
container := &Container{
|
||||||
|
types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
|
||||||
|
data: make([]byte, 0),
|
||||||
|
sections: make([]*Container, 0),
|
||||||
|
}
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, err := validateCode(code, 0, container, &pragueEOFInstructionSet, true)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkRJUMPV(b *testing.B) {
|
||||||
|
snippet := []byte{
|
||||||
|
byte(PUSH0),
|
||||||
|
byte(RJUMPV),
|
||||||
|
0xff, // count
|
||||||
|
0x00, 0x00,
|
||||||
|
}
|
||||||
|
for i := 0; i < 255; i++ {
|
||||||
|
snippet = append(snippet, []byte{0x00, 0x00}...)
|
||||||
|
}
|
||||||
|
code := []byte{}
|
||||||
|
for i := 0; i < 24576/len(snippet)-1; i++ {
|
||||||
|
code = append(code, snippet...)
|
||||||
|
}
|
||||||
|
code = append(code, byte(PUSH0))
|
||||||
|
code = append(code, byte(STOP))
|
||||||
|
container := &Container{
|
||||||
|
types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
|
||||||
|
data: make([]byte, 0),
|
||||||
|
sections: make([]*Container, 0),
|
||||||
|
}
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, err := validateCode(code, 0, container, &pragueEOFInstructionSet, true)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkEOFValidation(b *testing.B) {
|
||||||
|
var container Container
|
||||||
|
var code []byte
|
||||||
|
maxSections := 1024
|
||||||
|
for i := 0; i < maxSections; i++ {
|
||||||
|
code = append(code, byte(CALLF))
|
||||||
|
code = binary.BigEndian.AppendUint16(code, uint16(i%(maxSections-1))+1)
|
||||||
|
}
|
||||||
|
code = append(code, byte(STOP))
|
||||||
|
// First container
|
||||||
|
container.code = append(container.code, code)
|
||||||
|
container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: 0})
|
||||||
|
|
||||||
|
inner := []byte{
|
||||||
|
byte(STOP),
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < 1023; i++ {
|
||||||
|
container.code = append(container.code, inner)
|
||||||
|
container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: 0})
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < 12; i++ {
|
||||||
|
container.code[i+1] = code
|
||||||
|
}
|
||||||
|
|
||||||
|
bin := container.MarshalBinary()
|
||||||
|
if len(bin) > 48*1024 {
|
||||||
|
b.Fatal("Exceeds 48Kb")
|
||||||
|
}
|
||||||
|
|
||||||
|
var container2 Container
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
if err := container2.UnmarshalBinary(bin, true); err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := container2.ValidateCode(&pragueEOFInstructionSet, true); err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkEOFValidation2(b *testing.B) {
|
||||||
|
var container Container
|
||||||
|
var code []byte
|
||||||
|
maxSections := 1024
|
||||||
|
for i := 0; i < maxSections; i++ {
|
||||||
|
code = append(code, byte(CALLF))
|
||||||
|
code = binary.BigEndian.AppendUint16(code, uint16(i%(maxSections-1))+1)
|
||||||
|
}
|
||||||
|
code = append(code, byte(STOP))
|
||||||
|
// First container
|
||||||
|
container.code = append(container.code, code)
|
||||||
|
container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: 0})
|
||||||
|
|
||||||
|
inner := []byte{
|
||||||
|
byte(CALLF), 0x03, 0xE8,
|
||||||
|
byte(CALLF), 0x03, 0xE9,
|
||||||
|
byte(CALLF), 0x03, 0xF0,
|
||||||
|
byte(CALLF), 0x03, 0xF1,
|
||||||
|
byte(CALLF), 0x03, 0xF2,
|
||||||
|
byte(CALLF), 0x03, 0xF3,
|
||||||
|
byte(CALLF), 0x03, 0xF4,
|
||||||
|
byte(CALLF), 0x03, 0xF5,
|
||||||
|
byte(CALLF), 0x03, 0xF6,
|
||||||
|
byte(CALLF), 0x03, 0xF7,
|
||||||
|
byte(CALLF), 0x03, 0xF8,
|
||||||
|
byte(JUMPF), 0x00, 0x00,
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < 1023; i++ {
|
||||||
|
container.code = append(container.code, inner)
|
||||||
|
container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: 0})
|
||||||
|
}
|
||||||
|
|
||||||
|
bin := container.MarshalBinary()
|
||||||
|
if len(bin) > 48*1024 {
|
||||||
|
b.Fatal("Exceeds 48Kb")
|
||||||
|
}
|
||||||
|
|
||||||
|
var container2 Container
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
if err := container2.UnmarshalBinary(bin, true); err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := container2.ValidateCode(&pragueEOFInstructionSet, true); err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkEOFValidation3(b *testing.B) {
|
||||||
|
var container Container
|
||||||
|
var code []byte
|
||||||
|
snippet := []byte{
|
||||||
|
byte(PUSH0),
|
||||||
|
byte(RJUMPV),
|
||||||
|
0xff, // count
|
||||||
|
0x00, 0x00,
|
||||||
|
}
|
||||||
|
for i := 0; i < 255; i++ {
|
||||||
|
snippet = append(snippet, []byte{0x00, 0x00}...)
|
||||||
|
}
|
||||||
|
code = append(code, snippet...)
|
||||||
|
maxSections := 1024
|
||||||
|
for i := 0; i < maxSections; i++ {
|
||||||
|
code = append(code, byte(CALLF))
|
||||||
|
code = binary.BigEndian.AppendUint16(code, uint16(i%(maxSections-1))+1)
|
||||||
|
}
|
||||||
|
code = append(code, byte(STOP))
|
||||||
|
// First container
|
||||||
|
container.code = append(container.code, code)
|
||||||
|
container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: 1})
|
||||||
|
|
||||||
|
for i := 0; i < 1023; i++ {
|
||||||
|
container.code = append(container.code, []byte{byte(RJUMP), 0x00, 0x00, byte(JUMPF), 0x00, 0x00})
|
||||||
|
container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: 0})
|
||||||
|
}
|
||||||
|
for i := 0; i < 65; i++ {
|
||||||
|
container.code[i+1] = append(snippet, byte(STOP))
|
||||||
|
container.types[i+1] = &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: 1}
|
||||||
|
}
|
||||||
|
bin := container.MarshalBinary()
|
||||||
|
if len(bin) > 48*1024 {
|
||||||
|
b.Fatal("Exceeds 48Kb")
|
||||||
|
}
|
||||||
|
b.ResetTimer()
|
||||||
|
b.ReportMetric(float64(len(bin)), "bytes")
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
for k := 0; k < 40; k++ {
|
||||||
|
var container2 Container
|
||||||
|
if err := container2.UnmarshalBinary(bin, true); err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := container2.ValidateCode(&pragueEOFInstructionSet, true); err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkRJUMPI_2(b *testing.B) {
|
||||||
|
code := []byte{
|
||||||
|
byte(PUSH0),
|
||||||
|
byte(RJUMPI), 0xFF, 0xFC,
|
||||||
|
}
|
||||||
|
for i := 0; i < params.MaxCodeSize/4-1; i++ {
|
||||||
|
code = append(code, byte(PUSH0))
|
||||||
|
x := -4 * i
|
||||||
|
code = append(code, byte(RJUMPI))
|
||||||
|
code = binary.BigEndian.AppendUint16(code, uint16(x))
|
||||||
|
}
|
||||||
|
code = append(code, byte(STOP))
|
||||||
|
container := &Container{
|
||||||
|
types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
|
||||||
|
data: make([]byte, 0),
|
||||||
|
sections: make([]*Container, 0),
|
||||||
|
}
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, err := validateCode(code, 0, container, &pragueEOFInstructionSet, true)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func FuzzUnmarshalBinary(f *testing.F) {
|
||||||
|
f.Fuzz(func(_ *testing.T, input []byte) {
|
||||||
|
var container Container
|
||||||
|
container.UnmarshalBinary(input, true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func FuzzValidate(f *testing.F) {
|
||||||
|
f.Fuzz(func(_ *testing.T, code []byte, maxStack uint16) {
|
||||||
|
var container Container
|
||||||
|
container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: maxStack})
|
||||||
|
validateCode(code, 0, &container, &pragueEOFInstructionSet, true)
|
||||||
|
})
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue