github.com/tirogen/go-ethereum@v1.10.12-0.20221226051715-250cfede41b6/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  	"math/big"
    22  	"os"
    23  	"path/filepath"
    24  	"strings"
    25  	"testing"
    26  
    27  	"github.com/tirogen/go-ethereum/common"
    28  	"github.com/tirogen/go-ethereum/common/hexutil"
    29  	"github.com/tirogen/go-ethereum/common/math"
    30  	"github.com/tirogen/go-ethereum/core"
    31  	"github.com/tirogen/go-ethereum/core/rawdb"
    32  	"github.com/tirogen/go-ethereum/core/types"
    33  	"github.com/tirogen/go-ethereum/core/vm"
    34  	"github.com/tirogen/go-ethereum/crypto"
    35  	"github.com/tirogen/go-ethereum/eth/tracers"
    36  	"github.com/tirogen/go-ethereum/params"
    37  	"github.com/tirogen/go-ethereum/rlp"
    38  	"github.com/tirogen/go-ethereum/tests"
    39  )
    40  
    41  type callContext struct {
    42  	Number     math.HexOrDecimal64   `json:"number"`
    43  	Difficulty *math.HexOrDecimal256 `json:"difficulty"`
    44  	Time       math.HexOrDecimal64   `json:"timestamp"`
    45  	GasLimit   math.HexOrDecimal64   `json:"gasLimit"`
    46  	Miner      common.Address        `json:"miner"`
    47  }
    48  
    49  // callLog is the result of LOG opCode
    50  type callLog struct {
    51  	Address common.Address `json:"address"`
    52  	Topics  []common.Hash  `json:"topics"`
    53  	Data    hexutil.Bytes  `json:"data"`
    54  }
    55  
    56  // callTrace is the result of a callTracer run.
    57  type callTrace struct {
    58  	From     common.Address  `json:"from"`
    59  	Gas      *hexutil.Uint64 `json:"gas"`
    60  	GasUsed  *hexutil.Uint64 `json:"gasUsed"`
    61  	To       common.Address  `json:"to,omitempty"`
    62  	Input    hexutil.Bytes   `json:"input"`
    63  	Output   hexutil.Bytes   `json:"output,omitempty"`
    64  	Error    string          `json:"error,omitempty"`
    65  	Revertal string          `json:"revertReason,omitempty"`
    66  	Calls    []callTrace     `json:"calls,omitempty"`
    67  	Logs     []callLog       `json:"logs,omitempty"`
    68  	Value    *hexutil.Big    `json:"value,omitempty"`
    69  	// Gencodec adds overridden fields at the end
    70  	Type string `json:"type"`
    71  }
    72  
    73  // callTracerTest defines a single test to check the call tracer against.
    74  type callTracerTest struct {
    75  	Genesis      *core.Genesis   `json:"genesis"`
    76  	Context      *callContext    `json:"context"`
    77  	Input        string          `json:"input"`
    78  	TracerConfig json.RawMessage `json:"tracerConfig"`
    79  	Result       *callTrace      `json:"result"`
    80  }
    81  
    82  // Iterates over all the input-output datasets in the tracer test harness and
    83  // runs the JavaScript tracers against them.
    84  func TestCallTracerLegacy(t *testing.T) {
    85  	testCallTracer("callTracerLegacy", "call_tracer_legacy", t)
    86  }
    87  
    88  func TestCallTracerNative(t *testing.T) {
    89  	testCallTracer("callTracer", "call_tracer", t)
    90  }
    91  
    92  func TestCallTracerNativeWithLog(t *testing.T) {
    93  	testCallTracer("callTracer", "call_tracer_withLog", t)
    94  }
    95  
    96  func testCallTracer(tracerName string, dirPath string, t *testing.T) {
    97  	isLegacy := strings.HasSuffix(dirPath, "_legacy")
    98  	files, err := os.ReadDir(filepath.Join("testdata", dirPath))
    99  	if err != nil {
   100  		t.Fatalf("failed to retrieve tracer test suite: %v", err)
   101  	}
   102  	for _, file := range files {
   103  		if !strings.HasSuffix(file.Name(), ".json") {
   104  			continue
   105  		}
   106  		file := file // capture range variable
   107  		t.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(t *testing.T) {
   108  			t.Parallel()
   109  
   110  			var (
   111  				test = new(callTracerTest)
   112  				tx   = new(types.Transaction)
   113  			)
   114  			// Call tracer test found, read if from disk
   115  			if blob, err := os.ReadFile(filepath.Join("testdata", dirPath, file.Name())); err != nil {
   116  				t.Fatalf("failed to read testcase: %v", err)
   117  			} else if err := json.Unmarshal(blob, test); err != nil {
   118  				t.Fatalf("failed to parse testcase: %v", err)
   119  			}
   120  			if err := tx.UnmarshalBinary(common.FromHex(test.Input)); err != nil {
   121  				t.Fatalf("failed to parse testcase input: %v", err)
   122  			}
   123  			// Configure a blockchain with the given prestate
   124  			var (
   125  				signer    = types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)))
   126  				origin, _ = signer.Sender(tx)
   127  				txContext = vm.TxContext{
   128  					Origin:   origin,
   129  					GasPrice: tx.GasPrice(),
   130  				}
   131  				context = vm.BlockContext{
   132  					CanTransfer: core.CanTransfer,
   133  					Transfer:    core.Transfer,
   134  					Coinbase:    test.Context.Miner,
   135  					BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)),
   136  					Time:        new(big.Int).SetUint64(uint64(test.Context.Time)),
   137  					Difficulty:  (*big.Int)(test.Context.Difficulty),
   138  					GasLimit:    uint64(test.Context.GasLimit),
   139  					BaseFee:     test.Genesis.BaseFee,
   140  				}
   141  				_, statedb = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false)
   142  			)
   143  			tracer, err := tracers.New(tracerName, new(tracers.Context), test.TracerConfig)
   144  			if err != nil {
   145  				t.Fatalf("failed to create call tracer: %v", err)
   146  			}
   147  			evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer})
   148  			msg, err := tx.AsMessage(signer, nil)
   149  			if err != nil {
   150  				t.Fatalf("failed to prepare transaction for tracing: %v", err)
   151  			}
   152  			vmRet, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
   153  			if err != nil {
   154  				t.Fatalf("failed to execute transaction: %v", err)
   155  			}
   156  			// Retrieve the trace result and compare against the expected.
   157  			res, err := tracer.GetResult()
   158  			if err != nil {
   159  				t.Fatalf("failed to retrieve trace result: %v", err)
   160  			}
   161  			// The legacy javascript calltracer marshals json in js, which
   162  			// is not deterministic (as opposed to the golang json encoder).
   163  			if isLegacy {
   164  				// This is a tweak to make it deterministic. Can be removed when
   165  				// we remove the legacy tracer.
   166  				var x callTrace
   167  				json.Unmarshal(res, &x)
   168  				res, _ = json.Marshal(x)
   169  			}
   170  			want, err := json.Marshal(test.Result)
   171  			if err != nil {
   172  				t.Fatalf("failed to marshal test: %v", err)
   173  			}
   174  			if string(want) != string(res) {
   175  				t.Fatalf("trace mismatch\n have: %v\n want: %v\n", string(res), string(want))
   176  			}
   177  			// Sanity check: compare top call's gas used against vm result
   178  			type simpleResult struct {
   179  				GasUsed hexutil.Uint64
   180  			}
   181  			var topCall simpleResult
   182  			if err := json.Unmarshal(res, &topCall); err != nil {
   183  				t.Fatalf("failed to unmarshal top calls gasUsed: %v", err)
   184  			}
   185  			if uint64(topCall.GasUsed) != vmRet.UsedGas {
   186  				t.Fatalf("top call has invalid gasUsed. have: %d want: %d", topCall.GasUsed, vmRet.UsedGas)
   187  			}
   188  		})
   189  	}
   190  }
   191  
   192  func BenchmarkTracers(b *testing.B) {
   193  	files, err := os.ReadDir(filepath.Join("testdata", "call_tracer"))
   194  	if err != nil {
   195  		b.Fatalf("failed to retrieve tracer test suite: %v", err)
   196  	}
   197  	for _, file := range files {
   198  		if !strings.HasSuffix(file.Name(), ".json") {
   199  			continue
   200  		}
   201  		file := file // capture range variable
   202  		b.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(b *testing.B) {
   203  			blob, err := os.ReadFile(filepath.Join("testdata", "call_tracer", file.Name()))
   204  			if err != nil {
   205  				b.Fatalf("failed to read testcase: %v", err)
   206  			}
   207  			test := new(callTracerTest)
   208  			if err := json.Unmarshal(blob, test); err != nil {
   209  				b.Fatalf("failed to parse testcase: %v", err)
   210  			}
   211  			benchTracer("callTracer", test, b)
   212  		})
   213  	}
   214  }
   215  
   216  func benchTracer(tracerName string, test *callTracerTest, b *testing.B) {
   217  	// Configure a blockchain with the given prestate
   218  	tx := new(types.Transaction)
   219  	if err := rlp.DecodeBytes(common.FromHex(test.Input), tx); err != nil {
   220  		b.Fatalf("failed to parse testcase input: %v", err)
   221  	}
   222  	signer := types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)))
   223  	msg, err := tx.AsMessage(signer, nil)
   224  	if err != nil {
   225  		b.Fatalf("failed to prepare transaction for tracing: %v", err)
   226  	}
   227  	origin, _ := signer.Sender(tx)
   228  	txContext := vm.TxContext{
   229  		Origin:   origin,
   230  		GasPrice: tx.GasPrice(),
   231  	}
   232  	context := vm.BlockContext{
   233  		CanTransfer: core.CanTransfer,
   234  		Transfer:    core.Transfer,
   235  		Coinbase:    test.Context.Miner,
   236  		BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)),
   237  		Time:        new(big.Int).SetUint64(uint64(test.Context.Time)),
   238  		Difficulty:  (*big.Int)(test.Context.Difficulty),
   239  		GasLimit:    uint64(test.Context.GasLimit),
   240  	}
   241  	_, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false)
   242  
   243  	b.ReportAllocs()
   244  	b.ResetTimer()
   245  	for i := 0; i < b.N; i++ {
   246  		tracer, err := tracers.New(tracerName, new(tracers.Context), nil)
   247  		if err != nil {
   248  			b.Fatalf("failed to create call tracer: %v", err)
   249  		}
   250  		evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer})
   251  		snap := statedb.Snapshot()
   252  		st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
   253  		if _, err = st.TransitionDb(); err != nil {
   254  			b.Fatalf("failed to execute transaction: %v", err)
   255  		}
   256  		if _, err = tracer.GetResult(); err != nil {
   257  			b.Fatal(err)
   258  		}
   259  		statedb.RevertToSnapshot(snap)
   260  	}
   261  }
   262  
   263  // TestZeroValueToNotExitCall tests the calltracer(s) on the following:
   264  // Tx to A, A calls B with zero value. B does not already exist.
   265  // Expected: that enter/exit is invoked and the inner call is shown in the result
   266  func TestZeroValueToNotExitCall(t *testing.T) {
   267  	var to = common.HexToAddress("0x00000000000000000000000000000000deadbeef")
   268  	privkey, err := crypto.HexToECDSA("0000000000000000deadbeef00000000000000000000000000000000deadbeef")
   269  	if err != nil {
   270  		t.Fatalf("err %v", err)
   271  	}
   272  	signer := types.NewEIP155Signer(big.NewInt(1))
   273  	tx, err := types.SignNewTx(privkey, signer, &types.LegacyTx{
   274  		GasPrice: big.NewInt(0),
   275  		Gas:      50000,
   276  		To:       &to,
   277  	})
   278  	if err != nil {
   279  		t.Fatalf("err %v", err)
   280  	}
   281  	origin, _ := signer.Sender(tx)
   282  	txContext := vm.TxContext{
   283  		Origin:   origin,
   284  		GasPrice: big.NewInt(1),
   285  	}
   286  	context := vm.BlockContext{
   287  		CanTransfer: core.CanTransfer,
   288  		Transfer:    core.Transfer,
   289  		Coinbase:    common.Address{},
   290  		BlockNumber: new(big.Int).SetUint64(8000000),
   291  		Time:        new(big.Int).SetUint64(5),
   292  		Difficulty:  big.NewInt(0x30000),
   293  		GasLimit:    uint64(6000000),
   294  	}
   295  	var code = []byte{
   296  		byte(vm.PUSH1), 0x0, byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), // in and outs zero
   297  		byte(vm.DUP1), byte(vm.PUSH1), 0xff, byte(vm.GAS), // value=0,address=0xff, gas=GAS
   298  		byte(vm.CALL),
   299  	}
   300  	var alloc = core.GenesisAlloc{
   301  		to: core.GenesisAccount{
   302  			Nonce: 1,
   303  			Code:  code,
   304  		},
   305  		origin: core.GenesisAccount{
   306  			Nonce:   0,
   307  			Balance: big.NewInt(500000000000000),
   308  		},
   309  	}
   310  	_, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), alloc, false)
   311  	// Create the tracer, the EVM environment and run it
   312  	tracer, err := tracers.New("callTracer", nil, nil)
   313  	if err != nil {
   314  		t.Fatalf("failed to create call tracer: %v", err)
   315  	}
   316  	evm := vm.NewEVM(context, txContext, statedb, params.MainnetChainConfig, vm.Config{Debug: true, Tracer: tracer})
   317  	msg, err := tx.AsMessage(signer, nil)
   318  	if err != nil {
   319  		t.Fatalf("failed to prepare transaction for tracing: %v", err)
   320  	}
   321  	st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
   322  	if _, err = st.TransitionDb(); err != nil {
   323  		t.Fatalf("failed to execute transaction: %v", err)
   324  	}
   325  	// Retrieve the trace result and compare against the etalon
   326  	res, err := tracer.GetResult()
   327  	if err != nil {
   328  		t.Fatalf("failed to retrieve trace result: %v", err)
   329  	}
   330  	wantStr := `{"from":"0x682a80a6f560eec50d54e63cbeda1c324c5f8d1b","gas":"0x7148","gasUsed":"0x54d8","to":"0x00000000000000000000000000000000deadbeef","input":"0x","calls":[{"from":"0x00000000000000000000000000000000deadbeef","gas":"0x6cbf","gasUsed":"0x0","to":"0x00000000000000000000000000000000000000ff","input":"0x","value":"0x0","type":"CALL"}],"value":"0x0","type":"CALL"}`
   331  	if string(res) != wantStr {
   332  		t.Fatalf("trace mismatch\n have: %v\n want: %v\n", string(res), wantStr)
   333  	}
   334  }