github.com/klaytn/klaytn@v1.10.2/node/cn/tracers/tracers_test.go (about)

     1  // Copyright 2018 The klaytn Authors
     2  // Copyright 2017 The go-ethereum Authors
     3  // This file is part of the go-ethereum library.
     4  //
     5  // The go-ethereum library is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Lesser General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // The go-ethereum library is distributed in the hope that it will be useful,
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    13  // GNU Lesser General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Lesser General Public License
    16  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    17  //
    18  // This file is derived from eth/tracers/tracers_test.go (2018/06/04).
    19  // Modified and improved for the klaytn development.
    20  
    21  package tracers
    22  
    23  import (
    24  	"crypto/ecdsa"
    25  	"crypto/rand"
    26  	"encoding/json"
    27  	"io/ioutil"
    28  	"math/big"
    29  	"path/filepath"
    30  	"strings"
    31  	"testing"
    32  
    33  	"github.com/klaytn/klaytn/blockchain"
    34  	"github.com/klaytn/klaytn/blockchain/types"
    35  	"github.com/klaytn/klaytn/blockchain/vm"
    36  	"github.com/klaytn/klaytn/common"
    37  	"github.com/klaytn/klaytn/common/hexutil"
    38  	"github.com/klaytn/klaytn/common/math"
    39  	"github.com/klaytn/klaytn/crypto"
    40  	"github.com/klaytn/klaytn/fork"
    41  	"github.com/klaytn/klaytn/params"
    42  	"github.com/klaytn/klaytn/rlp"
    43  	"github.com/klaytn/klaytn/storage/database"
    44  	"github.com/klaytn/klaytn/tests"
    45  	"github.com/stretchr/testify/assert"
    46  	"github.com/stretchr/testify/require"
    47  )
    48  
    49  // To generate a new callTracer test, use the `make_testdata.sh` script.
    50  
    51  type reverted struct {
    52  	Contract *common.Address `json:"contract"`
    53  	Message  string          `json:"message"`
    54  }
    55  
    56  // callTrace is the result of a callTracer run.
    57  type callTrace struct {
    58  	Type     string          `json:"type"`
    59  	From     *common.Address `json:"from"`
    60  	To       *common.Address `json:"to"`
    61  	Input    hexutil.Bytes   `json:"input"`
    62  	Output   hexutil.Bytes   `json:"output"`
    63  	Gas      hexutil.Uint64  `json:"gas,omitempty"`
    64  	GasUsed  hexutil.Uint64  `json:"gasUsed,omitempty"`
    65  	Value    hexutil.Uint64  `json:"value,omitempty"`
    66  	Error    string          `json:"error,omitempty"`
    67  	Calls    []callTrace     `json:"calls,omitempty"`
    68  	Reverted *reverted       `json:"reverted,omitempty"`
    69  }
    70  
    71  type callContext struct {
    72  	Number     math.HexOrDecimal64   `json:"number"`
    73  	BlockScore *math.HexOrDecimal256 `json:"blockScore"`
    74  	Time       math.HexOrDecimal64   `json:"timestamp"`
    75  	GasLimit   math.HexOrDecimal64   `json:"gasLimit"`
    76  	Miner      common.Address        `json:"miner"`
    77  }
    78  
    79  // callTracerTest defines a single test to check the call tracer against.
    80  type callTracerTest struct {
    81  	Genesis     *blockchain.Genesis `json:"genesis"`
    82  	Context     *callContext        `json:"context"`
    83  	Input       string              `json:"input,omitempty"`
    84  	Transaction map[string]string   `json:"transaction,omitempty"`
    85  	Result      *callTrace          `json:"result"`
    86  }
    87  
    88  func TestPrestateTracerCreate2(t *testing.T) {
    89  	unsignedTx := types.NewTransaction(1, common.HexToAddress("0x00000000000000000000000000000000deadbeef"),
    90  		new(big.Int), 5000000, big.NewInt(1), []byte{})
    91  
    92  	privateKeyECDSA, err := ecdsa.GenerateKey(crypto.S256(), rand.Reader)
    93  	if err != nil {
    94  		t.Fatalf("err %v", err)
    95  	}
    96  	signer := types.LatestSignerForChainID(big.NewInt(1))
    97  	tx, err := types.SignTx(unsignedTx, signer, privateKeyECDSA)
    98  	if err != nil {
    99  		t.Fatalf("err %v", err)
   100  	}
   101  	/**
   102  		This comes from one of the test-vectors on the Skinny Create2 - EIP
   103  	    address 0x00000000000000000000000000000000deadbeef
   104  	    salt 0x00000000000000000000000000000000000000000000000000000000cafebabe
   105  	    init_code 0xdeadbeef
   106  	    gas (assuming no mem expansion): 32006
   107  	    result: 0x60f3f640a8508fC6a86d45DF051962668E1e8AC7
   108  	*/
   109  	origin, _ := signer.Sender(tx)
   110  	context := vm.Context{
   111  		CanTransfer: blockchain.CanTransfer,
   112  		Transfer:    blockchain.Transfer,
   113  		Origin:      origin,
   114  		Coinbase:    common.Address{},
   115  		BlockNumber: new(big.Int).SetUint64(8000000),
   116  		Time:        new(big.Int).SetUint64(5),
   117  		BlockScore:  big.NewInt(0x30000),
   118  		GasLimit:    uint64(6000000),
   119  		GasPrice:    big.NewInt(1),
   120  	}
   121  	alloc := blockchain.GenesisAlloc{}
   122  	// The code pushes 'deadbeef' into memory, then the other params, and calls CREATE2, then returns
   123  	// the address
   124  	alloc[common.HexToAddress("0x00000000000000000000000000000000deadbeef")] = blockchain.GenesisAccount{
   125  		Nonce:   1,
   126  		Code:    hexutil.MustDecode("0x63deadbeef60005263cafebabe6004601c6000F560005260206000F3"),
   127  		Balance: big.NewInt(1),
   128  	}
   129  	alloc[origin] = blockchain.GenesisAccount{
   130  		Nonce:   1,
   131  		Code:    []byte{},
   132  		Balance: big.NewInt(500000000000000),
   133  	}
   134  	statedb := tests.MakePreState(database.NewMemoryDBManager(), alloc)
   135  	// Create the tracer, the EVM environment and run it
   136  	tracer, err := New("prestateTracer", false)
   137  	if err != nil {
   138  		t.Fatalf("failed to create call tracer: %v", err)
   139  	}
   140  	evm := vm.NewEVM(context, statedb, params.CypressChainConfig, &vm.Config{Debug: true, Tracer: tracer})
   141  
   142  	fork.SetHardForkBlockNumberConfig(&params.ChainConfig{})
   143  	msg, err := tx.AsMessageWithAccountKeyPicker(signer, statedb, context.BlockNumber.Uint64())
   144  	if err != nil {
   145  		t.Fatalf("failed to prepare transaction for tracing: %v", err)
   146  	}
   147  	st := blockchain.NewStateTransition(evm, msg)
   148  	if _, _, kerr := st.TransitionDb(); kerr.ErrTxInvalid != nil {
   149  		t.Fatalf("failed to execute transaction: %v", kerr.ErrTxInvalid)
   150  	}
   151  	// Retrieve the trace result and compare against the etalon
   152  	res, err := tracer.GetResult()
   153  	if err != nil {
   154  		t.Fatalf("failed to retrieve trace result: %v", err)
   155  	}
   156  	ret := make(map[string]interface{})
   157  	if err := json.Unmarshal(res, &ret); err != nil {
   158  		t.Fatalf("failed to unmarshal trace result: %v", err)
   159  	}
   160  	if _, has := ret["0x60f3f640a8508fc6a86d45df051962668e1e8ac7"]; !has {
   161  		t.Fatalf("Expected 0x60f3f640a8508fc6a86d45df051962668e1e8ac7 in result")
   162  	}
   163  }
   164  
   165  func covertToCallTrace(t *testing.T, internalTx *vm.InternalTxTrace) *callTrace {
   166  	// coverts nested InternalTxTraces
   167  	var nestedCalls []callTrace
   168  	for _, call := range internalTx.Calls {
   169  		nestedCalls = append(nestedCalls, *covertToCallTrace(t, call))
   170  	}
   171  
   172  	// decodes input and output if they are not an empty string
   173  	var decodedInput []byte
   174  	var decodedOutput []byte
   175  	var err error
   176  	if internalTx.Input != "" {
   177  		decodedInput, err = hexutil.Decode(internalTx.Input)
   178  		if err != nil {
   179  			t.Fatal("failed to decode input of an internal transaction", "err", err)
   180  		}
   181  	}
   182  	if internalTx.Output != "" {
   183  		decodedOutput, err = hexutil.Decode(internalTx.Output)
   184  		if err != nil {
   185  			t.Fatal("failed to decode output of an internal transaction", "err", err)
   186  		}
   187  	}
   188  
   189  	// decodes value into *big.Int if it is not an empty string
   190  	var value *big.Int
   191  	if internalTx.Value != "" {
   192  		value, err = hexutil.DecodeBig(internalTx.Value)
   193  		if err != nil {
   194  			t.Fatal("failed to decode value of an internal transaction", "err", err)
   195  		}
   196  	}
   197  	var val hexutil.Uint64
   198  	if value != nil {
   199  		val = hexutil.Uint64(value.Uint64())
   200  	}
   201  
   202  	errStr := ""
   203  	if internalTx.Error != nil {
   204  		errStr = internalTx.Error.Error()
   205  	}
   206  
   207  	var revertedInfo *reverted
   208  	if internalTx.Reverted != nil {
   209  		revertedInfo = &reverted{
   210  			Contract: internalTx.Reverted.Contract,
   211  			Message:  internalTx.Reverted.Message,
   212  		}
   213  	}
   214  
   215  	ct := &callTrace{
   216  		Type:     internalTx.Type,
   217  		From:     internalTx.From,
   218  		To:       internalTx.To,
   219  		Input:    decodedInput,
   220  		Output:   decodedOutput,
   221  		Gas:      hexutil.Uint64(internalTx.Gas),
   222  		GasUsed:  hexutil.Uint64(internalTx.GasUsed),
   223  		Value:    val,
   224  		Error:    errStr,
   225  		Calls:    nestedCalls,
   226  		Reverted: revertedInfo,
   227  	}
   228  
   229  	return ct
   230  }
   231  
   232  // Iterates over all the input-output datasets in the tracer test harness and
   233  // runs the JavaScript tracers against them.
   234  func TestCallTracer(t *testing.T) {
   235  	files, err := ioutil.ReadDir("testdata")
   236  	if err != nil {
   237  		t.Fatalf("failed to retrieve tracer test suite: %v", err)
   238  	}
   239  	for _, file := range files {
   240  		if !strings.HasPrefix(file.Name(), "call_tracer_") {
   241  			continue
   242  		}
   243  		file := file // capture range variable
   244  		t.Run(camel(strings.TrimSuffix(strings.TrimPrefix(file.Name(), "call_tracer_"), ".json")), func(t *testing.T) {
   245  			// t.Parallel()
   246  
   247  			// Call tracer test found, read if from disk
   248  			blob, err := ioutil.ReadFile(filepath.Join("testdata", file.Name()))
   249  			if err != nil {
   250  				t.Fatalf("failed to read testcase: %v", err)
   251  			}
   252  			test := new(callTracerTest)
   253  			if err := json.Unmarshal(blob, test); err != nil {
   254  				t.Fatalf("failed to parse testcase: %v", err)
   255  			}
   256  
   257  			signer := types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)))
   258  			tx := new(types.Transaction)
   259  			// Configure a blockchain with the given prestate
   260  			if test.Input != "" {
   261  				if err := rlp.DecodeBytes(common.FromHex(test.Input), tx); err != nil {
   262  					t.Fatalf("failed to parse testcase input: %v", err)
   263  				}
   264  			} else {
   265  				// Configure a blockchain with the given prestate
   266  				value := new(big.Int)
   267  				gasPrice := new(big.Int)
   268  				err = value.UnmarshalJSON([]byte(test.Transaction["value"]))
   269  				require.NoError(t, err)
   270  				err = gasPrice.UnmarshalJSON([]byte(test.Transaction["gasPrice"]))
   271  				require.NoError(t, err)
   272  				nonce, b := math.ParseUint64(test.Transaction["nonce"])
   273  				require.True(t, b)
   274  				gas, b := math.ParseUint64(test.Transaction["gas"])
   275  				require.True(t, b)
   276  
   277  				to := common.HexToAddress(test.Transaction["to"])
   278  				input := common.FromHex(test.Transaction["input"])
   279  
   280  				tx = types.NewTransaction(nonce, to, value, gas, gasPrice, input)
   281  
   282  				testKey, err := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
   283  				require.NoError(t, err)
   284  				err = tx.Sign(signer, testKey)
   285  				require.NoError(t, err)
   286  			}
   287  
   288  			origin, _ := signer.Sender(tx)
   289  
   290  			context := vm.Context{
   291  				CanTransfer: blockchain.CanTransfer,
   292  				Transfer:    blockchain.Transfer,
   293  				Origin:      origin,
   294  				BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)),
   295  				Time:        new(big.Int).SetUint64(uint64(test.Context.Time)),
   296  				BlockScore:  (*big.Int)(test.Context.BlockScore),
   297  				GasLimit:    uint64(test.Context.GasLimit),
   298  				GasPrice:    tx.GasPrice(),
   299  			}
   300  			statedb := tests.MakePreState(database.NewMemoryDBManager(), test.Genesis.Alloc)
   301  
   302  			// Create the tracer, the EVM environment and run it
   303  			tracer, err := New("callTracer", false)
   304  			if err != nil {
   305  				t.Fatalf("failed to create call tracer: %v", err)
   306  			}
   307  			evm := vm.NewEVM(context, statedb, test.Genesis.Config, &vm.Config{Debug: true, Tracer: tracer})
   308  
   309  			fork.SetHardForkBlockNumberConfig(test.Genesis.Config)
   310  			msg, err := tx.AsMessageWithAccountKeyPicker(signer, statedb, context.BlockNumber.Uint64())
   311  			if err != nil {
   312  				t.Fatalf("failed to prepare transaction for tracing: %v", err)
   313  			}
   314  			st := blockchain.NewStateTransition(evm, msg)
   315  			if _, _, kerr := st.TransitionDb(); kerr.ErrTxInvalid != nil {
   316  				t.Fatalf("failed to execute transaction: %v", kerr.ErrTxInvalid)
   317  			}
   318  			// Retrieve the trace result and compare against the etalon
   319  			res, err := tracer.GetResult()
   320  			if err != nil {
   321  				t.Fatalf("failed to retrieve trace result: %v", err)
   322  			}
   323  			ret := new(callTrace)
   324  			if err := json.Unmarshal(res, ret); err != nil {
   325  				t.Fatalf("failed to unmarshal trace result: %v", err)
   326  			}
   327  			jsonEqual(t, ret, test.Result)
   328  		})
   329  	}
   330  }
   331  
   332  // Compare JSON representations for human-friendly diffs.
   333  func jsonEqual(t *testing.T, x, y interface{}) {
   334  	xj, err := json.MarshalIndent(x, "", "  ")
   335  	assert.Nil(t, err)
   336  
   337  	yj, err := json.MarshalIndent(y, "", "  ")
   338  	assert.Nil(t, err)
   339  
   340  	assert.Equal(t, string(xj), string(yj))
   341  }
   342  
   343  // Iterates over all the input-output datasets in the tracer test harness and
   344  // runs the InternalCallTracer against them.
   345  func TestInternalCallTracer(t *testing.T) {
   346  	files, err := ioutil.ReadDir("testdata")
   347  	if err != nil {
   348  		t.Fatalf("failed to retrieve tracer test suite: %v", err)
   349  	}
   350  	for _, file := range files {
   351  		if !strings.HasPrefix(file.Name(), "call_tracer_") {
   352  			continue
   353  		}
   354  		file := file // capture range variable
   355  		t.Run(camel(strings.TrimSuffix(strings.TrimPrefix(file.Name(), "call_tracer_"), ".json")), func(t *testing.T) {
   356  			// t.Parallel()
   357  
   358  			// Call tracer test found, read if from disk
   359  			blob, err := ioutil.ReadFile(filepath.Join("testdata", file.Name()))
   360  			if err != nil {
   361  				t.Fatalf("failed to read testcase: %v", err)
   362  			}
   363  			test := new(callTracerTest)
   364  			if err := json.Unmarshal(blob, test); err != nil {
   365  				t.Fatalf("failed to parse testcase: %v", err)
   366  			}
   367  
   368  			signer := types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)))
   369  			tx := new(types.Transaction)
   370  			// Configure a blockchain with the given prestate
   371  			if test.Input != "" {
   372  				if err := rlp.DecodeBytes(common.FromHex(test.Input), tx); err != nil {
   373  					t.Fatalf("failed to parse testcase input: %v", err)
   374  				}
   375  			} else {
   376  				// Configure a blockchain with the given prestate
   377  				value := new(big.Int)
   378  				gasPrice := new(big.Int)
   379  				err = value.UnmarshalJSON([]byte(test.Transaction["value"]))
   380  				require.NoError(t, err)
   381  				err = gasPrice.UnmarshalJSON([]byte(test.Transaction["gasPrice"]))
   382  				require.NoError(t, err)
   383  				nonce, b := math.ParseUint64(test.Transaction["nonce"])
   384  				require.True(t, b)
   385  				gas, b := math.ParseUint64(test.Transaction["gas"])
   386  				require.True(t, b)
   387  
   388  				to := common.HexToAddress(test.Transaction["to"])
   389  				input := common.FromHex(test.Transaction["input"])
   390  
   391  				tx = types.NewTransaction(nonce, to, value, gas, gasPrice, input)
   392  
   393  				testKey, err := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
   394  				require.NoError(t, err)
   395  				err = tx.Sign(signer, testKey)
   396  				require.NoError(t, err)
   397  			}
   398  
   399  			origin, _ := signer.Sender(tx)
   400  
   401  			context := vm.Context{
   402  				CanTransfer: blockchain.CanTransfer,
   403  				Transfer:    blockchain.Transfer,
   404  				Origin:      origin,
   405  				BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)),
   406  				Time:        new(big.Int).SetUint64(uint64(test.Context.Time)),
   407  				BlockScore:  (*big.Int)(test.Context.BlockScore),
   408  				GasLimit:    uint64(test.Context.GasLimit),
   409  				GasPrice:    tx.GasPrice(),
   410  			}
   411  			statedb := tests.MakePreState(database.NewMemoryDBManager(), test.Genesis.Alloc)
   412  
   413  			// Create the tracer, the EVM environment and run it
   414  			tracer := vm.NewInternalTxTracer()
   415  			evm := vm.NewEVM(context, statedb, test.Genesis.Config, &vm.Config{Debug: true, Tracer: tracer})
   416  
   417  			fork.SetHardForkBlockNumberConfig(test.Genesis.Config)
   418  			msg, err := tx.AsMessageWithAccountKeyPicker(signer, statedb, context.BlockNumber.Uint64())
   419  			if err != nil {
   420  				t.Fatalf("failed to prepare transaction for tracing: %v", err)
   421  			}
   422  			st := blockchain.NewStateTransition(evm, msg)
   423  			if _, _, kerr := st.TransitionDb(); kerr.ErrTxInvalid != nil {
   424  				t.Fatalf("failed to execute transaction: %v", kerr.ErrTxInvalid)
   425  			}
   426  			// Retrieve the trace result and compare against the etalon
   427  			res, err := tracer.GetResult()
   428  			if err != nil {
   429  				t.Fatalf("failed to retrieve trace result: %v", err)
   430  			}
   431  
   432  			resultFromInternalCallTracer := covertToCallTrace(t, res)
   433  			jsonEqual(t, test.Result, resultFromInternalCallTracer)
   434  		})
   435  	}
   436  }