github.com/ethereum/go-ethereum@v1.16.1/eth/tracers/internal/tracetest/calltrace_test.go (about)

     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  
    17  package tracetest
    18  
    19  import (
    20  	"encoding/json"
    21  	"fmt"
    22  	"math/big"
    23  	"os"
    24  	"path/filepath"
    25  	"strings"
    26  	"testing"
    27  
    28  	"github.com/ethereum/go-ethereum/common"
    29  	"github.com/ethereum/go-ethereum/common/hexutil"
    30  	"github.com/ethereum/go-ethereum/core"
    31  	"github.com/ethereum/go-ethereum/core/rawdb"
    32  	"github.com/ethereum/go-ethereum/core/state"
    33  	"github.com/ethereum/go-ethereum/core/types"
    34  	"github.com/ethereum/go-ethereum/core/vm"
    35  	"github.com/ethereum/go-ethereum/crypto"
    36  	"github.com/ethereum/go-ethereum/eth/tracers"
    37  	"github.com/ethereum/go-ethereum/params"
    38  	"github.com/ethereum/go-ethereum/tests"
    39  )
    40  
    41  // callLog is the result of LOG opCode
    42  type callLog struct {
    43  	Address  common.Address `json:"address"`
    44  	Topics   []common.Hash  `json:"topics"`
    45  	Data     hexutil.Bytes  `json:"data"`
    46  	Position hexutil.Uint   `json:"position"`
    47  }
    48  
    49  // callTrace is the result of a callTracer run.
    50  type callTrace struct {
    51  	From         common.Address  `json:"from"`
    52  	Gas          *hexutil.Uint64 `json:"gas"`
    53  	GasUsed      *hexutil.Uint64 `json:"gasUsed"`
    54  	To           *common.Address `json:"to,omitempty"`
    55  	Input        hexutil.Bytes   `json:"input"`
    56  	Output       hexutil.Bytes   `json:"output,omitempty"`
    57  	Error        string          `json:"error,omitempty"`
    58  	RevertReason string          `json:"revertReason,omitempty"`
    59  	Calls        []callTrace     `json:"calls,omitempty"`
    60  	Logs         []callLog       `json:"logs,omitempty"`
    61  	Value        *hexutil.Big    `json:"value,omitempty"`
    62  	// Gencodec adds overridden fields at the end
    63  	Type string `json:"type"`
    64  }
    65  
    66  // callTracerTest defines a single test to check the call tracer against.
    67  type callTracerTest struct {
    68  	tracerTestEnv
    69  	Result *callTrace `json:"result"`
    70  }
    71  
    72  // Iterates over all the input-output datasets in the tracer test harness and
    73  // runs the JavaScript tracers against them.
    74  func TestCallTracerLegacy(t *testing.T) {
    75  	testCallTracer("callTracerLegacy", "call_tracer_legacy", t)
    76  }
    77  
    78  func TestCallTracerNative(t *testing.T) {
    79  	testCallTracer("callTracer", "call_tracer", t)
    80  }
    81  
    82  func TestCallTracerNativeWithLog(t *testing.T) {
    83  	testCallTracer("callTracer", "call_tracer_withLog", t)
    84  }
    85  
    86  func testCallTracer(tracerName string, dirPath string, t *testing.T) {
    87  	isLegacy := strings.HasSuffix(dirPath, "_legacy")
    88  	files, err := os.ReadDir(filepath.Join("testdata", dirPath))
    89  	if err != nil {
    90  		t.Fatalf("failed to retrieve tracer test suite: %v", err)
    91  	}
    92  	for _, file := range files {
    93  		if !strings.HasSuffix(file.Name(), ".json") {
    94  			continue
    95  		}
    96  		t.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(t *testing.T) {
    97  			t.Parallel()
    98  
    99  			var (
   100  				test = new(callTracerTest)
   101  				tx   = new(types.Transaction)
   102  			)
   103  			// Call tracer test found, read if from disk
   104  			if blob, err := os.ReadFile(filepath.Join("testdata", dirPath, file.Name())); err != nil {
   105  				t.Fatalf("failed to read testcase: %v", err)
   106  			} else if err := json.Unmarshal(blob, test); err != nil {
   107  				t.Fatalf("failed to parse testcase: %v", err)
   108  			}
   109  			if err := tx.UnmarshalBinary(common.FromHex(test.Input)); err != nil {
   110  				t.Fatalf("failed to parse testcase input: %v", err)
   111  			}
   112  			// Configure a blockchain with the given prestate
   113  			var (
   114  				signer  = types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)), uint64(test.Context.Time))
   115  				context = test.Context.toBlockContext(test.Genesis)
   116  				st      = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false, rawdb.HashScheme)
   117  			)
   118  			st.Close()
   119  
   120  			tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), test.TracerConfig, test.Genesis.Config)
   121  			if err != nil {
   122  				t.Fatalf("failed to create call tracer: %v", err)
   123  			}
   124  			logState := vm.StateDB(st.StateDB)
   125  			if tracer.Hooks != nil {
   126  				logState = state.NewHookedState(st.StateDB, tracer.Hooks)
   127  			}
   128  			msg, err := core.TransactionToMessage(tx, signer, context.BaseFee)
   129  			if err != nil {
   130  				t.Fatalf("failed to prepare transaction for tracing: %v", err)
   131  			}
   132  			evm := vm.NewEVM(context, logState, test.Genesis.Config, vm.Config{Tracer: tracer.Hooks})
   133  			tracer.OnTxStart(evm.GetVMContext(), tx, msg.From)
   134  			vmRet, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
   135  			if err != nil {
   136  				t.Fatalf("failed to execute transaction: %v", err)
   137  			}
   138  			tracer.OnTxEnd(&types.Receipt{GasUsed: vmRet.UsedGas}, nil)
   139  			// Retrieve the trace result and compare against the expected.
   140  			res, err := tracer.GetResult()
   141  			if err != nil {
   142  				t.Fatalf("failed to retrieve trace result: %v", err)
   143  			}
   144  			// The legacy javascript calltracer marshals json in js, which
   145  			// is not deterministic (as opposed to the golang json encoder).
   146  			if isLegacy {
   147  				// This is a tweak to make it deterministic. Can be removed when
   148  				// we remove the legacy tracer.
   149  				var x callTrace
   150  				json.Unmarshal(res, &x)
   151  				res, _ = json.Marshal(x)
   152  			}
   153  			want, err := json.Marshal(test.Result)
   154  			if err != nil {
   155  				t.Fatalf("failed to marshal test: %v", err)
   156  			}
   157  			if string(want) != string(res) {
   158  				t.Fatalf("trace mismatch\n have: %v\n want: %v\n", string(res), string(want))
   159  			}
   160  			// Sanity check: compare top call's gas used against vm result
   161  			type simpleResult struct {
   162  				GasUsed hexutil.Uint64
   163  			}
   164  			var topCall simpleResult
   165  			if err := json.Unmarshal(res, &topCall); err != nil {
   166  				t.Fatalf("failed to unmarshal top calls gasUsed: %v", err)
   167  			}
   168  			if uint64(topCall.GasUsed) != vmRet.UsedGas {
   169  				t.Fatalf("top call has invalid gasUsed. have: %d want: %d", topCall.GasUsed, vmRet.UsedGas)
   170  			}
   171  		})
   172  	}
   173  }
   174  
   175  func BenchmarkTracers(b *testing.B) {
   176  	files, err := os.ReadDir(filepath.Join("testdata", "call_tracer"))
   177  	if err != nil {
   178  		b.Fatalf("failed to retrieve tracer test suite: %v", err)
   179  	}
   180  	for _, file := range files {
   181  		if !strings.HasSuffix(file.Name(), ".json") {
   182  			continue
   183  		}
   184  		b.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(b *testing.B) {
   185  			blob, err := os.ReadFile(filepath.Join("testdata", "call_tracer", file.Name()))
   186  			if err != nil {
   187  				b.Fatalf("failed to read testcase: %v", err)
   188  			}
   189  			test := new(callTracerTest)
   190  			if err := json.Unmarshal(blob, test); err != nil {
   191  				b.Fatalf("failed to parse testcase: %v", err)
   192  			}
   193  			benchTracer("callTracer", test, b)
   194  		})
   195  	}
   196  }
   197  
   198  func benchTracer(tracerName string, test *callTracerTest, b *testing.B) {
   199  	// Configure a blockchain with the given prestate
   200  	tx := new(types.Transaction)
   201  	if err := tx.UnmarshalBinary(common.FromHex(test.Input)); err != nil {
   202  		b.Fatalf("failed to parse testcase input: %v", err)
   203  	}
   204  	signer := types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)), uint64(test.Context.Time))
   205  	context := test.Context.toBlockContext(test.Genesis)
   206  	msg, err := core.TransactionToMessage(tx, signer, context.BaseFee)
   207  	if err != nil {
   208  		b.Fatalf("failed to prepare transaction for tracing: %v", err)
   209  	}
   210  	state := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false, rawdb.HashScheme)
   211  	defer state.Close()
   212  
   213  	b.ReportAllocs()
   214  	b.ResetTimer()
   215  
   216  	evm := vm.NewEVM(context, state.StateDB, test.Genesis.Config, vm.Config{})
   217  
   218  	for i := 0; i < b.N; i++ {
   219  		snap := state.StateDB.Snapshot()
   220  		tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), nil, test.Genesis.Config)
   221  		if err != nil {
   222  			b.Fatalf("failed to create call tracer: %v", err)
   223  		}
   224  		evm.Config.Tracer = tracer.Hooks
   225  		if tracer.OnTxStart != nil {
   226  			tracer.OnTxStart(evm.GetVMContext(), tx, msg.From)
   227  		}
   228  		_, err = core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
   229  		if err != nil {
   230  			b.Fatalf("failed to execute transaction: %v", err)
   231  		}
   232  		if tracer.OnTxEnd != nil {
   233  			tracer.OnTxEnd(&types.Receipt{GasUsed: tx.Gas()}, nil)
   234  		}
   235  		if _, err = tracer.GetResult(); err != nil {
   236  			b.Fatal(err)
   237  		}
   238  		state.StateDB.RevertToSnapshot(snap)
   239  	}
   240  }
   241  
   242  func TestInternals(t *testing.T) {
   243  	var (
   244  		config    = params.MainnetChainConfig
   245  		to        = common.HexToAddress("0x00000000000000000000000000000000deadbeef")
   246  		originHex = "0x71562b71999873db5b286df957af199ec94617f7"
   247  		origin    = common.HexToAddress(originHex)
   248  		signer    = types.LatestSigner(config)
   249  		key, _    = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
   250  		context   = vm.BlockContext{
   251  			CanTransfer: core.CanTransfer,
   252  			Transfer:    core.Transfer,
   253  			Coinbase:    common.Address{},
   254  			BlockNumber: new(big.Int).SetUint64(8000000),
   255  			Time:        5,
   256  			Difficulty:  big.NewInt(0x30000),
   257  			GasLimit:    uint64(6000000),
   258  			BaseFee:     new(big.Int),
   259  		}
   260  	)
   261  	mkTracer := func(name string, cfg json.RawMessage) *tracers.Tracer {
   262  		tr, err := tracers.DefaultDirectory.New(name, nil, cfg, config)
   263  		if err != nil {
   264  			t.Fatalf("failed to create call tracer: %v", err)
   265  		}
   266  		return tr
   267  	}
   268  
   269  	for _, tc := range []struct {
   270  		name   string
   271  		code   []byte
   272  		tracer *tracers.Tracer
   273  		want   string
   274  	}{
   275  		{
   276  			// TestZeroValueToNotExitCall tests the calltracer(s) on the following:
   277  			// Tx to A, A calls B with zero value. B does not already exist.
   278  			// Expected: that enter/exit is invoked and the inner call is shown in the result
   279  			name: "ZeroValueToNotExitCall",
   280  			code: []byte{
   281  				byte(vm.PUSH1), 0x0, byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), // in and outs zero
   282  				byte(vm.DUP1), byte(vm.PUSH1), 0xff, byte(vm.GAS), // value=0,address=0xff, gas=GAS
   283  				byte(vm.CALL),
   284  			},
   285  			tracer: mkTracer("callTracer", nil),
   286  			want:   fmt.Sprintf(`{"from":"%s","gas":"0x13880","gasUsed":"0x54d8","to":"0x00000000000000000000000000000000deadbeef","input":"0x","calls":[{"from":"0x00000000000000000000000000000000deadbeef","gas":"0xe01a","gasUsed":"0x0","to":"0x00000000000000000000000000000000000000ff","input":"0x","value":"0x0","type":"CALL"}],"value":"0x0","type":"CALL"}`, originHex),
   287  		},
   288  		{
   289  			name:   "Stack depletion in LOG0",
   290  			code:   []byte{byte(vm.LOG3)},
   291  			tracer: mkTracer("callTracer", json.RawMessage(`{ "withLog": true }`)),
   292  			want:   fmt.Sprintf(`{"from":"%s","gas":"0x13880","gasUsed":"0x13880","to":"0x00000000000000000000000000000000deadbeef","input":"0x","error":"stack underflow (0 \u003c=\u003e 5)","value":"0x0","type":"CALL"}`, originHex),
   293  		},
   294  		{
   295  			name: "Mem expansion in LOG0",
   296  			code: []byte{
   297  				byte(vm.PUSH1), 0x1,
   298  				byte(vm.PUSH1), 0x0,
   299  				byte(vm.MSTORE),
   300  				byte(vm.PUSH1), 0xff,
   301  				byte(vm.PUSH1), 0x0,
   302  				byte(vm.LOG0),
   303  			},
   304  			tracer: mkTracer("callTracer", json.RawMessage(`{ "withLog": true }`)),
   305  			want:   fmt.Sprintf(`{"from":"%s","gas":"0x13880","gasUsed":"0x5b9e","to":"0x00000000000000000000000000000000deadbeef","input":"0x","logs":[{"address":"0x00000000000000000000000000000000deadbeef","topics":[],"data":"0x000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","position":"0x0"}],"value":"0x0","type":"CALL"}`, originHex),
   306  		},
   307  		{
   308  			// Leads to OOM on the prestate tracer
   309  			name: "Prestate-tracer - CREATE2 OOM",
   310  			code: []byte{
   311  				byte(vm.PUSH1), 0x1,
   312  				byte(vm.PUSH1), 0x0,
   313  				byte(vm.MSTORE),
   314  				byte(vm.PUSH1), 0x1,
   315  				byte(vm.PUSH5), 0xff, 0xff, 0xff, 0xff, 0xff,
   316  				byte(vm.PUSH1), 0x1,
   317  				byte(vm.PUSH1), 0x0,
   318  				byte(vm.CREATE2),
   319  				byte(vm.PUSH1), 0xff,
   320  				byte(vm.PUSH1), 0x0,
   321  				byte(vm.LOG0),
   322  			},
   323  			tracer: mkTracer("prestateTracer", nil),
   324  			want:   fmt.Sprintf(`{"0x00000000000000000000000000000000deadbeef":{"balance":"0x0","code":"0x6001600052600164ffffffffff60016000f560ff6000a0"},"%s":{"balance":"0x1c6bf52634000"}}`, originHex),
   325  		},
   326  		{
   327  			// CREATE2 which requires padding memory by prestate tracer
   328  			name: "Prestate-tracer - CREATE2 Memory padding",
   329  			code: []byte{
   330  				byte(vm.PUSH1), 0x1,
   331  				byte(vm.PUSH1), 0x0,
   332  				byte(vm.MSTORE),
   333  				byte(vm.PUSH1), 0x1,
   334  				byte(vm.PUSH1), 0xff,
   335  				byte(vm.PUSH1), 0x1,
   336  				byte(vm.PUSH1), 0x0,
   337  				byte(vm.CREATE2),
   338  				byte(vm.PUSH1), 0xff,
   339  				byte(vm.PUSH1), 0x0,
   340  				byte(vm.LOG0),
   341  			},
   342  			tracer: mkTracer("prestateTracer", nil),
   343  			want:   fmt.Sprintf(`{"0x00000000000000000000000000000000deadbeef":{"balance":"0x0","code":"0x6001600052600160ff60016000f560ff6000a0"},"%s":{"balance":"0x1c6bf52634000"}}`, originHex),
   344  		},
   345  	} {
   346  		t.Run(tc.name, func(t *testing.T) {
   347  			st := tests.MakePreState(rawdb.NewMemoryDatabase(),
   348  				types.GenesisAlloc{
   349  					to: types.Account{
   350  						Code: tc.code,
   351  					},
   352  					origin: types.Account{
   353  						Balance: big.NewInt(500000000000000),
   354  					},
   355  				}, false, rawdb.HashScheme)
   356  			defer st.Close()
   357  
   358  			logState := vm.StateDB(st.StateDB)
   359  			if hooks := tc.tracer.Hooks; hooks != nil {
   360  				logState = state.NewHookedState(st.StateDB, hooks)
   361  			}
   362  
   363  			tx, err := types.SignNewTx(key, signer, &types.LegacyTx{
   364  				To:       &to,
   365  				Value:    big.NewInt(0),
   366  				Gas:      80000,
   367  				GasPrice: big.NewInt(1),
   368  			})
   369  			if err != nil {
   370  				t.Fatalf("test %v: failed to sign transaction: %v", tc.name, err)
   371  			}
   372  			evm := vm.NewEVM(context, logState, config, vm.Config{Tracer: tc.tracer.Hooks})
   373  			msg, err := core.TransactionToMessage(tx, signer, big.NewInt(0))
   374  			if err != nil {
   375  				t.Fatalf("test %v: failed to create message: %v", tc.name, err)
   376  			}
   377  			tc.tracer.OnTxStart(evm.GetVMContext(), tx, msg.From)
   378  			vmRet, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
   379  			if err != nil {
   380  				t.Fatalf("test %v: failed to execute transaction: %v", tc.name, err)
   381  			}
   382  			tc.tracer.OnTxEnd(&types.Receipt{GasUsed: vmRet.UsedGas}, nil)
   383  			// Retrieve the trace result and compare against the expected
   384  			res, err := tc.tracer.GetResult()
   385  			if err != nil {
   386  				t.Fatalf("test %v: failed to retrieve trace result: %v", tc.name, err)
   387  			}
   388  			if string(res) != tc.want {
   389  				t.Errorf("test %v: trace mismatch\n have: %v\n want: %v\n", tc.name, string(res), tc.want)
   390  			}
   391  		})
   392  	}
   393  }