github.com/theQRL/go-zond@v0.2.1/zond/tracers/internal/tracetest/flat_calltrace_test.go (about)

     1  package tracetest
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"math/big"
     7  	"os"
     8  	"path/filepath"
     9  	"reflect"
    10  	"strings"
    11  	"testing"
    12  
    13  	"github.com/theQRL/go-zond/common"
    14  	"github.com/theQRL/go-zond/common/hexutil"
    15  	"github.com/theQRL/go-zond/core"
    16  	"github.com/theQRL/go-zond/core/rawdb"
    17  	"github.com/theQRL/go-zond/core/types"
    18  	"github.com/theQRL/go-zond/core/vm"
    19  	"github.com/theQRL/go-zond/params"
    20  	"github.com/theQRL/go-zond/rlp"
    21  	"github.com/theQRL/go-zond/tests"
    22  
    23  	// Force-load the native, to trigger registration
    24  	"github.com/theQRL/go-zond/zond/tracers"
    25  )
    26  
    27  // flatCallTrace is the result of a callTracerParity run.
    28  type flatCallTrace struct {
    29  	Action              flatCallTraceAction `json:"action"`
    30  	BlockHash           common.Hash         `json:"-"`
    31  	BlockNumber         uint64              `json:"-"`
    32  	Error               string              `json:"error,omitempty"`
    33  	Result              flatCallTraceResult `json:"result,omitempty"`
    34  	Subtraces           int                 `json:"subtraces"`
    35  	TraceAddress        []int               `json:"traceAddress"`
    36  	TransactionHash     common.Hash         `json:"-"`
    37  	TransactionPosition uint64              `json:"-"`
    38  	Type                string              `json:"type"`
    39  	Time                string              `json:"-"`
    40  }
    41  
    42  type flatCallTraceAction struct {
    43  	Author         common.Address `json:"author,omitempty"`
    44  	RewardType     string         `json:"rewardType,omitempty"`
    45  	Balance        hexutil.Big    `json:"balance,omitempty"`
    46  	CallType       string         `json:"callType,omitempty"`
    47  	CreationMethod string         `json:"creationMethod,omitempty"`
    48  	From           common.Address `json:"from,omitempty"`
    49  	Gas            hexutil.Uint64 `json:"gas,omitempty"`
    50  	Init           hexutil.Bytes  `json:"init,omitempty"`
    51  	Input          hexutil.Bytes  `json:"input,omitempty"`
    52  	RefundAddress  common.Address `json:"refundAddress,omitempty"`
    53  	To             common.Address `json:"to,omitempty"`
    54  	Value          hexutil.Big    `json:"value,omitempty"`
    55  }
    56  
    57  type flatCallTraceResult struct {
    58  	Address common.Address `json:"address,omitempty"`
    59  	Code    hexutil.Bytes  `json:"code,omitempty"`
    60  	GasUsed hexutil.Uint64 `json:"gasUsed,omitempty"`
    61  	Output  hexutil.Bytes  `json:"output,omitempty"`
    62  }
    63  
    64  // flatCallTracerTest defines a single test to check the call tracer against.
    65  type flatCallTracerTest struct {
    66  	Genesis      core.Genesis    `json:"genesis"`
    67  	Context      callContext     `json:"context"`
    68  	Input        string          `json:"input"`
    69  	TracerConfig json.RawMessage `json:"tracerConfig"`
    70  	Result       []flatCallTrace `json:"result"`
    71  }
    72  
    73  func flatCallTracerTestRunner(tracerName string, filename string, dirPath string, t testing.TB) error {
    74  	// Call tracer test found, read if from disk
    75  	blob, err := os.ReadFile(filepath.Join("testdata", dirPath, filename))
    76  	if err != nil {
    77  		return fmt.Errorf("failed to read testcase: %v", err)
    78  	}
    79  	test := new(flatCallTracerTest)
    80  	if err := json.Unmarshal(blob, test); err != nil {
    81  		return fmt.Errorf("failed to parse testcase: %v", err)
    82  	}
    83  	// Configure a blockchain with the given prestate
    84  	tx := new(types.Transaction)
    85  	if err := rlp.DecodeBytes(common.FromHex(test.Input), tx); err != nil {
    86  		return fmt.Errorf("failed to parse testcase input: %v", err)
    87  	}
    88  	signer := types.MakeSigner(test.Genesis.Config)
    89  	origin, _ := signer.Sender(tx)
    90  	txContext := vm.TxContext{
    91  		Origin:   origin,
    92  		GasPrice: tx.GasPrice(),
    93  	}
    94  	context := vm.BlockContext{
    95  		CanTransfer: core.CanTransfer,
    96  		Transfer:    core.Transfer,
    97  		Coinbase:    test.Context.Miner,
    98  		BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)),
    99  		Time:        uint64(test.Context.Time),
   100  		GasLimit:    uint64(test.Context.GasLimit),
   101  		BaseFee:     big.NewInt(params.InitialBaseFee),
   102  	}
   103  	triedb, _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false, rawdb.HashScheme)
   104  	defer triedb.Close()
   105  
   106  	// Create the tracer, the ZVM environment and run it
   107  	tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), test.TracerConfig)
   108  	if err != nil {
   109  		return fmt.Errorf("failed to create call tracer: %v", err)
   110  	}
   111  	zvm := vm.NewZVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Tracer: tracer})
   112  
   113  	msg, err := core.TransactionToMessage(tx, signer, nil)
   114  	if err != nil {
   115  		return fmt.Errorf("failed to prepare transaction for tracing: %v", err)
   116  	}
   117  	st := core.NewStateTransition(zvm, msg, new(core.GasPool).AddGas(tx.Gas()))
   118  
   119  	if _, err = st.TransitionDb(); err != nil {
   120  		return fmt.Errorf("failed to execute transaction: %v", err)
   121  	}
   122  
   123  	// Retrieve the trace result and compare against the etalon
   124  	res, err := tracer.GetResult()
   125  	if err != nil {
   126  		return fmt.Errorf("failed to retrieve trace result: %v", err)
   127  	}
   128  	ret := make([]flatCallTrace, 0)
   129  	if err := json.Unmarshal(res, &ret); err != nil {
   130  		return fmt.Errorf("failed to unmarshal trace result: %v", err)
   131  	}
   132  	if !jsonEqualFlat(ret, test.Result) {
   133  		t.Logf("tracer name: %s", tracerName)
   134  
   135  		// uncomment this for easier debugging
   136  		// have, _ := json.MarshalIndent(ret, "", " ")
   137  		// want, _ := json.MarshalIndent(test.Result, "", " ")
   138  		// t.Logf("trace mismatch: \nhave %+v\nwant %+v", string(have), string(want))
   139  
   140  		// uncomment this for harder debugging <3 meowsbits
   141  		// lines := deep.Equal(ret, test.Result)
   142  		// for _, l := range lines {
   143  		// 	t.Logf("%s", l)
   144  		// 	t.FailNow()
   145  		// }
   146  
   147  		t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", ret, test.Result)
   148  	}
   149  	return nil
   150  }
   151  
   152  // TODO(now.youtrack.cloud/issue/TGZ-13)
   153  // Iterates over all the input-output datasets in the tracer parity test harness and
   154  // runs the Native tracer against them.
   155  func TestFlatCallTracerNative(t *testing.T) {
   156  	testFlatCallTracer("flatCallTracer", "call_tracer_flat", t)
   157  }
   158  
   159  func testFlatCallTracer(tracerName string, dirPath string, t *testing.T) {
   160  	files, err := os.ReadDir(filepath.Join("testdata", dirPath))
   161  	if err != nil {
   162  		t.Fatalf("failed to retrieve tracer test suite: %v", err)
   163  	}
   164  	for _, file := range files {
   165  		if !strings.HasSuffix(file.Name(), ".json") {
   166  			continue
   167  		}
   168  		file := file // capture range variable
   169  		t.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(t *testing.T) {
   170  			t.Parallel()
   171  
   172  			err := flatCallTracerTestRunner(tracerName, file.Name(), dirPath, t)
   173  			if err != nil {
   174  				t.Fatal(err)
   175  			}
   176  		})
   177  	}
   178  }
   179  
   180  // jsonEqual is similar to reflect.DeepEqual, but does a 'bounce' via json prior to
   181  // comparison
   182  func jsonEqualFlat(x, y interface{}) bool {
   183  	xTrace := new([]flatCallTrace)
   184  	yTrace := new([]flatCallTrace)
   185  	if xj, err := json.Marshal(x); err == nil {
   186  		json.Unmarshal(xj, xTrace)
   187  	} else {
   188  		return false
   189  	}
   190  	if yj, err := json.Marshal(y); err == nil {
   191  		json.Unmarshal(yj, yTrace)
   192  	} else {
   193  		return false
   194  	}
   195  	return reflect.DeepEqual(xTrace, yTrace)
   196  }
   197  
   198  func BenchmarkFlatCallTracer(b *testing.B) {
   199  	files, err := filepath.Glob("testdata/call_tracer_flat/*.json")
   200  	if err != nil {
   201  		b.Fatalf("failed to read testdata: %v", err)
   202  	}
   203  
   204  	for _, file := range files {
   205  		filename := strings.TrimPrefix(file, "testdata/call_tracer_flat/")
   206  		b.Run(camel(strings.TrimSuffix(filename, ".json")), func(b *testing.B) {
   207  			for n := 0; n < b.N; n++ {
   208  				err := flatCallTracerTestRunner("flatCallTracer", filename, "call_tracer_flat", b)
   209  				if err != nil {
   210  					b.Fatal(err)
   211  				}
   212  			}
   213  		})
   214  	}
   215  }