core/types: implement mega-eof spec

This commit is contained in:
Marius van der Wijden 2024-04-12 17:28:29 +02:00
parent 1dccd90224
commit 07db5b86b1
31 changed files with 1787 additions and 382 deletions

View file

@ -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
View 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)
}
}

View file

@ -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)

View file

@ -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
} }

View file

@ -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))

View file

@ -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 {

View file

@ -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
} }

View file

@ -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")

View file

@ -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

View file

@ -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

View file

@ -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
}

View file

@ -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
}

View file

@ -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)
}
}
}

View file

@ -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.

View file

@ -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 }

View file

@ -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

View file

@ -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
}

View file

@ -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 {

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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),

View file

@ -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 {

View file

@ -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,

View file

@ -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])
} }

View file

@ -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
View 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
}

View file

@ -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)
})
}

View file

@ -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)

View file

@ -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)
} }

View file

@ -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