github.com/klaytn/klaytn@v1.12.1/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 "math/big" 28 "os" 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 math.HexOrDecimal256 `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 txContext := vm.TxContext{ 111 Origin: origin, 112 GasPrice: big.NewInt(1), 113 } 114 blockContext := vm.BlockContext{ 115 CanTransfer: blockchain.CanTransfer, 116 Transfer: blockchain.Transfer, 117 Coinbase: common.Address{}, 118 BlockNumber: new(big.Int).SetUint64(8000000), 119 Time: new(big.Int).SetUint64(5), 120 BlockScore: big.NewInt(0x30000), 121 GasLimit: uint64(6000000), 122 } 123 alloc := blockchain.GenesisAlloc{} 124 // The code pushes 'deadbeef' into memory, then the other params, and calls CREATE2, then returns 125 // the address 126 alloc[common.HexToAddress("0x00000000000000000000000000000000deadbeef")] = blockchain.GenesisAccount{ 127 Nonce: 1, 128 Code: hexutil.MustDecode("0x63deadbeef60005263cafebabe6004601c6000F560005260206000F3"), 129 Balance: big.NewInt(1), 130 } 131 alloc[origin] = blockchain.GenesisAccount{ 132 Nonce: 1, 133 Code: []byte{}, 134 Balance: big.NewInt(500000000000000), 135 } 136 statedb := tests.MakePreState(database.NewMemoryDBManager(), alloc) 137 // Create the tracer, the EVM environment and run it 138 tracer, err := New("prestateTracer", new(Context), false) 139 if err != nil { 140 t.Fatalf("failed to create call tracer: %v", err) 141 } 142 evm := vm.NewEVM(blockContext, txContext, statedb, params.CypressChainConfig, &vm.Config{Debug: true, Tracer: tracer}) 143 144 fork.SetHardForkBlockNumberConfig(¶ms.ChainConfig{}) 145 msg, err := tx.AsMessageWithAccountKeyPicker(signer, statedb, blockContext.BlockNumber.Uint64()) 146 if err != nil { 147 t.Fatalf("failed to prepare transaction for tracing: %v", err) 148 } 149 st := blockchain.NewStateTransition(evm, msg) 150 if _, err := st.TransitionDb(); err != nil { 151 t.Fatalf("failed to execute transaction: %v", err) 152 } 153 // Retrieve the trace result and compare against the etalon 154 res, err := tracer.GetResult() 155 if err != nil { 156 t.Fatalf("failed to retrieve trace result: %v", err) 157 } 158 ret := make(map[string]interface{}) 159 if err := json.Unmarshal(res, &ret); err != nil { 160 t.Fatalf("failed to unmarshal trace result: %v", err) 161 } 162 if _, has := ret["0x60f3f640a8508fc6a86d45df051962668e1e8ac7"]; !has { 163 t.Fatalf("Expected 0x60f3f640a8508fc6a86d45df051962668e1e8ac7 in result") 164 } 165 } 166 167 func covertToCallTrace(t *testing.T, internalTx *vm.InternalTxTrace) *callTrace { 168 // coverts nested InternalTxTraces 169 var nestedCalls []callTrace 170 for _, call := range internalTx.Calls { 171 nestedCalls = append(nestedCalls, *covertToCallTrace(t, call)) 172 } 173 174 // decodes input and output if they are not an empty string 175 var decodedInput []byte 176 var decodedOutput []byte 177 var err error 178 if internalTx.Input != "" { 179 decodedInput, err = hexutil.Decode(internalTx.Input) 180 if err != nil { 181 t.Fatal("failed to decode input of an internal transaction", "err", err) 182 } 183 } 184 if internalTx.Output != "" { 185 decodedOutput, err = hexutil.Decode(internalTx.Output) 186 if err != nil { 187 t.Fatal("failed to decode output of an internal transaction", "err", err) 188 } 189 } 190 191 // decodes value into *big.Int if it is not an empty string 192 var value *big.Int 193 if internalTx.Value != "" { 194 value, err = hexutil.DecodeBig(internalTx.Value) 195 if err != nil { 196 t.Fatal("failed to decode value of an internal transaction", "err", err) 197 } 198 } 199 var val math.HexOrDecimal256 200 if value != nil { 201 val = math.HexOrDecimal256(*value) 202 } 203 204 errStr := "" 205 if internalTx.Error != nil { 206 errStr = internalTx.Error.Error() 207 } 208 209 var revertedInfo *reverted 210 if internalTx.Reverted != nil { 211 revertedInfo = &reverted{ 212 Contract: internalTx.Reverted.Contract, 213 Message: internalTx.Reverted.Message, 214 } 215 } 216 217 ct := &callTrace{ 218 Type: internalTx.Type, 219 From: internalTx.From, 220 To: internalTx.To, 221 Input: decodedInput, 222 Output: decodedOutput, 223 Gas: hexutil.Uint64(internalTx.Gas), 224 GasUsed: hexutil.Uint64(internalTx.GasUsed), 225 Value: val, 226 Error: errStr, 227 Calls: nestedCalls, 228 Reverted: revertedInfo, 229 } 230 231 return ct 232 } 233 234 // Iterates over all the input-output datasets in the tracer test harness and 235 // runs the JavaScript tracers against them. 236 func TestCallTracer(t *testing.T) { 237 files, err := os.ReadDir("testdata") 238 if err != nil { 239 t.Fatalf("failed to retrieve tracer test suite: %v", err) 240 } 241 for _, file := range files { 242 if !strings.HasPrefix(file.Name(), "call_tracer_") { 243 continue 244 } 245 file := file // capture range variable 246 t.Run(camel(strings.TrimSuffix(strings.TrimPrefix(file.Name(), "call_tracer_"), ".json")), func(t *testing.T) { 247 // t.Parallel() 248 249 // Call tracer test found, read if from disk 250 blob, err := os.ReadFile(filepath.Join("testdata", file.Name())) 251 if err != nil { 252 t.Fatalf("failed to read testcase: %v", err) 253 } 254 test := new(callTracerTest) 255 if err := json.Unmarshal(blob, test); err != nil { 256 t.Fatalf("failed to parse testcase: %v", err) 257 } 258 259 signer := types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number))) 260 tx := new(types.Transaction) 261 // Configure a blockchain with the given prestate 262 if test.Input != "" { 263 if err := rlp.DecodeBytes(common.FromHex(test.Input), tx); err != nil { 264 t.Fatalf("failed to parse testcase input: %v", err) 265 } 266 } else { 267 // Configure a blockchain with the given prestate 268 value := new(big.Int) 269 gasPrice := new(big.Int) 270 err = value.UnmarshalJSON([]byte(test.Transaction["value"])) 271 require.NoError(t, err) 272 err = gasPrice.UnmarshalJSON([]byte(test.Transaction["gasPrice"])) 273 require.NoError(t, err) 274 nonce, b := math.ParseUint64(test.Transaction["nonce"]) 275 require.True(t, b) 276 gas, b := math.ParseUint64(test.Transaction["gas"]) 277 require.True(t, b) 278 279 to := common.HexToAddress(test.Transaction["to"]) 280 input := common.FromHex(test.Transaction["input"]) 281 282 tx = types.NewTransaction(nonce, to, value, gas, gasPrice, input) 283 284 testKey, err := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") 285 require.NoError(t, err) 286 err = tx.Sign(signer, testKey) 287 require.NoError(t, err) 288 } 289 290 origin, _ := signer.Sender(tx) 291 292 txContext := vm.TxContext{ 293 Origin: origin, 294 GasPrice: tx.GasPrice(), 295 } 296 blockContext := vm.BlockContext{ 297 CanTransfer: blockchain.CanTransfer, 298 Transfer: blockchain.Transfer, 299 BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)), 300 Time: new(big.Int).SetUint64(uint64(test.Context.Time)), 301 BlockScore: (*big.Int)(test.Context.BlockScore), 302 GasLimit: uint64(test.Context.GasLimit), 303 } 304 statedb := tests.MakePreState(database.NewMemoryDBManager(), test.Genesis.Alloc) 305 306 // Create the tracer, the EVM environment and run it 307 tracer, err := New("callTracer", new(Context), false) 308 if err != nil { 309 t.Fatalf("failed to create call tracer: %v", err) 310 } 311 evm := vm.NewEVM(blockContext, txContext, statedb, test.Genesis.Config, &vm.Config{Debug: true, Tracer: tracer}) 312 313 fork.SetHardForkBlockNumberConfig(test.Genesis.Config) 314 msg, err := tx.AsMessageWithAccountKeyPicker(signer, statedb, blockContext.BlockNumber.Uint64()) 315 if err != nil { 316 t.Fatalf("failed to prepare transaction for tracing: %v", err) 317 } 318 st := blockchain.NewStateTransition(evm, msg) 319 if _, err := st.TransitionDb(); err != nil { 320 t.Fatalf("failed to execute transaction: %v", err) 321 } 322 // Retrieve the trace result and compare against the etalon 323 res, err := tracer.GetResult() 324 if err != nil { 325 t.Fatalf("failed to retrieve trace result: %v", err) 326 } 327 ret := new(callTrace) 328 if err := json.Unmarshal(res, ret); err != nil { 329 t.Fatalf("failed to unmarshal trace result: %v", err) 330 } 331 jsonEqual(t, ret, test.Result) 332 }) 333 } 334 } 335 336 // Compare JSON representations for human-friendly diffs. 337 func jsonEqual(t *testing.T, x, y interface{}) { 338 xj, err := json.MarshalIndent(x, "", " ") 339 assert.Nil(t, err) 340 341 yj, err := json.MarshalIndent(y, "", " ") 342 assert.Nil(t, err) 343 344 assert.Equal(t, string(xj), string(yj)) 345 } 346 347 // Iterates over all the input-output datasets in the tracer test harness and 348 // runs the InternalCallTracer against them. 349 func TestInternalCallTracer(t *testing.T) { 350 files, err := os.ReadDir("testdata") 351 if err != nil { 352 t.Fatalf("failed to retrieve tracer test suite: %v", err) 353 } 354 for _, file := range files { 355 if !strings.HasPrefix(file.Name(), "call_tracer_") { 356 continue 357 } 358 file := file // capture range variable 359 t.Run(camel(strings.TrimSuffix(strings.TrimPrefix(file.Name(), "call_tracer_"), ".json")), func(t *testing.T) { 360 // t.Parallel() 361 362 // Call tracer test found, read if from disk 363 blob, err := os.ReadFile(filepath.Join("testdata", file.Name())) 364 if err != nil { 365 t.Fatalf("failed to read testcase: %v", err) 366 } 367 test := new(callTracerTest) 368 if err := json.Unmarshal(blob, test); err != nil { 369 t.Fatalf("failed to parse testcase: %v", err) 370 } 371 372 signer := types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number))) 373 tx := new(types.Transaction) 374 // Configure a blockchain with the given prestate 375 if test.Input != "" { 376 if err := rlp.DecodeBytes(common.FromHex(test.Input), tx); err != nil { 377 t.Fatalf("failed to parse testcase input: %v", err) 378 } 379 } else { 380 // Configure a blockchain with the given prestate 381 value := new(big.Int) 382 gasPrice := new(big.Int) 383 err = value.UnmarshalJSON([]byte(test.Transaction["value"])) 384 require.NoError(t, err) 385 err = gasPrice.UnmarshalJSON([]byte(test.Transaction["gasPrice"])) 386 require.NoError(t, err) 387 nonce, b := math.ParseUint64(test.Transaction["nonce"]) 388 require.True(t, b) 389 gas, b := math.ParseUint64(test.Transaction["gas"]) 390 require.True(t, b) 391 392 to := common.HexToAddress(test.Transaction["to"]) 393 input := common.FromHex(test.Transaction["input"]) 394 395 tx = types.NewTransaction(nonce, to, value, gas, gasPrice, input) 396 397 testKey, err := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") 398 require.NoError(t, err) 399 err = tx.Sign(signer, testKey) 400 require.NoError(t, err) 401 } 402 403 origin, _ := signer.Sender(tx) 404 405 txContext := vm.TxContext{ 406 Origin: origin, 407 GasPrice: tx.GasPrice(), 408 } 409 blockContext := vm.BlockContext{ 410 CanTransfer: blockchain.CanTransfer, 411 Transfer: blockchain.Transfer, 412 BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)), 413 Time: new(big.Int).SetUint64(uint64(test.Context.Time)), 414 BlockScore: (*big.Int)(test.Context.BlockScore), 415 GasLimit: uint64(test.Context.GasLimit), 416 } 417 statedb := tests.MakePreState(database.NewMemoryDBManager(), test.Genesis.Alloc) 418 419 // Create the tracer, the EVM environment and run it 420 tracer := vm.NewInternalTxTracer() 421 evm := vm.NewEVM(blockContext, txContext, statedb, test.Genesis.Config, &vm.Config{Debug: true, Tracer: tracer}) 422 423 fork.SetHardForkBlockNumberConfig(test.Genesis.Config) 424 msg, err := tx.AsMessageWithAccountKeyPicker(signer, statedb, blockContext.BlockNumber.Uint64()) 425 if err != nil { 426 t.Fatalf("failed to prepare transaction for tracing: %v", err) 427 } 428 st := blockchain.NewStateTransition(evm, msg) 429 if _, err := st.TransitionDb(); err != nil { 430 t.Fatalf("failed to execute transaction: %v", err) 431 } 432 // Retrieve the trace result and compare against the etalon 433 res, err := tracer.GetResult() 434 if err != nil { 435 t.Fatalf("failed to retrieve trace result: %v", err) 436 } 437 438 resultFromInternalCallTracer := covertToCallTrace(t, res) 439 jsonEqual(t, test.Result, resultFromInternalCallTracer) 440 }) 441 } 442 }