mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-19 10:22:23 +00:00
core/types: implement mega-eof spec
This commit is contained in:
parent
1dccd90224
commit
07db5b86b1
31 changed files with 1787 additions and 382 deletions
|
|
@ -23,8 +23,11 @@ import (
|
|||
"errors"
|
||||
"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
65
cmd/eofdump/main.go
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/ethereum/go-ethereum/internal/debug"
|
||||
"github.com/ethereum/go-ethereum/internal/flags"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var app = flags.NewApp("the evm command line interface")
|
||||
|
||||
var (
|
||||
RefTestFlag = &cli.StringFlag{
|
||||
Name: "test",
|
||||
Usage: "Path to EOF validation reference test.",
|
||||
}
|
||||
HexFlag = &cli.StringFlag{
|
||||
Name: "hex",
|
||||
Usage: "single container data parse and validation",
|
||||
}
|
||||
)
|
||||
|
||||
var eofParserCommand = &cli.Command{
|
||||
Name: "eofparser",
|
||||
Aliases: []string{"eof"},
|
||||
Usage: "parses hex eof container and returns validation errors (if any)",
|
||||
Action: eofParser,
|
||||
Flags: []cli.Flag{
|
||||
HexFlag,
|
||||
RefTestFlag,
|
||||
},
|
||||
}
|
||||
|
||||
var eofDumpCommand = &cli.Command{
|
||||
Name: "eofdump",
|
||||
Usage: "parses hex eof container",
|
||||
Action: eofDump,
|
||||
Flags: []cli.Flag{
|
||||
HexFlag,
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
app.Commands = []*cli.Command{
|
||||
eofParserCommand,
|
||||
eofDumpCommand,
|
||||
}
|
||||
app.Before = func(ctx *cli.Context) error {
|
||||
flags.MigrateGlobalFlags(ctx)
|
||||
return debug.Setup(ctx)
|
||||
}
|
||||
app.After = func(ctx *cli.Context) error {
|
||||
debug.Exit()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
|
@ -141,10 +141,6 @@ var (
|
|||
Usage: "enable return data output",
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
||||
|
|
|
|||
|
|
@ -426,7 +426,6 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error {
|
|||
return err
|
||||
}
|
||||
}
|
||||
|
||||
case SetCodeTxType:
|
||||
var itx SetCodeTx
|
||||
inner = &itx
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
474
core/vm/eips.go
474
core/vm/eips.go
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
134
core/vm/eof.go
134
core/vm/eof.go
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import (
|
|||
|
||||
// Gas costs
|
||||
const (
|
||||
GasZeroStep uint64 = 0
|
||||
GasQuickStep uint64 = 2
|
||||
GasFastestStep uint64 = 3
|
||||
GasFastishStep uint64 = 4
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
204
core/vm/validate_linear.go
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
package vm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
type bounds struct {
|
||||
min int
|
||||
max int
|
||||
}
|
||||
|
||||
func validateControlFlow2(code []byte, section int, metadata []*FunctionMetadata, jt *JumpTable) (int, error) {
|
||||
var (
|
||||
stackBounds = make(map[int]*bounds)
|
||||
maxStackHeight = int(metadata[section].Input)
|
||||
debugging = !true
|
||||
)
|
||||
|
||||
setBounds := func(pos, min, maxi int) *bounds {
|
||||
stackBounds[pos] = &bounds{min, maxi}
|
||||
maxStackHeight = max(maxStackHeight, maxi)
|
||||
return stackBounds[pos]
|
||||
}
|
||||
// set the initial stack bounds
|
||||
setBounds(0, int(metadata[section].Input), int(metadata[section].Input))
|
||||
|
||||
for pos := 0; pos < len(code); pos++ {
|
||||
op := OpCode(code[pos])
|
||||
currentBounds := stackBounds[pos]
|
||||
if currentBounds == nil {
|
||||
fmt.Printf("Stack bounds not set: %v at %v \n", op, pos)
|
||||
return 0, ErrUnreachableCode
|
||||
}
|
||||
|
||||
if debugging {
|
||||
fmt.Println(pos, op, maxStackHeight, currentBounds)
|
||||
}
|
||||
|
||||
var (
|
||||
currentStackMax = currentBounds.max
|
||||
currentStackMin = currentBounds.min
|
||||
)
|
||||
|
||||
switch op {
|
||||
case CALLF:
|
||||
arg, _ := parseUint16(code[pos+1:])
|
||||
newSection := metadata[arg]
|
||||
if want, have := int(newSection.Input), currentBounds.min; want > have {
|
||||
return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos)
|
||||
}
|
||||
if have, limit := currentBounds.max+int(newSection.MaxStackHeight)-int(newSection.Input), int(params.StackLimit); have > limit {
|
||||
return 0, fmt.Errorf("%w: at pos %d", ErrStackOverflow{stackLen: have, limit: limit}, pos)
|
||||
}
|
||||
change := int(newSection.Output) - int(newSection.Input)
|
||||
currentStackMax += change
|
||||
currentStackMin += change
|
||||
case RETF:
|
||||
if currentBounds.max != currentBounds.min {
|
||||
return 0, fmt.Errorf("%w: max %d, min %d, at pos %d", ErrInvalidNumberOfOutputs, currentBounds.max, currentBounds.min, pos)
|
||||
}
|
||||
if have, want := int(metadata[section].Output), currentBounds.min; have != want {
|
||||
return 0, fmt.Errorf("%w: have %d, want %d, at pos %d", ErrInvalidOutputs, have, want, pos)
|
||||
}
|
||||
case JUMPF:
|
||||
arg, _ := parseUint16(code[pos+1:])
|
||||
newSection := metadata[arg]
|
||||
if have, limit := currentBounds.max+int(newSection.MaxStackHeight)-int(newSection.Input), int(params.StackLimit); have > limit {
|
||||
return 0, fmt.Errorf("%w: at pos %d", ErrStackOverflow{stackLen: have, limit: limit}, pos)
|
||||
}
|
||||
if newSection.Output == 0x80 {
|
||||
if want, have := int(newSection.Input), currentBounds.min; want > have {
|
||||
return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos)
|
||||
}
|
||||
} else {
|
||||
if currentBounds.max != currentBounds.min {
|
||||
return 0, fmt.Errorf("%w: max %d, min %d, at pos %d", ErrInvalidNumberOfOutputs, currentBounds.max, currentBounds.min, pos)
|
||||
}
|
||||
if have, want := currentBounds.max, int(metadata[section].Output)+int(newSection.Input)-int(newSection.Output); have != want {
|
||||
return 0, fmt.Errorf("%w: at pos %d", ErrInvalidNumberOfOutputs, pos)
|
||||
}
|
||||
}
|
||||
case DUPN:
|
||||
arg := int(code[pos+1]) + 1
|
||||
if want, have := arg, currentBounds.min; want > have {
|
||||
return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos)
|
||||
}
|
||||
case SWAPN:
|
||||
arg := int(code[pos+1]) + 1
|
||||
if want, have := arg+1, currentBounds.min; want > have {
|
||||
return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos)
|
||||
}
|
||||
case EXCHANGE:
|
||||
arg := int(code[pos+1])
|
||||
n := arg>>4 + 1
|
||||
m := arg&0x0f + 1
|
||||
if want, have := n+m+1, currentBounds.min; want > have {
|
||||
return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos)
|
||||
}
|
||||
default:
|
||||
if want, have := jt[op].minStack, currentBounds.min; want > have {
|
||||
return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos)
|
||||
}
|
||||
}
|
||||
|
||||
if !jt[op].terminal && op != CALLF {
|
||||
change := int(params.StackLimit) - jt[op].maxStack
|
||||
currentStackMax += change
|
||||
currentStackMin += change
|
||||
}
|
||||
|
||||
var next []int
|
||||
switch op {
|
||||
case RJUMP:
|
||||
nextPos := pos + 2 + parseInt16(code[pos+1:])
|
||||
next = append(next, nextPos)
|
||||
// We set the stack bounds of the destination
|
||||
// and skip the argument
|
||||
if nextPos+1 < pos {
|
||||
nextBounds, ok := stackBounds[nextPos+1]
|
||||
if !ok {
|
||||
return 0, ErrInvalidBackwardJump
|
||||
}
|
||||
if nextBounds.max != currentStackMax || nextBounds.min != currentStackMin {
|
||||
return 0, ErrInvalidMaxStackHeight
|
||||
}
|
||||
}
|
||||
setBounds(next[0]+1, currentStackMin, currentStackMax)
|
||||
case RJUMPI:
|
||||
arg := parseInt16(code[pos+1:])
|
||||
next = append(next, pos+2)
|
||||
next = append(next, pos+2+arg)
|
||||
case RJUMPV:
|
||||
count := int(code[pos+1]) + 1
|
||||
next = append(next, pos+1+2*count)
|
||||
for i := 0; i < count; i++ {
|
||||
arg := parseInt16(code[pos+2+2*i:])
|
||||
next = append(next, pos+1+2*count+arg)
|
||||
}
|
||||
default:
|
||||
if jt[op].immediate != 0 {
|
||||
next = append(next, pos+jt[op].immediate)
|
||||
} else {
|
||||
// Simple op, no operand.
|
||||
next = append(next, pos)
|
||||
}
|
||||
}
|
||||
if debugging {
|
||||
fmt.Println(next)
|
||||
}
|
||||
|
||||
if op != RJUMP && !jt[op].terminal {
|
||||
for _, instr := range next {
|
||||
nextPC := instr + 1
|
||||
if nextPC >= len(code) {
|
||||
return 0, fmt.Errorf("%w: end with %s, pos %d", ErrInvalidCodeTermination, op, pos)
|
||||
}
|
||||
nextOP := code[nextPC]
|
||||
if nextPC > pos {
|
||||
// target reached via forward jump or seq flow
|
||||
nextBounds, ok := stackBounds[nextPC]
|
||||
if !ok {
|
||||
setBounds(nextPC, currentStackMin, currentStackMax)
|
||||
} else {
|
||||
setBounds(nextPC, min(nextBounds.min, currentStackMin), max(nextBounds.max, currentStackMax))
|
||||
}
|
||||
} else {
|
||||
// target reached via backwards jump
|
||||
nextBounds, ok := stackBounds[nextPC]
|
||||
if !ok {
|
||||
return 0, ErrInvalidBackwardJump
|
||||
}
|
||||
change := int(params.StackLimit) - jt[nextOP].maxStack + jt[nextOP].minStack
|
||||
if have, want := nextBounds.max+change, currentBounds.max; have != want {
|
||||
fmt.Println(nextPC, have, want, change)
|
||||
return 0, fmt.Errorf("%w want %d as max got %d at pos %d,", ErrInvalidBackwardJump, want, have, pos)
|
||||
}
|
||||
if have, want := nextBounds.min+change, currentBounds.min; have != want {
|
||||
return 0, fmt.Errorf("%w want %d as min got %d at pos %d,", ErrInvalidBackwardJump, want, have, pos)
|
||||
}
|
||||
if currentStackMax != nextBounds.max {
|
||||
return 0, fmt.Errorf("%w want %d as current max got %d at pos %d,", ErrInvalidBackwardJump, currentStackMax, nextBounds.max, pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if op == RJUMP {
|
||||
pos += 2 // skip the immediate
|
||||
} else {
|
||||
pos = next[0]
|
||||
}
|
||||
|
||||
}
|
||||
if maxStackHeight >= int(params.StackLimit) {
|
||||
return 0, ErrStackOverflow{maxStackHeight, int(params.StackLimit)}
|
||||
}
|
||||
if maxStackHeight != int(metadata[section].MaxStackHeight) {
|
||||
fmt.Print(maxStackHeight, metadata[section].MaxStackHeight)
|
||||
return 0, fmt.Errorf("%w in code section %d: have %d, want %d", ErrInvalidMaxStackHeight, section, maxStackHeight, metadata[section].MaxStackHeight)
|
||||
}
|
||||
return len(stackBounds), nil
|
||||
}
|
||||
|
|
@ -17,10 +17,12 @@
|
|||
package vm
|
||||
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue