github.com/calmw/ethereum@v0.1.1/eth/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/calmw/ethereum/common" 28 "github.com/calmw/ethereum/common/hexutil" 29 "github.com/calmw/ethereum/common/math" 30 "github.com/calmw/ethereum/core" 31 "github.com/calmw/ethereum/core/rawdb" 32 "github.com/calmw/ethereum/core/types" 33 "github.com/calmw/ethereum/core/vm" 34 "github.com/calmw/ethereum/eth/tracers" 35 "github.com/calmw/ethereum/params" 36 "github.com/calmw/ethereum/rlp" 37 "github.com/calmw/ethereum/tests" 38 ) 39 40 type callContext struct { 41 Number math.HexOrDecimal64 `json:"number"` 42 Difficulty *math.HexOrDecimal256 `json:"difficulty"` 43 Time math.HexOrDecimal64 `json:"timestamp"` 44 GasLimit math.HexOrDecimal64 `json:"gasLimit"` 45 Miner common.Address `json:"miner"` 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 // Iterates over all the input-output datasets in the tracer test harness and 82 // runs the JavaScript tracers against them. 83 func TestCallTracerLegacy(t *testing.T) { 84 testCallTracer("callTracerLegacy", "call_tracer_legacy", t) 85 } 86 87 func TestCallTracerNative(t *testing.T) { 88 testCallTracer("callTracer", "call_tracer", t) 89 } 90 91 func TestCallTracerNativeWithLog(t *testing.T) { 92 testCallTracer("callTracer", "call_tracer_withLog", t) 93 } 94 95 func testCallTracer(tracerName string, dirPath string, t *testing.T) { 96 isLegacy := strings.HasSuffix(dirPath, "_legacy") 97 files, err := os.ReadDir(filepath.Join("testdata", dirPath)) 98 if err != nil { 99 t.Fatalf("failed to retrieve tracer test suite: %v", err) 100 } 101 for _, file := range files { 102 if !strings.HasSuffix(file.Name(), ".json") { 103 continue 104 } 105 file := file // capture range variable 106 t.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(t *testing.T) { 107 t.Parallel() 108 109 var ( 110 test = new(callTracerTest) 111 tx = new(types.Transaction) 112 ) 113 // Call tracer test found, read if from disk 114 if blob, err := os.ReadFile(filepath.Join("testdata", dirPath, file.Name())); err != nil { 115 t.Fatalf("failed to read testcase: %v", err) 116 } else if err := json.Unmarshal(blob, test); err != nil { 117 t.Fatalf("failed to parse testcase: %v", err) 118 } 119 if err := tx.UnmarshalBinary(common.FromHex(test.Input)); err != nil { 120 t.Fatalf("failed to parse testcase input: %v", err) 121 } 122 // Configure a blockchain with the given prestate 123 var ( 124 signer = types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)), uint64(test.Context.Time)) 125 origin, _ = signer.Sender(tx) 126 txContext = vm.TxContext{ 127 Origin: origin, 128 GasPrice: tx.GasPrice(), 129 } 130 context = vm.BlockContext{ 131 CanTransfer: core.CanTransfer, 132 Transfer: core.Transfer, 133 Coinbase: test.Context.Miner, 134 BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)), 135 Time: uint64(test.Context.Time), 136 Difficulty: (*big.Int)(test.Context.Difficulty), 137 GasLimit: uint64(test.Context.GasLimit), 138 BaseFee: test.Genesis.BaseFee, 139 } 140 _, statedb = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false) 141 ) 142 tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), test.TracerConfig) 143 if err != nil { 144 t.Fatalf("failed to create call tracer: %v", err) 145 } 146 evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Tracer: tracer}) 147 msg, err := core.TransactionToMessage(tx, signer, nil) 148 if err != nil { 149 t.Fatalf("failed to prepare transaction for tracing: %v", err) 150 } 151 vmRet, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.Gas())) 152 if err != nil { 153 t.Fatalf("failed to execute transaction: %v", err) 154 } 155 // Retrieve the trace result and compare against the expected. 156 res, err := tracer.GetResult() 157 if err != nil { 158 t.Fatalf("failed to retrieve trace result: %v", err) 159 } 160 // The legacy javascript calltracer marshals json in js, which 161 // is not deterministic (as opposed to the golang json encoder). 162 if isLegacy { 163 // This is a tweak to make it deterministic. Can be removed when 164 // we remove the legacy tracer. 165 var x callTrace 166 json.Unmarshal(res, &x) 167 res, _ = json.Marshal(x) 168 } 169 want, err := json.Marshal(test.Result) 170 if err != nil { 171 t.Fatalf("failed to marshal test: %v", err) 172 } 173 if string(want) != string(res) { 174 t.Fatalf("trace mismatch\n have: %v\n want: %v\n", string(res), string(want)) 175 } 176 // Sanity check: compare top call's gas used against vm result 177 type simpleResult struct { 178 GasUsed hexutil.Uint64 179 } 180 var topCall simpleResult 181 if err := json.Unmarshal(res, &topCall); err != nil { 182 t.Fatalf("failed to unmarshal top calls gasUsed: %v", err) 183 } 184 if uint64(topCall.GasUsed) != vmRet.UsedGas { 185 t.Fatalf("top call has invalid gasUsed. have: %d want: %d", topCall.GasUsed, vmRet.UsedGas) 186 } 187 }) 188 } 189 } 190 191 func BenchmarkTracers(b *testing.B) { 192 files, err := os.ReadDir(filepath.Join("testdata", "call_tracer")) 193 if err != nil { 194 b.Fatalf("failed to retrieve tracer test suite: %v", err) 195 } 196 for _, file := range files { 197 if !strings.HasSuffix(file.Name(), ".json") { 198 continue 199 } 200 file := file // capture range variable 201 b.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(b *testing.B) { 202 blob, err := os.ReadFile(filepath.Join("testdata", "call_tracer", file.Name())) 203 if err != nil { 204 b.Fatalf("failed to read testcase: %v", err) 205 } 206 test := new(callTracerTest) 207 if err := json.Unmarshal(blob, test); err != nil { 208 b.Fatalf("failed to parse testcase: %v", err) 209 } 210 benchTracer("callTracer", test, b) 211 }) 212 } 213 } 214 215 func benchTracer(tracerName string, test *callTracerTest, b *testing.B) { 216 // Configure a blockchain with the given prestate 217 tx := new(types.Transaction) 218 if err := rlp.DecodeBytes(common.FromHex(test.Input), tx); err != nil { 219 b.Fatalf("failed to parse testcase input: %v", err) 220 } 221 signer := types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)), uint64(test.Context.Time)) 222 msg, err := core.TransactionToMessage(tx, signer, nil) 223 if err != nil { 224 b.Fatalf("failed to prepare transaction for tracing: %v", err) 225 } 226 origin, _ := signer.Sender(tx) 227 txContext := vm.TxContext{ 228 Origin: origin, 229 GasPrice: tx.GasPrice(), 230 } 231 context := vm.BlockContext{ 232 CanTransfer: core.CanTransfer, 233 Transfer: core.Transfer, 234 Coinbase: test.Context.Miner, 235 BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)), 236 Time: uint64(test.Context.Time), 237 Difficulty: (*big.Int)(test.Context.Difficulty), 238 GasLimit: uint64(test.Context.GasLimit), 239 } 240 _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false) 241 242 b.ReportAllocs() 243 b.ResetTimer() 244 for i := 0; i < b.N; i++ { 245 tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), nil) 246 if err != nil { 247 b.Fatalf("failed to create call tracer: %v", err) 248 } 249 evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Tracer: tracer}) 250 snap := statedb.Snapshot() 251 st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas())) 252 if _, err = st.TransitionDb(); err != nil { 253 b.Fatalf("failed to execute transaction: %v", err) 254 } 255 if _, err = tracer.GetResult(); err != nil { 256 b.Fatal(err) 257 } 258 statedb.RevertToSnapshot(snap) 259 } 260 } 261 262 func TestInternals(t *testing.T) { 263 var ( 264 to = common.HexToAddress("0x00000000000000000000000000000000deadbeef") 265 origin = common.HexToAddress("0x00000000000000000000000000000000feed") 266 txContext = vm.TxContext{ 267 Origin: origin, 268 GasPrice: big.NewInt(1), 269 } 270 context = vm.BlockContext{ 271 CanTransfer: core.CanTransfer, 272 Transfer: core.Transfer, 273 Coinbase: common.Address{}, 274 BlockNumber: new(big.Int).SetUint64(8000000), 275 Time: 5, 276 Difficulty: big.NewInt(0x30000), 277 GasLimit: uint64(6000000), 278 } 279 ) 280 mkTracer := func(name string, cfg json.RawMessage) tracers.Tracer { 281 tr, err := tracers.DefaultDirectory.New(name, nil, cfg) 282 if err != nil { 283 t.Fatalf("failed to create call tracer: %v", err) 284 } 285 return tr 286 } 287 288 for _, tc := range []struct { 289 name string 290 code []byte 291 tracer tracers.Tracer 292 want string 293 }{ 294 { 295 // TestZeroValueToNotExitCall tests the calltracer(s) on the following: 296 // Tx to A, A calls B with zero value. B does not already exist. 297 // Expected: that enter/exit is invoked and the inner call is shown in the result 298 name: "ZeroValueToNotExitCall", 299 code: []byte{ 300 byte(vm.PUSH1), 0x0, byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), // in and outs zero 301 byte(vm.DUP1), byte(vm.PUSH1), 0xff, byte(vm.GAS), // value=0,address=0xff, gas=GAS 302 byte(vm.CALL), 303 }, 304 tracer: mkTracer("callTracer", nil), 305 want: `{"from":"0x000000000000000000000000000000000000feed","gas":"0xc350","gasUsed":"0x54d8","to":"0x00000000000000000000000000000000deadbeef","input":"0x","calls":[{"from":"0x00000000000000000000000000000000deadbeef","gas":"0x6cbf","gasUsed":"0x0","to":"0x00000000000000000000000000000000000000ff","input":"0x","value":"0x0","type":"CALL"}],"value":"0x0","type":"CALL"}`, 306 }, 307 { 308 name: "Stack depletion in LOG0", 309 code: []byte{byte(vm.LOG3)}, 310 tracer: mkTracer("callTracer", json.RawMessage(`{ "withLog": true }`)), 311 want: `{"from":"0x000000000000000000000000000000000000feed","gas":"0xc350","gasUsed":"0xc350","to":"0x00000000000000000000000000000000deadbeef","input":"0x","error":"stack underflow (0 \u003c=\u003e 5)","value":"0x0","type":"CALL"}`, 312 }, 313 { 314 name: "Mem expansion in LOG0", 315 code: []byte{ 316 byte(vm.PUSH1), 0x1, 317 byte(vm.PUSH1), 0x0, 318 byte(vm.MSTORE), 319 byte(vm.PUSH1), 0xff, 320 byte(vm.PUSH1), 0x0, 321 byte(vm.LOG0), 322 }, 323 tracer: mkTracer("callTracer", json.RawMessage(`{ "withLog": true }`)), 324 want: `{"from":"0x000000000000000000000000000000000000feed","gas":"0xc350","gasUsed":"0x5b9e","to":"0x00000000000000000000000000000000deadbeef","input":"0x","logs":[{"address":"0x00000000000000000000000000000000deadbeef","topics":[],"data":"0x000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}],"value":"0x0","type":"CALL"}`, 325 }, 326 { 327 // Leads to OOM on the prestate tracer 328 name: "Prestate-tracer - mem expansion in CREATE2", 329 code: []byte{ 330 byte(vm.PUSH1), 0x1, 331 byte(vm.PUSH1), 0x0, 332 byte(vm.MSTORE), 333 byte(vm.PUSH1), 0x1, 334 byte(vm.PUSH5), 0xff, 0xff, 0xff, 0xff, 0xff, 335 byte(vm.PUSH1), 0x1, 336 byte(vm.PUSH1), 0x0, 337 byte(vm.CREATE2), 338 byte(vm.PUSH1), 0xff, 339 byte(vm.PUSH1), 0x0, 340 byte(vm.LOG0), 341 }, 342 tracer: mkTracer("prestateTracer", json.RawMessage(`{ "withLog": true }`)), 343 want: `{"0x0000000000000000000000000000000000000000":{"balance":"0x0"},"0x000000000000000000000000000000000000feed":{"balance":"0x1c6bf52640350"},"0x00000000000000000000000000000000deadbeef":{"balance":"0x0","code":"0x6001600052600164ffffffffff60016000f560ff6000a0"}}`, 344 }, 345 } { 346 _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), 347 core.GenesisAlloc{ 348 to: core.GenesisAccount{ 349 Code: tc.code, 350 }, 351 origin: core.GenesisAccount{ 352 Balance: big.NewInt(500000000000000), 353 }, 354 }, false) 355 evm := vm.NewEVM(context, txContext, statedb, params.MainnetChainConfig, vm.Config{Tracer: tc.tracer}) 356 msg := &core.Message{ 357 To: &to, 358 From: origin, 359 Value: big.NewInt(0), 360 GasLimit: 50000, 361 GasPrice: big.NewInt(0), 362 GasFeeCap: big.NewInt(0), 363 GasTipCap: big.NewInt(0), 364 SkipAccountChecks: false, 365 } 366 st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(msg.GasLimit)) 367 if _, err := st.TransitionDb(); err != nil { 368 t.Fatalf("test %v: failed to execute transaction: %v", tc.name, err) 369 } 370 // Retrieve the trace result and compare against the expected 371 res, err := tc.tracer.GetResult() 372 if err != nil { 373 t.Fatalf("test %v: failed to retrieve trace result: %v", tc.name, err) 374 } 375 if string(res) != tc.want { 376 t.Fatalf("test %v: trace mismatch\n have: %v\n want: %v\n", tc.name, string(res), tc.want) 377 } 378 } 379 }