github.com/tirogen/go-ethereum@v1.10.12-0.20221226051715-250cfede41b6/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/tirogen/go-ethereum/common" 28 "github.com/tirogen/go-ethereum/common/hexutil" 29 "github.com/tirogen/go-ethereum/common/math" 30 "github.com/tirogen/go-ethereum/core" 31 "github.com/tirogen/go-ethereum/core/rawdb" 32 "github.com/tirogen/go-ethereum/core/types" 33 "github.com/tirogen/go-ethereum/core/vm" 34 "github.com/tirogen/go-ethereum/crypto" 35 "github.com/tirogen/go-ethereum/eth/tracers" 36 "github.com/tirogen/go-ethereum/params" 37 "github.com/tirogen/go-ethereum/rlp" 38 "github.com/tirogen/go-ethereum/tests" 39 ) 40 41 type callContext struct { 42 Number math.HexOrDecimal64 `json:"number"` 43 Difficulty *math.HexOrDecimal256 `json:"difficulty"` 44 Time math.HexOrDecimal64 `json:"timestamp"` 45 GasLimit math.HexOrDecimal64 `json:"gasLimit"` 46 Miner common.Address `json:"miner"` 47 } 48 49 // callLog is the result of LOG opCode 50 type callLog struct { 51 Address common.Address `json:"address"` 52 Topics []common.Hash `json:"topics"` 53 Data hexutil.Bytes `json:"data"` 54 } 55 56 // callTrace is the result of a callTracer run. 57 type callTrace struct { 58 From common.Address `json:"from"` 59 Gas *hexutil.Uint64 `json:"gas"` 60 GasUsed *hexutil.Uint64 `json:"gasUsed"` 61 To common.Address `json:"to,omitempty"` 62 Input hexutil.Bytes `json:"input"` 63 Output hexutil.Bytes `json:"output,omitempty"` 64 Error string `json:"error,omitempty"` 65 Revertal string `json:"revertReason,omitempty"` 66 Calls []callTrace `json:"calls,omitempty"` 67 Logs []callLog `json:"logs,omitempty"` 68 Value *hexutil.Big `json:"value,omitempty"` 69 // Gencodec adds overridden fields at the end 70 Type string `json:"type"` 71 } 72 73 // callTracerTest defines a single test to check the call tracer against. 74 type callTracerTest struct { 75 Genesis *core.Genesis `json:"genesis"` 76 Context *callContext `json:"context"` 77 Input string `json:"input"` 78 TracerConfig json.RawMessage `json:"tracerConfig"` 79 Result *callTrace `json:"result"` 80 } 81 82 // Iterates over all the input-output datasets in the tracer test harness and 83 // runs the JavaScript tracers against them. 84 func TestCallTracerLegacy(t *testing.T) { 85 testCallTracer("callTracerLegacy", "call_tracer_legacy", t) 86 } 87 88 func TestCallTracerNative(t *testing.T) { 89 testCallTracer("callTracer", "call_tracer", t) 90 } 91 92 func TestCallTracerNativeWithLog(t *testing.T) { 93 testCallTracer("callTracer", "call_tracer_withLog", t) 94 } 95 96 func testCallTracer(tracerName string, dirPath string, t *testing.T) { 97 isLegacy := strings.HasSuffix(dirPath, "_legacy") 98 files, err := os.ReadDir(filepath.Join("testdata", dirPath)) 99 if err != nil { 100 t.Fatalf("failed to retrieve tracer test suite: %v", err) 101 } 102 for _, file := range files { 103 if !strings.HasSuffix(file.Name(), ".json") { 104 continue 105 } 106 file := file // capture range variable 107 t.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(t *testing.T) { 108 t.Parallel() 109 110 var ( 111 test = new(callTracerTest) 112 tx = new(types.Transaction) 113 ) 114 // Call tracer test found, read if from disk 115 if blob, err := os.ReadFile(filepath.Join("testdata", dirPath, file.Name())); err != nil { 116 t.Fatalf("failed to read testcase: %v", err) 117 } else if err := json.Unmarshal(blob, test); err != nil { 118 t.Fatalf("failed to parse testcase: %v", err) 119 } 120 if err := tx.UnmarshalBinary(common.FromHex(test.Input)); err != nil { 121 t.Fatalf("failed to parse testcase input: %v", err) 122 } 123 // Configure a blockchain with the given prestate 124 var ( 125 signer = types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number))) 126 origin, _ = signer.Sender(tx) 127 txContext = vm.TxContext{ 128 Origin: origin, 129 GasPrice: tx.GasPrice(), 130 } 131 context = vm.BlockContext{ 132 CanTransfer: core.CanTransfer, 133 Transfer: core.Transfer, 134 Coinbase: test.Context.Miner, 135 BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)), 136 Time: new(big.Int).SetUint64(uint64(test.Context.Time)), 137 Difficulty: (*big.Int)(test.Context.Difficulty), 138 GasLimit: uint64(test.Context.GasLimit), 139 BaseFee: test.Genesis.BaseFee, 140 } 141 _, statedb = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false) 142 ) 143 tracer, err := tracers.New(tracerName, new(tracers.Context), test.TracerConfig) 144 if err != nil { 145 t.Fatalf("failed to create call tracer: %v", err) 146 } 147 evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer}) 148 msg, err := tx.AsMessage(signer, nil) 149 if err != nil { 150 t.Fatalf("failed to prepare transaction for tracing: %v", err) 151 } 152 vmRet, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.Gas())) 153 if err != nil { 154 t.Fatalf("failed to execute transaction: %v", err) 155 } 156 // Retrieve the trace result and compare against the expected. 157 res, err := tracer.GetResult() 158 if err != nil { 159 t.Fatalf("failed to retrieve trace result: %v", err) 160 } 161 // The legacy javascript calltracer marshals json in js, which 162 // is not deterministic (as opposed to the golang json encoder). 163 if isLegacy { 164 // This is a tweak to make it deterministic. Can be removed when 165 // we remove the legacy tracer. 166 var x callTrace 167 json.Unmarshal(res, &x) 168 res, _ = json.Marshal(x) 169 } 170 want, err := json.Marshal(test.Result) 171 if err != nil { 172 t.Fatalf("failed to marshal test: %v", err) 173 } 174 if string(want) != string(res) { 175 t.Fatalf("trace mismatch\n have: %v\n want: %v\n", string(res), string(want)) 176 } 177 // Sanity check: compare top call's gas used against vm result 178 type simpleResult struct { 179 GasUsed hexutil.Uint64 180 } 181 var topCall simpleResult 182 if err := json.Unmarshal(res, &topCall); err != nil { 183 t.Fatalf("failed to unmarshal top calls gasUsed: %v", err) 184 } 185 if uint64(topCall.GasUsed) != vmRet.UsedGas { 186 t.Fatalf("top call has invalid gasUsed. have: %d want: %d", topCall.GasUsed, vmRet.UsedGas) 187 } 188 }) 189 } 190 } 191 192 func BenchmarkTracers(b *testing.B) { 193 files, err := os.ReadDir(filepath.Join("testdata", "call_tracer")) 194 if err != nil { 195 b.Fatalf("failed to retrieve tracer test suite: %v", err) 196 } 197 for _, file := range files { 198 if !strings.HasSuffix(file.Name(), ".json") { 199 continue 200 } 201 file := file // capture range variable 202 b.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(b *testing.B) { 203 blob, err := os.ReadFile(filepath.Join("testdata", "call_tracer", file.Name())) 204 if err != nil { 205 b.Fatalf("failed to read testcase: %v", err) 206 } 207 test := new(callTracerTest) 208 if err := json.Unmarshal(blob, test); err != nil { 209 b.Fatalf("failed to parse testcase: %v", err) 210 } 211 benchTracer("callTracer", test, b) 212 }) 213 } 214 } 215 216 func benchTracer(tracerName string, test *callTracerTest, b *testing.B) { 217 // Configure a blockchain with the given prestate 218 tx := new(types.Transaction) 219 if err := rlp.DecodeBytes(common.FromHex(test.Input), tx); err != nil { 220 b.Fatalf("failed to parse testcase input: %v", err) 221 } 222 signer := types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number))) 223 msg, err := tx.AsMessage(signer, nil) 224 if err != nil { 225 b.Fatalf("failed to prepare transaction for tracing: %v", err) 226 } 227 origin, _ := signer.Sender(tx) 228 txContext := vm.TxContext{ 229 Origin: origin, 230 GasPrice: tx.GasPrice(), 231 } 232 context := vm.BlockContext{ 233 CanTransfer: core.CanTransfer, 234 Transfer: core.Transfer, 235 Coinbase: test.Context.Miner, 236 BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)), 237 Time: new(big.Int).SetUint64(uint64(test.Context.Time)), 238 Difficulty: (*big.Int)(test.Context.Difficulty), 239 GasLimit: uint64(test.Context.GasLimit), 240 } 241 _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false) 242 243 b.ReportAllocs() 244 b.ResetTimer() 245 for i := 0; i < b.N; i++ { 246 tracer, err := tracers.New(tracerName, new(tracers.Context), nil) 247 if err != nil { 248 b.Fatalf("failed to create call tracer: %v", err) 249 } 250 evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer}) 251 snap := statedb.Snapshot() 252 st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas())) 253 if _, err = st.TransitionDb(); err != nil { 254 b.Fatalf("failed to execute transaction: %v", err) 255 } 256 if _, err = tracer.GetResult(); err != nil { 257 b.Fatal(err) 258 } 259 statedb.RevertToSnapshot(snap) 260 } 261 } 262 263 // TestZeroValueToNotExitCall tests the calltracer(s) on the following: 264 // Tx to A, A calls B with zero value. B does not already exist. 265 // Expected: that enter/exit is invoked and the inner call is shown in the result 266 func TestZeroValueToNotExitCall(t *testing.T) { 267 var to = common.HexToAddress("0x00000000000000000000000000000000deadbeef") 268 privkey, err := crypto.HexToECDSA("0000000000000000deadbeef00000000000000000000000000000000deadbeef") 269 if err != nil { 270 t.Fatalf("err %v", err) 271 } 272 signer := types.NewEIP155Signer(big.NewInt(1)) 273 tx, err := types.SignNewTx(privkey, signer, &types.LegacyTx{ 274 GasPrice: big.NewInt(0), 275 Gas: 50000, 276 To: &to, 277 }) 278 if err != nil { 279 t.Fatalf("err %v", err) 280 } 281 origin, _ := signer.Sender(tx) 282 txContext := vm.TxContext{ 283 Origin: origin, 284 GasPrice: big.NewInt(1), 285 } 286 context := vm.BlockContext{ 287 CanTransfer: core.CanTransfer, 288 Transfer: core.Transfer, 289 Coinbase: common.Address{}, 290 BlockNumber: new(big.Int).SetUint64(8000000), 291 Time: new(big.Int).SetUint64(5), 292 Difficulty: big.NewInt(0x30000), 293 GasLimit: uint64(6000000), 294 } 295 var code = []byte{ 296 byte(vm.PUSH1), 0x0, byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), // in and outs zero 297 byte(vm.DUP1), byte(vm.PUSH1), 0xff, byte(vm.GAS), // value=0,address=0xff, gas=GAS 298 byte(vm.CALL), 299 } 300 var alloc = core.GenesisAlloc{ 301 to: core.GenesisAccount{ 302 Nonce: 1, 303 Code: code, 304 }, 305 origin: core.GenesisAccount{ 306 Nonce: 0, 307 Balance: big.NewInt(500000000000000), 308 }, 309 } 310 _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), alloc, false) 311 // Create the tracer, the EVM environment and run it 312 tracer, err := tracers.New("callTracer", nil, nil) 313 if err != nil { 314 t.Fatalf("failed to create call tracer: %v", err) 315 } 316 evm := vm.NewEVM(context, txContext, statedb, params.MainnetChainConfig, vm.Config{Debug: true, Tracer: tracer}) 317 msg, err := tx.AsMessage(signer, nil) 318 if err != nil { 319 t.Fatalf("failed to prepare transaction for tracing: %v", err) 320 } 321 st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas())) 322 if _, err = st.TransitionDb(); err != nil { 323 t.Fatalf("failed to execute transaction: %v", err) 324 } 325 // Retrieve the trace result and compare against the etalon 326 res, err := tracer.GetResult() 327 if err != nil { 328 t.Fatalf("failed to retrieve trace result: %v", err) 329 } 330 wantStr := `{"from":"0x682a80a6f560eec50d54e63cbeda1c324c5f8d1b","gas":"0x7148","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"}` 331 if string(res) != wantStr { 332 t.Fatalf("trace mismatch\n have: %v\n want: %v\n", string(res), wantStr) 333 } 334 }