1// Copyright 2021 The go-ethereum Authors
2// This file is part of the go-ethereum library.
3//
4// The go-ethereum library is free software: you can redistribute it and/or modify
5// it under the terms of the GNU Lesser General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// The go-ethereum library is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU Lesser General Public License for more details.
13//
14// You should have received a copy of the GNU Lesser General Public License
15// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16
17package tracetest
18
19import (
20	"encoding/json"
21	"io/ioutil"
22	"math/big"
23	"path/filepath"
24	"reflect"
25	"strings"
26	"testing"
27	"unicode"
28
29	"github.com/ethereum/go-ethereum/common"
30	"github.com/ethereum/go-ethereum/common/hexutil"
31	"github.com/ethereum/go-ethereum/common/math"
32	"github.com/ethereum/go-ethereum/core"
33	"github.com/ethereum/go-ethereum/core/rawdb"
34	"github.com/ethereum/go-ethereum/core/types"
35	"github.com/ethereum/go-ethereum/core/vm"
36	"github.com/ethereum/go-ethereum/crypto"
37	"github.com/ethereum/go-ethereum/eth/tracers"
38	"github.com/ethereum/go-ethereum/params"
39	"github.com/ethereum/go-ethereum/rlp"
40	"github.com/ethereum/go-ethereum/tests"
41
42	// Force-load native and js pacakges, to trigger registration
43	_ "github.com/ethereum/go-ethereum/eth/tracers/js"
44	_ "github.com/ethereum/go-ethereum/eth/tracers/native"
45)
46
47// To generate a new callTracer test, copy paste the makeTest method below into
48// a Geth console and call it with a transaction hash you which to export.
49
50/*
51// makeTest generates a callTracer test by running a prestate reassembled and a
52// call trace run, assembling all the gathered information into a test case.
53var makeTest = function(tx, rewind) {
54  // Generate the genesis block from the block, transaction and prestate data
55  var block   = eth.getBlock(eth.getTransaction(tx).blockHash);
56  var genesis = eth.getBlock(block.parentHash);
57
58  delete genesis.gasUsed;
59  delete genesis.logsBloom;
60  delete genesis.parentHash;
61  delete genesis.receiptsRoot;
62  delete genesis.sha3Uncles;
63  delete genesis.size;
64  delete genesis.transactions;
65  delete genesis.transactionsRoot;
66  delete genesis.uncles;
67
68  genesis.gasLimit  = genesis.gasLimit.toString();
69  genesis.number    = genesis.number.toString();
70  genesis.timestamp = genesis.timestamp.toString();
71
72  genesis.alloc = debug.traceTransaction(tx, {tracer: "prestateTracer", rewind: rewind});
73  for (var key in genesis.alloc) {
74    genesis.alloc[key].nonce = genesis.alloc[key].nonce.toString();
75  }
76  genesis.config = admin.nodeInfo.protocols.eth.config;
77
78  // Generate the call trace and produce the test input
79  var result = debug.traceTransaction(tx, {tracer: "callTracer", rewind: rewind});
80  delete result.time;
81
82  console.log(JSON.stringify({
83    genesis: genesis,
84    context: {
85      number:     block.number.toString(),
86      difficulty: block.difficulty,
87      timestamp:  block.timestamp.toString(),
88      gasLimit:   block.gasLimit.toString(),
89      miner:      block.miner,
90    },
91    input:  eth.getRawTransaction(tx),
92    result: result,
93  }, null, 2));
94}
95*/
96
97type callContext struct {
98	Number     math.HexOrDecimal64   `json:"number"`
99	Difficulty *math.HexOrDecimal256 `json:"difficulty"`
100	Time       math.HexOrDecimal64   `json:"timestamp"`
101	GasLimit   math.HexOrDecimal64   `json:"gasLimit"`
102	Miner      common.Address        `json:"miner"`
103}
104
105// callTrace is the result of a callTracer run.
106type callTrace struct {
107	Type    string          `json:"type"`
108	From    common.Address  `json:"from"`
109	To      common.Address  `json:"to"`
110	Input   hexutil.Bytes   `json:"input"`
111	Output  hexutil.Bytes   `json:"output"`
112	Gas     *hexutil.Uint64 `json:"gas,omitempty"`
113	GasUsed *hexutil.Uint64 `json:"gasUsed,omitempty"`
114	Value   *hexutil.Big    `json:"value,omitempty"`
115	Error   string          `json:"error,omitempty"`
116	Calls   []callTrace     `json:"calls,omitempty"`
117}
118
119// callTracerTest defines a single test to check the call tracer against.
120type callTracerTest struct {
121	Genesis *core.Genesis `json:"genesis"`
122	Context *callContext  `json:"context"`
123	Input   string        `json:"input"`
124	Result  *callTrace    `json:"result"`
125}
126
127// Iterates over all the input-output datasets in the tracer test harness and
128// runs the JavaScript tracers against them.
129func TestCallTracerLegacy(t *testing.T) {
130	testCallTracer("callTracerLegacy", "call_tracer_legacy", t)
131}
132
133func TestCallTracerJs(t *testing.T) {
134	testCallTracer("callTracerJs", "call_tracer", t)
135}
136
137func TestCallTracerNative(t *testing.T) {
138	testCallTracer("callTracer", "call_tracer", t)
139}
140
141func testCallTracer(tracerName string, dirPath string, t *testing.T) {
142	files, err := ioutil.ReadDir(filepath.Join("testdata", dirPath))
143	if err != nil {
144		t.Fatalf("failed to retrieve tracer test suite: %v", err)
145	}
146	for _, file := range files {
147		if !strings.HasSuffix(file.Name(), ".json") {
148			continue
149		}
150		file := file // capture range variable
151		t.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(t *testing.T) {
152			t.Parallel()
153
154			var (
155				test = new(callTracerTest)
156				tx   = new(types.Transaction)
157			)
158			// Call tracer test found, read if from disk
159			if blob, err := ioutil.ReadFile(filepath.Join("testdata", dirPath, file.Name())); err != nil {
160				t.Fatalf("failed to read testcase: %v", err)
161			} else if err := json.Unmarshal(blob, test); err != nil {
162				t.Fatalf("failed to parse testcase: %v", err)
163			}
164			if err := rlp.DecodeBytes(common.FromHex(test.Input), tx); err != nil {
165				t.Fatalf("failed to parse testcase input: %v", err)
166			}
167			// Configure a blockchain with the given prestate
168			var (
169				signer    = types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)))
170				origin, _ = signer.Sender(tx)
171				txContext = vm.TxContext{
172					Origin:   origin,
173					GasPrice: tx.GasPrice(),
174				}
175				context = vm.BlockContext{
176					CanTransfer: core.CanTransfer,
177					Transfer:    core.Transfer,
178					Coinbase:    test.Context.Miner,
179					BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)),
180					Time:        new(big.Int).SetUint64(uint64(test.Context.Time)),
181					Difficulty:  (*big.Int)(test.Context.Difficulty),
182					GasLimit:    uint64(test.Context.GasLimit),
183				}
184				_, statedb = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false)
185			)
186			tracer, err := tracers.New(tracerName, new(tracers.Context))
187			if err != nil {
188				t.Fatalf("failed to create call tracer: %v", err)
189			}
190			evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer})
191			msg, err := tx.AsMessage(signer, nil)
192			if err != nil {
193				t.Fatalf("failed to prepare transaction for tracing: %v", err)
194			}
195			st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
196			if _, err = st.TransitionDb(); err != nil {
197				t.Fatalf("failed to execute transaction: %v", err)
198			}
199			// Retrieve the trace result and compare against the etalon
200			res, err := tracer.GetResult()
201			if err != nil {
202				t.Fatalf("failed to retrieve trace result: %v", err)
203			}
204			ret := new(callTrace)
205			if err := json.Unmarshal(res, ret); err != nil {
206				t.Fatalf("failed to unmarshal trace result: %v", err)
207			}
208
209			if !jsonEqual(ret, test.Result) {
210				// uncomment this for easier debugging
211				//have, _ := json.MarshalIndent(ret, "", " ")
212				//want, _ := json.MarshalIndent(test.Result, "", " ")
213				//t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", string(have), string(want))
214				t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", ret, test.Result)
215			}
216		})
217	}
218}
219
220// jsonEqual is similar to reflect.DeepEqual, but does a 'bounce' via json prior to
221// comparison
222func jsonEqual(x, y interface{}) bool {
223	xTrace := new(callTrace)
224	yTrace := new(callTrace)
225	if xj, err := json.Marshal(x); err == nil {
226		json.Unmarshal(xj, xTrace)
227	} else {
228		return false
229	}
230	if yj, err := json.Marshal(y); err == nil {
231		json.Unmarshal(yj, yTrace)
232	} else {
233		return false
234	}
235	return reflect.DeepEqual(xTrace, yTrace)
236}
237
238// camel converts a snake cased input string into a camel cased output.
239func camel(str string) string {
240	pieces := strings.Split(str, "_")
241	for i := 1; i < len(pieces); i++ {
242		pieces[i] = string(unicode.ToUpper(rune(pieces[i][0]))) + pieces[i][1:]
243	}
244	return strings.Join(pieces, "")
245}
246func BenchmarkTracers(b *testing.B) {
247	files, err := ioutil.ReadDir(filepath.Join("testdata", "call_tracer"))
248	if err != nil {
249		b.Fatalf("failed to retrieve tracer test suite: %v", err)
250	}
251	for _, file := range files {
252		if !strings.HasSuffix(file.Name(), ".json") {
253			continue
254		}
255		file := file // capture range variable
256		b.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(b *testing.B) {
257			blob, err := ioutil.ReadFile(filepath.Join("testdata", "call_tracer", file.Name()))
258			if err != nil {
259				b.Fatalf("failed to read testcase: %v", err)
260			}
261			test := new(callTracerTest)
262			if err := json.Unmarshal(blob, test); err != nil {
263				b.Fatalf("failed to parse testcase: %v", err)
264			}
265			benchTracer("callTracerNative", test, b)
266		})
267	}
268}
269
270func benchTracer(tracerName string, test *callTracerTest, b *testing.B) {
271	// Configure a blockchain with the given prestate
272	tx := new(types.Transaction)
273	if err := rlp.DecodeBytes(common.FromHex(test.Input), tx); err != nil {
274		b.Fatalf("failed to parse testcase input: %v", err)
275	}
276	signer := types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)))
277	msg, err := tx.AsMessage(signer, nil)
278	if err != nil {
279		b.Fatalf("failed to prepare transaction for tracing: %v", err)
280	}
281	origin, _ := signer.Sender(tx)
282	txContext := vm.TxContext{
283		Origin:   origin,
284		GasPrice: tx.GasPrice(),
285	}
286	context := vm.BlockContext{
287		CanTransfer: core.CanTransfer,
288		Transfer:    core.Transfer,
289		Coinbase:    test.Context.Miner,
290		BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)),
291		Time:        new(big.Int).SetUint64(uint64(test.Context.Time)),
292		Difficulty:  (*big.Int)(test.Context.Difficulty),
293		GasLimit:    uint64(test.Context.GasLimit),
294	}
295	_, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false)
296
297	b.ReportAllocs()
298	b.ResetTimer()
299	for i := 0; i < b.N; i++ {
300		tracer, err := tracers.New(tracerName, new(tracers.Context))
301		if err != nil {
302			b.Fatalf("failed to create call tracer: %v", err)
303		}
304		evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer})
305		snap := statedb.Snapshot()
306		st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
307		if _, err = st.TransitionDb(); err != nil {
308			b.Fatalf("failed to execute transaction: %v", err)
309		}
310		if _, err = tracer.GetResult(); err != nil {
311			b.Fatal(err)
312		}
313		statedb.RevertToSnapshot(snap)
314	}
315}
316
317// TestZeroValueToNotExitCall tests the calltracer(s) on the following:
318// Tx to A, A calls B with zero value. B does not already exist.
319// Expected: that enter/exit is invoked and the inner call is shown in the result
320func TestZeroValueToNotExitCall(t *testing.T) {
321	var to = common.HexToAddress("0x00000000000000000000000000000000deadbeef")
322	privkey, err := crypto.HexToECDSA("0000000000000000deadbeef00000000000000000000000000000000deadbeef")
323	if err != nil {
324		t.Fatalf("err %v", err)
325	}
326	signer := types.NewEIP155Signer(big.NewInt(1))
327	tx, err := types.SignNewTx(privkey, signer, &types.LegacyTx{
328		GasPrice: big.NewInt(0),
329		Gas:      50000,
330		To:       &to,
331	})
332	if err != nil {
333		t.Fatalf("err %v", err)
334	}
335	origin, _ := signer.Sender(tx)
336	txContext := vm.TxContext{
337		Origin:   origin,
338		GasPrice: big.NewInt(1),
339	}
340	context := vm.BlockContext{
341		CanTransfer: core.CanTransfer,
342		Transfer:    core.Transfer,
343		Coinbase:    common.Address{},
344		BlockNumber: new(big.Int).SetUint64(8000000),
345		Time:        new(big.Int).SetUint64(5),
346		Difficulty:  big.NewInt(0x30000),
347		GasLimit:    uint64(6000000),
348	}
349	var code = []byte{
350		byte(vm.PUSH1), 0x0, byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), // in and outs zero
351		byte(vm.DUP1), byte(vm.PUSH1), 0xff, byte(vm.GAS), // value=0,address=0xff, gas=GAS
352		byte(vm.CALL),
353	}
354	var alloc = core.GenesisAlloc{
355		to: core.GenesisAccount{
356			Nonce: 1,
357			Code:  code,
358		},
359		origin: core.GenesisAccount{
360			Nonce:   0,
361			Balance: big.NewInt(500000000000000),
362		},
363	}
364	_, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), alloc, false)
365	// Create the tracer, the EVM environment and run it
366	tracer, err := tracers.New("callTracer", nil)
367	if err != nil {
368		t.Fatalf("failed to create call tracer: %v", err)
369	}
370	evm := vm.NewEVM(context, txContext, statedb, params.MainnetChainConfig, vm.Config{Debug: true, Tracer: tracer})
371	msg, err := tx.AsMessage(signer, nil)
372	if err != nil {
373		t.Fatalf("failed to prepare transaction for tracing: %v", err)
374	}
375	st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
376	if _, err = st.TransitionDb(); err != nil {
377		t.Fatalf("failed to execute transaction: %v", err)
378	}
379	// Retrieve the trace result and compare against the etalon
380	res, err := tracer.GetResult()
381	if err != nil {
382		t.Fatalf("failed to retrieve trace result: %v", err)
383	}
384	have := new(callTrace)
385	if err := json.Unmarshal(res, have); err != nil {
386		t.Fatalf("failed to unmarshal trace result: %v", err)
387	}
388	wantStr := `{"type":"CALL","from":"0x682a80a6f560eec50d54e63cbeda1c324c5f8d1b","to":"0x00000000000000000000000000000000deadbeef","value":"0x0","gas":"0x7148","gasUsed":"0x2d0","input":"0x","output":"0x","calls":[{"type":"CALL","from":"0x00000000000000000000000000000000deadbeef","to":"0x00000000000000000000000000000000000000ff","value":"0x0","gas":"0x6cbf","gasUsed":"0x0","input":"0x","output":"0x"}]}`
389	want := new(callTrace)
390	json.Unmarshal([]byte(wantStr), want)
391	if !jsonEqual(have, want) {
392		t.Error("have != want")
393	}
394}
395