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