@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.
- package/Cargo.toml +47 -16
- package/LICENSE +5 -1
- package/index.d.ts +773 -104
- package/index.js +31 -1
- package/package.json +20 -17
- package/src/account.rs +102 -55
- package/src/block.rs +2 -103
- package/src/call_override.rs +8 -8
- package/src/cast.rs +7 -29
- package/src/chains/generic.rs +1 -1
- package/src/chains/l1.rs +20 -18
- package/src/chains/op.rs +95 -38
- package/src/config.rs +305 -161
- package/src/context.rs +260 -21
- package/src/debug_trace.rs +2 -2
- package/src/instrument.rs +109 -0
- package/src/lib.rs +10 -0
- package/src/log.rs +12 -14
- package/src/logger.rs +28 -30
- package/src/mock.rs +68 -0
- package/src/precompile.rs +50 -0
- package/src/provider/response.rs +8 -5
- package/src/provider.rs +14 -8
- package/src/result.rs +18 -27
- package/src/scenarios.rs +2 -2
- package/src/serde.rs +57 -0
- package/src/solidity_tests/artifact.rs +184 -0
- package/src/solidity_tests/config.rs +725 -0
- package/src/solidity_tests/factory.rs +22 -0
- package/src/solidity_tests/l1.rs +68 -0
- package/src/solidity_tests/op.rs +69 -0
- package/src/solidity_tests/runner.rs +51 -0
- package/src/solidity_tests/test_results.rs +668 -0
- package/src/solidity_tests.rs +56 -0
- package/src/subscription.rs +1 -1
- package/src/trace/debug.rs +1 -1
- package/src/trace/exit.rs +4 -4
- package/src/trace/library_utils.rs +7 -2
- package/src/trace/return_data.rs +10 -10
- package/src/trace/solidity_stack_trace.rs +7 -5
- package/src/trace.rs +29 -38
- package/src/withdrawal.rs +3 -3
|
@@ -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
|
+
}
|