From 80f7c6c2996ad47f70a5070c400b1fd87a20c59c Mon Sep 17 00:00:00 2001
From: Martin Holst Swende <martin@swende.se>
Date: Wed, 7 Jun 2017 17:09:08 +0200
Subject: [PATCH] cmd/evm: add --prestate, --sender, --json flags for fuzzing
 (#14476)

---
 cmd/evm/json_logger.go          | 60 +++++++++++++++++++++
 cmd/evm/main.go                 | 15 ++++++
 cmd/evm/runner.go               | 87 ++++++++++++++++++++++++------
 core/vm/gen_structlog.go        | 95 +++++++++++++++++++++++++++++++++
 core/vm/logger.go               | 52 +++++++++++++-----
 core/vm/runtime/runtime.go      | 12 ++---
 core/vm/runtime/runtime_test.go |  2 +-
 internal/ethapi/tracer.go       |  7 +++
 8 files changed, 295 insertions(+), 35 deletions(-)
 create mode 100644 cmd/evm/json_logger.go
 create mode 100644 core/vm/gen_structlog.go

diff --git a/cmd/evm/json_logger.go b/cmd/evm/json_logger.go
new file mode 100644
index 000000000..a84d5daeb
--- /dev/null
+++ b/cmd/evm/json_logger.go
@@ -0,0 +1,60 @@
+// Copyright 2017 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package main
+
+import (
+	"encoding/json"
+	"io"
+	"time"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/common/math"
+	"github.com/ethereum/go-ethereum/core/vm"
+)
+
+type JSONLogger struct {
+	encoder *json.Encoder
+}
+
+func NewJSONLogger(writer io.Writer) *JSONLogger {
+	return &JSONLogger{json.NewEncoder(writer)}
+}
+
+// CaptureState outputs state information on the logger.
+func (l *JSONLogger) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, contract *vm.Contract, depth int, err error) error {
+	return l.encoder.Encode(vm.StructLog{
+		Pc:      pc,
+		Op:      op,
+		Gas:     gas + cost,
+		GasCost: cost,
+		Memory:  memory.Data(),
+		Stack:   stack.Data(),
+		Storage: nil,
+		Depth:   depth,
+		Err:     err,
+	})
+}
+
+// CaptureEnd is triggered at end of execution.
+func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration) error {
+	type endLog struct {
+		Output  string              `json:"output"`
+		GasUsed math.HexOrDecimal64 `json:"gasUsed"`
+		Time    time.Duration       `json:"time"`
+	}
+	return l.encoder.Encode(endLog{common.Bytes2Hex(output), math.HexOrDecimal64(gasUsed), t})
+}
diff --git a/cmd/evm/main.go b/cmd/evm/main.go
index e85d31d03..48a1b92cb 100644
--- a/cmd/evm/main.go
+++ b/cmd/evm/main.go
@@ -90,6 +90,18 @@ var (
 		Name:  "nogasmetering",
 		Usage: "disable gas metering",
 	}
+	GenesisFlag = cli.StringFlag{
+		Name:  "prestate",
+		Usage: "JSON file with prestate (genesis) config",
+	}
+	MachineFlag = cli.BoolFlag{
+		Name:  "json",
+		Usage: "output trace logs in machine readable format (json)",
+	}
+	SenderFlag = cli.StringFlag{
+		Name:  "sender",
+		Usage: "The transaction origin",
+	}
 )
 
 func init() {
@@ -108,6 +120,9 @@ func init() {
 		MemProfileFlag,
 		CPUProfileFlag,
 		StatDumpFlag,
+		GenesisFlag,
+		MachineFlag,
+		SenderFlag,
 	}
 	app.Commands = []cli.Command{
 		compileCommand,
diff --git a/cmd/evm/runner.go b/cmd/evm/runner.go
index 22538d7b1..b1fb8998f 100644
--- a/cmd/evm/runner.go
+++ b/cmd/evm/runner.go
@@ -18,6 +18,7 @@ package main
 
 import (
 	"bytes"
+	"encoding/json"
 	"fmt"
 	"io/ioutil"
 	"os"
@@ -29,11 +30,13 @@ import (
 	"github.com/ethereum/go-ethereum/cmd/evm/internal/compiler"
 	"github.com/ethereum/go-ethereum/cmd/utils"
 	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/core"
 	"github.com/ethereum/go-ethereum/core/state"
 	"github.com/ethereum/go-ethereum/core/vm"
 	"github.com/ethereum/go-ethereum/core/vm/runtime"
 	"github.com/ethereum/go-ethereum/ethdb"
 	"github.com/ethereum/go-ethereum/log"
+	"github.com/ethereum/go-ethereum/params"
 	cli "gopkg.in/urfave/cli.v1"
 )
 
@@ -45,17 +48,59 @@ var runCommand = cli.Command{
 	Description: `The run command runs arbitrary EVM code.`,
 }
 
+// readGenesis will read the given JSON format genesis file and return
+// the initialized Genesis structure
+func readGenesis(genesisPath string) *core.Genesis {
+	// Make sure we have a valid genesis JSON
+	//genesisPath := ctx.Args().First()
+	if len(genesisPath) == 0 {
+		utils.Fatalf("Must supply path to genesis JSON file")
+	}
+	file, err := os.Open(genesisPath)
+	if err != nil {
+		utils.Fatalf("Failed to read genesis file: %v", err)
+	}
+	defer file.Close()
+
+	genesis := new(core.Genesis)
+	if err := json.NewDecoder(file).Decode(genesis); err != nil {
+		utils.Fatalf("invalid genesis file: %v", err)
+	}
+	return genesis
+}
+
 func runCmd(ctx *cli.Context) error {
 	glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false)))
 	glogger.Verbosity(log.Lvl(ctx.GlobalInt(VerbosityFlag.Name)))
 	log.Root().SetHandler(glogger)
 
 	var (
-		db, _      = ethdb.NewMemDatabase()
-		statedb, _ = state.New(common.Hash{}, db)
-		sender     = common.StringToAddress("sender")
-		logger     = vm.NewStructLogger(nil)
+		tracer      vm.Tracer
+		debugLogger *vm.StructLogger
+		statedb     *state.StateDB
+		chainConfig *params.ChainConfig
+		sender      = common.StringToAddress("sender")
 	)
+	if ctx.GlobalBool(MachineFlag.Name) {
+		tracer = NewJSONLogger(os.Stdout)
+	} else if ctx.GlobalBool(DebugFlag.Name) {
+		debugLogger = vm.NewStructLogger(nil)
+		tracer = debugLogger
+	} else {
+		debugLogger = vm.NewStructLogger(nil)
+	}
+	if ctx.GlobalString(GenesisFlag.Name) != "" {
+		gen := readGenesis(ctx.GlobalString(GenesisFlag.Name))
+		_, statedb = gen.ToBlock()
+		chainConfig = gen.Config
+	} else {
+		var db, _ = ethdb.NewMemDatabase()
+		statedb, _ = state.New(common.Hash{}, db)
+	}
+	if ctx.GlobalString(SenderFlag.Name) != "" {
+		sender = common.HexToAddress(ctx.GlobalString(SenderFlag.Name))
+	}
+
 	statedb.CreateAccount(sender)
 
 	var (
@@ -95,16 +140,16 @@ func runCmd(ctx *cli.Context) error {
 		}
 		code = common.Hex2Bytes(string(bytes.TrimRight(hexcode, "\n")))
 	}
-
+	initialGas := ctx.GlobalUint64(GasFlag.Name)
 	runtimeConfig := runtime.Config{
 		Origin:   sender,
 		State:    statedb,
-		GasLimit: ctx.GlobalUint64(GasFlag.Name),
+		GasLimit: initialGas,
 		GasPrice: utils.GlobalBig(ctx, PriceFlag.Name),
 		Value:    utils.GlobalBig(ctx, ValueFlag.Name),
 		EVMConfig: vm.Config{
-			Tracer:             logger,
-			Debug:              ctx.GlobalBool(DebugFlag.Name),
+			Tracer:             tracer,
+			Debug:              ctx.GlobalBool(DebugFlag.Name) || ctx.GlobalBool(MachineFlag.Name),
 			DisableGasMetering: ctx.GlobalBool(DisableGasMeteringFlag.Name),
 		},
 	}
@@ -122,15 +167,19 @@ func runCmd(ctx *cli.Context) error {
 		defer pprof.StopCPUProfile()
 	}
 
+	if chainConfig != nil {
+		runtimeConfig.ChainConfig = chainConfig
+	}
 	tstart := time.Now()
+	var leftOverGas uint64
 	if ctx.GlobalBool(CreateFlag.Name) {
 		input := append(code, common.Hex2Bytes(ctx.GlobalString(InputFlag.Name))...)
-		ret, _, err = runtime.Create(input, &runtimeConfig)
+		ret, _, leftOverGas, err = runtime.Create(input, &runtimeConfig)
 	} else {
 		receiver := common.StringToAddress("receiver")
 		statedb.SetCode(receiver, code)
 
-		ret, err = runtime.Call(receiver, common.Hex2Bytes(ctx.GlobalString(InputFlag.Name)), &runtimeConfig)
+		ret, leftOverGas, err = runtime.Call(receiver, common.Hex2Bytes(ctx.GlobalString(InputFlag.Name)), &runtimeConfig)
 	}
 	execTime := time.Since(tstart)
 
@@ -153,8 +202,10 @@ func runCmd(ctx *cli.Context) error {
 	}
 
 	if ctx.GlobalBool(DebugFlag.Name) {
-		fmt.Fprintln(os.Stderr, "#### TRACE ####")
-		vm.WriteTrace(os.Stderr, logger.StructLogs())
+		if debugLogger != nil {
+			fmt.Fprintln(os.Stderr, "#### TRACE ####")
+			vm.WriteTrace(os.Stderr, debugLogger.StructLogs())
+		}
 		fmt.Fprintln(os.Stderr, "#### LOGS ####")
 		vm.WriteLogs(os.Stderr, statedb.Logs())
 	}
@@ -167,14 +218,18 @@ heap objects:       %d
 allocations:        %d
 total allocations:  %d
 GC calls:           %d
+Gas used:           %d
 
-`, execTime, mem.HeapObjects, mem.Alloc, mem.TotalAlloc, mem.NumGC)
+`, execTime, mem.HeapObjects, mem.Alloc, mem.TotalAlloc, mem.NumGC, initialGas-leftOverGas)
+	}
+	if tracer != nil {
+		tracer.CaptureEnd(ret, initialGas-leftOverGas, execTime)
+	} else {
+		fmt.Printf("0x%x\n", ret)
 	}
 
-	fmt.Printf("0x%x", ret)
 	if err != nil {
-		fmt.Printf(" error: %v", err)
+		fmt.Printf(" error: %v\n", err)
 	}
-	fmt.Println()
 	return nil
 }
diff --git a/core/vm/gen_structlog.go b/core/vm/gen_structlog.go
new file mode 100644
index 000000000..1c86b2256
--- /dev/null
+++ b/core/vm/gen_structlog.go
@@ -0,0 +1,95 @@
+// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
+
+package vm
+
+import (
+	"encoding/json"
+	"math/big"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/common/hexutil"
+	"github.com/ethereum/go-ethereum/common/math"
+)
+
+func (s StructLog) MarshalJSON() ([]byte, error) {
+	type StructLog struct {
+		Pc         uint64                      `json:"pc"`
+		Op         OpCode                      `json:"op"`
+		Gas        math.HexOrDecimal64         `json:"gas"`
+		GasCost    math.HexOrDecimal64         `json:"gasCost"`
+		Memory     hexutil.Bytes               `json:"memory"`
+		Stack      []*math.HexOrDecimal256     `json:"stack"`
+		Storage    map[common.Hash]common.Hash `json:"-"`
+		Depth      int                         `json:"depth"`
+		Err        error                       `json:"error"`
+		OpName     string                      `json:"opName"`
+		MemorySize int                         `json:"memSize"`
+	}
+	var enc StructLog
+	enc.Pc = s.Pc
+	enc.Op = s.Op
+	enc.Gas = math.HexOrDecimal64(s.Gas)
+	enc.GasCost = math.HexOrDecimal64(s.GasCost)
+	enc.Memory = s.Memory
+	if s.Stack != nil {
+		enc.Stack = make([]*math.HexOrDecimal256, len(s.Stack))
+		for k, v := range s.Stack {
+			enc.Stack[k] = (*math.HexOrDecimal256)(v)
+		}
+	}
+	enc.Storage = s.Storage
+	enc.Depth = s.Depth
+	enc.Err = s.Err
+	enc.OpName = s.OpName()
+	enc.MemorySize = s.MemorySize()
+	return json.Marshal(&enc)
+}
+
+func (s *StructLog) UnmarshalJSON(input []byte) error {
+	type StructLog struct {
+		Pc      *uint64                     `json:"pc"`
+		Op      *OpCode                     `json:"op"`
+		Gas     *math.HexOrDecimal64        `json:"gas"`
+		GasCost *math.HexOrDecimal64        `json:"gasCost"`
+		Memory  hexutil.Bytes               `json:"memory"`
+		Stack   []*math.HexOrDecimal256     `json:"stack"`
+		Storage map[common.Hash]common.Hash `json:"-"`
+		Depth   *int                        `json:"depth"`
+		Err     *error                      `json:"error"`
+	}
+	var dec StructLog
+	if err := json.Unmarshal(input, &dec); err != nil {
+		return err
+	}
+	if dec.Pc != nil {
+		s.Pc = *dec.Pc
+	}
+	if dec.Op != nil {
+		s.Op = *dec.Op
+	}
+	if dec.Gas != nil {
+		s.Gas = uint64(*dec.Gas)
+	}
+	if dec.GasCost != nil {
+		s.GasCost = uint64(*dec.GasCost)
+	}
+	if dec.Memory != nil {
+		s.Memory = dec.Memory
+	}
+	if dec.Stack != nil {
+		s.Stack = make([]*big.Int, len(dec.Stack))
+		for k, v := range dec.Stack {
+			s.Stack[k] = (*big.Int)(v)
+		}
+	}
+	if dec.Storage != nil {
+		s.Storage = dec.Storage
+	}
+	if dec.Depth != nil {
+		s.Depth = *dec.Depth
+	}
+	if dec.Err != nil {
+		s.Err = *dec.Err
+	}
+	return nil
+}
diff --git a/core/vm/logger.go b/core/vm/logger.go
index 825025b05..405ab169c 100644
--- a/core/vm/logger.go
+++ b/core/vm/logger.go
@@ -21,8 +21,10 @@ import (
 	"fmt"
 	"io"
 	"math/big"
+	"time"
 
 	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/common/hexutil"
 	"github.com/ethereum/go-ethereum/common/math"
 	"github.com/ethereum/go-ethereum/core/types"
 )
@@ -47,18 +49,38 @@ type LogConfig struct {
 	Limit          int  // maximum length of output, but zero means unlimited
 }
 
+//go:generate gencodec -type StructLog -field-override structLogMarshaling -out gen_structlog.go
+
 // StructLog is emitted to the EVM each cycle and lists information about the current internal state
 // prior to the execution of the statement.
 type StructLog struct {
-	Pc      uint64
-	Op      OpCode
-	Gas     uint64
-	GasCost uint64
-	Memory  []byte
-	Stack   []*big.Int
-	Storage map[common.Hash]common.Hash
-	Depth   int
-	Err     error
+	Pc      uint64                      `json:"pc"`
+	Op      OpCode                      `json:"op"`
+	Gas     uint64                      `json:"gas"`
+	GasCost uint64                      `json:"gasCost"`
+	Memory  []byte                      `json:"memory"`
+	Stack   []*big.Int                  `json:"stack"`
+	Storage map[common.Hash]common.Hash `json:"-"`
+	Depth   int                         `json:"depth"`
+	Err     error                       `json:"error"`
+}
+
+// overrides for gencodec
+type structLogMarshaling struct {
+	Stack      []*math.HexOrDecimal256
+	Gas        math.HexOrDecimal64
+	GasCost    math.HexOrDecimal64
+	Memory     hexutil.Bytes
+	OpName     string `json:"opName"`
+	MemorySize int    `json:"memSize"`
+}
+
+func (s *StructLog) OpName() string {
+	return s.Op.String()
+}
+
+func (s *StructLog) MemorySize() int {
+	return len(s.Memory)
 }
 
 // Tracer is used to collect execution traces from an EVM transaction
@@ -68,6 +90,7 @@ type StructLog struct {
 // if you need to retain them beyond the current call.
 type Tracer interface {
 	CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error
+	CaptureEnd(output []byte, gasUsed uint64, t time.Duration) error
 }
 
 // StructLogger is an EVM state logger and implements Tracer.
@@ -82,7 +105,7 @@ type StructLogger struct {
 	changedValues map[common.Address]Storage
 }
 
-// NewLogger returns a new logger
+// NewStructLogger returns a new logger
 func NewStructLogger(cfg *LogConfig) *StructLogger {
 	logger := &StructLogger{
 		changedValues: make(map[common.Address]Storage),
@@ -93,9 +116,9 @@ func NewStructLogger(cfg *LogConfig) *StructLogger {
 	return logger
 }
 
-// captureState logs a new structured log message and pushes it out to the environment
+// CaptureState logs a new structured log message and pushes it out to the environment
 //
-// captureState also tracks SSTORE ops to track dirty values.
+// CaptureState also tracks SSTORE ops to track dirty values.
 func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error {
 	// check if already accumulated the specified number of logs
 	if l.cfg.Limit != 0 && l.cfg.Limit <= len(l.logs) {
@@ -164,6 +187,11 @@ func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost ui
 	return nil
 }
 
+func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration) error {
+	fmt.Printf("0x%x", output)
+	return nil
+}
+
 // StructLogs returns a list of captured log entries
 func (l *StructLogger) StructLogs() []StructLog {
 	return l.logs
diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go
index 94265626f..aa386a995 100644
--- a/core/vm/runtime/runtime.go
+++ b/core/vm/runtime/runtime.go
@@ -125,7 +125,7 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) {
 }
 
 // Create executes the code using the EVM create method
-func Create(input []byte, cfg *Config) ([]byte, common.Address, error) {
+func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) {
 	if cfg == nil {
 		cfg = new(Config)
 	}
@@ -141,13 +141,13 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, error) {
 	)
 
 	// Call the code with the given configuration.
-	code, address, _, err := vmenv.Create(
+	code, address, leftOverGas, err := vmenv.Create(
 		sender,
 		input,
 		cfg.GasLimit,
 		cfg.Value,
 	)
-	return code, address, err
+	return code, address, leftOverGas, err
 }
 
 // Call executes the code given by the contract's address. It will return the
@@ -155,14 +155,14 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, error) {
 //
 // Call, unlike Execute, requires a config and also requires the State field to
 // be set.
-func Call(address common.Address, input []byte, cfg *Config) ([]byte, error) {
+func Call(address common.Address, input []byte, cfg *Config) ([]byte, uint64, error) {
 	setDefaults(cfg)
 
 	vmenv := NewEnv(cfg, cfg.State)
 
 	sender := cfg.State.GetOrNewStateObject(cfg.Origin)
 	// Call the code with the given configuration.
-	ret, _, err := vmenv.Call(
+	ret, leftOverGas, err := vmenv.Call(
 		sender,
 		address,
 		input,
@@ -170,5 +170,5 @@ func Call(address common.Address, input []byte, cfg *Config) ([]byte, error) {
 		cfg.Value,
 	)
 
-	return ret, err
+	return ret, leftOverGas, err
 }
diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go
index fe39e97a0..7f40770d2 100644
--- a/core/vm/runtime/runtime_test.go
+++ b/core/vm/runtime/runtime_test.go
@@ -106,7 +106,7 @@ func TestCall(t *testing.T) {
 		byte(vm.RETURN),
 	})
 
-	ret, err := Call(address, nil, &Config{State: state})
+	ret, _, err := Call(address, nil, &Config{State: state})
 	if err != nil {
 		t.Fatal("didn't expect error", err)
 	}
diff --git a/internal/ethapi/tracer.go b/internal/ethapi/tracer.go
index d34363564..fc66839ea 100644
--- a/internal/ethapi/tracer.go
+++ b/internal/ethapi/tracer.go
@@ -21,6 +21,7 @@ import (
 	"errors"
 	"fmt"
 	"math/big"
+	"time"
 
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/common/hexutil"
@@ -344,6 +345,12 @@ func (jst *JavascriptTracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode,
 	return nil
 }
 
+// CaptureEnd is called after the call finishes
+func (jst *JavascriptTracer) CaptureEnd(output []byte, gasUsed uint64, t time.Duration) error {
+	//TODO! @Arachnid please figure out of there's anything we can use this method for
+	return nil
+}
+
 // GetResult calls the Javascript 'result' function and returns its value, or any accumulated error
 func (jst *JavascriptTracer) GetResult() (result interface{}, err error) {
 	if jst.err != nil {
-- 
GitLab