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"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
"strings"
"sync/atomic"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/urfave/cli/v2"
@ -64,6 +67,10 @@ var (
}
)
type RefTests struct {
Vectors map[string]EOFTest `json:"vectors"`
}
type EOFTest struct {
Code string `json:"code"`
Results map[string]etResult `json:"results"`
@ -71,7 +78,7 @@ type EOFTest struct {
type etResult struct {
Result bool `json:"result"`
Exception int `json:"exception,omitempty"`
Exception string `json:"exception,omitempty"`
}
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 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 {
return err
}
var tests map[string]EOFTest
if err = json.Unmarshal(src, &tests); err != nil {
_, _, err = ExecuteTest(src)
return err
} else {
err = filepath.Walk(file, func(path string, info fs.FileInfo, err error) error {
if err != nil {
return err
}
passed, total := 0, 0
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)
if info.IsDir() {
return nil
}
fmt.Printf("Executing Tests: %v\n", info.Name())
src, err := os.ReadFile(path)
if err != nil {
return err
}
passed, total, err := ExecuteTest(src)
passedTests.Add(int32(passed))
executedTests.Add(int32(total))
return err
})
if err != nil {
return err
}
fmt.Printf("Passed %v tests out of %v\n", passedTests.Load(), executedTests.Load())
return nil
}
}
// If neither are passed in, read input from stdin.
scanner := bufio.NewScanner(os.Stdin)
@ -144,6 +154,45 @@ func eofParser(ctx *cli.Context) error {
return nil
}
func ExecuteTest(src []byte) (int, int, error) {
var testsByName map[string]RefTests
if err := json.Unmarshal(src, &testsByName); err != nil {
return 0, 0, err
}
passed, total := 0, 0
for _, 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) {
if len(s) >= 2 && strings.HasPrefix(s, "0x") {
s = s[2:]
@ -161,3 +210,24 @@ func parseAndValidate(s string) (*vm.Container, error) {
}
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",
Category: flags.VMCategory,
}
HexFlag = &cli.StringFlag{
Name: "hex",
Usage: "single container data parse and validation",
}
ForknameFlag = &cli.StringFlag{
Name: "state.fork",
Usage: fmt.Sprintf("Name of ruleset to use."+
@ -157,10 +153,6 @@ var (
strings.Join(vm.ActivateableEips(), ", ")),
Value: "Shanghai",
}
RefTestFlag = &cli.StringFlag{
Name: "test",
Usage: "Path to EOF validation reference test.",
}
)
var stateTransitionCommand = &cli.Command{
@ -245,17 +237,6 @@ var traceFlags = []cli.Flag{
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")
func init() {
@ -269,7 +250,6 @@ func init() {
stateTransitionCommand,
transactionCommand,
blockBuilderCommand,
eofParserCommand,
}
app.Before = func(ctx *cli.Context) error {
flags.MigrateGlobalFlags(ctx)

View file

@ -87,6 +87,9 @@ func NewEVMTxContext(msg *Message) vm.TxContext {
if msg.BlobGasFeeCap != nil {
ctx.BlobFeeCap = new(big.Int).Set(msg.BlobGasFeeCap)
}
if msg.InitCodes != nil {
ctx.InitCodes = msg.InitCodes
}
return ctx
}

View file

@ -564,9 +564,13 @@ func (s *stateObject) CodeSize() int {
if len(s.code) != 0 {
return len(s.code)
}
if bytes.Equal(s.CodeHash(), types.EmptyCodeHash.Bytes()) {
codeHash := s.CodeHash()
if bytes.Equal(codeHash, types.EmptyCodeHash.Bytes()) {
return 0
}
if bytes.Equal(codeHash, types.EmptyEOFCodeHash.Bytes()) {
return 2
}
size, err := s.db.db.ContractCodeSize(s.address, common.BytesToHash(s.CodeHash()))
if err != nil {
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) {
stateObject := s.getOrNewStateObject(addr)
if stateObject != nil {

View file

@ -148,6 +148,7 @@ type Message struct {
BlobGasFeeCap *big.Int
BlobHashes []common.Hash
AuthList types.AuthorizationList
InitCodes [][]byte
// 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.
@ -171,6 +172,7 @@ func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.In
SkipAccountChecks: false,
BlobHashes: tx.BlobHashes(),
BlobGasFeeCap: tx.BlobGasFeeCap(),
InitCodes: tx.InitCodes(),
}
// If baseFee provided, set gasPrice to effectiveGasPrice.
if baseFee != nil {
@ -422,8 +424,17 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
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
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 {
return nil, err
}

View file

@ -32,6 +32,9 @@ var (
// EmptyCodeHash is the known hash of the empty EVM bytecode.
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 = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")

View file

@ -426,7 +426,6 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error {
return err
}
}
case SetCodeTxType:
var itx SetCodeTx
inner = &itx

View file

@ -139,7 +139,7 @@ func eofCodeBitmapInternal(code, bits bitvec) bitvec {
switch {
case op >= PUSH1 && op <= PUSH32:
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
case op == RJUMPV:
// RJUMPV is unique as it has a variable sized operand.
@ -159,6 +159,8 @@ func eofCodeBitmapInternal(code, bits bitvec) bitvec {
// as possible.
numbits = uint8(end - pc)
}
case op == DUPN || op == SWAPN || op == EXCHANGE || op == EOFCREATE || op == RETURNCONTRACT:
numbits = 1
default:
// Op had no immediate operand, continue.
continue

View file

@ -17,6 +17,7 @@
package vm
import (
"errors"
"fmt"
"math"
"sort"
@ -25,6 +26,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256"
)
@ -570,11 +572,30 @@ func enableEOF(jt *JumpTable) {
maxStack: maxStack(0, 0),
undefined: true,
}
jt[CALL] = undefined
jt[CALLCODE] = undefined
jt[DELEGATECALL] = undefined
jt[STATICCALL] = undefined
jt[SELFDESTRUCT] = undefined
jt[JUMP] = undefined
jt[JUMPI] = undefined
jt[PC] = undefined
jt[CREATE] = undefined
jt[CREATE2] = undefined
jt[CODESIZE] = undefined
jt[CODECOPY] = undefined
jt[EXTCODESIZE] = undefined
jt[EXTCODECOPY] = undefined
jt[EXTCODEHASH] = undefined
jt[GAS] = undefined
// Allow 0xFE to terminate sections
jt[INVALID] = &operation{
execute: opUndefined,
constantGas: 0,
minStack: minStack(0, 0),
maxStack: maxStack(0, 0),
terminal: true,
}
// New opcodes
jt[RJUMP] = &operation{
@ -582,25 +603,28 @@ func enableEOF(jt *JumpTable) {
constantGas: GasQuickStep,
minStack: minStack(0, 0),
maxStack: maxStack(0, 0),
terminal: true,
immediate: 2,
}
jt[RJUMPI] = &operation{
execute: opRjumpi,
constantGas: GasFastishStep,
minStack: minStack(1, 0),
maxStack: maxStack(1, 0),
immediate: 2,
}
jt[RJUMPV] = &operation{
execute: opRjumpv,
constantGas: GasFastishStep,
minStack: minStack(1, 0),
maxStack: maxStack(1, 0),
immediate: 3, // at least 3, maybe more
}
jt[CALLF] = &operation{
execute: opCallf,
constantGas: GasFastStep,
minStack: minStack(0, 0),
maxStack: maxStack(0, 0),
immediate: 2,
}
jt[RETF] = &operation{
execute: opRetf,
@ -609,6 +633,143 @@ func enableEOF(jt *JumpTable) {
maxStack: maxStack(0, 0),
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.
@ -691,3 +852,314 @@ func opRetf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt
return nil, nil
>>>>>>> 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 (
"bytes"
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"io"
@ -38,7 +39,7 @@ const (
eof1Version = 1
maxInputItems = 127
maxOutputItems = 127
maxOutputItems = 128
maxStackHeight = 1023
maxContainerSections = 256
)
@ -56,7 +57,7 @@ var (
ErrMissingTerminator = errors.New("missing header terminator")
ErrTooManyInputs = 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")
ErrInvalidContainerSize = errors.New("invalid container size")
)
@ -83,8 +84,10 @@ func isEOFVersion1(code []byte) bool {
type Container struct {
Types []*FunctionMetadata
Code [][]byte
ContainerSections [][]byte
ContainerSections []*Container
ContainerCode [][]byte
Data []byte
DataSize int // might be less than len(Data)
}
// FunctionMetadata is an EOF function signature.
@ -109,15 +112,18 @@ func (c *Container) MarshalBinary() []byte {
for _, code := range c.Code {
b = binary.BigEndian.AppendUint16(b, uint16(len(code)))
}
var encodedContainer [][]byte
if len(c.ContainerSections) != 0 {
b = append(b, kindContainer)
b = binary.BigEndian.AppendUint16(b, uint16(len(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 = binary.BigEndian.AppendUint16(b, uint16(len(c.Data)))
b = binary.BigEndian.AppendUint16(b, uint16(c.DataSize))
b = append(b, 0) // terminator
// Write section contents.
@ -127,7 +133,7 @@ func (c *Container) MarshalBinary() []byte {
for _, code := range c.Code {
b = append(b, code...)
}
for _, section := range c.ContainerSections {
for _, section := range encodedContainer {
b = append(b, section...)
}
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))
}
// Parse container section header.
// Parse (optional) container section header.
var containerSizes []int
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 {
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
} 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.
@ -204,11 +208,12 @@ func (c *Container) UnmarshalBinary(b []byte) error {
if kind != kindData {
return fmt.Errorf("%w: found section %x instead", ErrMissingDataHeader, kind)
}
c.DataSize = dataSize
// Check for terminator.
offsetTerminator := offset + 3
if len(b) < offsetTerminator {
return io.ErrUnexpectedEOF
return fmt.Errorf("%w: invalid offset terminator", io.ErrUnexpectedEOF)
}
if b[offsetTerminator] != 0 {
return fmt.Errorf("%w: have %x", ErrMissingTerminator, b[offsetTerminator])
@ -219,7 +224,7 @@ func (c *Container) UnmarshalBinary(b []byte) error {
if len(containerSizes) != 0 {
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)
}
@ -243,7 +248,7 @@ func (c *Container) UnmarshalBinary(b []byte) error {
}
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)
}
c.Types = types
@ -265,19 +270,29 @@ func (c *Container) UnmarshalBinary(b []byte) error {
if len(containerSizes) > maxContainerSections {
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 {
if size == 0 {
if size == 0 || idx+size > len(b) {
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
}
c.ContainerSections = container
c.ContainerCode = containerCode
}
// Parse data section.
c.Data = b[idx : idx+dataSize]
end := min(idx+dataSize, len(b))
c.Data = b[idx:end]
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
// rule set.
func (c *Container) ValidateCode(jt *JumpTable) error {
for i, code := range c.Code {
if err := validateCode(code, i, c.Types, jt); err != nil {
visited := make(map[int]struct{})
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
}
}
@ -361,3 +408,42 @@ func sum(list []int) (s int) {
}
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
import (
"encoding/hex"
"fmt"
"reflect"
"testing"
@ -30,23 +32,24 @@ func TestEOFMarshaling(t *testing.T) {
}{
{
want: Container{
Types: []*FunctionMetadata{{Input: 0, Output: 0, MaxStackHeight: 1}},
Types: []*FunctionMetadata{{Input: 0, Output: 0x80, MaxStackHeight: 1}},
Code: [][]byte{common.Hex2Bytes("604200")},
Data: []byte{0x01, 0x02, 0x03},
DataSize: 3,
},
},
{
want: Container{
Types: []*FunctionMetadata{{Input: 0, Output: 0, MaxStackHeight: 1}},
Types: []*FunctionMetadata{{Input: 0, Output: 0x80, MaxStackHeight: 1}},
Code: [][]byte{common.Hex2Bytes("604200")},
ContainerSections: [][]byte{common.Hex2Bytes("604200")},
Data: []byte{0x01, 0x02, 0x03},
DataSize: 3,
},
},
{
want: Container{
Types: []*FunctionMetadata{
{Input: 0, Output: 0, MaxStackHeight: 1},
{Input: 0, Output: 0x80, MaxStackHeight: 1},
{Input: 2, Output: 3, MaxStackHeight: 4},
{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")
ErrInvalidEOFInitcode = errors.New("invalid eof initcode")
ErrNonceUintOverflow = errors.New("nonce uint64 overflow")
ErrInvalidNumberOfOutputs = errors.New("invalid number of outputs")
// errStopToken is an internal token indicating interpreter loop termination,
// never returned to outside callers.

View file

@ -95,6 +95,7 @@ type TxContext struct {
BlobHashes []common.Hash // Provides information for BLOBHASH
BlobFeeCap *big.Int // Is used to zero the blobbasefee if NoBaseFee is set
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
@ -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
contract := NewContract(caller, AccountRef(addrCopy), value, gas)
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
}
}
@ -310,7 +311,7 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
witness.AddCode(evm.StateDB.ResolveCode(addrCopy))
}
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
}
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'
// 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
if evm.Config.Tracer != nil {
// 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 {
addrCopy := addr
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
contract := NewContract(caller, AccountRef(caller.Address()), nil, gas).AsDelegate()
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))
}
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
}
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
// 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.
ret, err = evm.interpreter.Run(contract, input, true)
ret, err = evm.interpreter.Run(contract, input, true, false)
gas = contract.Gas
}
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.
isInitcodeEOF := hasEOFMagic(codeAndHash.code)
if evm.chainRules.IsShanghai {
if evm.chainRules.IsPrague {
if isInitcodeEOF {
// If the initcode is EOF, verify it is well-formed.
var c Container
@ -548,7 +552,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
}
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.
@ -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)
}
// 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
func (evm *EVM) ChainConfig() *params.ChainConfig { return evm.chainConfig }

View file

@ -22,6 +22,7 @@ import (
// Gas costs
const (
GasZeroStep uint64 = 0
GasQuickStep uint64 = 2
GasFastestStep uint64 = 3
GasFastishStep uint64 = 4

View file

@ -502,3 +502,23 @@ func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me
}
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.
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 {
temp.Clear()
} 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))
stack.push(x)
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 {
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(y)
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()
if actual.Cmp(expected) != 0 {
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))
stack.push(x)
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()
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 (
env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{})
stack = newstack()
scope = &ScopeContext{nil, stack, nil, 0, nil}
scope = &ScopeContext{nil, stack, nil, 0, nil, false}
evmInterpreter = NewEVMInterpreter(env)
)
@ -546,13 +546,13 @@ func TestOpMstore(t *testing.T) {
v := "abcdef00000000000000abba000000000deaf000000c0de00100000000133700"
stack.push(new(uint256.Int).SetBytes(common.Hex2Bytes(v)))
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 {
t.Fatalf("Mstore fail, got %v, expected %v", got, v)
}
stack.push(new(uint256.Int).SetUint64(0x1))
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" {
t.Fatalf("Mstore failed to overwrite previous value")
}
@ -576,7 +576,7 @@ func BenchmarkOpMstore(bench *testing.B) {
for i := 0; i < bench.N; i++ {
stack.push(value)
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}
contractRef = contractRef{caller}
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")
)
@ -639,7 +639,7 @@ func BenchmarkOpKeccak256(bench *testing.B) {
for i := 0; i < bench.N; i++ {
stack.push(uint256.NewInt(32))
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)
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 {
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
)
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 {
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)
}
// 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, " ", ""))
if have := mem.store; !bytes.Equal(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
GetCode(common.Address) []byte
SetCode(common.Address, []byte)
SetCodeEOF(common.Address, []byte)
GetCodeSize(common.Address) int
ResolveCodeHash(common.Address) common.Hash

View file

@ -45,6 +45,7 @@ type ScopeContext struct {
CodeSection uint64
ReturnStack []*ReturnContext
InitCodeMode bool
}
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
// considered a revert-and-consume-all-gas operation except for
// 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
in.evm.depth++
defer func() { in.evm.depth-- }()
@ -193,6 +194,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
Contract: contract,
CodeSection: 0,
ReturnStack: []*ReturnContext{{Section: 0, Pc: 0, StackHeight: 0}},
InitCodeMode: isInitCode,
}
// 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

View file

@ -48,6 +48,8 @@ type operation struct {
// terminal denotes if the instruction can be the final opcode in a code section
terminal bool
// immediate denotes how many immediate bytes the operation uses
immediate int
}
var (
@ -628,192 +630,224 @@ func newFrontierInstructionSet() JumpTable {
constantGas: GasFastestStep,
minStack: minStack(0, 1),
maxStack: maxStack(0, 1),
immediate: 1,
},
PUSH2: {
execute: makePush(2, 2),
constantGas: GasFastestStep,
minStack: minStack(0, 1),
maxStack: maxStack(0, 1),
immediate: 2,
},
PUSH3: {
execute: makePush(3, 3),
constantGas: GasFastestStep,
minStack: minStack(0, 1),
maxStack: maxStack(0, 1),
immediate: 3,
},
PUSH4: {
execute: makePush(4, 4),
constantGas: GasFastestStep,
minStack: minStack(0, 1),
maxStack: maxStack(0, 1),
immediate: 4,
},
PUSH5: {
execute: makePush(5, 5),
constantGas: GasFastestStep,
minStack: minStack(0, 1),
maxStack: maxStack(0, 1),
immediate: 5,
},
PUSH6: {
execute: makePush(6, 6),
constantGas: GasFastestStep,
minStack: minStack(0, 1),
maxStack: maxStack(0, 1),
immediate: 6,
},
PUSH7: {
execute: makePush(7, 7),
constantGas: GasFastestStep,
minStack: minStack(0, 1),
maxStack: maxStack(0, 1),
immediate: 7,
},
PUSH8: {
execute: makePush(8, 8),
constantGas: GasFastestStep,
minStack: minStack(0, 1),
maxStack: maxStack(0, 1),
immediate: 8,
},
PUSH9: {
execute: makePush(9, 9),
constantGas: GasFastestStep,
minStack: minStack(0, 1),
maxStack: maxStack(0, 1),
immediate: 9,
},
PUSH10: {
execute: makePush(10, 10),
constantGas: GasFastestStep,
minStack: minStack(0, 1),
maxStack: maxStack(0, 1),
immediate: 10,
},
PUSH11: {
execute: makePush(11, 11),
constantGas: GasFastestStep,
minStack: minStack(0, 1),
maxStack: maxStack(0, 1),
immediate: 11,
},
PUSH12: {
execute: makePush(12, 12),
constantGas: GasFastestStep,
minStack: minStack(0, 1),
maxStack: maxStack(0, 1),
immediate: 12,
},
PUSH13: {
execute: makePush(13, 13),
constantGas: GasFastestStep,
minStack: minStack(0, 1),
maxStack: maxStack(0, 1),
immediate: 13,
},
PUSH14: {
execute: makePush(14, 14),
constantGas: GasFastestStep,
minStack: minStack(0, 1),
maxStack: maxStack(0, 1),
immediate: 14,
},
PUSH15: {
execute: makePush(15, 15),
constantGas: GasFastestStep,
minStack: minStack(0, 1),
maxStack: maxStack(0, 1),
immediate: 15,
},
PUSH16: {
execute: makePush(16, 16),
constantGas: GasFastestStep,
minStack: minStack(0, 1),
maxStack: maxStack(0, 1),
immediate: 16,
},
PUSH17: {
execute: makePush(17, 17),
constantGas: GasFastestStep,
minStack: minStack(0, 1),
maxStack: maxStack(0, 1),
immediate: 17,
},
PUSH18: {
execute: makePush(18, 18),
constantGas: GasFastestStep,
minStack: minStack(0, 1),
maxStack: maxStack(0, 1),
immediate: 18,
},
PUSH19: {
execute: makePush(19, 19),
constantGas: GasFastestStep,
minStack: minStack(0, 1),
maxStack: maxStack(0, 1),
immediate: 19,
},
PUSH20: {
execute: makePush(20, 20),
constantGas: GasFastestStep,
minStack: minStack(0, 1),
maxStack: maxStack(0, 1),
immediate: 20,
},
PUSH21: {
execute: makePush(21, 21),
constantGas: GasFastestStep,
minStack: minStack(0, 1),
maxStack: maxStack(0, 1),
immediate: 21,
},
PUSH22: {
execute: makePush(22, 22),
constantGas: GasFastestStep,
minStack: minStack(0, 1),
maxStack: maxStack(0, 1),
immediate: 22,
},
PUSH23: {
execute: makePush(23, 23),
constantGas: GasFastestStep,
minStack: minStack(0, 1),
maxStack: maxStack(0, 1),
immediate: 23,
},
PUSH24: {
execute: makePush(24, 24),
constantGas: GasFastestStep,
minStack: minStack(0, 1),
maxStack: maxStack(0, 1),
immediate: 24,
},
PUSH25: {
execute: makePush(25, 25),
constantGas: GasFastestStep,
minStack: minStack(0, 1),
maxStack: maxStack(0, 1),
immediate: 25,
},
PUSH26: {
execute: makePush(26, 26),
constantGas: GasFastestStep,
minStack: minStack(0, 1),
maxStack: maxStack(0, 1),
immediate: 26,
},
PUSH27: {
execute: makePush(27, 27),
constantGas: GasFastestStep,
minStack: minStack(0, 1),
maxStack: maxStack(0, 1),
immediate: 27,
},
PUSH28: {
execute: makePush(28, 28),
constantGas: GasFastestStep,
minStack: minStack(0, 1),
maxStack: maxStack(0, 1),
immediate: 28,
},
PUSH29: {
execute: makePush(29, 29),
constantGas: GasFastestStep,
minStack: minStack(0, 1),
maxStack: maxStack(0, 1),
immediate: 29,
},
PUSH30: {
execute: makePush(30, 30),
constantGas: GasFastestStep,
minStack: minStack(0, 1),
maxStack: maxStack(0, 1),
immediate: 30,
},
PUSH31: {
execute: makePush(31, 31),
constantGas: GasFastestStep,
minStack: minStack(0, 1),
maxStack: maxStack(0, 1),
immediate: 31,
},
PUSH32: {
execute: makePush(32, 32),
constantGas: GasFastestStep,
minStack: minStack(0, 1),
maxStack: maxStack(0, 1),
immediate: 32,
},
DUP1: {
execute: makeDup(1),

View file

@ -64,6 +64,14 @@ func memoryCreate2(stack *Stack) (uint64, bool) {
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) {
x, overflow := calcMemSize64(stack.Back(5), stack.Back(6))
if overflow {

View file

@ -121,9 +121,6 @@ const (
TLOAD OpCode = 0x5c
TSTORE OpCode = 0x5d
MCOPY OpCode = 0x5e
RJUMP OpCode = 0x5c
RJUMPI OpCode = 0x5d
RJUMPV OpCode = 0x5e
PUSH0 OpCode = 0x5f
)
@ -212,10 +209,28 @@ const (
LOG4
)
// 0xb0 range - control flow ops.
// 0xd0 range - eof operations.
const (
CALLF = 0xb0
RETF = 0xb1
DATALOAD OpCode = 0xd0
DATALOADN OpCode = 0xd1
DATASIZE OpCode = 0xd2
DATACOPY OpCode = 0xd3
)
// 0xe0 range - eof operations.
const (
RJUMP OpCode = 0xe0
RJUMPI OpCode = 0xe1
RJUMPV OpCode = 0xe2
CALLF OpCode = 0xe3
RETF OpCode = 0xe4
JUMPF OpCode = 0xe5
DUPN OpCode = 0xe6
SWAPN OpCode = 0xe7
EXCHANGE OpCode = 0xe8
EOFCREATE OpCode = 0xec
TXCREATE OpCode = 0xed
RETURNCONTRACT OpCode = 0xee
)
// 0xf0 range - closures.
@ -227,7 +242,12 @@ const (
DELEGATECALL OpCode = 0xf4
CREATE2 OpCode = 0xf5
RETURNDATALOAD OpCode = 0xf7
EXTCALL OpCode = 0xf8
EXTDELEGATECALL OpCode = 0xf9
STATICCALL OpCode = 0xfa
EXTSTATICCALL OpCode = 0xfb
REVERT OpCode = 0xfd
INVALID OpCode = 0xfe
SELFDESTRUCT OpCode = 0xff
@ -314,10 +334,6 @@ var opCodeToString = [256]string{
TLOAD: "TLOAD",
TSTORE: "TSTORE",
MCOPY: "MCOPY",
// TODO (MariusVanDerWijden) reenable once moved
//RJUMP: "RJUMP",
//RJUMPI: "RJUMPI",
//RJUMPV: "RJUMPV",
PUSH0: "PUSH0",
// 0x60 range - pushes.
@ -397,9 +413,25 @@ var opCodeToString = [256]string{
LOG3: "LOG3",
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",
RETF: "RETF",
JUMPF: "JUMPF",
DUPN: "DUPN",
SWAPN: "SWAPN",
EXCHANGE: "EXCHANGE",
EOFCREATE: "EOFCREATE",
TXCREATE: "TXCREATE",
RETURNCONTRACT: "RETURNCONTRACT",
// 0xf0 range - closures.
CREATE: "CREATE",
@ -408,7 +440,13 @@ var opCodeToString = [256]string{
CALLCODE: "CALLCODE",
DELEGATECALL: "DELEGATECALL",
CREATE2: "CREATE2",
RETURNDATALOAD: "RETURNDATALOAD",
EXTCALL: "EXTCALL",
EXTDELEGATECALL: "EXTDELEGATECALL",
STATICCALL: "STATICCALL",
EXTSTATICCALL: "EXTSTATICCALL",
REVERT: "REVERT",
INVALID: "INVALID",
SELFDESTRUCT: "SELFDESTRUCT",
@ -563,8 +601,28 @@ var stringToOp = map[string]OpCode{
"LOG2": LOG2,
"LOG3": LOG3,
"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,
"CREATE2": CREATE2,
"RETURNDATALOAD": RETURNDATALOAD,
"EXTCALL": EXTCALL,
"EXTDELEGATECALL": EXTDELEGATECALL,
"EXTSTATICCALL": EXTSTATICCALL,
"CALL": CALL,
"RETURN": RETURN,
"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]
}
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) {
st.push(&st.data[st.len()-n])
}

View file

@ -23,20 +23,24 @@ import (
)
var (
ErrUndefinedInstruction = errors.New("undefined instrustion")
ErrUndefinedInstruction = errors.New("undefined instruction")
ErrTruncatedImmediate = errors.New("truncated immediate")
ErrInvalidSectionArgument = errors.New("invalid section argument")
ErrInvalidCallArgument = errors.New("callf into non-returning section")
ErrInvalidDataloadNArgument = errors.New("invalid dataloadN argument")
ErrInvalidJumpDest = errors.New("invalid jump destination")
ErrInvalidBackwardJump = errors.New("invalid backward jump")
ErrConflictingStack = errors.New("conflicting stack height")
ErrInvalidBranchCount = errors.New("invalid number of branches in jump table")
ErrInvalidOutputs = errors.New("invalid number of outputs")
ErrInvalidMaxStackHeight = errors.New("invalid max stack height")
ErrInvalidCodeTermination = errors.New("invalid code termination")
ErrEOFCreateWithTruncatedSection = errors.New("eofcreate with truncated section")
ErrUnreachableCode = errors.New("unreachable code")
)
// 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 (
i = 0
// 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
op OpCode
analysis bitvec
visited = make(map[int]struct{})
)
// This loop visits every single instruction and verifies:
// * 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++
op = OpCode(code[i])
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 {
case op >= PUSH1 && op <= PUSH32:
size := int(op - PUSH0)
if len(code) <= i+size {
return fmt.Errorf("%w: op %s, pos %d", ErrTruncatedImmediate, op, i)
case op == RJUMP || op == RJUMPI:
if err := checkDest(code, &analysis, i+1, i+3, len(code)); err != nil {
return visited, err
}
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
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
}
// Code sections may not "fall through" and require proper termination.
// Therefore, the last instruction must be considered terminal.
if !jt[op].terminal {
return fmt.Errorf("%w: end with %s, pos %d", ErrInvalidCodeTermination, op, i)
// Therefore, the last instruction must be considered terminal or RJUMP.
if !jt[op].terminal && op != RJUMP {
return visited, fmt.Errorf("%w: end with %s, pos %d", ErrInvalidCodeTermination, op, i)
}
if paths, err := validateControlFlow(code, section, metadata, jt); err != nil {
return err
if paths, err := validateControlFlow2(code, section, container.Types, jt); err != nil {
return visited, err
} else if paths != count {
// TODO(matt): return actual position of unreacable code
return ErrUnreachableCode
fmt.Printf("Paths: %v Count: %v\n", paths, count)
// 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.
@ -141,10 +163,11 @@ func validateControlFlow(code []byte, section int, metadata []*FunctionMetadata,
type item struct {
pos int
height int
backwards bool
}
var (
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)
)
for 0 < len(worklist) {
@ -152,21 +175,25 @@ func validateControlFlow(code []byte, section int, metadata []*FunctionMetadata,
idx = len(worklist) - 1
pos = worklist[idx].pos
height = worklist[idx].height
backwards = worklist[idx].backwards
)
worklist = worklist[:idx]
outer:
for pos < len(code) {
op := OpCode(code[pos])
// Check if pos has already be visited; if so, the stack heights should be the same.
if want, ok := heights[pos]; ok {
if height != want {
return 0, fmt.Errorf("%w: have %d, want %d", ErrConflictingStack, height, want)
}
if height == want {
// Already visited this path and stack height
// matches.
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
// Validate height for current op and update as needed.
@ -181,14 +208,15 @@ func validateControlFlow(code []byte, section int, metadata []*FunctionMetadata,
switch {
case op == CALLF:
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)
}
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)
}
height -= int(metadata[arg].Input)
height += int(metadata[arg].Output)
height -= int(newSection.Input)
height += int(newSection.Output)
pos += 3
case op == RETF:
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:
arg := parseInt16(code[pos+1:])
pos += 3 + arg
worklist = append(worklist, item{pos: pos, height: height, backwards: arg < 0})
break outer
case op == RJUMPI:
arg := parseInt16(code[pos+1:])
worklist = append(worklist, item{pos: pos + 3 + arg, height: height})
pos += 3
case op == RJUMPV:
count := int(code[pos+1])
count := int(code[pos+1]) + 1
for i := 0; i < count; i++ {
arg := parseInt16(code[pos+2+2*i:])
worklist = append(worklist, item{pos: pos + 2 + 2*count + arg, height: height})
}
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:
if op >= PUSH1 && op <= PUSH32 {
pos += 1 + int(op-PUSH0)
} else if jt[op].terminal {
break outer
if jt[op].immediate != 0 {
pos += jt[op].immediate + 1
} else {
// Simple op, no operand.
pos += 1
}
if jt[op].terminal {
break outer
}
}
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
import (
"encoding/binary"
"errors"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/params"
)
func TestValidateCode(t *testing.T) {
@ -118,7 +120,7 @@ func TestValidateCode(t *testing.T) {
code: []byte{
byte(PUSH0),
byte(RJUMPV),
byte(0x02),
byte(0x01),
byte(0x00),
byte(0x01),
byte(0x00),
@ -141,24 +143,25 @@ func TestValidateCode(t *testing.T) {
},
section: 0,
metadata: []*FunctionMetadata{{Input: 0, Output: 0, MaxStackHeight: 1}},
err: ErrInvalidBranchCount,
err: ErrTruncatedImmediate,
},
{
code: []byte{
byte(RJUMP), 0x00, 0x03,
byte(JUMPDEST),
byte(JUMPDEST), // this code is unreachable to forward jumps alone
byte(JUMPDEST),
byte(RETURN),
byte(PUSH1), 20,
byte(PUSH1), 39,
byte(PUSH1), 0x00,
byte(CODECOPY),
byte(DATACOPY),
byte(PUSH1), 20,
byte(PUSH1), 0x00,
byte(RJUMP), 0xff, 0xef,
},
section: 0,
metadata: []*FunctionMetadata{{Input: 0, Output: 0, MaxStackHeight: 3}},
err: ErrUnreachableCode,
},
{
code: []byte{
@ -170,7 +173,7 @@ func TestValidateCode(t *testing.T) {
byte(PUSH1), 20,
byte(PUSH1), 39,
byte(PUSH1), 0x00,
byte(CODECOPY),
byte(DATACOPY),
byte(PUSH1), 20,
byte(PUSH1), 0x00,
byte(RETURN),
@ -181,14 +184,14 @@ func TestValidateCode(t *testing.T) {
{
code: []byte{
byte(PUSH1), 1,
byte(RJUMPV), 0x02, 0x00, 0x03, 0xff, 0xf8,
byte(RJUMPV), 0x01, 0x00, 0x03, 0xff, 0xf8,
byte(JUMPDEST),
byte(JUMPDEST),
byte(STOP),
byte(PUSH1), 20,
byte(PUSH1), 39,
byte(PUSH1), 0x00,
byte(CODECOPY),
byte(DATACOPY),
byte(PUSH1), 20,
byte(PUSH1), 0x00,
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}},
},
} {
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) {
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.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)
// Rest gas assumes no refund
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)}
var index common.Hash
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 {
t.Fatal(err)
}

View file

@ -130,8 +130,9 @@ const (
DefaultElasticityMultiplier = 2 // Bounds the maximum gas limit an EIP-1559 block may have.
InitialBaseFee = 1000000000 // Initial base fee for EIP-1559 blocks.
MaxCodeSize = 24576 // Maximum bytecode to permit for a contract
MaxInitCodeSize = 2 * MaxCodeSize // Maximum initcode to permit in a creation transaction and create instructions
MaxCodeSize = 24576 // Maximum bytecode to permit for a contract.
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