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