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 }