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