mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-19 10:22:23 +00:00
core/types: implement mega-eof spec
This commit is contained in:
parent
1dccd90224
commit
07db5b86b1
31 changed files with 1787 additions and 382 deletions
|
|
@ -23,8 +23,11 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/core/vm"
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
|
|
@ -64,6 +67,10 @@ var (
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type RefTests struct {
|
||||||
|
Vectors map[string]EOFTest `json:"vectors"`
|
||||||
|
}
|
||||||
|
|
||||||
type EOFTest struct {
|
type EOFTest struct {
|
||||||
Code string `json:"code"`
|
Code string `json:"code"`
|
||||||
Results map[string]etResult `json:"results"`
|
Results map[string]etResult `json:"results"`
|
||||||
|
|
@ -71,7 +78,7 @@ type EOFTest struct {
|
||||||
|
|
||||||
type etResult struct {
|
type etResult struct {
|
||||||
Result bool `json:"result"`
|
Result bool `json:"result"`
|
||||||
Exception int `json:"exception,omitempty"`
|
Exception string `json:"exception,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func eofParser(ctx *cli.Context) error {
|
func eofParser(ctx *cli.Context) error {
|
||||||
|
|
@ -89,42 +96,45 @@ func eofParser(ctx *cli.Context) error {
|
||||||
|
|
||||||
// If `--test` is set, parse and validate the reference test at the provided path.
|
// If `--test` is set, parse and validate the reference test at the provided path.
|
||||||
if ctx.IsSet(RefTestFlag.Name) {
|
if ctx.IsSet(RefTestFlag.Name) {
|
||||||
src, err := os.ReadFile(ctx.String(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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
var tests map[string]EOFTest
|
_, _, err = ExecuteTest(src)
|
||||||
if err = json.Unmarshal(src, &tests); err != nil {
|
return err
|
||||||
|
} else {
|
||||||
|
err = filepath.Walk(file, func(path string, info fs.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
passed, total := 0, 0
|
if info.IsDir() {
|
||||||
for name, tt := range tests {
|
|
||||||
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)
|
|
||||||
if err2 := errors.Unwrap(err); err2 != nil {
|
|
||||||
err = err2
|
|
||||||
}
|
|
||||||
if r.Result && err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "%s, %s: expected success, got %v\n", name, fork, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !r.Result && err == nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "%s, %s: expected error %d, got %v\n", name, fork, r.Exception, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !r.Result && err != nil && r.Exception != errorMap[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 nil
|
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.
|
// If neither are passed in, read input from stdin.
|
||||||
scanner := bufio.NewScanner(os.Stdin)
|
scanner := bufio.NewScanner(os.Stdin)
|
||||||
|
|
@ -144,6 +154,45 @@ func eofParser(ctx *cli.Context) error {
|
||||||
return nil
|
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 _, 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)
|
||||||
|
if err2 := errors.Unwrap(err); err2 != nil {
|
||||||
|
err = err2
|
||||||
|
}
|
||||||
|
if r.Result && err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "%s, %s: expected success, got %v\n", name, fork, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !r.Result && err == nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "%s, %s: expected error %s, got %v\n", 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) (*vm.Container, error) {
|
func parseAndValidate(s string) (*vm.Container, error) {
|
||||||
if len(s) >= 2 && strings.HasPrefix(s, "0x") {
|
if len(s) >= 2 && strings.HasPrefix(s, "0x") {
|
||||||
s = s[2:]
|
s = s[2:]
|
||||||
|
|
@ -161,3 +210,24 @@ func parseAndValidate(s string) (*vm.Container, error) {
|
||||||
}
|
}
|
||||||
return &c, nil
|
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); 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -141,10 +141,6 @@ var (
|
||||||
Usage: "enable return data output",
|
Usage: "enable return data output",
|
||||||
Category: flags.VMCategory,
|
Category: flags.VMCategory,
|
||||||
}
|
}
|
||||||
HexFlag = &cli.StringFlag{
|
|
||||||
Name: "hex",
|
|
||||||
Usage: "single container data parse and validation",
|
|
||||||
}
|
|
||||||
ForknameFlag = &cli.StringFlag{
|
ForknameFlag = &cli.StringFlag{
|
||||||
Name: "state.fork",
|
Name: "state.fork",
|
||||||
Usage: fmt.Sprintf("Name of ruleset to use."+
|
Usage: fmt.Sprintf("Name of ruleset to use."+
|
||||||
|
|
@ -157,10 +153,6 @@ var (
|
||||||
strings.Join(vm.ActivateableEips(), ", ")),
|
strings.Join(vm.ActivateableEips(), ", ")),
|
||||||
Value: "Shanghai",
|
Value: "Shanghai",
|
||||||
}
|
}
|
||||||
RefTestFlag = &cli.StringFlag{
|
|
||||||
Name: "test",
|
|
||||||
Usage: "Path to EOF validation reference test.",
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var stateTransitionCommand = &cli.Command{
|
var stateTransitionCommand = &cli.Command{
|
||||||
|
|
@ -245,17 +237,6 @@ var traceFlags = []cli.Flag{
|
||||||
DisableReturnDataFlag,
|
DisableReturnDataFlag,
|
||||||
}
|
}
|
||||||
|
|
||||||
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 app = flags.NewApp("the evm command line interface")
|
var app = flags.NewApp("the evm command line interface")
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
@ -269,7 +250,6 @@ func init() {
|
||||||
stateTransitionCommand,
|
stateTransitionCommand,
|
||||||
transactionCommand,
|
transactionCommand,
|
||||||
blockBuilderCommand,
|
blockBuilderCommand,
|
||||||
eofParserCommand,
|
|
||||||
}
|
}
|
||||||
app.Before = func(ctx *cli.Context) error {
|
app.Before = func(ctx *cli.Context) error {
|
||||||
flags.MigrateGlobalFlags(ctx)
|
flags.MigrateGlobalFlags(ctx)
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,9 @@ func NewEVMTxContext(msg *Message) vm.TxContext {
|
||||||
if msg.BlobGasFeeCap != nil {
|
if msg.BlobGasFeeCap != nil {
|
||||||
ctx.BlobFeeCap = new(big.Int).Set(msg.BlobGasFeeCap)
|
ctx.BlobFeeCap = new(big.Int).Set(msg.BlobGasFeeCap)
|
||||||
}
|
}
|
||||||
|
if msg.InitCodes != nil {
|
||||||
|
ctx.InitCodes = msg.InitCodes
|
||||||
|
}
|
||||||
return ctx
|
return ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -564,9 +564,13 @@ func (s *stateObject) CodeSize() int {
|
||||||
if len(s.code) != 0 {
|
if len(s.code) != 0 {
|
||||||
return len(s.code)
|
return len(s.code)
|
||||||
}
|
}
|
||||||
if bytes.Equal(s.CodeHash(), types.EmptyCodeHash.Bytes()) {
|
codeHash := s.CodeHash()
|
||||||
|
if bytes.Equal(codeHash, types.EmptyCodeHash.Bytes()) {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
if bytes.Equal(codeHash, types.EmptyEOFCodeHash.Bytes()) {
|
||||||
|
return 2
|
||||||
|
}
|
||||||
size, err := s.db.db.ContractCodeSize(s.address, common.BytesToHash(s.CodeHash()))
|
size, err := s.db.db.ContractCodeSize(s.address, common.BytesToHash(s.CodeHash()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.db.setError(fmt.Errorf("can't load code size %x: %v", s.CodeHash(), err))
|
s.db.setError(fmt.Errorf("can't load code size %x: %v", s.CodeHash(), err))
|
||||||
|
|
|
||||||
|
|
@ -473,6 +473,13 @@ func (s *StateDB) SetCode(addr common.Address, code []byte) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *StateDB) SetCodeEOF(addr common.Address, code []byte) {
|
||||||
|
stateObject := s.getOrNewStateObject(addr)
|
||||||
|
if stateObject != nil {
|
||||||
|
stateObject.SetCode(types.EmptyEOFCodeHash, code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *StateDB) SetState(addr common.Address, key, value common.Hash) {
|
func (s *StateDB) SetState(addr common.Address, key, value common.Hash) {
|
||||||
stateObject := s.getOrNewStateObject(addr)
|
stateObject := s.getOrNewStateObject(addr)
|
||||||
if stateObject != nil {
|
if stateObject != nil {
|
||||||
|
|
|
||||||
|
|
@ -148,6 +148,7 @@ type Message struct {
|
||||||
BlobGasFeeCap *big.Int
|
BlobGasFeeCap *big.Int
|
||||||
BlobHashes []common.Hash
|
BlobHashes []common.Hash
|
||||||
AuthList types.AuthorizationList
|
AuthList types.AuthorizationList
|
||||||
|
InitCodes [][]byte
|
||||||
|
|
||||||
// When SkipAccountChecks is true, the message nonce is not checked against the
|
// When SkipAccountChecks is true, the message nonce is not checked against the
|
||||||
// account nonce in state. It also disables checking that the sender is an EOA.
|
// account nonce in state. It also disables checking that the sender is an EOA.
|
||||||
|
|
@ -171,6 +172,7 @@ func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.In
|
||||||
SkipAccountChecks: false,
|
SkipAccountChecks: false,
|
||||||
BlobHashes: tx.BlobHashes(),
|
BlobHashes: tx.BlobHashes(),
|
||||||
BlobGasFeeCap: tx.BlobGasFeeCap(),
|
BlobGasFeeCap: tx.BlobGasFeeCap(),
|
||||||
|
InitCodes: tx.InitCodes(),
|
||||||
}
|
}
|
||||||
// If baseFee provided, set gasPrice to effectiveGasPrice.
|
// If baseFee provided, set gasPrice to effectiveGasPrice.
|
||||||
if baseFee != nil {
|
if baseFee != nil {
|
||||||
|
|
@ -422,8 +424,17 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
|
||||||
contractCreation = msg.To == nil
|
contractCreation = msg.To == nil
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Add the initcode data for calculation of the intrinsic gas
|
||||||
|
// TODO (MariusVanDerWijden): while this should work, it is very
|
||||||
|
// dirty, better to pass the initcodes directly to IntrinsicGas
|
||||||
|
// and duplicate the cost accounting logic there.
|
||||||
|
data := msg.Data
|
||||||
|
for _, initcode := range msg.InitCodes {
|
||||||
|
data = append(data, initcode...)
|
||||||
|
}
|
||||||
|
|
||||||
// Check clauses 4-5, subtract intrinsic gas if everything is correct
|
// Check clauses 4-5, subtract intrinsic gas if everything is correct
|
||||||
gas, err := IntrinsicGas(msg.Data, msg.AccessList, msg.AuthList, contractCreation, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai)
|
gas, err := IntrinsicGas(data, msg.AccessList, msg.AuthList, contractCreation, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,9 @@ var (
|
||||||
// EmptyCodeHash is the known hash of the empty EVM bytecode.
|
// EmptyCodeHash is the known hash of the empty EVM bytecode.
|
||||||
EmptyCodeHash = crypto.Keccak256Hash(nil) // c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
|
EmptyCodeHash = crypto.Keccak256Hash(nil) // c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
|
||||||
|
|
||||||
|
// EmptyEOFCodeHash is the known hash of the an empty EOF bytecode.
|
||||||
|
EmptyEOFCodeHash = crypto.Keccak256Hash([]byte{0xFE, 00}) // 9dbf3648db8210552e9c4f75c6a1c3057c0ca432043bd648be15fe7be05646f5
|
||||||
|
|
||||||
// EmptyTxsHash is the known hash of the empty transaction set.
|
// EmptyTxsHash is the known hash of the empty transaction set.
|
||||||
EmptyTxsHash = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
|
EmptyTxsHash = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -426,7 +426,6 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case SetCodeTxType:
|
case SetCodeTxType:
|
||||||
var itx SetCodeTx
|
var itx SetCodeTx
|
||||||
inner = &itx
|
inner = &itx
|
||||||
|
|
|
||||||
|
|
@ -139,7 +139,7 @@ func eofCodeBitmapInternal(code, bits bitvec) bitvec {
|
||||||
switch {
|
switch {
|
||||||
case op >= PUSH1 && op <= PUSH32:
|
case op >= PUSH1 && op <= PUSH32:
|
||||||
numbits = uint8(op - PUSH1 + 1)
|
numbits = uint8(op - PUSH1 + 1)
|
||||||
case op == RJUMP || op == RJUMPI || op == CALLF:
|
case op == RJUMP || op == RJUMPI || op == CALLF || op == JUMPF || op == DATALOADN:
|
||||||
numbits = 2
|
numbits = 2
|
||||||
case op == RJUMPV:
|
case op == RJUMPV:
|
||||||
// RJUMPV is unique as it has a variable sized operand.
|
// RJUMPV is unique as it has a variable sized operand.
|
||||||
|
|
@ -159,6 +159,8 @@ func eofCodeBitmapInternal(code, bits bitvec) bitvec {
|
||||||
// as possible.
|
// as possible.
|
||||||
numbits = uint8(end - pc)
|
numbits = uint8(end - pc)
|
||||||
}
|
}
|
||||||
|
case op == DUPN || op == SWAPN || op == EXCHANGE || op == EOFCREATE || op == RETURNCONTRACT:
|
||||||
|
numbits = 1
|
||||||
default:
|
default:
|
||||||
// Op had no immediate operand, continue.
|
// Op had no immediate operand, continue.
|
||||||
continue
|
continue
|
||||||
|
|
|
||||||
474
core/vm/eips.go
474
core/vm/eips.go
|
|
@ -17,6 +17,7 @@
|
||||||
package vm
|
package vm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
@ -25,6 +26,7 @@ import (
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/tracing"
|
"github.com/ethereum/go-ethereum/core/tracing"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/holiman/uint256"
|
"github.com/holiman/uint256"
|
||||||
)
|
)
|
||||||
|
|
@ -570,11 +572,30 @@ func enableEOF(jt *JumpTable) {
|
||||||
maxStack: maxStack(0, 0),
|
maxStack: maxStack(0, 0),
|
||||||
undefined: true,
|
undefined: true,
|
||||||
}
|
}
|
||||||
|
jt[CALL] = undefined
|
||||||
jt[CALLCODE] = undefined
|
jt[CALLCODE] = undefined
|
||||||
|
jt[DELEGATECALL] = undefined
|
||||||
|
jt[STATICCALL] = undefined
|
||||||
jt[SELFDESTRUCT] = undefined
|
jt[SELFDESTRUCT] = undefined
|
||||||
jt[JUMP] = undefined
|
jt[JUMP] = undefined
|
||||||
jt[JUMPI] = undefined
|
jt[JUMPI] = undefined
|
||||||
jt[PC] = 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),
|
||||||
|
terminal: true,
|
||||||
|
}
|
||||||
|
|
||||||
// New opcodes
|
// New opcodes
|
||||||
jt[RJUMP] = &operation{
|
jt[RJUMP] = &operation{
|
||||||
|
|
@ -582,25 +603,28 @@ func enableEOF(jt *JumpTable) {
|
||||||
constantGas: GasQuickStep,
|
constantGas: GasQuickStep,
|
||||||
minStack: minStack(0, 0),
|
minStack: minStack(0, 0),
|
||||||
maxStack: maxStack(0, 0),
|
maxStack: maxStack(0, 0),
|
||||||
terminal: true,
|
immediate: 2,
|
||||||
}
|
}
|
||||||
jt[RJUMPI] = &operation{
|
jt[RJUMPI] = &operation{
|
||||||
execute: opRjumpi,
|
execute: opRjumpi,
|
||||||
constantGas: GasFastishStep,
|
constantGas: GasFastishStep,
|
||||||
minStack: minStack(1, 0),
|
minStack: minStack(1, 0),
|
||||||
maxStack: maxStack(1, 0),
|
maxStack: maxStack(1, 0),
|
||||||
|
immediate: 2,
|
||||||
}
|
}
|
||||||
jt[RJUMPV] = &operation{
|
jt[RJUMPV] = &operation{
|
||||||
execute: opRjumpv,
|
execute: opRjumpv,
|
||||||
constantGas: GasFastishStep,
|
constantGas: GasFastishStep,
|
||||||
minStack: minStack(1, 0),
|
minStack: minStack(1, 0),
|
||||||
maxStack: maxStack(1, 0),
|
maxStack: maxStack(1, 0),
|
||||||
|
immediate: 3, // at least 3, maybe more
|
||||||
}
|
}
|
||||||
jt[CALLF] = &operation{
|
jt[CALLF] = &operation{
|
||||||
execute: opCallf,
|
execute: opCallf,
|
||||||
constantGas: GasFastStep,
|
constantGas: GasFastStep,
|
||||||
minStack: minStack(0, 0),
|
minStack: minStack(0, 0),
|
||||||
maxStack: maxStack(0, 0),
|
maxStack: maxStack(0, 0),
|
||||||
|
immediate: 2,
|
||||||
}
|
}
|
||||||
jt[RETF] = &operation{
|
jt[RETF] = &operation{
|
||||||
execute: opRetf,
|
execute: opRetf,
|
||||||
|
|
@ -609,6 +633,143 @@ func enableEOF(jt *JumpTable) {
|
||||||
maxStack: maxStack(0, 0),
|
maxStack: maxStack(0, 0),
|
||||||
terminal: true,
|
terminal: true,
|
||||||
}
|
}
|
||||||
|
jt[JUMPF] = &operation{
|
||||||
|
execute: opJumpf,
|
||||||
|
constantGas: GasFastStep,
|
||||||
|
minStack: minStack(0, 0),
|
||||||
|
maxStack: maxStack(0, 0),
|
||||||
|
immediate: 2,
|
||||||
|
terminal: true,
|
||||||
|
}
|
||||||
|
jt[EOFCREATE] = &operation{
|
||||||
|
execute: opEOFCreate,
|
||||||
|
constantGas: params.Create2Gas,
|
||||||
|
dynamicGas: gasEOFCreate,
|
||||||
|
minStack: minStack(4, 1),
|
||||||
|
maxStack: maxStack(4, 1),
|
||||||
|
memorySize: memoryEOFCreate,
|
||||||
|
immediate: 1,
|
||||||
|
}
|
||||||
|
jt[TXCREATE] = &operation{
|
||||||
|
execute: opTXCreate,
|
||||||
|
constantGas: params.Create2Gas,
|
||||||
|
dynamicGas: gasEOFCreate,
|
||||||
|
minStack: minStack(5, 1),
|
||||||
|
maxStack: maxStack(5, 1),
|
||||||
|
memorySize: memoryEOFCreate,
|
||||||
|
}
|
||||||
|
jt[RETURNCONTRACT] = &operation{
|
||||||
|
execute: opReturnContract,
|
||||||
|
constantGas: GasZeroStep,
|
||||||
|
dynamicGas: memoryCopierGas(1),
|
||||||
|
minStack: minStack(2, 0),
|
||||||
|
maxStack: maxStack(2, 0),
|
||||||
|
immediate: 1,
|
||||||
|
memorySize: memoryReturnContract,
|
||||||
|
terminal: true,
|
||||||
|
}
|
||||||
|
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),
|
||||||
|
immediate: 2,
|
||||||
|
}
|
||||||
|
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: memoryMcopy,
|
||||||
|
}
|
||||||
|
jt[DUPN] = &operation{
|
||||||
|
execute: opDupN,
|
||||||
|
constantGas: GasFastestStep,
|
||||||
|
minStack: minStack(0, 1),
|
||||||
|
maxStack: maxStack(0, 1),
|
||||||
|
immediate: 1,
|
||||||
|
}
|
||||||
|
jt[SWAPN] = &operation{
|
||||||
|
execute: opSwapN,
|
||||||
|
constantGas: GasFastestStep,
|
||||||
|
minStack: minStack(0, 0),
|
||||||
|
maxStack: maxStack(0, 0),
|
||||||
|
immediate: 1,
|
||||||
|
}
|
||||||
|
jt[EXCHANGE] = &operation{
|
||||||
|
execute: opExchange,
|
||||||
|
constantGas: GasFastestStep,
|
||||||
|
minStack: minStack(0, 0),
|
||||||
|
maxStack: maxStack(0, 0),
|
||||||
|
immediate: 1,
|
||||||
|
}
|
||||||
|
jt[RETURNDATALOAD] = &operation{
|
||||||
|
execute: opReturnDataLoad,
|
||||||
|
constantGas: GasFastestStep,
|
||||||
|
minStack: minStack(1, 1),
|
||||||
|
maxStack: maxStack(1, 1),
|
||||||
|
}
|
||||||
|
jt[EXTCALL] = &operation{
|
||||||
|
execute: opExtCall,
|
||||||
|
constantGas: params.CallGasEIP150,
|
||||||
|
dynamicGas: gasCall,
|
||||||
|
minStack: minStack(4, 1),
|
||||||
|
maxStack: maxStack(4, 1),
|
||||||
|
memorySize: memoryCall,
|
||||||
|
}
|
||||||
|
jt[EXTDELEGATECALL] = &operation{
|
||||||
|
execute: opExtDelegateCall,
|
||||||
|
dynamicGas: gasDelegateCall,
|
||||||
|
constantGas: params.CallGasEIP150,
|
||||||
|
minStack: minStack(3, 1),
|
||||||
|
maxStack: maxStack(3, 1),
|
||||||
|
memorySize: memoryDelegateCall,
|
||||||
|
}
|
||||||
|
jt[EXTSTATICCALL] = &operation{
|
||||||
|
execute: opExtStaticCall,
|
||||||
|
constantGas: params.CallGasEIP150,
|
||||||
|
dynamicGas: gasStaticCall,
|
||||||
|
minStack: minStack(3, 1),
|
||||||
|
maxStack: maxStack(3, 1),
|
||||||
|
memorySize: memoryStaticCall,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func opExtCodeCopyEOF(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||||
|
var (
|
||||||
|
stack = scope.Stack
|
||||||
|
a = stack.pop()
|
||||||
|
memOffset = stack.pop()
|
||||||
|
codeOffset = stack.pop()
|
||||||
|
length = stack.pop()
|
||||||
|
)
|
||||||
|
uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow()
|
||||||
|
if overflow {
|
||||||
|
uint64CodeOffset = math.MaxUint64
|
||||||
|
}
|
||||||
|
addr := common.Address(a.Bytes20())
|
||||||
|
code := interpreter.evm.StateDB.GetCode(addr)
|
||||||
|
// Check if we're copying an EOF contract
|
||||||
|
if len(code) >= 2 && code[0] == 0xEF && code[1] == 0x00 {
|
||||||
|
code = []byte{0xEF, 0x00}
|
||||||
|
}
|
||||||
|
codeCopy := getData(code, uint64CodeOffset, length.Uint64())
|
||||||
|
scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy)
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// opRjump implements the rjump opcode.
|
// opRjump implements the rjump opcode.
|
||||||
|
|
@ -691,3 +852,314 @@ func opRetf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt
|
||||||
return nil, nil
|
return nil, nil
|
||||||
>>>>>>> 3532f16eb7 (core/vm: add eof container)
|
>>>>>>> 3532f16eb7 (core/vm: add eof container)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func opJumpf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||||
|
var (
|
||||||
|
code = scope.Contract.CodeAt(scope.CodeSection)
|
||||||
|
idx = binary.BigEndian.Uint16(code[*pc+1:])
|
||||||
|
typ = scope.Contract.Container.Types[idx]
|
||||||
|
)
|
||||||
|
if scope.Stack.len()+int(typ.MaxStackHeight)-int(typ.Input) > 1024 {
|
||||||
|
return nil, fmt.Errorf("stack overflow")
|
||||||
|
}
|
||||||
|
scope.CodeSection = uint64(idx)
|
||||||
|
*pc = 0
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func opEOFCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||||
|
if interpreter.readOnly {
|
||||||
|
return nil, ErrWriteProtection
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
code = scope.Contract.CodeAt(scope.CodeSection)
|
||||||
|
idx = code[*pc+1]
|
||||||
|
value = scope.Stack.pop()
|
||||||
|
salt = scope.Stack.pop()
|
||||||
|
offset, size = scope.Stack.pop(), scope.Stack.pop()
|
||||||
|
input = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64()))
|
||||||
|
gas = scope.Contract.Gas
|
||||||
|
)
|
||||||
|
if int(idx) >= len(scope.Contract.Container.ContainerSections) {
|
||||||
|
return nil, fmt.Errorf("invalid subcontainer")
|
||||||
|
}
|
||||||
|
subcontainer := scope.Contract.Container.ContainerSections[idx]
|
||||||
|
|
||||||
|
// Reuse last popped value from stack
|
||||||
|
stackvalue := size
|
||||||
|
// Apply EIP150
|
||||||
|
gas -= gas / 64
|
||||||
|
scope.Contract.UseGas(gas, interpreter.evm.Config.Tracer, tracing.GasChangeCallContractCreation2)
|
||||||
|
|
||||||
|
res, addr, returnGas, suberr := interpreter.evm.EOFCreate(scope.Contract, input, subcontainer.MarshalBinary(), gas, &value, &salt)
|
||||||
|
if suberr != nil {
|
||||||
|
stackvalue.Clear()
|
||||||
|
} else {
|
||||||
|
stackvalue.SetBytes(addr.Bytes())
|
||||||
|
}
|
||||||
|
scope.Stack.push(&stackvalue)
|
||||||
|
|
||||||
|
scope.Contract.RefundGas(returnGas, interpreter.evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
|
||||||
|
|
||||||
|
if suberr == ErrExecutionReverted {
|
||||||
|
interpreter.returnData = res // set REVERT data to return data buffer
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
interpreter.returnData = nil // clear dirty return data buffer
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func opTXCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||||
|
if interpreter.readOnly {
|
||||||
|
return nil, ErrWriteProtection
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
value = scope.Stack.pop()
|
||||||
|
salt = scope.Stack.pop()
|
||||||
|
offset, size = scope.Stack.pop(), scope.Stack.pop()
|
||||||
|
txInitCodeHash = scope.Stack.pop()
|
||||||
|
input = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64()))
|
||||||
|
gas = scope.Contract.Gas
|
||||||
|
)
|
||||||
|
// Reuse last popped value from stack
|
||||||
|
stackvalue := txInitCodeHash
|
||||||
|
|
||||||
|
var initCode []byte
|
||||||
|
for _, code := range interpreter.evm.InitCodes {
|
||||||
|
if interpreter.hasher == nil {
|
||||||
|
interpreter.hasher = crypto.NewKeccakState()
|
||||||
|
} else {
|
||||||
|
interpreter.hasher.Reset()
|
||||||
|
}
|
||||||
|
interpreter.hasher.Write(code)
|
||||||
|
interpreter.hasher.Read(interpreter.hasherBuf[:])
|
||||||
|
if interpreter.hasherBuf.Cmp(txInitCodeHash.Bytes32()) == 0 {
|
||||||
|
initCode = code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(initCode) == 0 {
|
||||||
|
stackvalue.Clear()
|
||||||
|
scope.Stack.push(&stackvalue)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Additional hashing charge
|
||||||
|
hashingCharge := params.InitCodeWordGas * ((uint64(len(initCode)) + 31) / 32)
|
||||||
|
scope.Contract.UseGas(hashingCharge, interpreter.evm.Config.Tracer, tracing.GasChangeCallContractCreation2)
|
||||||
|
// Apply EIP150
|
||||||
|
gas -= gas / 64
|
||||||
|
scope.Contract.UseGas(gas, interpreter.evm.Config.Tracer, tracing.GasChangeCallContractCreation2)
|
||||||
|
scope.InitCodeMode = true
|
||||||
|
res, addr, returnGas, suberr := interpreter.evm.EOFCreate(scope.Contract, input, initCode, gas, &value, &salt)
|
||||||
|
if suberr != nil {
|
||||||
|
stackvalue.Clear()
|
||||||
|
} else {
|
||||||
|
stackvalue.SetBytes(addr.Bytes())
|
||||||
|
}
|
||||||
|
scope.Stack.push(&stackvalue)
|
||||||
|
|
||||||
|
scope.Contract.RefundGas(returnGas, interpreter.evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
|
||||||
|
|
||||||
|
if suberr == ErrExecutionReverted {
|
||||||
|
interpreter.returnData = res // set REVERT data to return data buffer
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
interpreter.returnData = nil // clear dirty return data buffer
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func opReturnContract(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||||
|
if !scope.InitCodeMode {
|
||||||
|
return nil, errors.New("returncontract in non-initcode mode")
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
code = scope.Contract.CodeAt(scope.CodeSection)
|
||||||
|
idx = code[*pc+1]
|
||||||
|
offset = scope.Stack.pop()
|
||||||
|
size = scope.Stack.pop()
|
||||||
|
)
|
||||||
|
if int(idx) >= len(scope.Contract.Container.ContainerSections) {
|
||||||
|
return nil, fmt.Errorf("invalid subcontainer")
|
||||||
|
}
|
||||||
|
ret := scope.Memory.GetPtr(int64(offset.Uint64()), int64(size.Uint64()))
|
||||||
|
containerCode := scope.Contract.Container.ContainerCode[idx]
|
||||||
|
deployedCode := append(containerCode, ret...)
|
||||||
|
if len(deployedCode) == 0 {
|
||||||
|
return nil, errors.New("nonexistant subcontainer")
|
||||||
|
}
|
||||||
|
return deployedCode, errStopToken
|
||||||
|
}
|
||||||
|
|
||||||
|
func opDataLoad(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||||
|
var (
|
||||||
|
stackItem = scope.Stack.pop()
|
||||||
|
offset, overflow = stackItem.Uint64WithOverflow()
|
||||||
|
)
|
||||||
|
|
||||||
|
if overflow {
|
||||||
|
stackItem.Clear()
|
||||||
|
scope.Stack.push(&stackItem)
|
||||||
|
} else {
|
||||||
|
data := getData(scope.Contract.Container.Data, offset, 32)
|
||||||
|
scope.Stack.push(stackItem.SetBytes(data))
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func opDataLoadN(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||||
|
var (
|
||||||
|
code = scope.Contract.CodeAt(scope.CodeSection)
|
||||||
|
offset = uint64(binary.BigEndian.Uint16(code[*pc+1:]))
|
||||||
|
)
|
||||||
|
data := getData(scope.Contract.Container.Data, offset, 32)
|
||||||
|
scope.Stack.push(new(uint256.Int).SetBytes(data))
|
||||||
|
*pc += 2
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func opDataSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||||
|
length := len(scope.Contract.Container.Data)
|
||||||
|
item := uint256.NewInt(uint64(length))
|
||||||
|
scope.Stack.push(item)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func opDataCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||||
|
var (
|
||||||
|
memOffset = scope.Stack.pop()
|
||||||
|
offset = scope.Stack.pop()
|
||||||
|
size = scope.Stack.pop()
|
||||||
|
)
|
||||||
|
// These values are checked for overflow during memory expansion calculation
|
||||||
|
// (the memorySize function on the opcode).
|
||||||
|
data := getData(scope.Contract.Container.Data, offset.Uint64(), size.Uint64())
|
||||||
|
scope.Memory.Set(memOffset.Uint64(), size.Uint64(), data)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func opDupN(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||||
|
var (
|
||||||
|
code = scope.Contract.CodeAt(scope.CodeSection)
|
||||||
|
index = int(code[*pc+1]) + 1
|
||||||
|
)
|
||||||
|
scope.Stack.dup(index)
|
||||||
|
*pc += 1
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func opSwapN(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||||
|
var (
|
||||||
|
code = scope.Contract.CodeAt(scope.CodeSection)
|
||||||
|
index = int(code[*pc+1]) + 1
|
||||||
|
)
|
||||||
|
scope.Stack.swap(index)
|
||||||
|
*pc += 1
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func opExchange(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||||
|
var (
|
||||||
|
code = scope.Contract.CodeAt(scope.CodeSection)
|
||||||
|
index = int(code[*pc+1]) + 1
|
||||||
|
n = index>>4 + 1
|
||||||
|
m = index%0x0F + 1
|
||||||
|
)
|
||||||
|
scope.Stack.swapN(n, m)
|
||||||
|
*pc += 1
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func opReturnDataLoad(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||||
|
var (
|
||||||
|
offset = scope.Stack.pop()
|
||||||
|
)
|
||||||
|
if offset.Uint64()+32 > uint64(len(interpreter.returnData)) {
|
||||||
|
return nil, errors.New("return buffer overflow")
|
||||||
|
}
|
||||||
|
scope.Stack.push(offset.SetBytes(interpreter.returnData[offset.Uint64() : offset.Uint64()+32]))
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func opExtCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||||
|
stack := scope.Stack
|
||||||
|
// Use all available gas
|
||||||
|
gas := (scope.Contract.Gas / 64) * 63
|
||||||
|
// Pop other call parameters.
|
||||||
|
addr, value, inOffset, inSize := stack.pop(), stack.pop(), stack.pop(), stack.pop()
|
||||||
|
toAddr := common.Address(addr.Bytes20())
|
||||||
|
// safe a memory alloc
|
||||||
|
temp := addr
|
||||||
|
// Get the arguments from the memory.
|
||||||
|
args := scope.Memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64()))
|
||||||
|
|
||||||
|
if interpreter.readOnly && !value.IsZero() {
|
||||||
|
return nil, ErrWriteProtection
|
||||||
|
}
|
||||||
|
if !value.IsZero() {
|
||||||
|
gas += params.CallStipend
|
||||||
|
}
|
||||||
|
ret, returnGas, err := interpreter.evm.Call(scope.Contract, toAddr, args, gas, &value)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
temp.Clear()
|
||||||
|
} else {
|
||||||
|
temp.SetOne()
|
||||||
|
}
|
||||||
|
stack.push(&temp)
|
||||||
|
|
||||||
|
scope.Contract.RefundGas(returnGas, interpreter.evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
|
||||||
|
|
||||||
|
interpreter.returnData = ret
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func opExtDelegateCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||||
|
stack := scope.Stack
|
||||||
|
// Use all available gas
|
||||||
|
gas := (scope.Contract.Gas / 64) * 63
|
||||||
|
// Pop other call parameters.
|
||||||
|
addr, inOffset, inSize := stack.pop(), stack.pop(), stack.pop()
|
||||||
|
toAddr := common.Address(addr.Bytes20())
|
||||||
|
// safe a memory alloc
|
||||||
|
temp := addr
|
||||||
|
// Get arguments from the memory.
|
||||||
|
args := scope.Memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64()))
|
||||||
|
|
||||||
|
ret, returnGas, err := interpreter.evm.DelegateCall(scope.Contract, toAddr, args, gas, true)
|
||||||
|
if err != nil {
|
||||||
|
temp.Clear()
|
||||||
|
} else {
|
||||||
|
temp.SetOne()
|
||||||
|
}
|
||||||
|
stack.push(&temp)
|
||||||
|
|
||||||
|
scope.Contract.RefundGas(returnGas, interpreter.evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
|
||||||
|
|
||||||
|
interpreter.returnData = ret
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func opExtStaticCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||||
|
stack := scope.Stack
|
||||||
|
// Use all available gas
|
||||||
|
gas := (scope.Contract.Gas / 64) * 63
|
||||||
|
// Pop other call parameters.
|
||||||
|
addr, inOffset, inSize := stack.pop(), stack.pop(), stack.pop()
|
||||||
|
toAddr := common.Address(addr.Bytes20())
|
||||||
|
// safe a memory alloc
|
||||||
|
temp := addr
|
||||||
|
// Get arguments from the memory.
|
||||||
|
args := scope.Memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64()))
|
||||||
|
|
||||||
|
ret, returnGas, err := interpreter.evm.StaticCall(scope.Contract, toAddr, args, gas)
|
||||||
|
if err != nil {
|
||||||
|
temp.Clear()
|
||||||
|
} else {
|
||||||
|
temp.SetOne()
|
||||||
|
}
|
||||||
|
stack.push(&temp)
|
||||||
|
|
||||||
|
scope.Contract.RefundGas(returnGas, interpreter.evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
|
||||||
|
|
||||||
|
interpreter.returnData = ret
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
134
core/vm/eof.go
134
core/vm/eof.go
|
|
@ -19,6 +19,7 @@ package vm
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
@ -38,7 +39,7 @@ const (
|
||||||
eof1Version = 1
|
eof1Version = 1
|
||||||
|
|
||||||
maxInputItems = 127
|
maxInputItems = 127
|
||||||
maxOutputItems = 127
|
maxOutputItems = 128
|
||||||
maxStackHeight = 1023
|
maxStackHeight = 1023
|
||||||
maxContainerSections = 256
|
maxContainerSections = 256
|
||||||
)
|
)
|
||||||
|
|
@ -56,7 +57,7 @@ var (
|
||||||
ErrMissingTerminator = errors.New("missing header terminator")
|
ErrMissingTerminator = errors.New("missing header terminator")
|
||||||
ErrTooManyInputs = errors.New("invalid type content, too many inputs")
|
ErrTooManyInputs = errors.New("invalid type content, too many inputs")
|
||||||
ErrTooManyOutputs = errors.New("invalid type content, too many inputs")
|
ErrTooManyOutputs = errors.New("invalid type content, too many inputs")
|
||||||
ErrInvalidSection0Type = errors.New("invalid section 0 type, input and output should be zero")
|
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")
|
ErrTooLargeMaxStackHeight = errors.New("invalid type content, max stack height exceeds limit")
|
||||||
ErrInvalidContainerSize = errors.New("invalid container size")
|
ErrInvalidContainerSize = errors.New("invalid container size")
|
||||||
)
|
)
|
||||||
|
|
@ -83,8 +84,10 @@ func isEOFVersion1(code []byte) bool {
|
||||||
type Container struct {
|
type Container struct {
|
||||||
Types []*FunctionMetadata
|
Types []*FunctionMetadata
|
||||||
Code [][]byte
|
Code [][]byte
|
||||||
ContainerSections [][]byte
|
ContainerSections []*Container
|
||||||
|
ContainerCode [][]byte
|
||||||
Data []byte
|
Data []byte
|
||||||
|
DataSize int // might be less than len(Data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FunctionMetadata is an EOF function signature.
|
// FunctionMetadata is an EOF function signature.
|
||||||
|
|
@ -109,15 +112,18 @@ func (c *Container) MarshalBinary() []byte {
|
||||||
for _, code := range c.Code {
|
for _, code := range c.Code {
|
||||||
b = binary.BigEndian.AppendUint16(b, uint16(len(code)))
|
b = binary.BigEndian.AppendUint16(b, uint16(len(code)))
|
||||||
}
|
}
|
||||||
|
var encodedContainer [][]byte
|
||||||
if len(c.ContainerSections) != 0 {
|
if len(c.ContainerSections) != 0 {
|
||||||
b = append(b, kindContainer)
|
b = append(b, kindContainer)
|
||||||
b = binary.BigEndian.AppendUint16(b, uint16(len(c.ContainerSections)))
|
b = binary.BigEndian.AppendUint16(b, uint16(len(c.ContainerSections)))
|
||||||
for _, section := range c.ContainerSections {
|
for _, section := range c.ContainerSections {
|
||||||
b = binary.BigEndian.AppendUint16(b, uint16(len(section)))
|
encoded := section.MarshalBinary()
|
||||||
|
b = binary.BigEndian.AppendUint16(b, uint16(len(encoded)))
|
||||||
|
encodedContainer = append(encodedContainer, encoded)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
b = append(b, kindData)
|
b = append(b, kindData)
|
||||||
b = binary.BigEndian.AppendUint16(b, uint16(len(c.Data)))
|
b = binary.BigEndian.AppendUint16(b, uint16(c.DataSize))
|
||||||
b = append(b, 0) // terminator
|
b = append(b, 0) // terminator
|
||||||
|
|
||||||
// Write section contents.
|
// Write section contents.
|
||||||
|
|
@ -127,7 +133,7 @@ func (c *Container) MarshalBinary() []byte {
|
||||||
for _, code := range c.Code {
|
for _, code := range c.Code {
|
||||||
b = append(b, code...)
|
b = append(b, code...)
|
||||||
}
|
}
|
||||||
for _, section := range c.ContainerSections {
|
for _, section := range encodedContainer {
|
||||||
b = append(b, section...)
|
b = append(b, section...)
|
||||||
}
|
}
|
||||||
b = append(b, c.Data...)
|
b = append(b, c.Data...)
|
||||||
|
|
@ -180,20 +186,18 @@ func (c *Container) UnmarshalBinary(b []byte) error {
|
||||||
return fmt.Errorf("%w: mismatch of code sections cound and type signatures, types %d, code %d", ErrInvalidCodeSize, typesSize/4, len(codeSizes))
|
return fmt.Errorf("%w: mismatch of code sections cound and type signatures, types %d, code %d", ErrInvalidCodeSize, typesSize/4, len(codeSizes))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse container section header.
|
// Parse (optional) container section header.
|
||||||
|
var containerSizes []int
|
||||||
offset := offsetCodeKind + 2 + 2*len(codeSizes) + 1
|
offset := offsetCodeKind + 2 + 2*len(codeSizes) + 1
|
||||||
kind, containerSizes, err := parseSectionList(b, offset)
|
if offset < len(b) && b[offset] == kindContainer {
|
||||||
|
kind, containerSizes, err = parseSectionList(b, offset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// The container section is optional, only unmarshal if container section is set.
|
if kind != kindContainer {
|
||||||
if kind == kindContainer {
|
panic("somethings wrong")
|
||||||
|
}
|
||||||
offset = offset + 2 + 2*len(containerSizes) + 1
|
offset = offset + 2 + 2*len(containerSizes) + 1
|
||||||
} else {
|
|
||||||
// empty out falsly parsed container sizes
|
|
||||||
// TODO (MariusVanDerWijden): clean this up, read the kind first before parsing the section list
|
|
||||||
// and if the kind is not KindContainer, just ignore it.
|
|
||||||
containerSizes = make([]int, 0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse data section header.
|
// Parse data section header.
|
||||||
|
|
@ -204,11 +208,12 @@ func (c *Container) UnmarshalBinary(b []byte) error {
|
||||||
if kind != kindData {
|
if kind != kindData {
|
||||||
return fmt.Errorf("%w: found section %x instead", ErrMissingDataHeader, kind)
|
return fmt.Errorf("%w: found section %x instead", ErrMissingDataHeader, kind)
|
||||||
}
|
}
|
||||||
|
c.DataSize = dataSize
|
||||||
|
|
||||||
// Check for terminator.
|
// Check for terminator.
|
||||||
offsetTerminator := offset + 3
|
offsetTerminator := offset + 3
|
||||||
if len(b) < offsetTerminator {
|
if len(b) < offsetTerminator {
|
||||||
return io.ErrUnexpectedEOF
|
return fmt.Errorf("%w: invalid offset terminator", io.ErrUnexpectedEOF)
|
||||||
}
|
}
|
||||||
if b[offsetTerminator] != 0 {
|
if b[offsetTerminator] != 0 {
|
||||||
return fmt.Errorf("%w: have %x", ErrMissingTerminator, b[offsetTerminator])
|
return fmt.Errorf("%w: have %x", ErrMissingTerminator, b[offsetTerminator])
|
||||||
|
|
@ -219,7 +224,7 @@ func (c *Container) UnmarshalBinary(b []byte) error {
|
||||||
if len(containerSizes) != 0 {
|
if len(containerSizes) != 0 {
|
||||||
expectedSize += sum(containerSizes)
|
expectedSize += sum(containerSizes)
|
||||||
}
|
}
|
||||||
if len(b) != expectedSize {
|
if len(b) < expectedSize-dataSize || len(b) > expectedSize {
|
||||||
return fmt.Errorf("%w: have %d, want %d", ErrInvalidContainerSize, len(b), expectedSize)
|
return fmt.Errorf("%w: have %d, want %d", ErrInvalidContainerSize, len(b), expectedSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -243,7 +248,7 @@ func (c *Container) UnmarshalBinary(b []byte) error {
|
||||||
}
|
}
|
||||||
types = append(types, sig)
|
types = append(types, sig)
|
||||||
}
|
}
|
||||||
if types[0].Input != 0 || types[0].Output != 0 {
|
if types[0].Input != 0 || types[0].Output != 0x80 {
|
||||||
return fmt.Errorf("%w: have %d, %d", ErrInvalidSection0Type, types[0].Input, types[0].Output)
|
return fmt.Errorf("%w: have %d, %d", ErrInvalidSection0Type, types[0].Input, types[0].Output)
|
||||||
}
|
}
|
||||||
c.Types = types
|
c.Types = types
|
||||||
|
|
@ -265,19 +270,29 @@ func (c *Container) UnmarshalBinary(b []byte) error {
|
||||||
if len(containerSizes) > maxContainerSections {
|
if len(containerSizes) > maxContainerSections {
|
||||||
return fmt.Errorf("%w number of container section exceed: %v: have %v", ErrInvalidContainerSectionSize, maxContainerSections, len(containerSizes))
|
return fmt.Errorf("%w number of container section exceed: %v: have %v", ErrInvalidContainerSectionSize, maxContainerSections, len(containerSizes))
|
||||||
}
|
}
|
||||||
container := make([][]byte, len(containerSizes))
|
containerCode := make([][]byte, 0, len(containerSizes))
|
||||||
|
container := make([]*Container, 0, len(containerSizes))
|
||||||
for i, size := range containerSizes {
|
for i, size := range containerSizes {
|
||||||
if size == 0 {
|
if size == 0 || idx+size > len(b) {
|
||||||
return fmt.Errorf("%w for section %d: size must not be 0", ErrInvalidContainerSectionSize, i)
|
return fmt.Errorf("%w for section %d: size must not be 0", ErrInvalidContainerSectionSize, i)
|
||||||
}
|
}
|
||||||
container[i] = b[idx : idx+size]
|
c := new(Container)
|
||||||
|
end := min(idx+size, len(b))
|
||||||
|
if err := c.UnmarshalBinary(b[idx:end]); err != nil {
|
||||||
|
return fmt.Errorf("%w for section %d", err, i)
|
||||||
|
}
|
||||||
|
container = append(container, c)
|
||||||
|
containerCode = append(containerCode, b[idx:end])
|
||||||
|
|
||||||
idx += size
|
idx += size
|
||||||
}
|
}
|
||||||
c.ContainerSections = container
|
c.ContainerSections = container
|
||||||
|
c.ContainerCode = containerCode
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse data section.
|
// Parse data section.
|
||||||
c.Data = b[idx : idx+dataSize]
|
end := min(idx+dataSize, len(b))
|
||||||
|
c.Data = b[idx:end]
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -285,8 +300,40 @@ func (c *Container) UnmarshalBinary(b []byte) error {
|
||||||
// ValidateCode validates each code section of the container against the EOF v1
|
// ValidateCode validates each code section of the container against the EOF v1
|
||||||
// rule set.
|
// rule set.
|
||||||
func (c *Container) ValidateCode(jt *JumpTable) error {
|
func (c *Container) ValidateCode(jt *JumpTable) error {
|
||||||
for i, code := range c.Code {
|
visited := make(map[int]struct{})
|
||||||
if err := validateCode(code, i, c.Types, jt); err != nil {
|
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 {
|
||||||
|
v, err := validateCode(code, index, c, jt)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
visited[index] = struct{}{}
|
||||||
|
// Mark all sections that can be visited from here.
|
||||||
|
for idx := range v {
|
||||||
|
if _, ok := visited[idx]; !ok {
|
||||||
|
toVisit = append(toVisit, idx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
toVisit = toVisit[1:]
|
||||||
|
}
|
||||||
|
// Make sure every code section is visited at least once.
|
||||||
|
if len(visited) != len(c.Code) {
|
||||||
|
return ErrUnreachableCode
|
||||||
|
}
|
||||||
|
for _, container := range c.ContainerSections {
|
||||||
|
if err := container.ValidateCode(jt); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -361,3 +408,42 @@ func sum(list []int) (s int) {
|
||||||
}
|
}
|
||||||
return
|
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.ContainerSections) != 0 {
|
||||||
|
result += fmt.Sprintf("KindContainer: %02x\n", kindContainer)
|
||||||
|
result += fmt.Sprintf("ContainerSize: %04x\n", len(c.ContainerSections))
|
||||||
|
for i, section := range c.ContainerSections {
|
||||||
|
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.Input, typ.Output, 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.ContainerSections {
|
||||||
|
result += fmt.Sprintf("Section %v: %v\n", i, hex.EncodeToString(section.MarshalBinary()))
|
||||||
|
}
|
||||||
|
result += fmt.Sprintf("Data: %v\n", hex.EncodeToString(c.Data))
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@
|
||||||
package vm
|
package vm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
|
@ -30,23 +32,24 @@ func TestEOFMarshaling(t *testing.T) {
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
want: Container{
|
want: Container{
|
||||||
Types: []*FunctionMetadata{{Input: 0, Output: 0, MaxStackHeight: 1}},
|
Types: []*FunctionMetadata{{Input: 0, Output: 0x80, MaxStackHeight: 1}},
|
||||||
Code: [][]byte{common.Hex2Bytes("604200")},
|
Code: [][]byte{common.Hex2Bytes("604200")},
|
||||||
Data: []byte{0x01, 0x02, 0x03},
|
Data: []byte{0x01, 0x02, 0x03},
|
||||||
|
DataSize: 3,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
want: Container{
|
want: Container{
|
||||||
Types: []*FunctionMetadata{{Input: 0, Output: 0, MaxStackHeight: 1}},
|
Types: []*FunctionMetadata{{Input: 0, Output: 0x80, MaxStackHeight: 1}},
|
||||||
Code: [][]byte{common.Hex2Bytes("604200")},
|
Code: [][]byte{common.Hex2Bytes("604200")},
|
||||||
ContainerSections: [][]byte{common.Hex2Bytes("604200")},
|
|
||||||
Data: []byte{0x01, 0x02, 0x03},
|
Data: []byte{0x01, 0x02, 0x03},
|
||||||
|
DataSize: 3,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
want: Container{
|
want: Container{
|
||||||
Types: []*FunctionMetadata{
|
Types: []*FunctionMetadata{
|
||||||
{Input: 0, Output: 0, MaxStackHeight: 1},
|
{Input: 0, Output: 0x80, MaxStackHeight: 1},
|
||||||
{Input: 2, Output: 3, MaxStackHeight: 4},
|
{Input: 2, Output: 3, MaxStackHeight: 4},
|
||||||
{Input: 1, Output: 1, MaxStackHeight: 1},
|
{Input: 1, Output: 1, MaxStackHeight: 1},
|
||||||
},
|
},
|
||||||
|
|
@ -72,3 +75,45 @@ func TestEOFMarshaling(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEOFSubcontainer(t *testing.T) {
|
||||||
|
var subcontainer = new(Container)
|
||||||
|
if err := subcontainer.UnmarshalBinary(common.Hex2Bytes("ef000101000402000100010400000000800000fe")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
container := Container{
|
||||||
|
Types: []*FunctionMetadata{{Input: 0, Output: 0x80, MaxStackHeight: 1}},
|
||||||
|
Code: [][]byte{common.Hex2Bytes("604200")},
|
||||||
|
ContainerSections: []*Container{subcontainer},
|
||||||
|
Data: []byte{0x01, 0x02, 0x03},
|
||||||
|
DataSize: 3,
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
b = container.MarshalBinary()
|
||||||
|
got Container
|
||||||
|
)
|
||||||
|
if err := got.UnmarshalBinary(b); 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); err != nil {
|
||||||
|
t.Fatalf("test %d: got error %v", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ var (
|
||||||
ErrInvalidEOF = errors.New("invalid eof")
|
ErrInvalidEOF = errors.New("invalid eof")
|
||||||
ErrInvalidEOFInitcode = errors.New("invalid eof initcode")
|
ErrInvalidEOFInitcode = errors.New("invalid eof initcode")
|
||||||
ErrNonceUintOverflow = errors.New("nonce uint64 overflow")
|
ErrNonceUintOverflow = errors.New("nonce uint64 overflow")
|
||||||
|
ErrInvalidNumberOfOutputs = errors.New("invalid number of outputs")
|
||||||
|
|
||||||
// errStopToken is an internal token indicating interpreter loop termination,
|
// errStopToken is an internal token indicating interpreter loop termination,
|
||||||
// never returned to outside callers.
|
// never returned to outside callers.
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,7 @@ type TxContext struct {
|
||||||
BlobHashes []common.Hash // Provides information for BLOBHASH
|
BlobHashes []common.Hash // Provides information for BLOBHASH
|
||||||
BlobFeeCap *big.Int // Is used to zero the blobbasefee if NoBaseFee is set
|
BlobFeeCap *big.Int // Is used to zero the blobbasefee if NoBaseFee is set
|
||||||
AccessEvents *state.AccessEvents // Capture all state accesses for this tx
|
AccessEvents *state.AccessEvents // Capture all state accesses for this tx
|
||||||
|
InitCodes [][]byte // Provides information for TXCREATE
|
||||||
}
|
}
|
||||||
|
|
||||||
// EVM is the Ethereum Virtual Machine base object and provides
|
// EVM is the Ethereum Virtual Machine base object and provides
|
||||||
|
|
@ -245,7 +246,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
|
||||||
// The depth-check is already done, and precompiles handled above
|
// The depth-check is already done, and precompiles handled above
|
||||||
contract := NewContract(caller, AccountRef(addrCopy), value, gas)
|
contract := NewContract(caller, AccountRef(addrCopy), value, gas)
|
||||||
contract.SetCallCode(&addrCopy, evm.StateDB.ResolveCodeHash(addrCopy), code, evm.parseContainer(code))
|
contract.SetCallCode(&addrCopy, evm.StateDB.ResolveCodeHash(addrCopy), code, evm.parseContainer(code))
|
||||||
ret, err = evm.interpreter.Run(contract, input, false)
|
ret, err = evm.interpreter.Run(contract, input, false, false)
|
||||||
gas = contract.Gas
|
gas = contract.Gas
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -310,7 +311,7 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
|
||||||
witness.AddCode(evm.StateDB.ResolveCode(addrCopy))
|
witness.AddCode(evm.StateDB.ResolveCode(addrCopy))
|
||||||
}
|
}
|
||||||
contract.SetCallCode(&addrCopy, evm.StateDB.ResolveCodeHash(addrCopy), code, evm.parseContainer(code))
|
contract.SetCallCode(&addrCopy, evm.StateDB.ResolveCodeHash(addrCopy), code, evm.parseContainer(code))
|
||||||
ret, err = evm.interpreter.Run(contract, input, false)
|
ret, err = evm.interpreter.Run(contract, input, false, false)
|
||||||
gas = contract.Gas
|
gas = contract.Gas
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -331,7 +332,7 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
|
||||||
//
|
//
|
||||||
// DelegateCall differs from CallCode in the sense that it executes the given address'
|
// DelegateCall differs from CallCode in the sense that it executes the given address'
|
||||||
// code with the caller as context and the caller is set to the caller of the caller.
|
// code with the caller as context and the caller is set to the caller of the caller.
|
||||||
func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []byte, gas uint64) (ret []byte, leftOverGas uint64, err error) {
|
func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []byte, gas uint64, fromEOF bool) (ret []byte, leftOverGas uint64, err error) {
|
||||||
// Invoke tracer hooks that signal entering/exiting a call frame
|
// Invoke tracer hooks that signal entering/exiting a call frame
|
||||||
if evm.Config.Tracer != nil {
|
if evm.Config.Tracer != nil {
|
||||||
// NOTE: caller must, at all times be a contract. It should never happen
|
// NOTE: caller must, at all times be a contract. It should never happen
|
||||||
|
|
@ -355,6 +356,9 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
|
||||||
} else {
|
} else {
|
||||||
addrCopy := addr
|
addrCopy := addr
|
||||||
code := evm.StateDB.GetCode(addrCopy)
|
code := evm.StateDB.GetCode(addrCopy)
|
||||||
|
if fromEOF && !hasEOFMagic(code) {
|
||||||
|
return nil, gas, errors.New("extDelegateCall to non-eof contract")
|
||||||
|
}
|
||||||
// Initialise a new contract and make initialise the delegate values
|
// Initialise a new contract and make initialise the delegate values
|
||||||
contract := NewContract(caller, AccountRef(caller.Address()), nil, gas).AsDelegate()
|
contract := NewContract(caller, AccountRef(caller.Address()), nil, gas).AsDelegate()
|
||||||
if witness := evm.StateDB.Witness(); witness != nil {
|
if witness := evm.StateDB.Witness(); witness != nil {
|
||||||
|
|
@ -362,7 +366,7 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
|
||||||
witness.AddCode(evm.StateDB.ResolveCode(addrCopy))
|
witness.AddCode(evm.StateDB.ResolveCode(addrCopy))
|
||||||
}
|
}
|
||||||
contract.SetCallCode(&addrCopy, evm.StateDB.ResolveCodeHash(addrCopy), code, evm.parseContainer(code))
|
contract.SetCallCode(&addrCopy, evm.StateDB.ResolveCodeHash(addrCopy), code, evm.parseContainer(code))
|
||||||
ret, err = evm.interpreter.Run(contract, input, false)
|
ret, err = evm.interpreter.Run(contract, input, false, false)
|
||||||
gas = contract.Gas
|
gas = contract.Gas
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -425,7 +429,7 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
|
||||||
// When an error was returned by the EVM or when setting the creation code
|
// When an error was returned by the EVM or when setting the creation code
|
||||||
// above we revert to the snapshot and consume any gas remaining. Additionally
|
// above we revert to the snapshot and consume any gas remaining. Additionally
|
||||||
// when we're in Homestead this also counts for code storage gas errors.
|
// when we're in Homestead this also counts for code storage gas errors.
|
||||||
ret, err = evm.interpreter.Run(contract, input, true)
|
ret, err = evm.interpreter.Run(contract, input, true, false)
|
||||||
gas = contract.Gas
|
gas = contract.Gas
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -479,7 +483,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
|
||||||
|
|
||||||
// Validate initcode per EOF rules. If caller is EOF and initcode is legacy, fail.
|
// Validate initcode per EOF rules. If caller is EOF and initcode is legacy, fail.
|
||||||
isInitcodeEOF := hasEOFMagic(codeAndHash.code)
|
isInitcodeEOF := hasEOFMagic(codeAndHash.code)
|
||||||
if evm.chainRules.IsShanghai {
|
if evm.chainRules.IsPrague {
|
||||||
if isInitcodeEOF {
|
if isInitcodeEOF {
|
||||||
// If the initcode is EOF, verify it is well-formed.
|
// If the initcode is EOF, verify it is well-formed.
|
||||||
var c Container
|
var c Container
|
||||||
|
|
@ -548,7 +552,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
ret, err = evm.interpreter.Run(contract, nil, false)
|
ret, err = evm.interpreter.Run(contract, nil, false, contract.IsDeployment)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check whether the max code size has been exceeded, assign err if the case.
|
// Check whether the max code size has been exceeded, assign err if the case.
|
||||||
|
|
@ -632,6 +636,13 @@ func (evm *EVM) Create2(caller ContractRef, code []byte, gas uint64, endowment *
|
||||||
return evm.create(caller, codeAndHash, gas, endowment, contractAddr, CREATE2, isEOF)
|
return evm.create(caller, codeAndHash, gas, endowment, contractAddr, CREATE2, isEOF)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EOFCreate
|
||||||
|
func (evm *EVM) EOFCreate(caller ContractRef, code []byte, subcontainer []byte, gas uint64, endowment *uint256.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) {
|
||||||
|
codeAndHash := &codeAndHash{code: subcontainer}
|
||||||
|
contractAddr = crypto.CreateAddress2(caller.Address(), salt.Bytes32(), codeAndHash.Hash().Bytes())
|
||||||
|
return evm.create(caller, codeAndHash, gas, endowment, contractAddr, CREATE2, true)
|
||||||
|
}
|
||||||
|
|
||||||
// ChainConfig returns the environment's chain configuration
|
// ChainConfig returns the environment's chain configuration
|
||||||
func (evm *EVM) ChainConfig() *params.ChainConfig { return evm.chainConfig }
|
func (evm *EVM) ChainConfig() *params.ChainConfig { return evm.chainConfig }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import (
|
||||||
|
|
||||||
// Gas costs
|
// Gas costs
|
||||||
const (
|
const (
|
||||||
|
GasZeroStep uint64 = 0
|
||||||
GasQuickStep uint64 = 2
|
GasQuickStep uint64 = 2
|
||||||
GasFastestStep uint64 = 3
|
GasFastestStep uint64 = 3
|
||||||
GasFastishStep uint64 = 4
|
GasFastishStep uint64 = 4
|
||||||
|
|
|
||||||
|
|
@ -502,3 +502,23 @@ func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me
|
||||||
}
|
}
|
||||||
return gas, nil
|
return gas, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func gasEOFCreate(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||||
|
gas, err := memoryGasCost(mem, memorySize)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
size, overflow := stack.Back(2).Uint64WithOverflow()
|
||||||
|
if overflow {
|
||||||
|
return 0, ErrGasUintOverflow
|
||||||
|
}
|
||||||
|
if size > params.MaxInitCodeSize {
|
||||||
|
return 0, fmt.Errorf("%w: size %d", ErrMaxInitCodeSizeExceeded, size)
|
||||||
|
}
|
||||||
|
// Since size <= params.MaxInitCodeSize, these multiplication cannot overflow
|
||||||
|
moreGas := (params.Keccak256WordGas) * ((size + 31) / 32)
|
||||||
|
if gas, overflow = math.SafeAdd(gas, moreGas); overflow {
|
||||||
|
return 0, ErrGasUintOverflow
|
||||||
|
}
|
||||||
|
return gas, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -828,7 +828,7 @@ func opDelegateCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext
|
||||||
// Get arguments from the memory.
|
// Get arguments from the memory.
|
||||||
args := scope.Memory.GetPtr(inOffset.Uint64(), inSize.Uint64())
|
args := scope.Memory.GetPtr(inOffset.Uint64(), inSize.Uint64())
|
||||||
|
|
||||||
ret, returnGas, err := interpreter.evm.DelegateCall(scope.Contract, toAddr, args, gas)
|
ret, returnGas, err := interpreter.evm.DelegateCall(scope.Contract, toAddr, args, gas, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
temp.Clear()
|
temp.Clear()
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -117,7 +117,7 @@ func testTwoOperandOp(t *testing.T, tests []TwoOperandTestcase, opFn executionFu
|
||||||
expected := new(uint256.Int).SetBytes(common.Hex2Bytes(test.Expected))
|
expected := new(uint256.Int).SetBytes(common.Hex2Bytes(test.Expected))
|
||||||
stack.push(x)
|
stack.push(x)
|
||||||
stack.push(y)
|
stack.push(y)
|
||||||
opFn(&pc, evmInterpreter, &ScopeContext{nil, stack, nil, 0, nil})
|
opFn(&pc, evmInterpreter, &ScopeContext{nil, stack, nil, 0, nil, false})
|
||||||
if len(stack.data) != 1 {
|
if len(stack.data) != 1 {
|
||||||
t.Errorf("Expected one item on stack after %v, got %d: ", name, len(stack.data))
|
t.Errorf("Expected one item on stack after %v, got %d: ", name, len(stack.data))
|
||||||
}
|
}
|
||||||
|
|
@ -232,7 +232,7 @@ func TestAddMod(t *testing.T) {
|
||||||
stack.push(z)
|
stack.push(z)
|
||||||
stack.push(y)
|
stack.push(y)
|
||||||
stack.push(x)
|
stack.push(x)
|
||||||
opAddmod(&pc, evmInterpreter, &ScopeContext{nil, stack, nil, 0, nil})
|
opAddmod(&pc, evmInterpreter, &ScopeContext{nil, stack, nil, 0, nil, false})
|
||||||
actual := stack.pop()
|
actual := stack.pop()
|
||||||
if actual.Cmp(expected) != 0 {
|
if actual.Cmp(expected) != 0 {
|
||||||
t.Errorf("Testcase %d, expected %x, got %x", i, expected, actual)
|
t.Errorf("Testcase %d, expected %x, got %x", i, expected, actual)
|
||||||
|
|
@ -259,7 +259,7 @@ func TestWriteExpectedValues(t *testing.T) {
|
||||||
y := new(uint256.Int).SetBytes(common.Hex2Bytes(param.y))
|
y := new(uint256.Int).SetBytes(common.Hex2Bytes(param.y))
|
||||||
stack.push(x)
|
stack.push(x)
|
||||||
stack.push(y)
|
stack.push(y)
|
||||||
opFn(&pc, interpreter, &ScopeContext{nil, stack, nil, 0, nil})
|
opFn(&pc, interpreter, &ScopeContext{nil, stack, nil, 0, nil, false})
|
||||||
actual := stack.pop()
|
actual := stack.pop()
|
||||||
result[i] = TwoOperandTestcase{param.x, param.y, fmt.Sprintf("%064x", actual)}
|
result[i] = TwoOperandTestcase{param.x, param.y, fmt.Sprintf("%064x", actual)}
|
||||||
}
|
}
|
||||||
|
|
@ -295,7 +295,7 @@ func opBenchmark(bench *testing.B, op executionFunc, args ...string) {
|
||||||
var (
|
var (
|
||||||
env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{})
|
env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{})
|
||||||
stack = newstack()
|
stack = newstack()
|
||||||
scope = &ScopeContext{nil, stack, nil, 0, nil}
|
scope = &ScopeContext{nil, stack, nil, 0, nil, false}
|
||||||
evmInterpreter = NewEVMInterpreter(env)
|
evmInterpreter = NewEVMInterpreter(env)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -546,13 +546,13 @@ func TestOpMstore(t *testing.T) {
|
||||||
v := "abcdef00000000000000abba000000000deaf000000c0de00100000000133700"
|
v := "abcdef00000000000000abba000000000deaf000000c0de00100000000133700"
|
||||||
stack.push(new(uint256.Int).SetBytes(common.Hex2Bytes(v)))
|
stack.push(new(uint256.Int).SetBytes(common.Hex2Bytes(v)))
|
||||||
stack.push(new(uint256.Int))
|
stack.push(new(uint256.Int))
|
||||||
opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil, 0, nil})
|
opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil, 0, nil, false})
|
||||||
if got := common.Bytes2Hex(mem.GetCopy(0, 32)); got != v {
|
if got := common.Bytes2Hex(mem.GetCopy(0, 32)); got != v {
|
||||||
t.Fatalf("Mstore fail, got %v, expected %v", got, v)
|
t.Fatalf("Mstore fail, got %v, expected %v", got, v)
|
||||||
}
|
}
|
||||||
stack.push(new(uint256.Int).SetUint64(0x1))
|
stack.push(new(uint256.Int).SetUint64(0x1))
|
||||||
stack.push(new(uint256.Int))
|
stack.push(new(uint256.Int))
|
||||||
opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil, 0, nil})
|
opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil, 0, nil, false})
|
||||||
if common.Bytes2Hex(mem.GetCopy(0, 32)) != "0000000000000000000000000000000000000000000000000000000000000001" {
|
if common.Bytes2Hex(mem.GetCopy(0, 32)) != "0000000000000000000000000000000000000000000000000000000000000001" {
|
||||||
t.Fatalf("Mstore failed to overwrite previous value")
|
t.Fatalf("Mstore failed to overwrite previous value")
|
||||||
}
|
}
|
||||||
|
|
@ -576,7 +576,7 @@ func BenchmarkOpMstore(bench *testing.B) {
|
||||||
for i := 0; i < bench.N; i++ {
|
for i := 0; i < bench.N; i++ {
|
||||||
stack.push(value)
|
stack.push(value)
|
||||||
stack.push(memStart)
|
stack.push(memStart)
|
||||||
opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil, 0, nil})
|
opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil, 0, nil, false})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -591,7 +591,7 @@ func TestOpTstore(t *testing.T) {
|
||||||
to = common.Address{1}
|
to = common.Address{1}
|
||||||
contractRef = contractRef{caller}
|
contractRef = contractRef{caller}
|
||||||
contract = NewContract(contractRef, AccountRef(to), new(uint256.Int), 0)
|
contract = NewContract(contractRef, AccountRef(to), new(uint256.Int), 0)
|
||||||
scopeContext = ScopeContext{mem, stack, contract, 0, nil}
|
scopeContext = ScopeContext{mem, stack, contract, 0, nil, false}
|
||||||
value = common.Hex2Bytes("abcdef00000000000000abba000000000deaf000000c0de00100000000133700")
|
value = common.Hex2Bytes("abcdef00000000000000abba000000000deaf000000c0de00100000000133700")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -639,7 +639,7 @@ func BenchmarkOpKeccak256(bench *testing.B) {
|
||||||
for i := 0; i < bench.N; i++ {
|
for i := 0; i < bench.N; i++ {
|
||||||
stack.push(uint256.NewInt(32))
|
stack.push(uint256.NewInt(32))
|
||||||
stack.push(start)
|
stack.push(start)
|
||||||
opKeccak256(&pc, evmInterpreter, &ScopeContext{mem, stack, nil, 0, nil})
|
opKeccak256(&pc, evmInterpreter, &ScopeContext{mem, stack, nil, 0, nil, false})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -734,7 +734,7 @@ func TestRandom(t *testing.T) {
|
||||||
pc = uint64(0)
|
pc = uint64(0)
|
||||||
evmInterpreter = env.interpreter
|
evmInterpreter = env.interpreter
|
||||||
)
|
)
|
||||||
opRandom(&pc, evmInterpreter, &ScopeContext{nil, stack, nil, 0, nil})
|
opRandom(&pc, evmInterpreter, &ScopeContext{nil, stack, nil, 0, nil, false})
|
||||||
if len(stack.data) != 1 {
|
if len(stack.data) != 1 {
|
||||||
t.Errorf("Expected one item on stack after %v, got %d: ", tt.name, len(stack.data))
|
t.Errorf("Expected one item on stack after %v, got %d: ", tt.name, len(stack.data))
|
||||||
}
|
}
|
||||||
|
|
@ -776,7 +776,7 @@ func TestBlobHash(t *testing.T) {
|
||||||
evmInterpreter = env.interpreter
|
evmInterpreter = env.interpreter
|
||||||
)
|
)
|
||||||
stack.push(uint256.NewInt(tt.idx))
|
stack.push(uint256.NewInt(tt.idx))
|
||||||
opBlobHash(&pc, evmInterpreter, &ScopeContext{nil, stack, nil, 0, nil})
|
opBlobHash(&pc, evmInterpreter, &ScopeContext{nil, stack, nil, 0, nil, false})
|
||||||
if len(stack.data) != 1 {
|
if len(stack.data) != 1 {
|
||||||
t.Errorf("Expected one item on stack after %v, got %d: ", tt.name, len(stack.data))
|
t.Errorf("Expected one item on stack after %v, got %d: ", tt.name, len(stack.data))
|
||||||
}
|
}
|
||||||
|
|
@ -917,7 +917,7 @@ func TestOpMCopy(t *testing.T) {
|
||||||
mem.Resize(memorySize)
|
mem.Resize(memorySize)
|
||||||
}
|
}
|
||||||
// Do the copy
|
// Do the copy
|
||||||
opMcopy(&pc, evmInterpreter, &ScopeContext{mem, stack, nil, 0, nil})
|
opMcopy(&pc, evmInterpreter, &ScopeContext{mem, stack, nil, 0, nil, false})
|
||||||
want := common.FromHex(strings.ReplaceAll(tc.want, " ", ""))
|
want := common.FromHex(strings.ReplaceAll(tc.want, " ", ""))
|
||||||
if have := mem.store; !bytes.Equal(want, have) {
|
if have := mem.store; !bytes.Equal(want, have) {
|
||||||
t.Errorf("case %d: \nwant: %#x\nhave: %#x\n", i, want, have)
|
t.Errorf("case %d: \nwant: %#x\nhave: %#x\n", i, want, have)
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ type StateDB interface {
|
||||||
GetCodeHash(common.Address) common.Hash
|
GetCodeHash(common.Address) common.Hash
|
||||||
GetCode(common.Address) []byte
|
GetCode(common.Address) []byte
|
||||||
SetCode(common.Address, []byte)
|
SetCode(common.Address, []byte)
|
||||||
|
SetCodeEOF(common.Address, []byte)
|
||||||
GetCodeSize(common.Address) int
|
GetCodeSize(common.Address) int
|
||||||
|
|
||||||
ResolveCodeHash(common.Address) common.Hash
|
ResolveCodeHash(common.Address) common.Hash
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ type ScopeContext struct {
|
||||||
|
|
||||||
CodeSection uint64
|
CodeSection uint64
|
||||||
ReturnStack []*ReturnContext
|
ReturnStack []*ReturnContext
|
||||||
|
InitCodeMode bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type ReturnContext struct {
|
type ReturnContext struct {
|
||||||
|
|
@ -161,7 +162,7 @@ func NewEVMInterpreter(evm *EVM) *EVMInterpreter {
|
||||||
// It's important to note that any errors returned by the interpreter should be
|
// It's important to note that any errors returned by the interpreter should be
|
||||||
// considered a revert-and-consume-all-gas operation except for
|
// considered a revert-and-consume-all-gas operation except for
|
||||||
// ErrExecutionReverted which means revert-and-keep-gas-left.
|
// ErrExecutionReverted which means revert-and-keep-gas-left.
|
||||||
func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (ret []byte, err error) {
|
func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool, isInitCode bool) (ret []byte, err error) {
|
||||||
// Increment the call depth which is restricted to 1024
|
// Increment the call depth which is restricted to 1024
|
||||||
in.evm.depth++
|
in.evm.depth++
|
||||||
defer func() { in.evm.depth-- }()
|
defer func() { in.evm.depth-- }()
|
||||||
|
|
@ -193,6 +194,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
|
||||||
Contract: contract,
|
Contract: contract,
|
||||||
CodeSection: 0,
|
CodeSection: 0,
|
||||||
ReturnStack: []*ReturnContext{{Section: 0, Pc: 0, StackHeight: 0}},
|
ReturnStack: []*ReturnContext{{Section: 0, Pc: 0, StackHeight: 0}},
|
||||||
|
InitCodeMode: isInitCode,
|
||||||
}
|
}
|
||||||
// For optimisation reason we're using uint64 as the program counter.
|
// For optimisation reason we're using uint64 as the program counter.
|
||||||
// It's theoretically possible to go above 2^64. The YP defines the PC
|
// It's theoretically possible to go above 2^64. The YP defines the PC
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,8 @@ type operation struct {
|
||||||
|
|
||||||
// terminal denotes if the instruction can be the final opcode in a code section
|
// terminal denotes if the instruction can be the final opcode in a code section
|
||||||
terminal bool
|
terminal bool
|
||||||
|
// immediate denotes how many immediate bytes the operation uses
|
||||||
|
immediate int
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -628,192 +630,224 @@ func newFrontierInstructionSet() JumpTable {
|
||||||
constantGas: GasFastestStep,
|
constantGas: GasFastestStep,
|
||||||
minStack: minStack(0, 1),
|
minStack: minStack(0, 1),
|
||||||
maxStack: maxStack(0, 1),
|
maxStack: maxStack(0, 1),
|
||||||
|
immediate: 1,
|
||||||
},
|
},
|
||||||
PUSH2: {
|
PUSH2: {
|
||||||
execute: makePush(2, 2),
|
execute: makePush(2, 2),
|
||||||
constantGas: GasFastestStep,
|
constantGas: GasFastestStep,
|
||||||
minStack: minStack(0, 1),
|
minStack: minStack(0, 1),
|
||||||
maxStack: maxStack(0, 1),
|
maxStack: maxStack(0, 1),
|
||||||
|
immediate: 2,
|
||||||
},
|
},
|
||||||
PUSH3: {
|
PUSH3: {
|
||||||
execute: makePush(3, 3),
|
execute: makePush(3, 3),
|
||||||
constantGas: GasFastestStep,
|
constantGas: GasFastestStep,
|
||||||
minStack: minStack(0, 1),
|
minStack: minStack(0, 1),
|
||||||
maxStack: maxStack(0, 1),
|
maxStack: maxStack(0, 1),
|
||||||
|
immediate: 3,
|
||||||
},
|
},
|
||||||
PUSH4: {
|
PUSH4: {
|
||||||
execute: makePush(4, 4),
|
execute: makePush(4, 4),
|
||||||
constantGas: GasFastestStep,
|
constantGas: GasFastestStep,
|
||||||
minStack: minStack(0, 1),
|
minStack: minStack(0, 1),
|
||||||
maxStack: maxStack(0, 1),
|
maxStack: maxStack(0, 1),
|
||||||
|
immediate: 4,
|
||||||
},
|
},
|
||||||
PUSH5: {
|
PUSH5: {
|
||||||
execute: makePush(5, 5),
|
execute: makePush(5, 5),
|
||||||
constantGas: GasFastestStep,
|
constantGas: GasFastestStep,
|
||||||
minStack: minStack(0, 1),
|
minStack: minStack(0, 1),
|
||||||
maxStack: maxStack(0, 1),
|
maxStack: maxStack(0, 1),
|
||||||
|
immediate: 5,
|
||||||
},
|
},
|
||||||
PUSH6: {
|
PUSH6: {
|
||||||
execute: makePush(6, 6),
|
execute: makePush(6, 6),
|
||||||
constantGas: GasFastestStep,
|
constantGas: GasFastestStep,
|
||||||
minStack: minStack(0, 1),
|
minStack: minStack(0, 1),
|
||||||
maxStack: maxStack(0, 1),
|
maxStack: maxStack(0, 1),
|
||||||
|
immediate: 6,
|
||||||
},
|
},
|
||||||
PUSH7: {
|
PUSH7: {
|
||||||
execute: makePush(7, 7),
|
execute: makePush(7, 7),
|
||||||
constantGas: GasFastestStep,
|
constantGas: GasFastestStep,
|
||||||
minStack: minStack(0, 1),
|
minStack: minStack(0, 1),
|
||||||
maxStack: maxStack(0, 1),
|
maxStack: maxStack(0, 1),
|
||||||
|
immediate: 7,
|
||||||
},
|
},
|
||||||
PUSH8: {
|
PUSH8: {
|
||||||
execute: makePush(8, 8),
|
execute: makePush(8, 8),
|
||||||
constantGas: GasFastestStep,
|
constantGas: GasFastestStep,
|
||||||
minStack: minStack(0, 1),
|
minStack: minStack(0, 1),
|
||||||
maxStack: maxStack(0, 1),
|
maxStack: maxStack(0, 1),
|
||||||
|
immediate: 8,
|
||||||
},
|
},
|
||||||
PUSH9: {
|
PUSH9: {
|
||||||
execute: makePush(9, 9),
|
execute: makePush(9, 9),
|
||||||
constantGas: GasFastestStep,
|
constantGas: GasFastestStep,
|
||||||
minStack: minStack(0, 1),
|
minStack: minStack(0, 1),
|
||||||
maxStack: maxStack(0, 1),
|
maxStack: maxStack(0, 1),
|
||||||
|
immediate: 9,
|
||||||
},
|
},
|
||||||
PUSH10: {
|
PUSH10: {
|
||||||
execute: makePush(10, 10),
|
execute: makePush(10, 10),
|
||||||
constantGas: GasFastestStep,
|
constantGas: GasFastestStep,
|
||||||
minStack: minStack(0, 1),
|
minStack: minStack(0, 1),
|
||||||
maxStack: maxStack(0, 1),
|
maxStack: maxStack(0, 1),
|
||||||
|
immediate: 10,
|
||||||
},
|
},
|
||||||
PUSH11: {
|
PUSH11: {
|
||||||
execute: makePush(11, 11),
|
execute: makePush(11, 11),
|
||||||
constantGas: GasFastestStep,
|
constantGas: GasFastestStep,
|
||||||
minStack: minStack(0, 1),
|
minStack: minStack(0, 1),
|
||||||
maxStack: maxStack(0, 1),
|
maxStack: maxStack(0, 1),
|
||||||
|
immediate: 11,
|
||||||
},
|
},
|
||||||
PUSH12: {
|
PUSH12: {
|
||||||
execute: makePush(12, 12),
|
execute: makePush(12, 12),
|
||||||
constantGas: GasFastestStep,
|
constantGas: GasFastestStep,
|
||||||
minStack: minStack(0, 1),
|
minStack: minStack(0, 1),
|
||||||
maxStack: maxStack(0, 1),
|
maxStack: maxStack(0, 1),
|
||||||
|
immediate: 12,
|
||||||
},
|
},
|
||||||
PUSH13: {
|
PUSH13: {
|
||||||
execute: makePush(13, 13),
|
execute: makePush(13, 13),
|
||||||
constantGas: GasFastestStep,
|
constantGas: GasFastestStep,
|
||||||
minStack: minStack(0, 1),
|
minStack: minStack(0, 1),
|
||||||
maxStack: maxStack(0, 1),
|
maxStack: maxStack(0, 1),
|
||||||
|
immediate: 13,
|
||||||
},
|
},
|
||||||
PUSH14: {
|
PUSH14: {
|
||||||
execute: makePush(14, 14),
|
execute: makePush(14, 14),
|
||||||
constantGas: GasFastestStep,
|
constantGas: GasFastestStep,
|
||||||
minStack: minStack(0, 1),
|
minStack: minStack(0, 1),
|
||||||
maxStack: maxStack(0, 1),
|
maxStack: maxStack(0, 1),
|
||||||
|
immediate: 14,
|
||||||
},
|
},
|
||||||
PUSH15: {
|
PUSH15: {
|
||||||
execute: makePush(15, 15),
|
execute: makePush(15, 15),
|
||||||
constantGas: GasFastestStep,
|
constantGas: GasFastestStep,
|
||||||
minStack: minStack(0, 1),
|
minStack: minStack(0, 1),
|
||||||
maxStack: maxStack(0, 1),
|
maxStack: maxStack(0, 1),
|
||||||
|
immediate: 15,
|
||||||
},
|
},
|
||||||
PUSH16: {
|
PUSH16: {
|
||||||
execute: makePush(16, 16),
|
execute: makePush(16, 16),
|
||||||
constantGas: GasFastestStep,
|
constantGas: GasFastestStep,
|
||||||
minStack: minStack(0, 1),
|
minStack: minStack(0, 1),
|
||||||
maxStack: maxStack(0, 1),
|
maxStack: maxStack(0, 1),
|
||||||
|
immediate: 16,
|
||||||
},
|
},
|
||||||
PUSH17: {
|
PUSH17: {
|
||||||
execute: makePush(17, 17),
|
execute: makePush(17, 17),
|
||||||
constantGas: GasFastestStep,
|
constantGas: GasFastestStep,
|
||||||
minStack: minStack(0, 1),
|
minStack: minStack(0, 1),
|
||||||
maxStack: maxStack(0, 1),
|
maxStack: maxStack(0, 1),
|
||||||
|
immediate: 17,
|
||||||
},
|
},
|
||||||
PUSH18: {
|
PUSH18: {
|
||||||
execute: makePush(18, 18),
|
execute: makePush(18, 18),
|
||||||
constantGas: GasFastestStep,
|
constantGas: GasFastestStep,
|
||||||
minStack: minStack(0, 1),
|
minStack: minStack(0, 1),
|
||||||
maxStack: maxStack(0, 1),
|
maxStack: maxStack(0, 1),
|
||||||
|
immediate: 18,
|
||||||
},
|
},
|
||||||
PUSH19: {
|
PUSH19: {
|
||||||
execute: makePush(19, 19),
|
execute: makePush(19, 19),
|
||||||
constantGas: GasFastestStep,
|
constantGas: GasFastestStep,
|
||||||
minStack: minStack(0, 1),
|
minStack: minStack(0, 1),
|
||||||
maxStack: maxStack(0, 1),
|
maxStack: maxStack(0, 1),
|
||||||
|
immediate: 19,
|
||||||
},
|
},
|
||||||
PUSH20: {
|
PUSH20: {
|
||||||
execute: makePush(20, 20),
|
execute: makePush(20, 20),
|
||||||
constantGas: GasFastestStep,
|
constantGas: GasFastestStep,
|
||||||
minStack: minStack(0, 1),
|
minStack: minStack(0, 1),
|
||||||
maxStack: maxStack(0, 1),
|
maxStack: maxStack(0, 1),
|
||||||
|
immediate: 20,
|
||||||
},
|
},
|
||||||
PUSH21: {
|
PUSH21: {
|
||||||
execute: makePush(21, 21),
|
execute: makePush(21, 21),
|
||||||
constantGas: GasFastestStep,
|
constantGas: GasFastestStep,
|
||||||
minStack: minStack(0, 1),
|
minStack: minStack(0, 1),
|
||||||
maxStack: maxStack(0, 1),
|
maxStack: maxStack(0, 1),
|
||||||
|
immediate: 21,
|
||||||
},
|
},
|
||||||
PUSH22: {
|
PUSH22: {
|
||||||
execute: makePush(22, 22),
|
execute: makePush(22, 22),
|
||||||
constantGas: GasFastestStep,
|
constantGas: GasFastestStep,
|
||||||
minStack: minStack(0, 1),
|
minStack: minStack(0, 1),
|
||||||
maxStack: maxStack(0, 1),
|
maxStack: maxStack(0, 1),
|
||||||
|
immediate: 22,
|
||||||
},
|
},
|
||||||
PUSH23: {
|
PUSH23: {
|
||||||
execute: makePush(23, 23),
|
execute: makePush(23, 23),
|
||||||
constantGas: GasFastestStep,
|
constantGas: GasFastestStep,
|
||||||
minStack: minStack(0, 1),
|
minStack: minStack(0, 1),
|
||||||
maxStack: maxStack(0, 1),
|
maxStack: maxStack(0, 1),
|
||||||
|
immediate: 23,
|
||||||
},
|
},
|
||||||
PUSH24: {
|
PUSH24: {
|
||||||
execute: makePush(24, 24),
|
execute: makePush(24, 24),
|
||||||
constantGas: GasFastestStep,
|
constantGas: GasFastestStep,
|
||||||
minStack: minStack(0, 1),
|
minStack: minStack(0, 1),
|
||||||
maxStack: maxStack(0, 1),
|
maxStack: maxStack(0, 1),
|
||||||
|
immediate: 24,
|
||||||
},
|
},
|
||||||
PUSH25: {
|
PUSH25: {
|
||||||
execute: makePush(25, 25),
|
execute: makePush(25, 25),
|
||||||
constantGas: GasFastestStep,
|
constantGas: GasFastestStep,
|
||||||
minStack: minStack(0, 1),
|
minStack: minStack(0, 1),
|
||||||
maxStack: maxStack(0, 1),
|
maxStack: maxStack(0, 1),
|
||||||
|
immediate: 25,
|
||||||
},
|
},
|
||||||
PUSH26: {
|
PUSH26: {
|
||||||
execute: makePush(26, 26),
|
execute: makePush(26, 26),
|
||||||
constantGas: GasFastestStep,
|
constantGas: GasFastestStep,
|
||||||
minStack: minStack(0, 1),
|
minStack: minStack(0, 1),
|
||||||
maxStack: maxStack(0, 1),
|
maxStack: maxStack(0, 1),
|
||||||
|
immediate: 26,
|
||||||
},
|
},
|
||||||
PUSH27: {
|
PUSH27: {
|
||||||
execute: makePush(27, 27),
|
execute: makePush(27, 27),
|
||||||
constantGas: GasFastestStep,
|
constantGas: GasFastestStep,
|
||||||
minStack: minStack(0, 1),
|
minStack: minStack(0, 1),
|
||||||
maxStack: maxStack(0, 1),
|
maxStack: maxStack(0, 1),
|
||||||
|
immediate: 27,
|
||||||
},
|
},
|
||||||
PUSH28: {
|
PUSH28: {
|
||||||
execute: makePush(28, 28),
|
execute: makePush(28, 28),
|
||||||
constantGas: GasFastestStep,
|
constantGas: GasFastestStep,
|
||||||
minStack: minStack(0, 1),
|
minStack: minStack(0, 1),
|
||||||
maxStack: maxStack(0, 1),
|
maxStack: maxStack(0, 1),
|
||||||
|
immediate: 28,
|
||||||
},
|
},
|
||||||
PUSH29: {
|
PUSH29: {
|
||||||
execute: makePush(29, 29),
|
execute: makePush(29, 29),
|
||||||
constantGas: GasFastestStep,
|
constantGas: GasFastestStep,
|
||||||
minStack: minStack(0, 1),
|
minStack: minStack(0, 1),
|
||||||
maxStack: maxStack(0, 1),
|
maxStack: maxStack(0, 1),
|
||||||
|
immediate: 29,
|
||||||
},
|
},
|
||||||
PUSH30: {
|
PUSH30: {
|
||||||
execute: makePush(30, 30),
|
execute: makePush(30, 30),
|
||||||
constantGas: GasFastestStep,
|
constantGas: GasFastestStep,
|
||||||
minStack: minStack(0, 1),
|
minStack: minStack(0, 1),
|
||||||
maxStack: maxStack(0, 1),
|
maxStack: maxStack(0, 1),
|
||||||
|
immediate: 30,
|
||||||
},
|
},
|
||||||
PUSH31: {
|
PUSH31: {
|
||||||
execute: makePush(31, 31),
|
execute: makePush(31, 31),
|
||||||
constantGas: GasFastestStep,
|
constantGas: GasFastestStep,
|
||||||
minStack: minStack(0, 1),
|
minStack: minStack(0, 1),
|
||||||
maxStack: maxStack(0, 1),
|
maxStack: maxStack(0, 1),
|
||||||
|
immediate: 31,
|
||||||
},
|
},
|
||||||
PUSH32: {
|
PUSH32: {
|
||||||
execute: makePush(32, 32),
|
execute: makePush(32, 32),
|
||||||
constantGas: GasFastestStep,
|
constantGas: GasFastestStep,
|
||||||
minStack: minStack(0, 1),
|
minStack: minStack(0, 1),
|
||||||
maxStack: maxStack(0, 1),
|
maxStack: maxStack(0, 1),
|
||||||
|
immediate: 32,
|
||||||
},
|
},
|
||||||
DUP1: {
|
DUP1: {
|
||||||
execute: makeDup(1),
|
execute: makeDup(1),
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,14 @@ func memoryCreate2(stack *Stack) (uint64, bool) {
|
||||||
return calcMemSize64(stack.Back(1), stack.Back(2))
|
return calcMemSize64(stack.Back(1), stack.Back(2))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func memoryEOFCreate(stack *Stack) (uint64, bool) {
|
||||||
|
return calcMemSize64(stack.Back(1), stack.Back(2))
|
||||||
|
}
|
||||||
|
|
||||||
|
func memoryReturnContract(stack *Stack) (uint64, bool) {
|
||||||
|
return calcMemSize64(stack.Back(0), stack.Back(1))
|
||||||
|
}
|
||||||
|
|
||||||
func memoryCall(stack *Stack) (uint64, bool) {
|
func memoryCall(stack *Stack) (uint64, bool) {
|
||||||
x, overflow := calcMemSize64(stack.Back(5), stack.Back(6))
|
x, overflow := calcMemSize64(stack.Back(5), stack.Back(6))
|
||||||
if overflow {
|
if overflow {
|
||||||
|
|
|
||||||
|
|
@ -121,9 +121,6 @@ const (
|
||||||
TLOAD OpCode = 0x5c
|
TLOAD OpCode = 0x5c
|
||||||
TSTORE OpCode = 0x5d
|
TSTORE OpCode = 0x5d
|
||||||
MCOPY OpCode = 0x5e
|
MCOPY OpCode = 0x5e
|
||||||
RJUMP OpCode = 0x5c
|
|
||||||
RJUMPI OpCode = 0x5d
|
|
||||||
RJUMPV OpCode = 0x5e
|
|
||||||
PUSH0 OpCode = 0x5f
|
PUSH0 OpCode = 0x5f
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -212,10 +209,28 @@ const (
|
||||||
LOG4
|
LOG4
|
||||||
)
|
)
|
||||||
|
|
||||||
// 0xb0 range - control flow ops.
|
// 0xd0 range - eof operations.
|
||||||
const (
|
const (
|
||||||
CALLF = 0xb0
|
DATALOAD OpCode = 0xd0
|
||||||
RETF = 0xb1
|
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
|
||||||
|
TXCREATE OpCode = 0xed
|
||||||
|
RETURNCONTRACT OpCode = 0xee
|
||||||
)
|
)
|
||||||
|
|
||||||
// 0xf0 range - closures.
|
// 0xf0 range - closures.
|
||||||
|
|
@ -227,7 +242,12 @@ const (
|
||||||
DELEGATECALL OpCode = 0xf4
|
DELEGATECALL OpCode = 0xf4
|
||||||
CREATE2 OpCode = 0xf5
|
CREATE2 OpCode = 0xf5
|
||||||
|
|
||||||
|
RETURNDATALOAD OpCode = 0xf7
|
||||||
|
EXTCALL OpCode = 0xf8
|
||||||
|
EXTDELEGATECALL OpCode = 0xf9
|
||||||
|
|
||||||
STATICCALL OpCode = 0xfa
|
STATICCALL OpCode = 0xfa
|
||||||
|
EXTSTATICCALL OpCode = 0xfb
|
||||||
REVERT OpCode = 0xfd
|
REVERT OpCode = 0xfd
|
||||||
INVALID OpCode = 0xfe
|
INVALID OpCode = 0xfe
|
||||||
SELFDESTRUCT OpCode = 0xff
|
SELFDESTRUCT OpCode = 0xff
|
||||||
|
|
@ -314,10 +334,6 @@ var opCodeToString = [256]string{
|
||||||
TLOAD: "TLOAD",
|
TLOAD: "TLOAD",
|
||||||
TSTORE: "TSTORE",
|
TSTORE: "TSTORE",
|
||||||
MCOPY: "MCOPY",
|
MCOPY: "MCOPY",
|
||||||
// TODO (MariusVanDerWijden) reenable once moved
|
|
||||||
//RJUMP: "RJUMP",
|
|
||||||
//RJUMPI: "RJUMPI",
|
|
||||||
//RJUMPV: "RJUMPV",
|
|
||||||
PUSH0: "PUSH0",
|
PUSH0: "PUSH0",
|
||||||
|
|
||||||
// 0x60 range - pushes.
|
// 0x60 range - pushes.
|
||||||
|
|
@ -397,9 +413,25 @@ var opCodeToString = [256]string{
|
||||||
LOG3: "LOG3",
|
LOG3: "LOG3",
|
||||||
LOG4: "LOG4",
|
LOG4: "LOG4",
|
||||||
|
|
||||||
// 0xb0 range.
|
// 0xd range - eof ops.
|
||||||
|
DATALOAD: "DATALOAD",
|
||||||
|
DATALOADN: "DATALOADN",
|
||||||
|
DATASIZE: "DATASIZE",
|
||||||
|
DATACOPY: "DATACOPY",
|
||||||
|
|
||||||
|
// 0xe0 range.
|
||||||
|
RJUMP: "RJUMP",
|
||||||
|
RJUMPI: "RJUMPI",
|
||||||
|
RJUMPV: "RJUMPV",
|
||||||
CALLF: "CALLF",
|
CALLF: "CALLF",
|
||||||
RETF: "RETF",
|
RETF: "RETF",
|
||||||
|
JUMPF: "JUMPF",
|
||||||
|
DUPN: "DUPN",
|
||||||
|
SWAPN: "SWAPN",
|
||||||
|
EXCHANGE: "EXCHANGE",
|
||||||
|
EOFCREATE: "EOFCREATE",
|
||||||
|
TXCREATE: "TXCREATE",
|
||||||
|
RETURNCONTRACT: "RETURNCONTRACT",
|
||||||
|
|
||||||
// 0xf0 range - closures.
|
// 0xf0 range - closures.
|
||||||
CREATE: "CREATE",
|
CREATE: "CREATE",
|
||||||
|
|
@ -408,7 +440,13 @@ var opCodeToString = [256]string{
|
||||||
CALLCODE: "CALLCODE",
|
CALLCODE: "CALLCODE",
|
||||||
DELEGATECALL: "DELEGATECALL",
|
DELEGATECALL: "DELEGATECALL",
|
||||||
CREATE2: "CREATE2",
|
CREATE2: "CREATE2",
|
||||||
|
|
||||||
|
RETURNDATALOAD: "RETURNDATALOAD",
|
||||||
|
EXTCALL: "EXTCALL",
|
||||||
|
EXTDELEGATECALL: "EXTDELEGATECALL",
|
||||||
|
|
||||||
STATICCALL: "STATICCALL",
|
STATICCALL: "STATICCALL",
|
||||||
|
EXTSTATICCALL: "EXTSTATICCALL",
|
||||||
REVERT: "REVERT",
|
REVERT: "REVERT",
|
||||||
INVALID: "INVALID",
|
INVALID: "INVALID",
|
||||||
SELFDESTRUCT: "SELFDESTRUCT",
|
SELFDESTRUCT: "SELFDESTRUCT",
|
||||||
|
|
@ -563,8 +601,28 @@ var stringToOp = map[string]OpCode{
|
||||||
"LOG2": LOG2,
|
"LOG2": LOG2,
|
||||||
"LOG3": LOG3,
|
"LOG3": LOG3,
|
||||||
"LOG4": LOG4,
|
"LOG4": LOG4,
|
||||||
|
"DATALOAD": DATALOAD,
|
||||||
|
"DATALOADN": DATALOADN,
|
||||||
|
"DATASIZE": DATASIZE,
|
||||||
|
"DATACOPY": DATACOPY,
|
||||||
|
"RJUMP": RJUMP,
|
||||||
|
"RJUMPI": RJUMPI,
|
||||||
|
"RJUMPV": RJUMPV,
|
||||||
|
"CALLF": CALLF,
|
||||||
|
"RETF": RETF,
|
||||||
|
"JUMPF": JUMPF,
|
||||||
|
"DUPN": DUPN,
|
||||||
|
"SWAPN": SWAPN,
|
||||||
|
"EXCHANGE": EXCHANGE,
|
||||||
|
"EOFCREATE": EOFCREATE,
|
||||||
|
"TXCREATE": TXCREATE,
|
||||||
|
"RETURNCONTRACT": RETURNCONTRACT,
|
||||||
"CREATE": CREATE,
|
"CREATE": CREATE,
|
||||||
"CREATE2": CREATE2,
|
"CREATE2": CREATE2,
|
||||||
|
"RETURNDATALOAD": RETURNDATALOAD,
|
||||||
|
"EXTCALL": EXTCALL,
|
||||||
|
"EXTDELEGATECALL": EXTDELEGATECALL,
|
||||||
|
"EXTSTATICCALL": EXTSTATICCALL,
|
||||||
"CALL": CALL,
|
"CALL": CALL,
|
||||||
"RETURN": RETURN,
|
"RETURN": RETURN,
|
||||||
"CALLCODE": CALLCODE,
|
"CALLCODE": CALLCODE,
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,14 @@ func (st *Stack) swap16() {
|
||||||
st.data[st.len()-17], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-17]
|
st.data[st.len()-17], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-17]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (st *Stack) swap(n int) {
|
||||||
|
st.swapN(n, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *Stack) swapN(n, m int) {
|
||||||
|
st.data[st.len()-n], st.data[st.len()-m] = st.data[st.len()-m], st.data[st.len()-n]
|
||||||
|
}
|
||||||
|
|
||||||
func (st *Stack) dup(n int) {
|
func (st *Stack) dup(n int) {
|
||||||
st.push(&st.data[st.len()-n])
|
st.push(&st.data[st.len()-n])
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,20 +23,24 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrUndefinedInstruction = errors.New("undefined instrustion")
|
ErrUndefinedInstruction = errors.New("undefined instruction")
|
||||||
ErrTruncatedImmediate = errors.New("truncated immediate")
|
ErrTruncatedImmediate = errors.New("truncated immediate")
|
||||||
ErrInvalidSectionArgument = errors.New("invalid section argument")
|
ErrInvalidSectionArgument = errors.New("invalid section argument")
|
||||||
|
ErrInvalidCallArgument = errors.New("callf into non-returning section")
|
||||||
|
ErrInvalidDataloadNArgument = errors.New("invalid dataloadN argument")
|
||||||
ErrInvalidJumpDest = errors.New("invalid jump destination")
|
ErrInvalidJumpDest = errors.New("invalid jump destination")
|
||||||
|
ErrInvalidBackwardJump = errors.New("invalid backward jump")
|
||||||
ErrConflictingStack = errors.New("conflicting stack height")
|
ErrConflictingStack = errors.New("conflicting stack height")
|
||||||
ErrInvalidBranchCount = errors.New("invalid number of branches in jump table")
|
ErrInvalidBranchCount = errors.New("invalid number of branches in jump table")
|
||||||
ErrInvalidOutputs = errors.New("invalid number of outputs")
|
ErrInvalidOutputs = errors.New("invalid number of outputs")
|
||||||
ErrInvalidMaxStackHeight = errors.New("invalid max stack height")
|
ErrInvalidMaxStackHeight = errors.New("invalid max stack height")
|
||||||
ErrInvalidCodeTermination = errors.New("invalid code termination")
|
ErrInvalidCodeTermination = errors.New("invalid code termination")
|
||||||
|
ErrEOFCreateWithTruncatedSection = errors.New("eofcreate with truncated section")
|
||||||
ErrUnreachableCode = errors.New("unreachable code")
|
ErrUnreachableCode = errors.New("unreachable code")
|
||||||
)
|
)
|
||||||
|
|
||||||
// validateCode validates the code parameter against the EOF v1 validity requirements.
|
// validateCode validates the code parameter against the EOF v1 validity requirements.
|
||||||
func validateCode(code []byte, section int, metadata []*FunctionMetadata, jt *JumpTable) error {
|
func validateCode(code []byte, section int, container *Container, jt *JumpTable) (map[int]struct{}, error) {
|
||||||
var (
|
var (
|
||||||
i = 0
|
i = 0
|
||||||
// Tracks the number of actual instructions in the code (e.g.
|
// Tracks the number of actual instructions in the code (e.g.
|
||||||
|
|
@ -45,6 +49,7 @@ func validateCode(code []byte, section int, metadata []*FunctionMetadata, jt *Ju
|
||||||
count = 0
|
count = 0
|
||||||
op OpCode
|
op OpCode
|
||||||
analysis bitvec
|
analysis bitvec
|
||||||
|
visited = make(map[int]struct{})
|
||||||
)
|
)
|
||||||
// This loop visits every single instruction and verifies:
|
// This loop visits every single instruction and verifies:
|
||||||
// * if the instruction is valid for the given jump table.
|
// * if the instruction is valid for the given jump table.
|
||||||
|
|
@ -56,64 +61,81 @@ func validateCode(code []byte, section int, metadata []*FunctionMetadata, jt *Ju
|
||||||
count++
|
count++
|
||||||
op = OpCode(code[i])
|
op = OpCode(code[i])
|
||||||
if jt[op].undefined {
|
if jt[op].undefined {
|
||||||
return fmt.Errorf("%w: op %s, pos %d", ErrUndefinedInstruction, op, i)
|
return visited, fmt.Errorf("%w: op %s, pos %d", ErrUndefinedInstruction, op, i)
|
||||||
|
}
|
||||||
|
if size := jt[op].immediate; size != 0 {
|
||||||
|
if len(code) <= i+size {
|
||||||
|
return visited, fmt.Errorf("%w: op %s, pos %d", ErrTruncatedImmediate, op, i)
|
||||||
}
|
}
|
||||||
switch {
|
switch {
|
||||||
case op >= PUSH1 && op <= PUSH32:
|
case op == RJUMP || op == RJUMPI:
|
||||||
size := int(op - PUSH0)
|
if err := checkDest(code, &analysis, i+1, i+3, len(code)); err != nil {
|
||||||
if len(code) <= i+size {
|
return visited, err
|
||||||
return fmt.Errorf("%w: op %s, pos %d", ErrTruncatedImmediate, op, i)
|
}
|
||||||
|
case op == RJUMPV:
|
||||||
|
max_size := int(code[i+1])
|
||||||
|
length := max_size + 1
|
||||||
|
if len(code) <= i+length {
|
||||||
|
return visited, 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 visited, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i += 2 * max_size
|
||||||
|
case op == CALLF:
|
||||||
|
arg, _ := parseUint16(code[i+1:])
|
||||||
|
if arg >= len(container.Types) {
|
||||||
|
return visited, fmt.Errorf("%w: arg %d, last %d, pos %d", ErrInvalidSectionArgument, arg, len(container.Types), i)
|
||||||
|
}
|
||||||
|
if container.Types[arg].Output == 0x80 {
|
||||||
|
return visited, fmt.Errorf("%w: section %v", ErrInvalidCallArgument, arg)
|
||||||
|
}
|
||||||
|
visited[arg] = struct{}{}
|
||||||
|
case op == JUMPF:
|
||||||
|
arg, _ := parseUint16(code[i+1:])
|
||||||
|
if arg >= len(container.Types) {
|
||||||
|
return visited, fmt.Errorf("%w: arg %d, last %d, pos %d", ErrInvalidSectionArgument, arg, len(container.Types), i)
|
||||||
|
}
|
||||||
|
visited[arg] = struct{}{}
|
||||||
|
case op == DATALOADN:
|
||||||
|
arg, _ := parseUint16(code[i+1:])
|
||||||
|
if arg+32 > len(container.Data) {
|
||||||
|
return visited, fmt.Errorf("%w: arg %d, last %d, pos %d", ErrInvalidDataloadNArgument, arg, len(container.Data), i)
|
||||||
|
}
|
||||||
|
case op == RETURNCONTRACT:
|
||||||
|
arg := int(code[i+1])
|
||||||
|
if arg >= len(container.ContainerSections) {
|
||||||
|
return visited, fmt.Errorf("%w: arg %d, last %d, pos %d", ErrUnreachableCode, arg, len(container.ContainerSections), i)
|
||||||
|
}
|
||||||
|
case op == EOFCREATE:
|
||||||
|
arg := int(code[i+1])
|
||||||
|
if arg >= len(container.ContainerSections) {
|
||||||
|
return visited, fmt.Errorf("%w: arg %d, last %d, pos %d", ErrUnreachableCode, arg, len(container.ContainerSections), i)
|
||||||
|
}
|
||||||
|
if ct := container.ContainerSections[arg]; len(ct.Data) != ct.DataSize {
|
||||||
|
return visited, fmt.Errorf("%w: container %d, have %d, claimed %d, pos %d", ErrEOFCreateWithTruncatedSection, arg, len(ct.Data), ct.DataSize, i)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
i += size
|
i += size
|
||||||
case op == RJUMP || op == RJUMPI:
|
|
||||||
if len(code) <= i+2 {
|
|
||||||
return fmt.Errorf("%w: op %s, pos %d", ErrTruncatedImmediate, op, i)
|
|
||||||
}
|
|
||||||
if err := checkDest(code, &analysis, i+1, i+3, len(code)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
i += 2
|
|
||||||
case op == RJUMPV:
|
|
||||||
if len(code) <= i+1 {
|
|
||||||
return fmt.Errorf("%w: jump table size missing, op %s, pos %d", ErrTruncatedImmediate, op, i)
|
|
||||||
}
|
|
||||||
count := int(code[i+1])
|
|
||||||
if count == 0 {
|
|
||||||
return fmt.Errorf("%w: must not be 0, pos %d", ErrInvalidBranchCount, i)
|
|
||||||
}
|
|
||||||
if len(code) <= i+count {
|
|
||||||
return fmt.Errorf("%w: jump table truncated, op %s, pos %d", ErrTruncatedImmediate, op, i)
|
|
||||||
}
|
|
||||||
for j := 0; j < count; j++ {
|
|
||||||
if err := checkDest(code, &analysis, i+2+j*2, i+2*count+2, len(code)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
i += 1 + 2*count
|
|
||||||
case op == CALLF:
|
|
||||||
if i+2 >= len(code) {
|
|
||||||
return fmt.Errorf("%w: op %s, pos %d", ErrTruncatedImmediate, op, i)
|
|
||||||
}
|
|
||||||
arg, _ := parseUint16(code[i+1:])
|
|
||||||
if arg >= len(metadata) {
|
|
||||||
return fmt.Errorf("%w: arg %d, last %d, pos %d", ErrInvalidSectionArgument, arg, len(metadata), i)
|
|
||||||
}
|
|
||||||
i += 2
|
|
||||||
}
|
}
|
||||||
i += 1
|
i += 1
|
||||||
}
|
}
|
||||||
// Code sections may not "fall through" and require proper termination.
|
// Code sections may not "fall through" and require proper termination.
|
||||||
// Therefore, the last instruction must be considered terminal.
|
// Therefore, the last instruction must be considered terminal or RJUMP.
|
||||||
if !jt[op].terminal {
|
if !jt[op].terminal && op != RJUMP {
|
||||||
return fmt.Errorf("%w: end with %s, pos %d", ErrInvalidCodeTermination, op, i)
|
return visited, fmt.Errorf("%w: end with %s, pos %d", ErrInvalidCodeTermination, op, i)
|
||||||
}
|
}
|
||||||
if paths, err := validateControlFlow(code, section, metadata, jt); err != nil {
|
if paths, err := validateControlFlow2(code, section, container.Types, jt); err != nil {
|
||||||
return err
|
return visited, err
|
||||||
} else if paths != count {
|
} else if paths != count {
|
||||||
// TODO(matt): return actual position of unreacable code
|
fmt.Printf("Paths: %v Count: %v\n", paths, count)
|
||||||
return ErrUnreachableCode
|
// TODO(matt): return actual position of unreachable code
|
||||||
|
return visited, ErrUnreachableCode
|
||||||
}
|
}
|
||||||
return nil
|
return visited, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkDest parses a relative offset at code[0:2] and checks if it is a valid jump destination.
|
// checkDest parses a relative offset at code[0:2] and checks if it is a valid jump destination.
|
||||||
|
|
@ -141,10 +163,11 @@ func validateControlFlow(code []byte, section int, metadata []*FunctionMetadata,
|
||||||
type item struct {
|
type item struct {
|
||||||
pos int
|
pos int
|
||||||
height int
|
height int
|
||||||
|
backwards bool
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
heights = make(map[int]int)
|
heights = make(map[int]int)
|
||||||
worklist = []item{{0, int(metadata[section].Input)}}
|
worklist = []item{{0, int(metadata[section].Input), false}}
|
||||||
maxStackHeight = int(metadata[section].Input)
|
maxStackHeight = int(metadata[section].Input)
|
||||||
)
|
)
|
||||||
for 0 < len(worklist) {
|
for 0 < len(worklist) {
|
||||||
|
|
@ -152,21 +175,25 @@ func validateControlFlow(code []byte, section int, metadata []*FunctionMetadata,
|
||||||
idx = len(worklist) - 1
|
idx = len(worklist) - 1
|
||||||
pos = worklist[idx].pos
|
pos = worklist[idx].pos
|
||||||
height = worklist[idx].height
|
height = worklist[idx].height
|
||||||
|
backwards = worklist[idx].backwards
|
||||||
)
|
)
|
||||||
worklist = worklist[:idx]
|
worklist = worklist[:idx]
|
||||||
outer:
|
outer:
|
||||||
for pos < len(code) {
|
for pos < len(code) {
|
||||||
op := OpCode(code[pos])
|
op := OpCode(code[pos])
|
||||||
|
|
||||||
// Check if pos has already be visited; if so, the stack heights should be the same.
|
// Check if pos has already be visited; if so, the stack heights should be the same.
|
||||||
if want, ok := heights[pos]; ok {
|
if want, ok := heights[pos]; ok {
|
||||||
if height != want {
|
if height == want {
|
||||||
return 0, fmt.Errorf("%w: have %d, want %d", ErrConflictingStack, height, want)
|
|
||||||
}
|
|
||||||
// Already visited this path and stack height
|
// Already visited this path and stack height
|
||||||
// matches.
|
// matches.
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
// Already visited this path but the stack height is not the same, need to revisit again
|
||||||
|
// TODO (MariusVanDerWijden): can this result in an infinite loop?
|
||||||
|
} else if backwards {
|
||||||
|
// If a instruction can only be reached by backwards jump, bail
|
||||||
|
break
|
||||||
|
}
|
||||||
heights[pos] = height
|
heights[pos] = height
|
||||||
|
|
||||||
// Validate height for current op and update as needed.
|
// Validate height for current op and update as needed.
|
||||||
|
|
@ -181,14 +208,15 @@ func validateControlFlow(code []byte, section int, metadata []*FunctionMetadata,
|
||||||
switch {
|
switch {
|
||||||
case op == CALLF:
|
case op == CALLF:
|
||||||
arg, _ := parseUint16(code[pos+1:])
|
arg, _ := parseUint16(code[pos+1:])
|
||||||
if want, have := int(metadata[arg].Input), height; want > have {
|
newSection := metadata[arg]
|
||||||
|
if want, have := int(newSection.Input), height; want > have {
|
||||||
return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos)
|
return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos)
|
||||||
}
|
}
|
||||||
if have, limit := int(metadata[arg].Output)+height, int(params.StackLimit); have > limit {
|
if have, limit := height+int(newSection.MaxStackHeight)-int(newSection.Input), int(params.StackLimit); have > limit {
|
||||||
return 0, fmt.Errorf("%w: at pos %d", ErrStackOverflow{stackLen: have, limit: limit}, pos)
|
return 0, fmt.Errorf("%w: at pos %d", ErrStackOverflow{stackLen: have, limit: limit}, pos)
|
||||||
}
|
}
|
||||||
height -= int(metadata[arg].Input)
|
height -= int(newSection.Input)
|
||||||
height += int(metadata[arg].Output)
|
height += int(newSection.Output)
|
||||||
pos += 3
|
pos += 3
|
||||||
case op == RETF:
|
case op == RETF:
|
||||||
if have, want := int(metadata[section].Output), height; have != want {
|
if have, want := int(metadata[section].Output), height; have != want {
|
||||||
|
|
@ -198,26 +226,61 @@ func validateControlFlow(code []byte, section int, metadata []*FunctionMetadata,
|
||||||
case op == RJUMP:
|
case op == RJUMP:
|
||||||
arg := parseInt16(code[pos+1:])
|
arg := parseInt16(code[pos+1:])
|
||||||
pos += 3 + arg
|
pos += 3 + arg
|
||||||
|
worklist = append(worklist, item{pos: pos, height: height, backwards: arg < 0})
|
||||||
|
break outer
|
||||||
case op == RJUMPI:
|
case op == RJUMPI:
|
||||||
arg := parseInt16(code[pos+1:])
|
arg := parseInt16(code[pos+1:])
|
||||||
worklist = append(worklist, item{pos: pos + 3 + arg, height: height})
|
worklist = append(worklist, item{pos: pos + 3 + arg, height: height})
|
||||||
pos += 3
|
pos += 3
|
||||||
case op == RJUMPV:
|
case op == RJUMPV:
|
||||||
count := int(code[pos+1])
|
count := int(code[pos+1]) + 1
|
||||||
for i := 0; i < count; i++ {
|
for i := 0; i < count; i++ {
|
||||||
arg := parseInt16(code[pos+2+2*i:])
|
arg := parseInt16(code[pos+2+2*i:])
|
||||||
worklist = append(worklist, item{pos: pos + 2 + 2*count + arg, height: height})
|
worklist = append(worklist, item{pos: pos + 2 + 2*count + arg, height: height})
|
||||||
}
|
}
|
||||||
pos += 2 + 2*count
|
pos += 2 + 2*count
|
||||||
|
case op == DUPN:
|
||||||
|
fallthrough
|
||||||
|
case op == SWAPN:
|
||||||
|
arg := int(code[pos+1]) + 1
|
||||||
|
if want, have := arg, height; want >= have {
|
||||||
|
return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos)
|
||||||
|
}
|
||||||
|
pos += 2
|
||||||
|
case op == EXCHANGE:
|
||||||
|
arg := int(code[pos+1])
|
||||||
|
n := arg>>4 + 1
|
||||||
|
m := arg&0x0f + 1
|
||||||
|
if want, have := n+m, height; want >= have {
|
||||||
|
return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos)
|
||||||
|
}
|
||||||
|
pos += 2
|
||||||
|
case op == JUMPF:
|
||||||
|
arg, _ := parseUint16(code[pos+1:])
|
||||||
|
newSection := metadata[arg]
|
||||||
|
if have, limit := height+int(newSection.MaxStackHeight)-int(newSection.Input), int(params.StackLimit); have > limit {
|
||||||
|
return 0, fmt.Errorf("%w: at pos %d", ErrStackOverflow{stackLen: have, limit: limit}, pos)
|
||||||
|
}
|
||||||
|
if newSection.Output == 0x80 {
|
||||||
|
if want, have := int(newSection.Input), height; want > have {
|
||||||
|
return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if have, want := height, int(metadata[section].Output)+int(newSection.Input)-int(newSection.Output); have != want {
|
||||||
|
return 0, fmt.Errorf("%w: at pos %d", ErrInvalidNumberOfOutputs, pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pos += 3
|
||||||
default:
|
default:
|
||||||
if op >= PUSH1 && op <= PUSH32 {
|
if jt[op].immediate != 0 {
|
||||||
pos += 1 + int(op-PUSH0)
|
pos += jt[op].immediate + 1
|
||||||
} else if jt[op].terminal {
|
|
||||||
break outer
|
|
||||||
} else {
|
} else {
|
||||||
// Simple op, no operand.
|
// Simple op, no operand.
|
||||||
pos += 1
|
pos += 1
|
||||||
}
|
}
|
||||||
|
if jt[op].terminal {
|
||||||
|
break outer
|
||||||
|
}
|
||||||
}
|
}
|
||||||
maxStackHeight = max(maxStackHeight, height)
|
maxStackHeight = max(maxStackHeight, height)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
204
core/vm/validate_linear.go
Normal file
204
core/vm/validate_linear.go
Normal file
|
|
@ -0,0 +1,204 @@
|
||||||
|
package vm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
)
|
||||||
|
|
||||||
|
type bounds struct {
|
||||||
|
min int
|
||||||
|
max int
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateControlFlow2(code []byte, section int, metadata []*FunctionMetadata, jt *JumpTable) (int, error) {
|
||||||
|
var (
|
||||||
|
stackBounds = make(map[int]*bounds)
|
||||||
|
maxStackHeight = int(metadata[section].Input)
|
||||||
|
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].Input), int(metadata[section].Input))
|
||||||
|
|
||||||
|
for pos := 0; pos < len(code); pos++ {
|
||||||
|
op := OpCode(code[pos])
|
||||||
|
currentBounds := stackBounds[pos]
|
||||||
|
if currentBounds == nil {
|
||||||
|
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.Input), 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.Input), int(params.StackLimit); have > limit {
|
||||||
|
return 0, fmt.Errorf("%w: at pos %d", ErrStackOverflow{stackLen: have, limit: limit}, pos)
|
||||||
|
}
|
||||||
|
change := int(newSection.Output) - int(newSection.Input)
|
||||||
|
currentStackMax += change
|
||||||
|
currentStackMin += change
|
||||||
|
case RETF:
|
||||||
|
if currentBounds.max != currentBounds.min {
|
||||||
|
return 0, fmt.Errorf("%w: max %d, min %d, at pos %d", ErrInvalidNumberOfOutputs, currentBounds.max, currentBounds.min, pos)
|
||||||
|
}
|
||||||
|
if have, want := int(metadata[section].Output), currentBounds.min; have != want {
|
||||||
|
return 0, fmt.Errorf("%w: have %d, want %d, at pos %d", ErrInvalidOutputs, have, want, pos)
|
||||||
|
}
|
||||||
|
case JUMPF:
|
||||||
|
arg, _ := parseUint16(code[pos+1:])
|
||||||
|
newSection := metadata[arg]
|
||||||
|
if have, limit := currentBounds.max+int(newSection.MaxStackHeight)-int(newSection.Input), int(params.StackLimit); have > limit {
|
||||||
|
return 0, fmt.Errorf("%w: at pos %d", ErrStackOverflow{stackLen: have, limit: limit}, pos)
|
||||||
|
}
|
||||||
|
if newSection.Output == 0x80 {
|
||||||
|
if want, have := int(newSection.Input), 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", ErrInvalidNumberOfOutputs, currentBounds.max, currentBounds.min, pos)
|
||||||
|
}
|
||||||
|
if have, want := currentBounds.max, int(metadata[section].Output)+int(newSection.Input)-int(newSection.Output); have != want {
|
||||||
|
return 0, fmt.Errorf("%w: at pos %d", ErrInvalidNumberOfOutputs, pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 !jt[op].terminal && 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
|
||||||
|
if nextPos+1 < pos {
|
||||||
|
nextBounds, ok := stackBounds[nextPos+1]
|
||||||
|
if !ok {
|
||||||
|
return 0, ErrInvalidBackwardJump
|
||||||
|
}
|
||||||
|
if nextBounds.max != currentStackMax || nextBounds.min != currentStackMin {
|
||||||
|
return 0, ErrInvalidMaxStackHeight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setBounds(next[0]+1, currentStackMin, 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 jt[op].immediate != 0 {
|
||||||
|
next = append(next, pos+jt[op].immediate)
|
||||||
|
} else {
|
||||||
|
// Simple op, no operand.
|
||||||
|
next = append(next, pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if debugging {
|
||||||
|
fmt.Println(next)
|
||||||
|
}
|
||||||
|
|
||||||
|
if op != RJUMP && !jt[op].terminal {
|
||||||
|
for _, instr := range next {
|
||||||
|
nextPC := instr + 1
|
||||||
|
if nextPC >= len(code) {
|
||||||
|
return 0, fmt.Errorf("%w: end with %s, pos %d", ErrInvalidCodeTermination, op, pos)
|
||||||
|
}
|
||||||
|
nextOP := code[nextPC]
|
||||||
|
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
|
||||||
|
}
|
||||||
|
change := int(params.StackLimit) - jt[nextOP].maxStack + jt[nextOP].minStack
|
||||||
|
if have, want := nextBounds.max+change, currentBounds.max; have != want {
|
||||||
|
fmt.Println(nextPC, have, want, change)
|
||||||
|
return 0, fmt.Errorf("%w want %d as max got %d at pos %d,", ErrInvalidBackwardJump, want, have, pos)
|
||||||
|
}
|
||||||
|
if have, want := nextBounds.min+change, currentBounds.min; have != want {
|
||||||
|
return 0, fmt.Errorf("%w want %d as min got %d at pos %d,", ErrInvalidBackwardJump, want, have, pos)
|
||||||
|
}
|
||||||
|
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 op == RJUMP {
|
||||||
|
pos += 2 // skip the immediate
|
||||||
|
} else {
|
||||||
|
pos = next[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
if maxStackHeight >= int(params.StackLimit) {
|
||||||
|
return 0, ErrStackOverflow{maxStackHeight, int(params.StackLimit)}
|
||||||
|
}
|
||||||
|
if maxStackHeight != int(metadata[section].MaxStackHeight) {
|
||||||
|
fmt.Print(maxStackHeight, metadata[section].MaxStackHeight)
|
||||||
|
return 0, fmt.Errorf("%w in code section %d: have %d, want %d", ErrInvalidMaxStackHeight, section, maxStackHeight, metadata[section].MaxStackHeight)
|
||||||
|
}
|
||||||
|
return len(stackBounds), nil
|
||||||
|
}
|
||||||
|
|
@ -17,10 +17,12 @@
|
||||||
package vm
|
package vm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestValidateCode(t *testing.T) {
|
func TestValidateCode(t *testing.T) {
|
||||||
|
|
@ -118,7 +120,7 @@ func TestValidateCode(t *testing.T) {
|
||||||
code: []byte{
|
code: []byte{
|
||||||
byte(PUSH0),
|
byte(PUSH0),
|
||||||
byte(RJUMPV),
|
byte(RJUMPV),
|
||||||
byte(0x02),
|
byte(0x01),
|
||||||
byte(0x00),
|
byte(0x00),
|
||||||
byte(0x01),
|
byte(0x01),
|
||||||
byte(0x00),
|
byte(0x00),
|
||||||
|
|
@ -141,24 +143,25 @@ func TestValidateCode(t *testing.T) {
|
||||||
},
|
},
|
||||||
section: 0,
|
section: 0,
|
||||||
metadata: []*FunctionMetadata{{Input: 0, Output: 0, MaxStackHeight: 1}},
|
metadata: []*FunctionMetadata{{Input: 0, Output: 0, MaxStackHeight: 1}},
|
||||||
err: ErrInvalidBranchCount,
|
err: ErrTruncatedImmediate,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: []byte{
|
code: []byte{
|
||||||
byte(RJUMP), 0x00, 0x03,
|
byte(RJUMP), 0x00, 0x03,
|
||||||
byte(JUMPDEST),
|
byte(JUMPDEST), // this code is unreachable to forward jumps alone
|
||||||
byte(JUMPDEST),
|
byte(JUMPDEST),
|
||||||
byte(RETURN),
|
byte(RETURN),
|
||||||
byte(PUSH1), 20,
|
byte(PUSH1), 20,
|
||||||
byte(PUSH1), 39,
|
byte(PUSH1), 39,
|
||||||
byte(PUSH1), 0x00,
|
byte(PUSH1), 0x00,
|
||||||
byte(CODECOPY),
|
byte(DATACOPY),
|
||||||
byte(PUSH1), 20,
|
byte(PUSH1), 20,
|
||||||
byte(PUSH1), 0x00,
|
byte(PUSH1), 0x00,
|
||||||
byte(RJUMP), 0xff, 0xef,
|
byte(RJUMP), 0xff, 0xef,
|
||||||
},
|
},
|
||||||
section: 0,
|
section: 0,
|
||||||
metadata: []*FunctionMetadata{{Input: 0, Output: 0, MaxStackHeight: 3}},
|
metadata: []*FunctionMetadata{{Input: 0, Output: 0, MaxStackHeight: 3}},
|
||||||
|
err: ErrUnreachableCode,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: []byte{
|
code: []byte{
|
||||||
|
|
@ -170,7 +173,7 @@ func TestValidateCode(t *testing.T) {
|
||||||
byte(PUSH1), 20,
|
byte(PUSH1), 20,
|
||||||
byte(PUSH1), 39,
|
byte(PUSH1), 39,
|
||||||
byte(PUSH1), 0x00,
|
byte(PUSH1), 0x00,
|
||||||
byte(CODECOPY),
|
byte(DATACOPY),
|
||||||
byte(PUSH1), 20,
|
byte(PUSH1), 20,
|
||||||
byte(PUSH1), 0x00,
|
byte(PUSH1), 0x00,
|
||||||
byte(RETURN),
|
byte(RETURN),
|
||||||
|
|
@ -181,14 +184,14 @@ func TestValidateCode(t *testing.T) {
|
||||||
{
|
{
|
||||||
code: []byte{
|
code: []byte{
|
||||||
byte(PUSH1), 1,
|
byte(PUSH1), 1,
|
||||||
byte(RJUMPV), 0x02, 0x00, 0x03, 0xff, 0xf8,
|
byte(RJUMPV), 0x01, 0x00, 0x03, 0xff, 0xf8,
|
||||||
byte(JUMPDEST),
|
byte(JUMPDEST),
|
||||||
byte(JUMPDEST),
|
byte(JUMPDEST),
|
||||||
byte(STOP),
|
byte(STOP),
|
||||||
byte(PUSH1), 20,
|
byte(PUSH1), 20,
|
||||||
byte(PUSH1), 39,
|
byte(PUSH1), 39,
|
||||||
byte(PUSH1), 0x00,
|
byte(PUSH1), 0x00,
|
||||||
byte(CODECOPY),
|
byte(DATACOPY),
|
||||||
byte(PUSH1), 20,
|
byte(PUSH1), 20,
|
||||||
byte(PUSH1), 0x00,
|
byte(PUSH1), 0x00,
|
||||||
byte(RETURN),
|
byte(RETURN),
|
||||||
|
|
@ -242,9 +245,252 @@ func TestValidateCode(t *testing.T) {
|
||||||
metadata: []*FunctionMetadata{{Input: 0, Output: 0, MaxStackHeight: 2}, {Input: 2, Output: 1, MaxStackHeight: 2}},
|
metadata: []*FunctionMetadata{{Input: 0, Output: 0, MaxStackHeight: 2}, {Input: 2, Output: 1, MaxStackHeight: 2}},
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
err := validateCode(test.code, test.section, test.metadata, &pragueEOFInstructionSet)
|
container := &Container{
|
||||||
|
Types: test.metadata,
|
||||||
|
Data: make([]byte, 0),
|
||||||
|
ContainerSections: make([]*Container, 0),
|
||||||
|
}
|
||||||
|
_, err := validateCode(test.code, test.section, container, &pragueEOFInstructionSet)
|
||||||
if !errors.Is(err, test.err) {
|
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)
|
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{{Input: 0, Output: 0, MaxStackHeight: 1}},
|
||||||
|
Data: make([]byte, 0),
|
||||||
|
ContainerSections: make([]*Container, 0),
|
||||||
|
}
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, err := validateCode(code, 0, container, &pragueEOFInstructionSet)
|
||||||
|
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{{Input: 0, Output: 0, MaxStackHeight: 1}},
|
||||||
|
Data: make([]byte, 0),
|
||||||
|
ContainerSections: make([]*Container, 0),
|
||||||
|
}
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, err := validateCode(code, 0, container, &pragueEOFInstructionSet)
|
||||||
|
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{Input: 0, Output: 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{Input: 0, Output: 0, 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); err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := container2.ValidateCode(&pragueEOFInstructionSet); 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{Input: 0, Output: 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{Input: 0, Output: 0, MaxStackHeight: 0})
|
||||||
|
}
|
||||||
|
|
||||||
|
bin := container.MarshalBinary()
|
||||||
|
if len(bin) > 48*1024 {
|
||||||
|
b.Fatal("Exceeds 48Kb")
|
||||||
|
}
|
||||||
|
|
||||||
|
var container2 Container
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
if err := container2.UnmarshalBinary(bin); err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := container2.ValidateCode(&pragueEOFInstructionSet); 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{Input: 0, Output: 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{Input: 0, Output: 0, MaxStackHeight: 0})
|
||||||
|
}
|
||||||
|
for i := 0; i < 65; i++ {
|
||||||
|
container.Code[i+1] = append(snippet, byte(STOP))
|
||||||
|
container.Types[i+1] = &FunctionMetadata{Input: 0, Output: 0, MaxStackHeight: 1}
|
||||||
|
}
|
||||||
|
bin := container.MarshalBinary()
|
||||||
|
if len(bin) > 48*1024 {
|
||||||
|
b.Fatal("Exceeds 48Kb")
|
||||||
|
}
|
||||||
|
b.ResetTimer()
|
||||||
|
b.ReportMetric(float64(len(bin)), "bytes")
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
for k := 0; k < 40; k++ {
|
||||||
|
var container2 Container
|
||||||
|
if err := container2.UnmarshalBinary(bin); err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := container2.ValidateCode(&pragueEOFInstructionSet); 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{{Input: 0, Output: 0, MaxStackHeight: 1}},
|
||||||
|
Data: make([]byte, 0),
|
||||||
|
ContainerSections: make([]*Container, 0),
|
||||||
|
}
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, err := validateCode(code, 0, container, &pragueEOFInstructionSet)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func FuzzUnmarshalBinary(f *testing.F) {
|
||||||
|
f.Fuzz(func(_ *testing.T, input []byte) {
|
||||||
|
var container Container
|
||||||
|
container.UnmarshalBinary(input)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func FuzzValidate(f *testing.F) {
|
||||||
|
f.Fuzz(func(_ *testing.T, code []byte, maxStack uint16) {
|
||||||
|
var container Container
|
||||||
|
container.Types = append(container.Types, &FunctionMetadata{Input: 0, Output: 0x80, MaxStackHeight: maxStack})
|
||||||
|
validateCode(code, 0, &container, &pragueEOFInstructionSet)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ func runTrace(tracer *tracers.Tracer, vmctx *vmContext, chaincfg *params.ChainCo
|
||||||
|
|
||||||
tracer.OnTxStart(env.GetVMContext(), types.NewTx(&types.LegacyTx{Gas: gasLimit}), contract.Caller())
|
tracer.OnTxStart(env.GetVMContext(), types.NewTx(&types.LegacyTx{Gas: gasLimit}), contract.Caller())
|
||||||
tracer.OnEnter(0, byte(vm.CALL), contract.Caller(), contract.Address(), []byte{}, startGas, value.ToBig())
|
tracer.OnEnter(0, byte(vm.CALL), contract.Caller(), contract.Address(), []byte{}, startGas, value.ToBig())
|
||||||
ret, err := env.Interpreter().Run(contract, []byte{}, false)
|
ret, err := env.Interpreter().Run(contract, []byte{}, false, false)
|
||||||
tracer.OnExit(0, ret, startGas-contract.Gas, err, true)
|
tracer.OnExit(0, ret, startGas-contract.Gas, err, true)
|
||||||
// Rest gas assumes no refund
|
// Rest gas assumes no refund
|
||||||
tracer.OnTxEnd(&types.Receipt{GasUsed: gasLimit - contract.Gas}, nil)
|
tracer.OnTxEnd(&types.Receipt{GasUsed: gasLimit - contract.Gas}, nil)
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ func TestStoreCapture(t *testing.T) {
|
||||||
contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x0, byte(vm.SSTORE)}
|
contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x0, byte(vm.SSTORE)}
|
||||||
var index common.Hash
|
var index common.Hash
|
||||||
logger.OnTxStart(env.GetVMContext(), nil, common.Address{})
|
logger.OnTxStart(env.GetVMContext(), nil, common.Address{})
|
||||||
_, err := env.Interpreter().Run(contract, []byte{}, false)
|
_, err := env.Interpreter().Run(contract, []byte{}, false, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -130,8 +130,9 @@ const (
|
||||||
DefaultElasticityMultiplier = 2 // Bounds the maximum gas limit an EIP-1559 block may have.
|
DefaultElasticityMultiplier = 2 // Bounds the maximum gas limit an EIP-1559 block may have.
|
||||||
InitialBaseFee = 1000000000 // Initial base fee for EIP-1559 blocks.
|
InitialBaseFee = 1000000000 // Initial base fee for EIP-1559 blocks.
|
||||||
|
|
||||||
MaxCodeSize = 24576 // Maximum bytecode to permit for a contract
|
MaxCodeSize = 24576 // Maximum bytecode to permit for a contract.
|
||||||
MaxInitCodeSize = 2 * MaxCodeSize // Maximum initcode to permit in a creation transaction and create instructions
|
MaxInitCodeSize = 2 * MaxCodeSize // Maximum initcode to permit in a creation transaction and create instructions.
|
||||||
|
MaxInitCodeCount = 256 // Maximum number of initcodes in an initcode transaction.
|
||||||
|
|
||||||
// Precompiled contract gas prices
|
// Precompiled contract gas prices
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue