@nomicfoundation/edr 0.12.0-alpha.0 → 0.12.0-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,707 @@
1
+ use std::{
2
+ borrow::Cow,
3
+ convert::Infallible,
4
+ fmt::{Debug, Formatter},
5
+ sync::Arc,
6
+ };
7
+
8
+ use edr_solidity_tests::{
9
+ constants::CHEATCODE_ADDRESS,
10
+ executors::stack_trace::StackTraceResult,
11
+ traces::{self, CallTraceArena, SparsedTraceArena},
12
+ };
13
+ use napi::{
14
+ bindgen_prelude::{BigInt, Either3, Either4, Uint8Array},
15
+ Either,
16
+ };
17
+ use napi_derive::napi;
18
+
19
+ use crate::{
20
+ cast::TryCast,
21
+ solidity_tests::{artifact::ArtifactId, config::IncludeTraces},
22
+ trace::{solidity_stack_trace::SolidityStackTraceEntry, u256_to_bigint},
23
+ };
24
+
25
+ /// A grouping of value snapshot entries for a test.
26
+ #[napi(object)]
27
+ #[derive(Clone, Debug)]
28
+ pub struct ValueSnapshotGroup {
29
+ /// The group name.
30
+ pub name: String,
31
+ /// The entries in the group.
32
+ pub entries: Vec<ValueSnapshotEntry>,
33
+ }
34
+
35
+ /// An entry in a value snapshot group.
36
+ #[napi(object)]
37
+ #[derive(Clone, Debug)]
38
+ pub struct ValueSnapshotEntry {
39
+ /// The name of the entry.
40
+ pub name: String,
41
+ /// The value of the entry.
42
+ pub value: String,
43
+ }
44
+
45
+ /// See [edr_solidity_tests::result::SuiteResult]
46
+ #[napi]
47
+ #[derive(Clone, Debug)]
48
+ pub struct SuiteResult {
49
+ /// The artifact id can be used to match input to result in the progress
50
+ /// callback
51
+ #[napi(readonly)]
52
+ pub id: ArtifactId,
53
+ /// See [edr_solidity_tests::result::SuiteResult::duration]
54
+ #[napi(readonly)]
55
+ pub duration_ns: BigInt,
56
+ /// See [edr_solidity_tests::result::SuiteResult::test_results]
57
+ #[napi(readonly)]
58
+ pub test_results: Vec<TestResult>,
59
+ /// See [edr_solidity_tests::result::SuiteResult::warnings]
60
+ #[napi(readonly)]
61
+ pub warnings: Vec<String>,
62
+ }
63
+
64
+ impl SuiteResult {
65
+ pub fn new(
66
+ id: edr_solidity::artifacts::ArtifactId,
67
+ suite_result: edr_solidity_tests::result::SuiteResult<String>,
68
+ include_traces: IncludeTraces,
69
+ ) -> Self {
70
+ Self {
71
+ id: id.into(),
72
+ duration_ns: BigInt::from(suite_result.duration.as_nanos()),
73
+ test_results: suite_result
74
+ .test_results
75
+ .into_iter()
76
+ .map(|(name, test_result)| TestResult::new(name, test_result, include_traces))
77
+ .collect(),
78
+ warnings: suite_result.warnings,
79
+ }
80
+ }
81
+ }
82
+
83
+ /// See [edr_solidity_tests::result::TestResult]
84
+ #[napi]
85
+ #[derive(Clone, Debug)]
86
+ pub struct TestResult {
87
+ /// The name of the test.
88
+ #[napi(readonly)]
89
+ pub name: String,
90
+ /// See [edr_solidity_tests::result::TestResult::status]
91
+ #[napi(readonly)]
92
+ pub status: TestStatus,
93
+ /// See [edr_solidity_tests::result::TestResult::reason]
94
+ #[napi(readonly)]
95
+ pub reason: Option<String>,
96
+ /// See [edr_solidity_tests::result::TestResult::counterexample]
97
+ #[napi(readonly)]
98
+ pub counterexample: Option<Either<BaseCounterExample, CounterExampleSequence>>,
99
+ /// See [edr_solidity_tests::result::TestResult::decoded_logs]
100
+ #[napi(readonly)]
101
+ pub decoded_logs: Vec<String>,
102
+ /// See [edr_solidity_tests::result::TestResult::kind]
103
+ #[napi(readonly)]
104
+ pub kind: Either3<StandardTestKind, FuzzTestKind, InvariantTestKind>,
105
+ /// See [edr_solidity_tests::result::TestResult::duration]
106
+ #[napi(readonly)]
107
+ pub duration_ns: BigInt,
108
+ /// Groups of value snapshot entries (incl. gas).
109
+ ///
110
+ /// Only present if the test runner collected scoped snapshots. Currently,
111
+ /// this is always the case.
112
+ #[napi(readonly)]
113
+ pub value_snapshot_groups: Option<Vec<ValueSnapshotGroup>>,
114
+
115
+ stack_trace_result: Option<Arc<StackTraceResult<String>>>,
116
+ call_trace_arenas: Vec<(traces::TraceKind, SparsedTraceArena)>,
117
+ }
118
+
119
+ /// The stack trace result
120
+ #[napi(object)]
121
+ pub struct StackTrace {
122
+ /// Enum tag for JS.
123
+ #[napi(ts_type = "\"StackTrace\"")]
124
+ pub kind: &'static str,
125
+ /// The stack trace entries
126
+ pub entries: Vec<SolidityStackTraceEntry>,
127
+ }
128
+
129
+ /// We couldn't generate stack traces, because an unexpected error occurred.
130
+ #[napi(object)]
131
+ pub struct UnexpectedError {
132
+ /// Enum tag for JS.
133
+ #[napi(ts_type = "\"UnexpectedError\"")]
134
+ pub kind: &'static str,
135
+ /// The error message from the unexpected error.
136
+ pub error_message: String,
137
+ }
138
+
139
+ /// We couldn't generate stack traces, because the stack trace generation
140
+ /// heuristics failed due to an unknown reason.
141
+ #[napi(object)]
142
+ pub struct HeuristicFailed {
143
+ /// Enum tag for JS.
144
+ #[napi(ts_type = "\"HeuristicFailed\"")]
145
+ pub kind: &'static str,
146
+ }
147
+
148
+ /// We couldn't generate stack traces, because the test execution is unsafe to
149
+ /// replay due to indeterminism. This can be caused by either specifying a fork
150
+ /// url without a fork block number in the test runner config or using impure
151
+ /// cheatcodes.
152
+ #[napi(object)]
153
+ pub struct UnsafeToReplay {
154
+ /// Enum tag for JS.
155
+ #[napi(ts_type = "\"UnsafeToReplay\"")]
156
+ pub kind: &'static str,
157
+ /// Indeterminism due to specifying a fork url without a fork block number
158
+ /// in the test runner config.
159
+ pub global_fork_latest: bool,
160
+ /// The list of executed impure cheatcode signatures. We collect function
161
+ /// signatures instead of function names as whether a cheatcode is impure
162
+ /// can depend on the arguments it takes (e.g. `createFork` without a second
163
+ /// argument means implicitly fork from “latest”). Example signature:
164
+ /// `function createSelectFork(string calldata urlOrAlias) external returns
165
+ /// (uint256 forkId);`.
166
+ pub impure_cheatcodes: Vec<String>,
167
+ }
168
+
169
+ #[napi]
170
+ impl TestResult {
171
+ /// Compute the error stack trace.
172
+ /// The result is either the stack trace or the reason why we couldn't
173
+ /// generate the stack trace.
174
+ /// Returns null if the test status is succeeded or skipped.
175
+ /// Cannot throw.
176
+ #[napi]
177
+ pub fn stack_trace(
178
+ &self,
179
+ ) -> Option<Either4<StackTrace, UnexpectedError, HeuristicFailed, UnsafeToReplay>> {
180
+ self.stack_trace_result.as_ref().map(|stack_trace_result| {
181
+ match stack_trace_result.as_ref() {
182
+ StackTraceResult::Success(stack_trace) => Either4::A(StackTrace {
183
+ kind: "StackTrace",
184
+ entries: stack_trace
185
+ .iter()
186
+ .cloned()
187
+ .map(TryCast::try_cast)
188
+ .collect::<Result<Vec<_>, Infallible>>()
189
+ .expect("infallible"),
190
+ }),
191
+ StackTraceResult::Error(error) => Either4::B(UnexpectedError {
192
+ kind: "UnexpectedError",
193
+ error_message: error.to_string(),
194
+ }),
195
+ StackTraceResult::HeuristicFailed => Either4::C(HeuristicFailed {
196
+ kind: "HeuristicFailed",
197
+ }),
198
+ StackTraceResult::UnsafeToReplay {
199
+ global_fork_latest,
200
+ impure_cheatcodes,
201
+ } => Either4::D(UnsafeToReplay {
202
+ kind: "UnsafeToReplay",
203
+ global_fork_latest: *global_fork_latest,
204
+ // napi-rs would clone `&'static str` under the hood anyway, so no performance
205
+ // hit from `Cow::into_owned`.
206
+ impure_cheatcodes: impure_cheatcodes
207
+ .iter()
208
+ .cloned()
209
+ .map(Cow::into_owned)
210
+ .collect(),
211
+ }),
212
+ }
213
+ })
214
+ }
215
+
216
+ /// Constructs the execution traces for the test. Returns an empty array if
217
+ /// traces for this test were not requested according to
218
+ /// [`crate::solidity_tests::config::SolidityTestRunnerConfigArgs::include_traces`]. Otherwise, returns
219
+ /// an array of the root calls of the trace, which always includes the test
220
+ /// call itself and may also include the setup call if there is one
221
+ /// (identified by the function name `setUp`).
222
+ #[napi]
223
+ pub fn call_traces(&self) -> Vec<CallTrace> {
224
+ self.call_trace_arenas
225
+ .iter()
226
+ .filter(|(k, _)| *k != traces::TraceKind::Deployment)
227
+ .map(|(_, a)| CallTrace::from_arena_node(&a.resolve_arena(), 0))
228
+ .collect()
229
+ }
230
+ }
231
+
232
+ impl TestResult {
233
+ fn new(
234
+ name: String,
235
+ test_result: edr_solidity_tests::result::TestResult<String>,
236
+ include_traces: IncludeTraces,
237
+ ) -> Self {
238
+ let include_trace = include_traces == IncludeTraces::All
239
+ || (include_traces == IncludeTraces::Failing && test_result.status.is_failure());
240
+
241
+ Self {
242
+ name,
243
+ status: test_result.status.into(),
244
+ reason: test_result.reason,
245
+ counterexample: test_result
246
+ .counterexample
247
+ .map(|counterexample| match counterexample {
248
+ edr_solidity_tests::fuzz::CounterExample::Single(counterexample) => {
249
+ Either::A(BaseCounterExample::from(counterexample))
250
+ }
251
+ edr_solidity_tests::fuzz::CounterExample::Sequence(
252
+ original_size,
253
+ counterexamples,
254
+ ) => Either::B(CounterExampleSequence {
255
+ original_sequence_size: u64::try_from(original_size)
256
+ .expect("usize fits into u64")
257
+ .into(),
258
+ sequence: counterexamples
259
+ .into_iter()
260
+ .map(BaseCounterExample::from)
261
+ .collect(),
262
+ }),
263
+ }),
264
+ decoded_logs: test_result.decoded_logs,
265
+ kind: match test_result.kind {
266
+ edr_solidity_tests::result::TestKind::Standard(gas_consumed) => {
267
+ Either3::A(StandardTestKind {
268
+ consumed_gas: BigInt::from(gas_consumed),
269
+ })
270
+ }
271
+ edr_solidity_tests::result::TestKind::Fuzz {
272
+ runs,
273
+ mean_gas,
274
+ median_gas,
275
+ } => Either3::B(FuzzTestKind {
276
+ // usize as u64 is always safe
277
+ runs: BigInt::from(runs as u64),
278
+ mean_gas: BigInt::from(mean_gas),
279
+ median_gas: BigInt::from(median_gas),
280
+ }),
281
+ edr_solidity_tests::result::TestKind::Invariant {
282
+ runs,
283
+ calls,
284
+ reverts,
285
+ } => Either3::C(InvariantTestKind {
286
+ // usize as u64 is always safe
287
+ runs: BigInt::from(runs as u64),
288
+ calls: BigInt::from(calls as u64),
289
+ reverts: BigInt::from(reverts as u64),
290
+ }),
291
+ },
292
+ duration_ns: BigInt::from(test_result.duration.as_nanos()),
293
+ value_snapshot_groups: Some(
294
+ test_result
295
+ .value_snapshots
296
+ .into_iter()
297
+ .map(|(group_name, entries)| ValueSnapshotGroup {
298
+ name: group_name,
299
+ entries: entries
300
+ .into_iter()
301
+ .map(|(name, value)| ValueSnapshotEntry { name, value })
302
+ .collect(),
303
+ })
304
+ .collect(),
305
+ ),
306
+ stack_trace_result: test_result.stack_trace_result.map(Arc::new),
307
+ call_trace_arenas: if include_trace {
308
+ test_result.traces
309
+ } else {
310
+ vec![]
311
+ },
312
+ }
313
+ }
314
+ }
315
+
316
+ #[derive(Debug)]
317
+ #[napi(string_enum)]
318
+ #[doc = "The result of a test execution."]
319
+ pub enum TestStatus {
320
+ #[doc = "Test success"]
321
+ Success,
322
+ #[doc = "Test failure"]
323
+ Failure,
324
+ #[doc = "Test skipped"]
325
+ Skipped,
326
+ }
327
+
328
+ impl From<edr_solidity_tests::result::TestStatus> for TestStatus {
329
+ fn from(value: edr_solidity_tests::result::TestStatus) -> Self {
330
+ match value {
331
+ edr_solidity_tests::result::TestStatus::Success => Self::Success,
332
+ edr_solidity_tests::result::TestStatus::Failure => Self::Failure,
333
+ edr_solidity_tests::result::TestStatus::Skipped => Self::Skipped,
334
+ }
335
+ }
336
+ }
337
+
338
+ /// See [edr_solidity_tests::result::TestKind::Standard]
339
+ #[napi(object)]
340
+ #[derive(Debug, Clone)]
341
+ pub struct StandardTestKind {
342
+ /// The gas consumed by the test.
343
+ #[napi(readonly)]
344
+ pub consumed_gas: BigInt,
345
+ }
346
+
347
+ /// See [edr_solidity_tests::result::TestKind::Fuzz]
348
+ #[napi(object)]
349
+ #[derive(Debug, Clone)]
350
+ pub struct FuzzTestKind {
351
+ /// See [edr_solidity_tests::result::TestKind::Fuzz]
352
+ #[napi(readonly)]
353
+ pub runs: BigInt,
354
+ /// See [edr_solidity_tests::result::TestKind::Fuzz]
355
+ #[napi(readonly)]
356
+ pub mean_gas: BigInt,
357
+ /// See [edr_solidity_tests::result::TestKind::Fuzz]
358
+ #[napi(readonly)]
359
+ pub median_gas: BigInt,
360
+ }
361
+
362
+ /// See [edr_solidity_tests::fuzz::FuzzCase]
363
+ #[napi(object)]
364
+ #[derive(Clone)]
365
+ pub struct FuzzCase {
366
+ /// The calldata used for this fuzz test
367
+ #[napi(readonly)]
368
+ pub calldata: Uint8Array,
369
+ /// Consumed gas
370
+ #[napi(readonly)]
371
+ pub gas: BigInt,
372
+ /// The initial gas stipend for the transaction
373
+ #[napi(readonly)]
374
+ pub stipend: BigInt,
375
+ }
376
+
377
+ impl Debug for FuzzCase {
378
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
379
+ f.debug_struct("FuzzCase")
380
+ .field("gas", &self.gas)
381
+ .field("stipend", &self.stipend)
382
+ .finish()
383
+ }
384
+ }
385
+
386
+ /// See [edr_solidity_tests::result::TestKind::Invariant]
387
+ #[napi(object)]
388
+ #[derive(Debug, Clone)]
389
+ pub struct InvariantTestKind {
390
+ /// See [edr_solidity_tests::result::TestKind::Invariant]
391
+ #[napi(readonly)]
392
+ pub runs: BigInt,
393
+ /// See [edr_solidity_tests::result::TestKind::Invariant]
394
+ #[napi(readonly)]
395
+ pub calls: BigInt,
396
+ /// See [edr_solidity_tests::result::TestKind::Invariant]
397
+ #[napi(readonly)]
398
+ pub reverts: BigInt,
399
+ }
400
+
401
+ /// Original sequence size and sequence of calls used as a counter example
402
+ /// for invariant tests.
403
+ #[napi(object)]
404
+ #[derive(Clone, Debug)]
405
+ pub struct CounterExampleSequence {
406
+ /// The original sequence size before shrinking.
407
+ pub original_sequence_size: BigInt,
408
+ /// The shrunk counterexample sequence.
409
+ pub sequence: Vec<BaseCounterExample>,
410
+ }
411
+
412
+ /// See [edr_solidity_tests::fuzz::BaseCounterExample]
413
+ #[napi(object)]
414
+ #[derive(Clone)]
415
+ pub struct BaseCounterExample {
416
+ /// See [edr_solidity_tests::fuzz::BaseCounterExample::sender]
417
+ #[napi(readonly)]
418
+ pub sender: Option<Uint8Array>,
419
+ /// See [edr_solidity_tests::fuzz::BaseCounterExample::addr]
420
+ #[napi(readonly)]
421
+ pub address: Option<Uint8Array>,
422
+ /// See [edr_solidity_tests::fuzz::BaseCounterExample::calldata]
423
+ #[napi(readonly)]
424
+ pub calldata: Uint8Array,
425
+ /// See [edr_solidity_tests::fuzz::BaseCounterExample::contract_name]
426
+ #[napi(readonly)]
427
+ pub contract_name: Option<String>,
428
+ /// See [edr_solidity_tests::fuzz::BaseCounterExample::signature]
429
+ #[napi(readonly)]
430
+ pub signature: Option<String>,
431
+ /// See [edr_solidity_tests::fuzz::BaseCounterExample::args]
432
+ #[napi(readonly)]
433
+ pub args: Option<String>,
434
+ }
435
+
436
+ impl Debug for BaseCounterExample {
437
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
438
+ f.debug_struct("BaseCounterExample")
439
+ .field("contract_name", &self.contract_name)
440
+ .field("signature", &self.signature)
441
+ .field("args", &self.args)
442
+ .finish()
443
+ }
444
+ }
445
+
446
+ impl From<edr_solidity_tests::fuzz::BaseCounterExample> for BaseCounterExample {
447
+ fn from(value: edr_solidity_tests::fuzz::BaseCounterExample) -> Self {
448
+ Self {
449
+ sender: value.sender.map(Uint8Array::with_data_copied),
450
+ address: value.addr.map(Uint8Array::with_data_copied),
451
+ calldata: Uint8Array::with_data_copied(value.calldata),
452
+ contract_name: value.contract_name,
453
+ signature: value.signature,
454
+ args: value.args,
455
+ }
456
+ }
457
+ }
458
+
459
+ /// Object representing a call in an execution trace, including contract
460
+ /// creation.
461
+ #[napi(object)]
462
+ pub struct CallTrace {
463
+ /// The kind of call or contract creation this represents.
464
+ pub kind: CallKind,
465
+ /// Whether the call succeeded or reverted.
466
+ pub success: bool,
467
+ /// Whether the call is a cheatcode.
468
+ pub is_cheatcode: bool,
469
+ /// The amount of gas that was consumed.
470
+ pub gas_used: BigInt,
471
+ /// The amount of native token that was included with the call.
472
+ pub value: BigInt,
473
+ /// The target of the call. Provided as a contract name if known, otherwise
474
+ /// a checksum address.
475
+ pub contract: String,
476
+ /// The input (calldata) to the call. If it encodes a known function call,
477
+ /// it will be decoded into the function name and a list of arguments.
478
+ /// For example, `{ name: "ownerOf", arguments: ["1"] }`. Note that the
479
+ /// function name may also be any of the special `fallback` and `receive`
480
+ /// functions. Otherwise, it will be provided as a raw byte array.
481
+ pub inputs: Either<DecodedTraceParameters, Uint8Array>,
482
+ /// The output of the call. This will be a decoded human-readable
483
+ /// representation of the value if the function is known, otherwise a
484
+ /// raw byte array.
485
+ pub outputs: Either<String, Uint8Array>,
486
+ /// Interleaved subcalls and event logs. Use `kind` to check if each member
487
+ /// of the array is a call or log trace.
488
+ pub children: Vec<Either<CallTrace, LogTrace>>,
489
+ }
490
+
491
+ /// Object representing an event log in an execution trace.
492
+ #[napi(object)]
493
+ pub struct LogTrace {
494
+ /// A constant to help discriminate the union `CallTrace | LogTrace`.
495
+ pub kind: LogKind,
496
+ /// If the log is a known event (based on its first topic), it will be
497
+ /// decoded into the event name and list of named parameters. For
498
+ /// example, `{ name: "Log", arguments: ["value: 1"] }`. Otherwise, it
499
+ /// will be provided as an array where all but the last element are the
500
+ /// log topics, and the last element is the log data.
501
+ pub parameters: Either<DecodedTraceParameters, Vec<Uint8Array>>,
502
+ }
503
+
504
+ /// The various kinds of call frames possible in the EVM.
505
+ #[napi]
506
+ #[derive(Debug)]
507
+ pub enum CallKind {
508
+ /// Regular call that may change state.
509
+ Call = 0,
510
+ /// Variant of `DelegateCall` that doesn't preserve sender or value in the
511
+ /// frame.
512
+ CallCode = 1,
513
+ /// Call that executes the code of the target in the context of the caller.
514
+ DelegateCall = 2,
515
+ /// Regular call that may not change state.
516
+ StaticCall = 3,
517
+ /// Contract creation.
518
+ Create = 4,
519
+ }
520
+
521
+ /// Kind marker for log traces.
522
+ #[napi]
523
+ #[derive(Debug)]
524
+ pub enum LogKind {
525
+ /// Single kind of log.
526
+ Log = 5,
527
+ // NOTE: The discriminants of LogKind and CallKind must be disjoint.
528
+ }
529
+
530
+ /// Decoded function call or event.
531
+ #[napi(object)]
532
+ pub struct DecodedTraceParameters {
533
+ /// The name of a function or an event.
534
+ pub name: String,
535
+ /// The arguments of the function call or the event, in their human-readable
536
+ /// representations.
537
+ pub arguments: Vec<String>,
538
+ }
539
+
540
+ impl CallTrace {
541
+ /// Instantiates a `CallTrace` with the details from a node and the supplied
542
+ /// children.
543
+ fn new(node: &traces::CallTraceNode, children: Vec<Either<CallTrace, LogTrace>>) -> Self {
544
+ let contract = node
545
+ .trace
546
+ .decoded
547
+ .label
548
+ .clone()
549
+ .unwrap_or(node.trace.address.to_checksum(None));
550
+
551
+ let inputs = match &node.trace.decoded.call_data {
552
+ Some(traces::DecodedCallData { signature, args }) => {
553
+ let name = signature
554
+ .split('(')
555
+ .next()
556
+ .expect("invalid function signature")
557
+ .to_string();
558
+ let arguments = args.clone();
559
+ Either::A(DecodedTraceParameters { name, arguments })
560
+ }
561
+ None => Either::B(node.trace.data.as_ref().into()),
562
+ };
563
+
564
+ let outputs = match &node.trace.decoded.return_data {
565
+ Some(outputs) => Either::A(outputs.clone()),
566
+ None => {
567
+ if node.kind().is_any_create() && node.trace.success {
568
+ Either::A(format!("{} bytes of code", node.trace.output.len()))
569
+ } else {
570
+ Either::B(node.trace.output.as_ref().into())
571
+ }
572
+ }
573
+ };
574
+
575
+ Self {
576
+ kind: node.kind().into(),
577
+ success: node.trace.success,
578
+ is_cheatcode: node.trace.address == CHEATCODE_ADDRESS,
579
+ gas_used: node.trace.gas_used.into(),
580
+ value: u256_to_bigint(&node.trace.value),
581
+ contract,
582
+ inputs,
583
+ outputs,
584
+ children,
585
+ }
586
+ }
587
+
588
+ /// Creates a tree of `CallTrace` rooted at some node in a trace arena.
589
+ fn from_arena_node(arena: &CallTraceArena, arena_index: usize) -> Self {
590
+ struct StackItem {
591
+ visited: bool,
592
+ parent_stack_index: Option<usize>,
593
+ arena_index: usize,
594
+ child_traces: Vec<Option<CallTrace>>,
595
+ }
596
+
597
+ let mut stack = Vec::new();
598
+
599
+ stack.push(StackItem {
600
+ visited: false,
601
+ arena_index,
602
+ parent_stack_index: None,
603
+ child_traces: Vec::new(),
604
+ });
605
+
606
+ loop {
607
+ // We will break out of the loop before the stack goes empty.
608
+ let mut item = stack.pop().unwrap();
609
+ let node = &arena.nodes()[item.arena_index];
610
+
611
+ if item.visited {
612
+ let mut logs = node
613
+ .logs
614
+ .iter()
615
+ .map(|log| Some(LogTrace::from(log)))
616
+ .collect::<Vec<_>>();
617
+
618
+ let children = node
619
+ .ordering
620
+ .iter()
621
+ .filter_map(|ord| match *ord {
622
+ traces::TraceMemberOrder::Log(i) => {
623
+ let log = logs[i].take().unwrap();
624
+ Some(Either::B(log))
625
+ }
626
+ traces::TraceMemberOrder::Call(i) => {
627
+ let child_trace = item.child_traces[i].take().unwrap();
628
+ Some(Either::A(child_trace))
629
+ }
630
+ traces::TraceMemberOrder::Step(_) => None,
631
+ })
632
+ .collect();
633
+
634
+ let trace = CallTrace::new(node, children);
635
+
636
+ if let Some(parent_stack_index) = item.parent_stack_index {
637
+ let parent = &mut stack[parent_stack_index];
638
+ parent.child_traces.push(Some(trace));
639
+ } else {
640
+ return trace;
641
+ }
642
+ } else {
643
+ item.visited = true;
644
+ item.child_traces.reserve(node.children.len());
645
+
646
+ stack.push(item);
647
+
648
+ let top_index = Some(stack.len() - 1);
649
+
650
+ // Push children in reverse order to result in linear traversal of the arena for
651
+ // cache efficiency, on the assumption that the arena contains a pre-order
652
+ // traversal of the trace.
653
+ stack.extend(node.children.iter().rev().map(|&arena_index| StackItem {
654
+ visited: false,
655
+ parent_stack_index: top_index,
656
+ arena_index,
657
+ child_traces: Vec::new(),
658
+ }));
659
+ }
660
+ }
661
+ }
662
+ }
663
+
664
+ impl From<&traces::CallLog> for LogTrace {
665
+ fn from(log: &traces::CallLog) -> Self {
666
+ let decoded_log = log.decoded.name.clone().zip(log.decoded.params.as_ref());
667
+
668
+ let parameters = decoded_log.map_or_else(
669
+ || {
670
+ let raw_log = &log.raw_log;
671
+ let mut params = Vec::with_capacity(raw_log.topics().len() + 1);
672
+ params.extend(raw_log.topics().iter().map(|topic| topic.as_slice().into()));
673
+ params.push(log.raw_log.data.as_ref().into());
674
+ Either::B(params)
675
+ },
676
+ |(name, params)| {
677
+ let arguments = params
678
+ .iter()
679
+ .map(|(name, value)| format!("{name}: {value}"))
680
+ .collect();
681
+ Either::A(DecodedTraceParameters { name, arguments })
682
+ },
683
+ );
684
+
685
+ Self {
686
+ kind: LogKind::Log,
687
+ parameters,
688
+ }
689
+ }
690
+ }
691
+
692
+ impl From<traces::CallKind> for CallKind {
693
+ fn from(value: traces::CallKind) -> Self {
694
+ match value {
695
+ traces::CallKind::Call => CallKind::Call,
696
+ traces::CallKind::StaticCall => CallKind::StaticCall,
697
+ traces::CallKind::CallCode => CallKind::CallCode,
698
+ traces::CallKind::DelegateCall => CallKind::DelegateCall,
699
+ traces::CallKind::Create | traces::CallKind::Create2 => CallKind::Create,
700
+
701
+ // We do not support these EVM features.
702
+ traces::CallKind::AuthCall => {
703
+ unreachable!("Unsupported EVM features")
704
+ }
705
+ }
706
+ }
707
+ }