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

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