@nomicfoundation/edr 0.6.4 → 0.7.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.
@@ -1,2255 +0,0 @@
1
- use std::{borrow::Cow, collections::HashSet};
2
-
3
- use alloy_dyn_abi::{DynSolValue, JsonAbiExt};
4
- use edr_evm::{hex, interpreter::OpCode};
5
- use edr_solidity::build_model::{
6
- Bytecode, ContractFunction, ContractFunctionType, ContractKind, Instruction, JumpType,
7
- SourceLocation,
8
- };
9
- use napi::{
10
- bindgen_prelude::{BigInt, Either24, Either3, Either4},
11
- Either,
12
- };
13
- use semver::{Version, VersionReq};
14
-
15
- use super::{
16
- exit::ExitCode,
17
- message_trace::{CallMessageTrace, CreateMessageTrace, EvmStep, PrecompileMessageTrace},
18
- model::ContractFunctionType as ContractFunctionTypeNapi,
19
- return_data::ReturnData,
20
- solidity_stack_trace::{
21
- CallFailedErrorStackTraceEntry, CallstackEntryStackTraceEntry, CustomErrorStackTraceEntry,
22
- FallbackNotPayableErrorStackTraceEntry, InternalFunctionCallStackEntry,
23
- InvalidParamsErrorStackTraceEntry, NonContractAccountCalledErrorStackTraceEntry,
24
- PanicErrorStackTraceEntry, SolidityStackTrace, SolidityStackTraceEntry,
25
- SolidityStackTraceEntryExt, SourceReference, UnmappedSolc063RevertErrorStackTraceEntry,
26
- },
27
- };
28
- use crate::trace::solidity_stack_trace::{
29
- ContractCallRunOutOfGasError, ContractTooLargeErrorStackTraceEntry,
30
- DirectLibraryCallErrorStackTraceEntry, FallbackNotPayableAndNoReceiveErrorStackTraceEntry,
31
- FunctionNotPayableErrorStackTraceEntry, MissingFallbackOrReceiveErrorStackTraceEntry,
32
- OtherExecutionErrorStackTraceEntry, ReturndataSizeErrorStackTraceEntry,
33
- RevertErrorStackTraceEntry, StackTraceEntryTypeConst,
34
- UnrecognizedFunctionWithoutFallbackErrorStackTraceEntry, CONSTRUCTOR_FUNCTION_NAME,
35
- FALLBACK_FUNCTION_NAME, RECEIVE_FUNCTION_NAME,
36
- };
37
-
38
- /// Specifies whether a heuristic was applied and modified the stack trace.
39
- ///
40
- /// Think of it as happy [`Result`] - the [`Heuristic::Hit`] should be
41
- /// propagated to the caller.
42
- #[must_use]
43
- pub enum Heuristic {
44
- /// The heuristic was applied and modified the stack trace.
45
- Hit(SolidityStackTrace),
46
- /// The heuristic did not apply; the stack trace is unchanged.
47
- Miss(SolidityStackTrace),
48
- }
49
-
50
- const FIRST_SOLC_VERSION_CREATE_PARAMS_VALIDATION: Version = Version::new(0, 5, 9);
51
- const FIRST_SOLC_VERSION_RECEIVE_FUNCTION: Version = Version::new(0, 6, 0);
52
- const FIRST_SOLC_VERSION_WITH_UNMAPPED_REVERTS: &str = "0.6.3";
53
-
54
- /// Port of `SubmessageData` from Hardhat to Rust
55
- // However, it borrows the traces (instead of copying them) because the traces
56
- // do not implement `Clone` due to inner references
57
- pub struct SubmessageDataRef<'a> {
58
- pub message_trace:
59
- Either3<&'a PrecompileMessageTrace, &'a CallMessageTrace, &'a CreateMessageTrace>,
60
- pub stacktrace: SolidityStackTrace,
61
- pub step_index: u32,
62
- }
63
- #[derive(Default)]
64
- pub struct ErrorInferrer;
65
-
66
- impl ErrorInferrer {
67
- pub fn infer_before_tracing_call_message(
68
- trace: &CallMessageTrace,
69
- ) -> napi::Result<Option<SolidityStackTrace>> {
70
- if Self::is_direct_library_call(trace)? {
71
- return Ok(Some(Self::get_direct_library_call_error_stack_trace(
72
- trace,
73
- )?));
74
- }
75
-
76
- let bytecode = trace
77
- .bytecode
78
- .as_ref()
79
- .expect("The TS code type-checks this to always have bytecode");
80
- let contract = bytecode.contract.borrow();
81
-
82
- let called_function = contract
83
- .get_function_from_selector(trace.calldata.get(..4).unwrap_or(&trace.calldata[..]));
84
-
85
- if let Some(called_function) = called_function {
86
- if Self::is_function_not_payable_error(trace, called_function)? {
87
- return Ok(Some(vec![FunctionNotPayableErrorStackTraceEntry {
88
- type_: StackTraceEntryTypeConst,
89
- source_reference: Self::get_function_start_source_reference(
90
- Either::A(trace),
91
- called_function,
92
- )?,
93
- value: trace.value.clone(),
94
- }
95
- .into()]));
96
- }
97
- }
98
-
99
- let called_function = called_function.map(AsRef::as_ref);
100
-
101
- if Self::is_missing_function_and_fallback_error(trace, called_function)? {
102
- let source_reference =
103
- Self::get_contract_start_without_function_source_reference(Either::A(trace))?;
104
-
105
- if Self::empty_calldata_and_no_receive(trace)? {
106
- return Ok(Some(vec![MissingFallbackOrReceiveErrorStackTraceEntry {
107
- type_: StackTraceEntryTypeConst,
108
- source_reference,
109
- }
110
- .into()]));
111
- }
112
-
113
- return Ok(Some(vec![
114
- UnrecognizedFunctionWithoutFallbackErrorStackTraceEntry {
115
- type_: StackTraceEntryTypeConst,
116
- source_reference,
117
- }
118
- .into(),
119
- ]));
120
- }
121
-
122
- if Self::is_fallback_not_payable_error(trace, called_function)? {
123
- let source_reference = Self::get_fallback_start_source_reference(trace)?;
124
-
125
- if Self::empty_calldata_and_no_receive(trace)? {
126
- return Ok(Some(vec![
127
- FallbackNotPayableAndNoReceiveErrorStackTraceEntry {
128
- type_: StackTraceEntryTypeConst,
129
- source_reference,
130
- value: trace.value.clone(),
131
- }
132
- .into(),
133
- ]));
134
- }
135
-
136
- return Ok(Some(vec![FallbackNotPayableErrorStackTraceEntry {
137
- type_: StackTraceEntryTypeConst,
138
- source_reference,
139
- value: trace.value.clone(),
140
- }
141
- .into()]));
142
- }
143
-
144
- Ok(None)
145
- }
146
-
147
- pub fn infer_before_tracing_create_message(
148
- trace: &CreateMessageTrace,
149
- ) -> napi::Result<Option<SolidityStackTrace>> {
150
- if Self::is_constructor_not_payable_error(trace)? {
151
- return Ok(Some(vec![FunctionNotPayableErrorStackTraceEntry {
152
- type_: StackTraceEntryTypeConst,
153
- source_reference: Self::get_constructor_start_source_reference(trace)?,
154
- value: trace.value.clone(),
155
- }
156
- .into()]));
157
- }
158
-
159
- if Self::is_constructor_invalid_arguments_error(trace)? {
160
- return Ok(Some(vec![InvalidParamsErrorStackTraceEntry {
161
- type_: StackTraceEntryTypeConst,
162
- source_reference: Self::get_constructor_start_source_reference(trace)?,
163
- }
164
- .into()]));
165
- }
166
-
167
- Ok(None)
168
- }
169
-
170
- pub fn infer_after_tracing(
171
- trace: Either<&CallMessageTrace, &CreateMessageTrace>,
172
- stacktrace: SolidityStackTrace,
173
- function_jumpdests: &[&Instruction],
174
- jumped_into_function: bool,
175
- last_submessage_data: Option<SubmessageDataRef<'_>>,
176
- ) -> napi::Result<SolidityStackTrace> {
177
- /// Convenience macro to early return the result if a heuristic hits.
178
- macro_rules! return_if_hit {
179
- ($heuristic: expr) => {
180
- match $heuristic {
181
- Heuristic::Hit(stacktrace) => return Ok(stacktrace),
182
- Heuristic::Miss(stacktrace) => stacktrace,
183
- }
184
- };
185
- }
186
-
187
- let result = Self::check_last_submessage(trace, stacktrace, last_submessage_data)?;
188
- let stacktrace = return_if_hit!(result);
189
-
190
- let result = Self::check_failed_last_call(trace, stacktrace)?;
191
- let stacktrace = return_if_hit!(result);
192
-
193
- let result = Self::check_last_instruction(
194
- trace,
195
- stacktrace,
196
- function_jumpdests,
197
- jumped_into_function,
198
- )?;
199
- let stacktrace = return_if_hit!(result);
200
-
201
- let result = Self::check_non_contract_called(trace, stacktrace)?;
202
- let stacktrace = return_if_hit!(result);
203
-
204
- let result = Self::check_solidity_0_6_3_unmapped_revert(trace, stacktrace)?;
205
- let stacktrace = return_if_hit!(result);
206
-
207
- if let Some(result) = Self::check_contract_too_large(trace)? {
208
- return Ok(result);
209
- }
210
-
211
- let stacktrace = Self::other_execution_error_stacktrace(trace, stacktrace)?;
212
- Ok(stacktrace)
213
- }
214
-
215
- pub fn filter_redundant_frames(
216
- stacktrace: SolidityStackTrace,
217
- ) -> napi::Result<SolidityStackTrace> {
218
- // To work around the borrow checker, we'll collect the indices of the frames we
219
- // want to keep. We can't clone the frames, because some of them contain
220
- // non-Clone `ClassInstance`s`
221
- let retained_indices: HashSet<_> = stacktrace
222
- .iter()
223
- .enumerate()
224
- .filter(|(idx, frame)| {
225
- let next_frame = stacktrace.get(idx + 1);
226
- let next_next_frame = stacktrace.get(idx + 2);
227
-
228
- let Some(next_frame) = next_frame else {
229
- return true;
230
- };
231
-
232
- // we can only filter frames if we know their sourceReference
233
- // and the one from the next frame
234
- let (Some(frame_source), Some(next_frame_source)) =
235
- (frame.source_reference(), next_frame.source_reference())
236
- else {
237
- return true;
238
- };
239
-
240
- // look TWO frames ahead to determine if this is a specific occurrence of
241
- // a redundant CALLSTACK_ENTRY frame observed when using Solidity 0.8.5:
242
- match (&frame, next_next_frame) {
243
- (
244
- Either24::A(CallstackEntryStackTraceEntry {
245
- source_reference, ..
246
- }),
247
- Some(Either24::N(ReturndataSizeErrorStackTraceEntry {
248
- source_reference: next_next_source_reference,
249
- ..
250
- })),
251
- ) if source_reference.range == next_next_source_reference.range
252
- && source_reference.line == next_next_source_reference.line =>
253
- {
254
- return false;
255
- }
256
- _ => {}
257
- }
258
-
259
- if frame_source.function.as_deref() == Some("constructor")
260
- && next_frame_source.function.as_deref() != Some("constructor")
261
- {
262
- return true;
263
- }
264
-
265
- // this is probably a recursive call
266
- if *idx > 0
267
- && frame.type_() == next_frame.type_()
268
- && frame_source.range == next_frame_source.range
269
- && frame_source.line == next_frame_source.line
270
- {
271
- return true;
272
- }
273
-
274
- if frame_source.range[0] <= next_frame_source.range[0]
275
- && frame_source.range[1] >= next_frame_source.range[1]
276
- {
277
- return false;
278
- }
279
-
280
- true
281
- })
282
- .map(|(idx, _)| idx)
283
- .collect();
284
-
285
- Ok(stacktrace
286
- .into_iter()
287
- .enumerate()
288
- .filter(|(idx, _)| retained_indices.contains(idx))
289
- .map(|(_, frame)| frame)
290
- .collect())
291
- }
292
-
293
- // Heuristics
294
-
295
- fn check_contract_too_large(
296
- trace: Either<&CallMessageTrace, &CreateMessageTrace>,
297
- ) -> napi::Result<Option<SolidityStackTrace>> {
298
- Ok(match trace {
299
- Either::B(create @ CreateMessageTrace { .. })
300
- if create.exit.kind() == ExitCode::CODESIZE_EXCEEDS_MAXIMUM =>
301
- {
302
- Some(vec![ContractTooLargeErrorStackTraceEntry {
303
- type_: StackTraceEntryTypeConst,
304
- source_reference: Some(Self::get_constructor_start_source_reference(create)?),
305
- }
306
- .into()])
307
- }
308
-
309
- _ => None,
310
- })
311
- }
312
- /// Check if the last call/create that was done failed.
313
- fn check_failed_last_call(
314
- trace: Either<&CallMessageTrace, &CreateMessageTrace>,
315
- stacktrace: SolidityStackTrace,
316
- ) -> napi::Result<Heuristic> {
317
- let (bytecode, steps) = match &trace {
318
- Either::A(call) => (&call.bytecode, &call.steps),
319
- Either::B(create) => (&create.bytecode, &create.steps),
320
- };
321
-
322
- let bytecode = bytecode.as_ref().expect("JS code asserts");
323
-
324
- if steps.is_empty() {
325
- return Ok(Heuristic::Miss(stacktrace));
326
- }
327
-
328
- for step_index in (0..steps.len() - 1).rev() {
329
- let (step, next_step) = match &steps[step_index..][..2] {
330
- &[Either4::A(ref step), ref next_step] => (step, next_step),
331
- _ => return Ok(Heuristic::Miss(stacktrace)),
332
- };
333
-
334
- let inst = bytecode.get_instruction(step.pc)?;
335
-
336
- if let (OpCode::CALL | OpCode::CREATE, Either4::A(EvmStep { .. })) =
337
- (inst.opcode, next_step)
338
- {
339
- if Self::is_call_failed_error(trace, step_index as u32, inst)? {
340
- let mut inferred_stacktrace = stacktrace.clone();
341
- inferred_stacktrace.push(
342
- Self::call_instruction_to_call_failed_to_execute_stack_trace_entry(
343
- bytecode, inst,
344
- )?
345
- .into(),
346
- );
347
-
348
- return Ok(Heuristic::Hit(Self::fix_initial_modifier(
349
- trace,
350
- inferred_stacktrace,
351
- )?));
352
- }
353
- }
354
- }
355
-
356
- Ok(Heuristic::Miss(stacktrace))
357
- }
358
-
359
- /// Check if the trace reverted with a panic error.
360
- fn check_panic(
361
- trace: Either<&CallMessageTrace, &CreateMessageTrace>,
362
- mut stacktrace: SolidityStackTrace,
363
- last_instruction: &Instruction,
364
- ) -> napi::Result<Heuristic> {
365
- let return_data = ReturnData::new(
366
- match &trace {
367
- Either::A(call) => &call.return_data,
368
- Either::B(create) => &create.return_data,
369
- }
370
- .clone(),
371
- );
372
-
373
- if !return_data.is_panic_return_data() {
374
- return Ok(Heuristic::Miss(stacktrace));
375
- }
376
-
377
- // If the last frame is an internal function, it means that the trace
378
- // jumped there to return the panic. If that's the case, we remove that
379
- // frame.
380
- if let Some(Either24::W(InternalFunctionCallStackEntry { .. })) = stacktrace.last() {
381
- stacktrace.pop();
382
- }
383
-
384
- // if the error comes from a call to a zero-initialized function,
385
- // we remove the last frame, which represents the call, to avoid
386
- // having duplicated frames
387
- let error_code = return_data.decode_panic()?;
388
- let (panic_error_code, lossless) = error_code.get_i64();
389
- if let (0x51, false) = (panic_error_code, lossless) {
390
- stacktrace.pop();
391
- }
392
-
393
- stacktrace.push(
394
- Self::instruction_within_function_to_panic_stack_trace_entry(
395
- trace,
396
- last_instruction,
397
- error_code,
398
- )?
399
- .into(),
400
- );
401
-
402
- Self::fix_initial_modifier(trace, stacktrace).map(Heuristic::Hit)
403
- }
404
-
405
- fn check_custom_errors(
406
- trace: Either<&CallMessageTrace, &CreateMessageTrace>,
407
- stacktrace: SolidityStackTrace,
408
- last_instruction: &Instruction,
409
- ) -> napi::Result<Heuristic> {
410
- let (bytecode, return_data) = match &trace {
411
- Either::A(call) => (&call.bytecode, &call.return_data),
412
- Either::B(create) => (&create.bytecode, &create.return_data),
413
- };
414
-
415
- let bytecode = bytecode.as_ref().expect("JS code asserts");
416
- let contract = bytecode.contract.borrow();
417
-
418
- let return_data = ReturnData::new(return_data.clone());
419
-
420
- if return_data.is_empty() || return_data.is_error_return_data() {
421
- // if there is no return data, or if it's a Error(string),
422
- // then it can't be a custom error
423
- return Ok(Heuristic::Miss(stacktrace));
424
- }
425
-
426
- let raw_return_data = hex::encode(&*return_data.value);
427
- let mut error_message = format!(
428
- "reverted with an unrecognized custom error (return data: 0x{raw_return_data})",
429
- );
430
-
431
- for custom_error in &contract.custom_errors {
432
- if return_data.matches_selector(custom_error.selector) {
433
- // if the return data matches a custom error in the called contract,
434
- // we format the message using the returnData and the custom error instance
435
- let decoded = custom_error
436
- .decode_error_data(&return_data.value)
437
- .map_err(|e| {
438
- napi::Error::from_reason(format!("Error decoding custom error: {e}"))
439
- })?;
440
-
441
- let params = decoded
442
- .body
443
- .iter()
444
- .map(format_dyn_sol_value)
445
- .collect::<Vec<_>>();
446
-
447
- error_message = format!(
448
- "reverted with custom error '{name}({params})'",
449
- name = custom_error.name,
450
- params = params.join(", ")
451
- );
452
-
453
- break;
454
- }
455
- }
456
-
457
- let mut stacktrace = stacktrace;
458
- stacktrace.push(
459
- Self::instruction_within_function_to_custom_error_stack_trace_entry(
460
- trace,
461
- last_instruction,
462
- error_message,
463
- )?
464
- .into(),
465
- );
466
-
467
- Self::fix_initial_modifier(trace, stacktrace).map(Heuristic::Hit)
468
- }
469
-
470
- fn check_solidity_0_6_3_unmapped_revert(
471
- trace: Either<&CallMessageTrace, &CreateMessageTrace>,
472
- mut stacktrace: SolidityStackTrace,
473
- ) -> napi::Result<Heuristic> {
474
- if Self::solidity_0_6_3_maybe_unmapped_revert(trace)? {
475
- let revert_frame =
476
- Self::solidity_0_6_3_get_frame_for_unmapped_revert_within_function(trace)?;
477
-
478
- if let Some(revert_frame) = revert_frame {
479
- stacktrace.push(revert_frame.into());
480
-
481
- return Ok(Heuristic::Hit(stacktrace));
482
- }
483
-
484
- return Ok(Heuristic::Hit(stacktrace));
485
- }
486
-
487
- Ok(Heuristic::Miss(stacktrace))
488
- }
489
-
490
- fn check_non_contract_called(
491
- trace: Either<&CallMessageTrace, &CreateMessageTrace>,
492
- mut stacktrace: SolidityStackTrace,
493
- ) -> napi::Result<Heuristic> {
494
- if Self::is_called_non_contract_account_error(trace)? {
495
- let source_reference = Self::get_last_source_reference(trace)?;
496
-
497
- // We are sure this is not undefined because there was at least a call
498
- // instruction
499
- let source_reference =
500
- source_reference.expect("Expected source reference to be defined");
501
-
502
- let non_contract_called_frame = NonContractAccountCalledErrorStackTraceEntry {
503
- type_: StackTraceEntryTypeConst,
504
- source_reference,
505
- };
506
-
507
- stacktrace.push(non_contract_called_frame.into());
508
-
509
- Ok(Heuristic::Hit(stacktrace))
510
- } else {
511
- Ok(Heuristic::Miss(stacktrace))
512
- }
513
- }
514
-
515
- /// Check if the last submessage can be used to generate the stack trace.
516
- fn check_last_submessage(
517
- trace: Either<&CallMessageTrace, &CreateMessageTrace>,
518
- stacktrace: SolidityStackTrace,
519
- last_submessage_data: Option<SubmessageDataRef<'_>>,
520
- ) -> napi::Result<Heuristic> {
521
- let (bytecode, steps) = match &trace {
522
- Either::A(call) => (&call.bytecode, &call.steps),
523
- Either::B(create) => (&create.bytecode, &create.steps),
524
- };
525
-
526
- let bytecode = bytecode.as_ref().expect("JS code asserts");
527
-
528
- let Some(last_submessage_data) = last_submessage_data else {
529
- return Ok(Heuristic::Miss(stacktrace));
530
- };
531
-
532
- let mut inferred_stacktrace = Cow::from(&stacktrace);
533
-
534
- // get the instruction before the submessage and add it to the stack trace
535
- let call_step = match steps.get(last_submessage_data.step_index as usize - 1) {
536
- Some(Either4::A(call_step)) => call_step,
537
- _ => panic!("This should not happen: MessageTrace should be preceded by a EVM step"),
538
- };
539
-
540
- let call_inst = bytecode.get_instruction(call_step.pc)?;
541
- let call_stack_frame = instruction_to_callstack_stack_trace_entry(bytecode, call_inst)?;
542
-
543
- let (call_stack_frame_source_reference, call_stack_frame) = match call_stack_frame {
544
- Either::A(frame) => (frame.source_reference.clone(), frame.into()),
545
- Either::B(frame) => (frame.source_reference.clone(), frame.into()),
546
- };
547
-
548
- let last_message_failed = match last_submessage_data.message_trace {
549
- Either3::A(precompile) => precompile.exit.is_error(),
550
- Either3::B(call) => call.exit.is_error(),
551
- Either3::C(create) => create.exit.is_error(),
552
- };
553
- if last_message_failed {
554
- // add the call/create that generated the message to the stack trace
555
- let inferred_stacktrace = inferred_stacktrace.to_mut();
556
- inferred_stacktrace.push(call_stack_frame);
557
-
558
- if Self::is_subtrace_error_propagated(trace, last_submessage_data.step_index)?
559
- || Self::is_proxy_error_propagated(trace, last_submessage_data.step_index)?
560
- {
561
- inferred_stacktrace.extend(last_submessage_data.stacktrace);
562
-
563
- if Self::is_contract_call_run_out_of_gas_error(
564
- trace,
565
- last_submessage_data.step_index,
566
- )? {
567
- let last_frame = match inferred_stacktrace.pop() {
568
- Some(frame) => frame,
569
- _ => panic!("Expected inferred stack trace to have at least one frame"),
570
- };
571
-
572
- inferred_stacktrace.push(
573
- ContractCallRunOutOfGasError {
574
- type_: StackTraceEntryTypeConst,
575
- source_reference: last_frame.source_reference().cloned(),
576
- }
577
- .into(),
578
- );
579
- }
580
-
581
- return Self::fix_initial_modifier(trace, inferred_stacktrace.to_owned())
582
- .map(Heuristic::Hit);
583
- }
584
- } else {
585
- let is_return_data_size_error =
586
- Self::fails_right_after_call(trace, last_submessage_data.step_index)?;
587
- if is_return_data_size_error {
588
- inferred_stacktrace.to_mut().push(
589
- ReturndataSizeErrorStackTraceEntry {
590
- type_: StackTraceEntryTypeConst,
591
- source_reference: call_stack_frame_source_reference,
592
- }
593
- .into(),
594
- );
595
-
596
- return Self::fix_initial_modifier(trace, inferred_stacktrace.into_owned())
597
- .map(Heuristic::Hit);
598
- }
599
- }
600
-
601
- Ok(Heuristic::Miss(stacktrace))
602
- }
603
-
604
- /// Check if the execution stopped with a revert or an invalid opcode.
605
- fn check_revert_or_invalid_opcode(
606
- trace: Either<&CallMessageTrace, &CreateMessageTrace>,
607
- stacktrace: SolidityStackTrace,
608
- last_instruction: &Instruction,
609
- function_jumpdests: &[&Instruction],
610
- jumped_into_function: bool,
611
- ) -> napi::Result<Heuristic> {
612
- match last_instruction.opcode {
613
- OpCode::REVERT | OpCode::INVALID => {}
614
- _ => return Ok(Heuristic::Miss(stacktrace)),
615
- }
616
-
617
- let (bytecode, return_data) = match &trace {
618
- Either::A(call) => (&call.bytecode, &call.return_data),
619
- Either::B(create) => (&create.bytecode, &create.return_data),
620
- };
621
- let bytecode = bytecode.as_ref().expect("JS code asserts");
622
-
623
- let mut inferred_stacktrace = stacktrace.clone();
624
-
625
- if let Some(location) = &last_instruction.location {
626
- if jumped_into_function || matches!(trace, Either::B(CreateMessageTrace { .. })) {
627
- // There should always be a function here, but that's not the case with
628
- // optimizations.
629
- //
630
- // If this is a create trace, we already checked args and nonpayable failures
631
- // before calling this function.
632
- //
633
- // If it's a call trace, we already jumped into a function. But optimizations
634
- // can happen.
635
- let failing_function = location.get_containing_function();
636
-
637
- // If the failure is in a modifier we add an entry with the function/constructor
638
- match failing_function {
639
- Some(func) if func.r#type == ContractFunctionType::Modifier => {
640
- let frame =
641
- Self::get_entry_before_failure_in_modifier(trace, function_jumpdests)?;
642
-
643
- inferred_stacktrace.push(match frame {
644
- Either::A(frame) => frame.into(),
645
- Either::B(frame) => frame.into(),
646
- });
647
- }
648
- _ => {}
649
- }
650
- }
651
- }
652
-
653
- let panic_stacktrace = Self::check_panic(trace, inferred_stacktrace, last_instruction)?;
654
- let inferred_stacktrace = match panic_stacktrace {
655
- hit @ Heuristic::Hit(..) => return Ok(hit),
656
- Heuristic::Miss(stacktrace) => stacktrace,
657
- };
658
-
659
- let custom_error_stacktrace =
660
- Self::check_custom_errors(trace, inferred_stacktrace, last_instruction)?;
661
- let mut inferred_stacktrace = match custom_error_stacktrace {
662
- hit @ Heuristic::Hit(..) => return Ok(hit),
663
- Heuristic::Miss(stacktrace) => stacktrace,
664
- };
665
-
666
- if let Some(location) = &last_instruction.location {
667
- if jumped_into_function || matches!(trace, Either::B(CreateMessageTrace { .. })) {
668
- let failing_function = location.get_containing_function();
669
-
670
- if failing_function.is_some() {
671
- let frame = Self::instruction_within_function_to_revert_stack_trace_entry(
672
- trace,
673
- last_instruction,
674
- )?;
675
-
676
- inferred_stacktrace.push(frame.into());
677
- } else {
678
- let is_invalid_opcode_error = last_instruction.opcode == OpCode::INVALID;
679
-
680
- match &trace {
681
- Either::A(CallMessageTrace { calldata, .. }) => {
682
- let contract = bytecode.contract.borrow();
683
-
684
- // This is here because of the optimizations
685
- let function_from_selector = contract.get_function_from_selector(
686
- calldata.get(..4).unwrap_or(&calldata[..]),
687
- );
688
-
689
- // in general this shouldn't happen, but it does when viaIR is enabled,
690
- // "optimizerSteps": "u" is used, and the called function is fallback or
691
- // receive
692
- let Some(function) = function_from_selector else {
693
- return Ok(Heuristic::Miss(inferred_stacktrace));
694
- };
695
-
696
- let frame = RevertErrorStackTraceEntry {
697
- type_: StackTraceEntryTypeConst,
698
- source_reference: Self::get_function_start_source_reference(
699
- trace, function,
700
- )?,
701
- return_data: return_data.clone(),
702
- is_invalid_opcode_error,
703
- };
704
-
705
- inferred_stacktrace.push(frame.into());
706
- }
707
- Either::B(trace @ CreateMessageTrace { .. }) => {
708
- // This is here because of the optimizations
709
- let frame = RevertErrorStackTraceEntry {
710
- type_: StackTraceEntryTypeConst,
711
- source_reference: Self::get_constructor_start_source_reference(
712
- trace,
713
- )?,
714
- return_data: return_data.clone(),
715
- is_invalid_opcode_error,
716
- };
717
-
718
- inferred_stacktrace.push(frame.into());
719
- }
720
- }
721
- }
722
-
723
- return Self::fix_initial_modifier(trace, inferred_stacktrace).map(Heuristic::Hit);
724
- }
725
- }
726
-
727
- // If the revert instruction is not mapped but there is return data,
728
- // we add the frame anyway, sith the best sourceReference we can get
729
- if last_instruction.location.is_none() && !return_data.is_empty() {
730
- let revert_frame = RevertErrorStackTraceEntry {
731
- type_: StackTraceEntryTypeConst,
732
- source_reference: Self::get_contract_start_without_function_source_reference(
733
- trace,
734
- )?,
735
- return_data: return_data.clone(),
736
- is_invalid_opcode_error: last_instruction.opcode == OpCode::INVALID,
737
- };
738
-
739
- inferred_stacktrace.push(revert_frame.into());
740
-
741
- return Self::fix_initial_modifier(trace, inferred_stacktrace).map(Heuristic::Hit);
742
- }
743
-
744
- Ok(Heuristic::Miss(stacktrace))
745
- }
746
-
747
- fn check_last_instruction(
748
- trace: Either<&CallMessageTrace, &CreateMessageTrace>,
749
- stacktrace: SolidityStackTrace,
750
- function_jumpdests: &[&Instruction],
751
- jumped_into_function: bool,
752
- ) -> napi::Result<Heuristic> {
753
- let (bytecode, steps) = match &trace {
754
- Either::A(call) => (&call.bytecode, &call.steps),
755
- Either::B(create) => (&create.bytecode, &create.steps),
756
- };
757
- let bytecode = bytecode.as_ref().expect("JS code asserts");
758
-
759
- if steps.is_empty() {
760
- return Ok(Heuristic::Miss(stacktrace));
761
- }
762
-
763
- let last_step = match steps.last() {
764
- Some(Either4::A(step)) => step,
765
- _ => panic!("This should not happen: MessageTrace ends with a subtrace"),
766
- };
767
-
768
- let last_instruction = bytecode.get_instruction(last_step.pc)?;
769
-
770
- let revert_or_invalid_stacktrace = Self::check_revert_or_invalid_opcode(
771
- trace,
772
- stacktrace,
773
- last_instruction,
774
- function_jumpdests,
775
- jumped_into_function,
776
- )?;
777
- let stacktrace = match revert_or_invalid_stacktrace {
778
- hit @ Heuristic::Hit(..) => return Ok(hit),
779
- Heuristic::Miss(stacktrace) => stacktrace,
780
- };
781
-
782
- let (Either::A(trace @ CallMessageTrace { ref calldata, .. }), false) =
783
- (&trace, jumped_into_function)
784
- else {
785
- return Ok(Heuristic::Miss(stacktrace));
786
- };
787
-
788
- if Self::has_failed_inside_the_fallback_function(trace)?
789
- || Self::has_failed_inside_the_receive_function(trace)?
790
- {
791
- let frame = Self::instruction_within_function_to_revert_stack_trace_entry(
792
- Either::A(trace),
793
- last_instruction,
794
- )?;
795
-
796
- return Ok(Heuristic::Hit(vec![frame.into()]));
797
- }
798
-
799
- // Sometimes we do fail inside of a function but there's no jump into
800
- if let Some(location) = &last_instruction.location {
801
- let failing_function = location.get_containing_function();
802
-
803
- if let Some(failing_function) = failing_function {
804
- let frame = RevertErrorStackTraceEntry {
805
- type_: StackTraceEntryTypeConst,
806
- source_reference: Self::get_function_start_source_reference(
807
- Either::A(trace),
808
- &failing_function,
809
- )?,
810
- return_data: trace.return_data.clone(),
811
- is_invalid_opcode_error: last_instruction.opcode == OpCode::INVALID,
812
- };
813
-
814
- return Ok(Heuristic::Hit(vec![frame.into()]));
815
- }
816
- }
817
-
818
- let contract = bytecode.contract.borrow();
819
-
820
- let selector = calldata.get(..4).unwrap_or(&calldata[..]);
821
- let calldata = &calldata.get(4..).unwrap_or(&[]);
822
-
823
- let called_function = contract.get_function_from_selector(selector);
824
-
825
- if let Some(called_function) = called_function {
826
- let abi = alloy_json_abi::Function::try_from(&**called_function).map_err(|e| {
827
- napi::Error::from_reason(format!("Error converting to alloy ABI: {e}"))
828
- })?;
829
-
830
- let is_valid_calldata = match &called_function.param_types {
831
- Some(_) => abi.abi_decode_input(calldata, true).is_ok(),
832
- // if we don't know the param types, we just assume that the call is valid
833
- None => true,
834
- };
835
-
836
- if !is_valid_calldata {
837
- let frame = InvalidParamsErrorStackTraceEntry {
838
- type_: StackTraceEntryTypeConst,
839
- source_reference: Self::get_function_start_source_reference(
840
- Either::A(trace),
841
- called_function,
842
- )?,
843
- };
844
-
845
- return Ok(Heuristic::Hit(vec![frame.into()]));
846
- }
847
- }
848
-
849
- if Self::solidity_0_6_3_maybe_unmapped_revert(Either::A(trace))? {
850
- let revert_frame =
851
- Self::solidity_0_6_3_get_frame_for_unmapped_revert_before_function(trace)?;
852
-
853
- if let Some(revert_frame) = revert_frame {
854
- return Ok(Heuristic::Hit(vec![revert_frame.into()]));
855
- }
856
- }
857
-
858
- let frame = Self::get_other_error_before_called_function_stack_trace_entry(trace)?;
859
-
860
- Ok(Heuristic::Hit(vec![frame.into()]))
861
- }
862
-
863
- // Helpers
864
-
865
- fn fix_initial_modifier(
866
- trace: Either<&CallMessageTrace, &CreateMessageTrace>,
867
- mut stacktrace: SolidityStackTrace,
868
- ) -> napi::Result<SolidityStackTrace> {
869
- if let Some(Either24::A(CallstackEntryStackTraceEntry {
870
- function_type: ContractFunctionTypeNapi::MODIFIER,
871
- ..
872
- })) = stacktrace.first()
873
- {
874
- let entry_before_initial_modifier =
875
- Self::get_entry_before_initial_modifier_callstack_entry(trace)?;
876
-
877
- stacktrace.insert(0, entry_before_initial_modifier);
878
- }
879
-
880
- Ok(stacktrace)
881
- }
882
-
883
- fn get_entry_before_initial_modifier_callstack_entry(
884
- trace: Either<&CallMessageTrace, &CreateMessageTrace>,
885
- ) -> napi::Result<SolidityStackTraceEntry> {
886
- let trace = match trace {
887
- Either::B(create) => {
888
- return Ok(CallstackEntryStackTraceEntry {
889
- type_: StackTraceEntryTypeConst,
890
- source_reference: Self::get_constructor_start_source_reference(create)?,
891
- function_type: ContractFunctionType::Constructor.into(),
892
- }
893
- .into())
894
- }
895
- Either::A(call) => call,
896
- };
897
-
898
- let bytecode = trace.bytecode.as_ref().expect("JS code asserts");
899
- let contract = bytecode.contract.borrow();
900
-
901
- let called_function = contract
902
- .get_function_from_selector(trace.calldata.get(..4).unwrap_or(&trace.calldata[..]));
903
-
904
- let source_reference = match called_function {
905
- Some(called_function) => {
906
- Self::get_function_start_source_reference(Either::A(trace), called_function)?
907
- }
908
- None => Self::get_fallback_start_source_reference(trace)?,
909
- };
910
-
911
- let function_type = match called_function {
912
- Some(_) => ContractFunctionType::Function,
913
- None => ContractFunctionType::Fallback,
914
- };
915
-
916
- Ok(CallstackEntryStackTraceEntry {
917
- type_: StackTraceEntryTypeConst,
918
- source_reference,
919
- function_type: function_type.into(),
920
- }
921
- .into())
922
- }
923
-
924
- fn call_instruction_to_call_failed_to_execute_stack_trace_entry(
925
- bytecode: &Bytecode,
926
- call_inst: &Instruction,
927
- ) -> napi::Result<CallFailedErrorStackTraceEntry> {
928
- let location = call_inst.location.as_deref();
929
-
930
- let source_reference = source_location_to_source_reference(bytecode, location)?;
931
- let source_reference = source_reference.expect("Expected source reference to be defined");
932
-
933
- // Calls only happen within functions
934
- Ok(CallFailedErrorStackTraceEntry {
935
- type_: StackTraceEntryTypeConst,
936
- source_reference,
937
- })
938
- }
939
-
940
- fn get_entry_before_failure_in_modifier(
941
- trace: Either<&CallMessageTrace, &CreateMessageTrace>,
942
- function_jumpdests: &[&Instruction],
943
- ) -> napi::Result<Either<CallstackEntryStackTraceEntry, InternalFunctionCallStackEntry>> {
944
- let bytecode = match &trace {
945
- Either::A(call) => &call.bytecode,
946
- Either::B(create) => &create.bytecode,
947
- };
948
- let bytecode = bytecode.as_ref().expect("JS code asserts");
949
-
950
- // If there's a jumpdest, this modifier belongs to the last function that it
951
- // represents
952
- if let Some(last_jumpdest) = function_jumpdests.last() {
953
- let entry = instruction_to_callstack_stack_trace_entry(bytecode, last_jumpdest)?;
954
-
955
- return Ok(entry);
956
- }
957
-
958
- // This function is only called after we jumped into the initial function in
959
- // call traces, so there should always be at least a function jumpdest.
960
- let trace = match trace {
961
- Either::A(_call) => return Err(
962
- napi::Error::new(
963
- napi::Status::GenericFailure,
964
- "This shouldn't happen: a call trace has no functionJumpdest but has already jumped into a function"
965
- )),
966
- Either::B(create) => create,
967
- };
968
-
969
- // If there's no jump dest, we point to the constructor.
970
- Ok(Either::A(CallstackEntryStackTraceEntry {
971
- type_: StackTraceEntryTypeConst,
972
- source_reference: Self::get_constructor_start_source_reference(trace)?,
973
- function_type: ContractFunctionType::Constructor.into(),
974
- }))
975
- }
976
-
977
- fn fails_right_after_call(
978
- trace: Either<&CallMessageTrace, &CreateMessageTrace>,
979
- call_subtrace_step_index: u32,
980
- ) -> napi::Result<bool> {
981
- let (bytecode, steps) = match &trace {
982
- Either::A(call) => (&call.bytecode, &call.steps),
983
- Either::B(create) => (&create.bytecode, &create.steps),
984
- };
985
-
986
- let bytecode = bytecode.as_ref().expect("JS code asserts");
987
-
988
- let Some(Either4::A(last_step)) = steps.last() else {
989
- return Ok(false);
990
- };
991
-
992
- let last_inst = bytecode.get_instruction(last_step.pc)?;
993
- if last_inst.opcode != OpCode::REVERT {
994
- return Ok(false);
995
- }
996
-
997
- let call_opcode_step = steps.get(call_subtrace_step_index as usize - 1);
998
- let call_opcode_step = match call_opcode_step {
999
- Some(Either4::A(step)) => step,
1000
- _ => panic!("JS code asserts this is always an EvmStep"),
1001
- };
1002
- let call_inst = bytecode.get_instruction(call_opcode_step.pc)?;
1003
-
1004
- // Calls are always made from within functions
1005
- let call_inst_location = call_inst
1006
- .location
1007
- .as_ref()
1008
- .expect("Expected call instruction location to be defined");
1009
-
1010
- Self::is_last_location(trace, call_subtrace_step_index + 1, call_inst_location)
1011
- }
1012
-
1013
- fn is_call_failed_error(
1014
- trace: Either<&CallMessageTrace, &CreateMessageTrace>,
1015
- inst_index: u32,
1016
- call_instruction: &Instruction,
1017
- ) -> napi::Result<bool> {
1018
- let call_location = match &call_instruction.location {
1019
- Some(location) => location,
1020
- None => panic!("Expected call location to be defined"),
1021
- };
1022
-
1023
- Self::is_last_location(trace, inst_index, call_location)
1024
- }
1025
-
1026
- fn is_last_location(
1027
- trace: Either<&CallMessageTrace, &CreateMessageTrace>,
1028
- from_step: u32,
1029
- location: &SourceLocation,
1030
- ) -> napi::Result<bool> {
1031
- let (bytecode, steps) = match &trace {
1032
- Either::A(call) => (&call.bytecode, &call.steps),
1033
- Either::B(create) => (&create.bytecode, &create.steps),
1034
- };
1035
-
1036
- let bytecode = bytecode.as_ref().expect("JS code asserts");
1037
-
1038
- for step in steps.iter().skip(from_step as usize) {
1039
- let step = match step {
1040
- Either4::A(step) => step,
1041
- _ => return Ok(false),
1042
- };
1043
-
1044
- let step_inst = bytecode.get_instruction(step.pc)?;
1045
-
1046
- if let Some(step_inst_location) = &step_inst.location {
1047
- if **step_inst_location != *location {
1048
- return Ok(false);
1049
- }
1050
- }
1051
- }
1052
-
1053
- Ok(true)
1054
- }
1055
-
1056
- fn is_subtrace_error_propagated(
1057
- trace: Either<&CallMessageTrace, &CreateMessageTrace>,
1058
- call_subtrace_step_index: u32,
1059
- ) -> napi::Result<bool> {
1060
- let (return_data, exit, steps) = match &trace {
1061
- Either::A(call) => (&call.return_data, call.exit.kind(), &call.steps),
1062
- Either::B(create) => (&create.return_data, create.exit.kind(), &create.steps),
1063
- };
1064
-
1065
- let (call_return_data, call_exit) = match steps.get(call_subtrace_step_index as usize) {
1066
- None | Some(Either4::A(_)) => panic!("Expected call to be a message trace"),
1067
- Some(Either4::B(precompile)) => (&precompile.return_data, precompile.exit.kind()),
1068
- Some(Either4::C(call)) => (&call.return_data, call.exit.kind()),
1069
- Some(Either4::D(create)) => (&create.return_data, create.exit.kind()),
1070
- };
1071
-
1072
- if return_data.as_ref() != call_return_data.as_ref() {
1073
- return Ok(false);
1074
- }
1075
-
1076
- if exit == ExitCode::OUT_OF_GAS && call_exit == ExitCode::OUT_OF_GAS {
1077
- return Ok(true);
1078
- }
1079
-
1080
- // If the return data is not empty, and it's still the same, we assume it
1081
- // is being propagated
1082
- if return_data.len() > 0 {
1083
- return Ok(true);
1084
- }
1085
-
1086
- Self::fails_right_after_call(trace, call_subtrace_step_index)
1087
- }
1088
-
1089
- fn is_contract_call_run_out_of_gas_error(
1090
- trace: Either<&CallMessageTrace, &CreateMessageTrace>,
1091
- call_step_index: u32,
1092
- ) -> napi::Result<bool> {
1093
- let (steps, return_data, exit_code) = match &trace {
1094
- Either::A(call) => (&call.steps, &call.return_data, call.exit.kind()),
1095
- Either::B(create) => (&create.steps, &create.return_data, create.exit.kind()),
1096
- };
1097
-
1098
- if return_data.len() > 0 {
1099
- return Ok(false);
1100
- }
1101
-
1102
- if exit_code != ExitCode::REVERT {
1103
- return Ok(false);
1104
- }
1105
-
1106
- let call_exit = match steps.get(call_step_index as usize) {
1107
- None | Some(Either4::A(_)) => panic!("Expected call to be a message trace"),
1108
- Some(Either4::B(precompile)) => precompile.exit.kind(),
1109
- Some(Either4::C(call)) => call.exit.kind(),
1110
- Some(Either4::D(create)) => create.exit.kind(),
1111
- };
1112
-
1113
- if call_exit != ExitCode::OUT_OF_GAS {
1114
- return Ok(false);
1115
- }
1116
-
1117
- Self::fails_right_after_call(trace, call_step_index)
1118
- }
1119
-
1120
- fn is_proxy_error_propagated(
1121
- trace: Either<&CallMessageTrace, &CreateMessageTrace>,
1122
- call_subtrace_step_index: u32,
1123
- ) -> napi::Result<bool> {
1124
- let trace = match &trace {
1125
- Either::A(call) => call,
1126
- Either::B(_) => return Ok(false),
1127
- };
1128
-
1129
- let bytecode = trace.bytecode.as_ref().expect("JS code asserts");
1130
-
1131
- let call_step = match trace.steps.get(call_subtrace_step_index as usize - 1) {
1132
- Some(Either4::A(step)) => step,
1133
- _ => return Ok(false),
1134
- };
1135
-
1136
- let call_inst = bytecode.get_instruction(call_step.pc)?;
1137
-
1138
- if call_inst.opcode != OpCode::DELEGATECALL {
1139
- return Ok(false);
1140
- }
1141
-
1142
- let subtrace = match trace.steps.get(call_subtrace_step_index as usize) {
1143
- None | Some(Either4::A(_) | Either4::B(_)) => return Ok(false),
1144
- Some(Either4::C(call)) => Either::A(call),
1145
- Some(Either4::D(create)) => Either::B(create),
1146
- };
1147
-
1148
- let (subtrace_bytecode, subtrace_return_data) = match &subtrace {
1149
- Either::A(call) => (&call.bytecode, &call.return_data),
1150
- Either::B(create) => (&create.bytecode, &create.return_data),
1151
- };
1152
- let subtrace_bytecode = match subtrace_bytecode {
1153
- Some(bytecode) => bytecode,
1154
- // If we can't recognize the implementation we'd better don't consider it as such
1155
- None => return Ok(false),
1156
- };
1157
-
1158
- if subtrace_bytecode.contract.borrow().r#type == ContractKind::Library {
1159
- return Ok(false);
1160
- }
1161
-
1162
- if trace.return_data.as_ref() != subtrace_return_data.as_ref() {
1163
- return Ok(false);
1164
- }
1165
-
1166
- for step in trace
1167
- .steps
1168
- .iter()
1169
- .skip(call_subtrace_step_index as usize + 1)
1170
- {
1171
- let step = match step {
1172
- Either4::A(step) => step,
1173
- _ => return Ok(false),
1174
- };
1175
-
1176
- let inst = bytecode.get_instruction(step.pc)?;
1177
-
1178
- // All the remaining locations should be valid, as they are part of the inline
1179
- // asm
1180
- if inst.location.is_none() {
1181
- return Ok(false);
1182
- }
1183
-
1184
- if matches!(
1185
- inst.jump_type,
1186
- JumpType::IntoFunction | JumpType::OutofFunction
1187
- ) {
1188
- return Ok(false);
1189
- }
1190
- }
1191
-
1192
- let last_step = match trace.steps.last() {
1193
- Some(Either4::A(step)) => step,
1194
- _ => panic!("Expected last step to be an EvmStep"),
1195
- };
1196
- let last_inst = bytecode.get_instruction(last_step.pc)?;
1197
-
1198
- Ok(last_inst.opcode == OpCode::REVERT)
1199
- }
1200
-
1201
- fn other_execution_error_stacktrace(
1202
- trace: Either<&CallMessageTrace, &CreateMessageTrace>,
1203
- mut stacktrace: SolidityStackTrace,
1204
- ) -> napi::Result<SolidityStackTrace> {
1205
- let other_execution_error_frame = OtherExecutionErrorStackTraceEntry {
1206
- type_: StackTraceEntryTypeConst,
1207
- source_reference: Self::get_last_source_reference(trace)?,
1208
- };
1209
-
1210
- stacktrace.push(other_execution_error_frame.into());
1211
- Ok(stacktrace)
1212
- }
1213
-
1214
- fn is_direct_library_call(trace: &CallMessageTrace) -> napi::Result<bool> {
1215
- let contract = &trace.bytecode.as_ref().expect("JS code asserts").contract;
1216
- let contract = contract.borrow();
1217
-
1218
- Ok(trace.depth == 0 && contract.r#type == ContractKind::Library)
1219
- }
1220
-
1221
- fn get_direct_library_call_error_stack_trace(
1222
- trace: &CallMessageTrace,
1223
- ) -> napi::Result<SolidityStackTrace> {
1224
- let contract = &trace.bytecode.as_ref().expect("JS code asserts").contract;
1225
- let contract = contract.borrow();
1226
-
1227
- let func = contract
1228
- .get_function_from_selector(trace.calldata.get(..4).unwrap_or(&trace.calldata[..]));
1229
-
1230
- let source_reference = match func {
1231
- Some(func) => Self::get_function_start_source_reference(Either::A(trace), func)?,
1232
- None => Self::get_contract_start_without_function_source_reference(Either::A(trace))?,
1233
- };
1234
-
1235
- Ok(vec![DirectLibraryCallErrorStackTraceEntry {
1236
- type_: StackTraceEntryTypeConst,
1237
- source_reference,
1238
- }
1239
- .into()])
1240
- }
1241
-
1242
- fn get_function_start_source_reference(
1243
- trace: Either<&CallMessageTrace, &CreateMessageTrace>,
1244
- func: &ContractFunction,
1245
- ) -> napi::Result<SourceReference> {
1246
- let bytecode = match &trace {
1247
- Either::A(create) => &create.bytecode,
1248
- Either::B(call) => &call.bytecode,
1249
- };
1250
-
1251
- let contract = &bytecode.as_ref().expect("JS code asserts").contract;
1252
- let contract = contract.borrow();
1253
-
1254
- let file = func.location.file();
1255
- let file = file.borrow();
1256
-
1257
- let location = &func.location;
1258
-
1259
- Ok(SourceReference {
1260
- source_name: file.source_name.clone(),
1261
- source_content: file.content.clone(),
1262
- contract: Some(contract.name.clone()),
1263
-
1264
- function: Some(func.name.clone()),
1265
- line: location.get_starting_line_number(),
1266
- range: [location.offset, location.offset + location.length].to_vec(),
1267
- })
1268
- }
1269
-
1270
- fn is_missing_function_and_fallback_error(
1271
- trace: &CallMessageTrace,
1272
- called_function: Option<&ContractFunction>,
1273
- ) -> napi::Result<bool> {
1274
- // This error doesn't return data
1275
- if trace.return_data.len() > 0 {
1276
- return Ok(false);
1277
- }
1278
-
1279
- // the called function exists in the contract
1280
- if called_function.is_some() {
1281
- return Ok(false);
1282
- }
1283
-
1284
- let bytecode = trace
1285
- .bytecode
1286
- .as_ref()
1287
- .expect("The TS code type-checks this to always have bytecode");
1288
- let contract = bytecode.contract.borrow();
1289
-
1290
- // there's a receive function and no calldata
1291
- if trace.calldata.len() == 0 && contract.receive.is_some() {
1292
- return Ok(false);
1293
- }
1294
-
1295
- Ok(contract.fallback.is_none())
1296
- }
1297
-
1298
- fn empty_calldata_and_no_receive(trace: &CallMessageTrace) -> napi::Result<bool> {
1299
- let bytecode = trace
1300
- .bytecode
1301
- .as_ref()
1302
- .expect("The TS code type-checks this to always have bytecode");
1303
- let contract = bytecode.contract.borrow();
1304
-
1305
- let version =
1306
- Version::parse(&bytecode.compiler_version).expect("Failed to parse SemVer version");
1307
-
1308
- // this only makes sense when receive functions are available
1309
- if version < FIRST_SOLC_VERSION_RECEIVE_FUNCTION {
1310
- return Ok(false);
1311
- }
1312
-
1313
- Ok(trace.calldata.is_empty() && contract.receive.is_none())
1314
- }
1315
-
1316
- fn is_fallback_not_payable_error(
1317
- trace: &CallMessageTrace,
1318
- called_function: Option<&ContractFunction>,
1319
- ) -> napi::Result<bool> {
1320
- // This error doesn't return data
1321
- if !trace.return_data.is_empty() {
1322
- return Ok(false);
1323
- }
1324
-
1325
- let (neg, value, _) = trace.value.get_u64();
1326
- if neg || value == 0 {
1327
- return Ok(false);
1328
- }
1329
-
1330
- // the called function exists in the contract
1331
- if called_function.is_some() {
1332
- return Ok(false);
1333
- }
1334
-
1335
- let bytecode = trace.bytecode.as_ref().expect("JS code asserts");
1336
- let contract = bytecode.contract.borrow();
1337
-
1338
- match &contract.fallback {
1339
- Some(fallback) => Ok(fallback.is_payable != Some(true)),
1340
- None => Ok(false),
1341
- }
1342
- }
1343
-
1344
- fn get_fallback_start_source_reference(
1345
- trace: &CallMessageTrace,
1346
- ) -> napi::Result<SourceReference> {
1347
- let bytecode = trace.bytecode.as_ref().expect("JS code asserts");
1348
- let contract = bytecode.contract.borrow();
1349
-
1350
- let func = match &contract.fallback {
1351
- Some(func) => func,
1352
- None => panic!("This shouldn't happen: trying to get fallback source reference from a contract without fallback"),
1353
- };
1354
-
1355
- let location = &func.location;
1356
- let file = location.file();
1357
- let file = file.borrow();
1358
-
1359
- Ok(SourceReference {
1360
- source_name: file.source_name.clone(),
1361
- source_content: file.content.clone(),
1362
- contract: Some(contract.name.clone()),
1363
- function: Some(FALLBACK_FUNCTION_NAME.to_string()),
1364
- line: location.get_starting_line_number(),
1365
- range: [location.offset, location.offset + location.length].to_vec(),
1366
- })
1367
- }
1368
-
1369
- fn is_constructor_not_payable_error(trace: &CreateMessageTrace) -> napi::Result<bool> {
1370
- // This error doesn't return data
1371
- if !trace.return_data.is_empty() {
1372
- return Ok(false);
1373
- }
1374
-
1375
- let bytecode = trace.bytecode.as_ref().expect("JS code asserts");
1376
- let contract = bytecode.contract.borrow();
1377
-
1378
- // This function is only matters with contracts that have constructors defined.
1379
- // The ones that don't are abstract contracts, or their constructor
1380
- // doesn't take any argument.
1381
- let constructor = match &contract.constructor {
1382
- Some(constructor) => constructor,
1383
- None => return Ok(false),
1384
- };
1385
-
1386
- let (neg, value, _) = trace.value.get_u64();
1387
- if neg || value == 0 {
1388
- return Ok(false);
1389
- }
1390
-
1391
- Ok(constructor.is_payable != Some(true))
1392
- }
1393
-
1394
- /// Returns a source reference pointing to the constructor if it exists, or
1395
- /// to the contract otherwise.
1396
- fn get_constructor_start_source_reference(
1397
- trace: &CreateMessageTrace,
1398
- ) -> napi::Result<SourceReference> {
1399
- let bytecode = trace.bytecode.as_ref().expect("JS code asserts");
1400
- let contract = bytecode.contract.borrow();
1401
- let contract_location = &contract.location;
1402
-
1403
- let line = match &contract.constructor {
1404
- Some(constructor) => constructor.location.get_starting_line_number(),
1405
- None => contract_location.get_starting_line_number(),
1406
- };
1407
-
1408
- let file = contract_location.file();
1409
- let file = file.borrow();
1410
-
1411
- Ok(SourceReference {
1412
- source_name: file.source_name.clone(),
1413
- source_content: file.content.clone(),
1414
- contract: Some(contract.name.clone()),
1415
- function: Some(CONSTRUCTOR_FUNCTION_NAME.to_string()),
1416
- line,
1417
- range: [
1418
- contract_location.offset,
1419
- contract_location.offset + contract_location.length,
1420
- ]
1421
- .to_vec(),
1422
- })
1423
- }
1424
-
1425
- fn is_constructor_invalid_arguments_error(trace: &CreateMessageTrace) -> napi::Result<bool> {
1426
- if trace.return_data.len() > 0 {
1427
- return Ok(false);
1428
- }
1429
-
1430
- let bytecode = trace.bytecode.as_ref().expect("JS code asserts");
1431
- let contract = bytecode.contract.borrow();
1432
-
1433
- // This function is only matters with contracts that have constructors defined.
1434
- // The ones that don't are abstract contracts, or their constructor
1435
- // doesn't take any argument.
1436
- let Some(constructor) = &contract.constructor else {
1437
- return Ok(false);
1438
- };
1439
-
1440
- let Ok(version) = Version::parse(&bytecode.compiler_version) else {
1441
- return Ok(false);
1442
- };
1443
- if version < FIRST_SOLC_VERSION_CREATE_PARAMS_VALIDATION {
1444
- return Ok(false);
1445
- }
1446
-
1447
- let last_step = trace.steps.last();
1448
- let Some(Either4::A(last_step)) = last_step else {
1449
- return Ok(false);
1450
- };
1451
-
1452
- let last_inst = bytecode.get_instruction(last_step.pc)?;
1453
-
1454
- if last_inst.opcode != OpCode::REVERT || last_inst.location.is_some() {
1455
- return Ok(false);
1456
- }
1457
-
1458
- let mut has_read_deployment_code_size = false;
1459
- for step in &trace.steps {
1460
- let step = match step {
1461
- Either4::A(step) => step,
1462
- _ => return Ok(false),
1463
- };
1464
-
1465
- let inst = bytecode.get_instruction(step.pc)?;
1466
-
1467
- if let Some(inst_location) = &inst.location {
1468
- if contract.location != *inst_location && constructor.location != *inst_location {
1469
- return Ok(false);
1470
- }
1471
- }
1472
-
1473
- if inst.opcode == OpCode::CODESIZE {
1474
- has_read_deployment_code_size = true;
1475
- }
1476
- }
1477
-
1478
- Ok(has_read_deployment_code_size)
1479
- }
1480
-
1481
- fn get_contract_start_without_function_source_reference(
1482
- trace: Either<&CallMessageTrace, &CreateMessageTrace>,
1483
- ) -> napi::Result<SourceReference> {
1484
- let bytecode = match &trace {
1485
- Either::A(create) => &create.bytecode,
1486
- Either::B(call) => &call.bytecode,
1487
- };
1488
-
1489
- let contract = &bytecode.as_ref().expect("JS code asserts").contract;
1490
-
1491
- let contract = contract.borrow();
1492
-
1493
- let location = &contract.location;
1494
- let file = location.file();
1495
- let file = file.borrow();
1496
-
1497
- Ok(SourceReference {
1498
- source_name: file.source_name.clone(),
1499
- source_content: file.content.clone(),
1500
- contract: Some(contract.name.clone()),
1501
-
1502
- function: None,
1503
- line: location.get_starting_line_number(),
1504
- range: [location.offset, location.offset + location.length].to_vec(),
1505
- })
1506
- }
1507
-
1508
- fn is_function_not_payable_error(
1509
- trace: &CallMessageTrace,
1510
- called_function: &ContractFunction,
1511
- ) -> napi::Result<bool> {
1512
- // This error doesn't return data
1513
- if !trace.return_data.is_empty() {
1514
- return Ok(false);
1515
- }
1516
-
1517
- let (neg, value, _) = trace.value.get_u64();
1518
- if neg || value == 0 {
1519
- return Ok(false);
1520
- }
1521
-
1522
- let bytecode = trace.bytecode.as_ref().expect("JS code asserts");
1523
- let contract = bytecode.contract.borrow();
1524
-
1525
- // Libraries don't have a nonpayable check
1526
- if contract.r#type == ContractKind::Library {
1527
- return Ok(false);
1528
- }
1529
-
1530
- Ok(called_function.is_payable != Some(true))
1531
- }
1532
-
1533
- fn get_last_source_reference(
1534
- trace: Either<&CallMessageTrace, &CreateMessageTrace>,
1535
- ) -> napi::Result<Option<SourceReference>> {
1536
- let (bytecode, steps) = match trace {
1537
- Either::A(create) => (&create.bytecode, &create.steps),
1538
- Either::B(call) => (&call.bytecode, &call.steps),
1539
- };
1540
-
1541
- let bytecode = bytecode
1542
- .as_ref()
1543
- .expect("JS code only accepted variants that had bytecode defined");
1544
-
1545
- for step in steps.iter().rev() {
1546
- let step = match step {
1547
- Either4::A(step) => step,
1548
- _ => continue,
1549
- };
1550
-
1551
- let inst = bytecode.get_instruction(step.pc)?;
1552
-
1553
- let Some(location) = &inst.location else {
1554
- continue;
1555
- };
1556
-
1557
- let source_reference = source_location_to_source_reference(bytecode, Some(location))?;
1558
-
1559
- if let Some(source_reference) = source_reference {
1560
- return Ok(Some(source_reference));
1561
- }
1562
- }
1563
-
1564
- Ok(None)
1565
- }
1566
-
1567
- fn has_failed_inside_the_fallback_function(trace: &CallMessageTrace) -> napi::Result<bool> {
1568
- let contract = &trace.bytecode.as_ref().expect("JS code asserts").contract;
1569
- let contract = contract.borrow();
1570
-
1571
- match &contract.fallback {
1572
- Some(fallback) => Self::has_failed_inside_function(trace, fallback),
1573
- None => Ok(false),
1574
- }
1575
- }
1576
-
1577
- fn has_failed_inside_the_receive_function(trace: &CallMessageTrace) -> napi::Result<bool> {
1578
- let contract = &trace.bytecode.as_ref().expect("JS code asserts").contract;
1579
- let contract = contract.borrow();
1580
-
1581
- match &contract.receive {
1582
- Some(receive) => Self::has_failed_inside_function(trace, receive),
1583
- None => Ok(false),
1584
- }
1585
- }
1586
-
1587
- fn has_failed_inside_function(
1588
- trace: &CallMessageTrace,
1589
- func: &ContractFunction,
1590
- ) -> napi::Result<bool> {
1591
- let last_step = trace
1592
- .steps
1593
- .last()
1594
- .expect("There should at least be one step");
1595
-
1596
- let last_step = match last_step {
1597
- Either4::A(step) => step,
1598
- _ => panic!("JS code asserted this is always an EvmStep"),
1599
- };
1600
-
1601
- let last_instruction = trace
1602
- .bytecode
1603
- .as_ref()
1604
- .expect("The TS code type-checks this to always have bytecode")
1605
- .get_instruction(last_step.pc)?;
1606
-
1607
- Ok(match &last_instruction.location {
1608
- Some(last_instruction_location) => {
1609
- last_instruction.opcode == OpCode::REVERT
1610
- && func.location.contains(last_instruction_location)
1611
- }
1612
- _ => false,
1613
- })
1614
- }
1615
-
1616
- fn instruction_within_function_to_revert_stack_trace_entry(
1617
- trace: Either<&CallMessageTrace, &CreateMessageTrace>,
1618
- inst: &Instruction,
1619
- ) -> napi::Result<RevertErrorStackTraceEntry> {
1620
- let bytecode = match &trace {
1621
- Either::A(create) => &create.bytecode,
1622
- Either::B(call) => &call.bytecode,
1623
- }
1624
- .as_ref()
1625
- .expect("JS code asserts");
1626
-
1627
- let source_reference =
1628
- source_location_to_source_reference(bytecode, inst.location.as_deref())?
1629
- .expect("Expected source reference to be defined");
1630
-
1631
- let return_data = match &trace {
1632
- Either::A(create) => &create.return_data,
1633
- Either::B(call) => &call.return_data,
1634
- };
1635
-
1636
- Ok(RevertErrorStackTraceEntry {
1637
- type_: StackTraceEntryTypeConst,
1638
- source_reference,
1639
- is_invalid_opcode_error: inst.opcode == OpCode::INVALID,
1640
- return_data: return_data.clone(),
1641
- })
1642
- }
1643
-
1644
- fn instruction_within_function_to_unmapped_solc_0_6_3_revert_error_stack_trace_entry(
1645
- trace: Either<&CallMessageTrace, &CreateMessageTrace>,
1646
- inst: &Instruction,
1647
- ) -> napi::Result<UnmappedSolc063RevertErrorStackTraceEntry> {
1648
- let bytecode = match &trace {
1649
- Either::A(create) => &create.bytecode,
1650
- Either::B(call) => &call.bytecode,
1651
- }
1652
- .as_ref()
1653
- .expect("JS code asserts");
1654
-
1655
- let source_reference =
1656
- source_location_to_source_reference(bytecode, inst.location.as_deref())?;
1657
-
1658
- Ok(UnmappedSolc063RevertErrorStackTraceEntry {
1659
- type_: StackTraceEntryTypeConst,
1660
- source_reference,
1661
- })
1662
- }
1663
-
1664
- fn instruction_within_function_to_panic_stack_trace_entry(
1665
- trace: Either<&CallMessageTrace, &CreateMessageTrace>,
1666
- inst: &Instruction,
1667
- error_code: BigInt,
1668
- ) -> napi::Result<PanicErrorStackTraceEntry> {
1669
- let last_source_reference = Self::get_last_source_reference(trace)?;
1670
-
1671
- let bytecode = match &trace {
1672
- Either::A(create) => &create.bytecode,
1673
- Either::B(call) => &call.bytecode,
1674
- }
1675
- .as_ref()
1676
- .expect("JS code asserts");
1677
-
1678
- let source_reference =
1679
- source_location_to_source_reference(bytecode, inst.location.as_deref())?;
1680
-
1681
- let source_reference = source_reference.or(last_source_reference);
1682
-
1683
- Ok(PanicErrorStackTraceEntry {
1684
- type_: StackTraceEntryTypeConst,
1685
- source_reference,
1686
- error_code,
1687
- })
1688
- }
1689
-
1690
- fn instruction_within_function_to_custom_error_stack_trace_entry(
1691
- trace: Either<&CallMessageTrace, &CreateMessageTrace>,
1692
- inst: &Instruction,
1693
- message: String,
1694
- ) -> napi::Result<CustomErrorStackTraceEntry> {
1695
- let last_source_reference = Self::get_last_source_reference(trace)?;
1696
- let last_source_reference =
1697
- last_source_reference.expect("Expected source reference to be defined");
1698
-
1699
- let bytecode = match &trace {
1700
- Either::A(create) => &create.bytecode,
1701
- Either::B(call) => &call.bytecode,
1702
- }
1703
- .as_ref()
1704
- .expect("JS code asserts");
1705
-
1706
- let source_reference =
1707
- source_location_to_source_reference(bytecode, inst.location.as_deref())?;
1708
-
1709
- let source_reference = source_reference.unwrap_or(last_source_reference);
1710
-
1711
- Ok(CustomErrorStackTraceEntry {
1712
- type_: StackTraceEntryTypeConst,
1713
- source_reference,
1714
- message,
1715
- })
1716
- }
1717
-
1718
- fn solidity_0_6_3_maybe_unmapped_revert(
1719
- trace: Either<&CallMessageTrace, &CreateMessageTrace>,
1720
- ) -> napi::Result<bool> {
1721
- let (bytecode, steps) = match &trace {
1722
- Either::A(create) => (&create.bytecode, &create.steps),
1723
- Either::B(call) => (&call.bytecode, &call.steps),
1724
- };
1725
-
1726
- let bytecode = bytecode
1727
- .as_ref()
1728
- .expect("JS code only accepted variants that had bytecode defined");
1729
-
1730
- if steps.is_empty() {
1731
- return Ok(false);
1732
- }
1733
-
1734
- let last_step = steps.last();
1735
- let last_step = match last_step {
1736
- Some(Either4::A(step)) => step,
1737
- _ => return Ok(false),
1738
- };
1739
-
1740
- let last_instruction = bytecode.get_instruction(last_step.pc)?;
1741
-
1742
- let Ok(version) = Version::parse(&bytecode.compiler_version) else {
1743
- return Ok(false);
1744
- };
1745
- let req = VersionReq::parse(&format!("^{FIRST_SOLC_VERSION_WITH_UNMAPPED_REVERTS}"))
1746
- .expect("valid semver");
1747
-
1748
- Ok(req.matches(&version) && last_instruction.opcode == OpCode::REVERT)
1749
- }
1750
-
1751
- // Solidity 0.6.3 unmapped reverts special handling
1752
- // For more info: https://github.com/ethereum/solidity/issues/9006
1753
- fn solidity_0_6_3_get_frame_for_unmapped_revert_before_function(
1754
- trace: &CallMessageTrace,
1755
- ) -> napi::Result<Option<UnmappedSolc063RevertErrorStackTraceEntry>> {
1756
- let bytecode = trace.bytecode.as_ref().expect("JS code asserts");
1757
- let contract = bytecode.contract.borrow();
1758
-
1759
- let revert_frame =
1760
- Self::solidity_0_6_3_get_frame_for_unmapped_revert_within_function(Either::A(trace))?;
1761
-
1762
- let revert_frame = match revert_frame {
1763
- None
1764
- | Some(UnmappedSolc063RevertErrorStackTraceEntry {
1765
- source_reference: None,
1766
- ..
1767
- }) => {
1768
- if contract.receive.is_none() || trace.calldata.len() > 0 {
1769
- // Failed within the fallback
1770
- if let Some(fallback) = &contract.fallback {
1771
- let location = &fallback.location;
1772
- let file = location.file();
1773
- let file = file.borrow();
1774
-
1775
- let revert_frame = UnmappedSolc063RevertErrorStackTraceEntry {
1776
- type_: StackTraceEntryTypeConst,
1777
- source_reference: Some(SourceReference {
1778
- contract: Some(contract.name.clone()),
1779
- function: Some(FALLBACK_FUNCTION_NAME.to_string()),
1780
- source_name: file.source_name.clone(),
1781
- source_content: file.content.clone(),
1782
- line: location.get_starting_line_number(),
1783
- range: [location.offset, location.offset + location.length]
1784
- .to_vec(),
1785
- }),
1786
- };
1787
-
1788
- Some(Self::solidity_0_6_3_correct_line_number(revert_frame))
1789
- } else {
1790
- None
1791
- }
1792
- } else {
1793
- let receive = contract
1794
- .receive
1795
- .as_ref()
1796
- .expect("None always hits branch above");
1797
-
1798
- let location = &receive.location;
1799
- let file = location.file();
1800
- let file = file.borrow();
1801
-
1802
- let revert_frame = UnmappedSolc063RevertErrorStackTraceEntry {
1803
- type_: StackTraceEntryTypeConst,
1804
- source_reference: Some(SourceReference {
1805
- contract: Some(contract.name.clone()),
1806
- function: Some(RECEIVE_FUNCTION_NAME.to_string()),
1807
- source_name: file.source_name.clone(),
1808
- source_content: file.content.clone(),
1809
- line: location.get_starting_line_number(),
1810
- range: [location.offset, location.offset + location.length].to_vec(),
1811
- }),
1812
- };
1813
-
1814
- Some(Self::solidity_0_6_3_correct_line_number(revert_frame))
1815
- }
1816
- }
1817
- Some(revert_frame) => Some(revert_frame),
1818
- };
1819
-
1820
- Ok(revert_frame)
1821
- }
1822
-
1823
- fn solidity_0_6_3_get_frame_for_unmapped_revert_within_function(
1824
- trace: Either<&CallMessageTrace, &CreateMessageTrace>,
1825
- ) -> napi::Result<Option<UnmappedSolc063RevertErrorStackTraceEntry>> {
1826
- let (bytecode, steps) = match &trace {
1827
- Either::A(create) => (&create.bytecode, &create.steps),
1828
- Either::B(call) => (&call.bytecode, &call.steps),
1829
- };
1830
-
1831
- let bytecode = bytecode
1832
- .as_ref()
1833
- .expect("JS code only accepted variants that had bytecode defined");
1834
-
1835
- let contract = bytecode.contract.borrow();
1836
-
1837
- // If we are within a function there's a last valid location. It may
1838
- // be the entire contract.
1839
- let prev_inst = Self::get_last_instruction_with_valid_location(trace)?;
1840
- let last_step = match steps.last() {
1841
- Some(Either4::A(step)) => step,
1842
- _ => panic!("JS code asserts this is always an EvmStep"),
1843
- };
1844
- let next_inst_pc = last_step.pc + 1;
1845
- let has_next_inst = bytecode.has_instruction(next_inst_pc);
1846
-
1847
- if has_next_inst {
1848
- let next_inst = bytecode.get_instruction(next_inst_pc)?;
1849
-
1850
- let prev_loc = prev_inst.and_then(|i| i.location.as_deref());
1851
- let next_loc = next_inst.location.as_deref();
1852
-
1853
- let prev_func = prev_loc.and_then(SourceLocation::get_containing_function);
1854
- let next_func = next_loc.and_then(SourceLocation::get_containing_function);
1855
-
1856
- // This is probably a require. This means that we have the exact
1857
- // line, but the stack trace may be degraded (e.g. missing our
1858
- // synthetic call frames when failing in a modifier) so we still
1859
- // add this frame as UNMAPPED_SOLC_0_6_3_REVERT_ERROR
1860
- match (&prev_func, &next_loc, &prev_loc) {
1861
- (Some(_), Some(next_loc), Some(prev_loc)) if prev_loc == next_loc => {
1862
- return Ok(Some(Self::instruction_within_function_to_unmapped_solc_0_6_3_revert_error_stack_trace_entry(
1863
- trace,
1864
- next_inst,
1865
-
1866
- )?));
1867
- }
1868
- _ => {}
1869
- }
1870
-
1871
- let revert_frame = if prev_func.is_some() && prev_inst.is_some() {
1872
- Some(Self::instruction_within_function_to_unmapped_solc_0_6_3_revert_error_stack_trace_entry(
1873
- trace,
1874
- prev_inst.as_ref().unwrap(),
1875
-
1876
- )?)
1877
- } else if next_func.is_some() {
1878
- Some(Self::instruction_within_function_to_unmapped_solc_0_6_3_revert_error_stack_trace_entry(
1879
- trace,
1880
- next_inst,
1881
-
1882
- )?)
1883
- } else {
1884
- None
1885
- };
1886
-
1887
- return Ok(revert_frame.map(Self::solidity_0_6_3_correct_line_number));
1888
- }
1889
-
1890
- if matches!(trace, Either::B(CreateMessageTrace { .. })) && prev_inst.is_some() {
1891
- // Solidity is smart enough to stop emitting extra instructions after
1892
- // an unconditional revert happens in a constructor. If this is the case
1893
- // we just return a special error.
1894
-
1895
- let mut constructor_revert_frame = Self::instruction_within_function_to_unmapped_solc_0_6_3_revert_error_stack_trace_entry(
1896
- trace,
1897
- prev_inst.as_ref().unwrap(),
1898
-
1899
- )?;
1900
-
1901
- // When the latest instruction is not within a function we need
1902
- // some default sourceReference to show to the user
1903
- if constructor_revert_frame.source_reference.is_none() {
1904
- let location = &contract.location;
1905
- let file = location.file();
1906
- let file = file.borrow();
1907
-
1908
- let mut default_source_reference = SourceReference {
1909
- function: Some(CONSTRUCTOR_FUNCTION_NAME.to_string()),
1910
- contract: Some(contract.name.clone()),
1911
- source_name: file.source_name.clone(),
1912
- source_content: file.content.clone(),
1913
- line: location.get_starting_line_number(),
1914
- range: [location.offset, location.offset + location.length].to_vec(),
1915
- };
1916
-
1917
- if let Some(constructor) = &contract.constructor {
1918
- default_source_reference.line = constructor.location.get_starting_line_number();
1919
- }
1920
-
1921
- constructor_revert_frame.source_reference = Some(default_source_reference);
1922
- } else {
1923
- constructor_revert_frame =
1924
- Self::solidity_0_6_3_correct_line_number(constructor_revert_frame);
1925
- }
1926
-
1927
- return Ok(Some(constructor_revert_frame));
1928
- }
1929
-
1930
- if let Some(prev_inst) = prev_inst {
1931
- // We may as well just be in a function or modifier and just happen
1932
- // to be at the last instruction of the runtime bytecode.
1933
- // In this case we just return whatever the last mapped intruction
1934
- // points to.
1935
- let mut latest_instruction_revert_frame = Self::instruction_within_function_to_unmapped_solc_0_6_3_revert_error_stack_trace_entry(
1936
- trace,
1937
- prev_inst,
1938
-
1939
- )?;
1940
-
1941
- if latest_instruction_revert_frame.source_reference.is_some() {
1942
- latest_instruction_revert_frame =
1943
- Self::solidity_0_6_3_correct_line_number(latest_instruction_revert_frame);
1944
- }
1945
- return Ok(Some(latest_instruction_revert_frame));
1946
- }
1947
-
1948
- Ok(None)
1949
- }
1950
-
1951
- fn solidity_0_6_3_correct_line_number(
1952
- mut revert_frame: UnmappedSolc063RevertErrorStackTraceEntry,
1953
- ) -> UnmappedSolc063RevertErrorStackTraceEntry {
1954
- let Some(source_reference) = &mut revert_frame.source_reference else {
1955
- return revert_frame;
1956
- };
1957
-
1958
- let lines: Vec<_> = source_reference.source_content.split('\n').collect();
1959
-
1960
- let current_line = lines[source_reference.line as usize - 1];
1961
- if current_line.contains("require") || current_line.contains("revert") {
1962
- return revert_frame;
1963
- }
1964
-
1965
- let next_lines = &lines
1966
- .get(source_reference.line as usize..)
1967
- .unwrap_or_default();
1968
- let first_non_empty_line = next_lines.iter().position(|l| !l.trim().is_empty());
1969
-
1970
- let Some(first_non_empty_line) = first_non_empty_line else {
1971
- return revert_frame;
1972
- };
1973
-
1974
- let next_line = next_lines[first_non_empty_line];
1975
- if next_line.contains("require") || next_line.contains("revert") {
1976
- source_reference.line += 1 + first_non_empty_line as u32;
1977
- }
1978
-
1979
- revert_frame
1980
- }
1981
-
1982
- fn get_other_error_before_called_function_stack_trace_entry(
1983
- trace: &CallMessageTrace,
1984
- ) -> napi::Result<OtherExecutionErrorStackTraceEntry> {
1985
- let source_reference =
1986
- Self::get_contract_start_without_function_source_reference(Either::A(trace))?;
1987
-
1988
- Ok(OtherExecutionErrorStackTraceEntry {
1989
- type_: StackTraceEntryTypeConst,
1990
- source_reference: Some(source_reference),
1991
- })
1992
- }
1993
-
1994
- fn is_called_non_contract_account_error(
1995
- trace: Either<&CallMessageTrace, &CreateMessageTrace>,
1996
- ) -> napi::Result<bool> {
1997
- // We could change this to checking that the last valid location maps to a call,
1998
- // but it's way more complex as we need to get the ast node from that
1999
- // location.
2000
-
2001
- let (bytecode, steps) = match &trace {
2002
- Either::A(create) => (&create.bytecode, &create.steps),
2003
- Either::B(call) => (&call.bytecode, &call.steps),
2004
- };
2005
-
2006
- let bytecode = bytecode
2007
- .as_ref()
2008
- .expect("JS code only accepted variants that had bytecode defined");
2009
-
2010
- let last_index = Self::get_last_instruction_with_valid_location_step_index(trace)?;
2011
-
2012
- let last_index = match last_index {
2013
- None | Some(0) => return Ok(false),
2014
- Some(last_index) => last_index as usize,
2015
- };
2016
-
2017
- let last_step = match &steps[last_index] {
2018
- Either4::A(step) => step,
2019
- _ => panic!("We know this is an EVM step"),
2020
- };
2021
-
2022
- let last_inst = bytecode.get_instruction(last_step.pc)?;
2023
-
2024
- if last_inst.opcode != OpCode::ISZERO {
2025
- return Ok(false);
2026
- }
2027
-
2028
- let prev_step = match &steps[last_index - 1] {
2029
- Either4::A(step) => step,
2030
- _ => panic!("We know this is an EVM step"),
2031
- };
2032
-
2033
- let prev_inst = bytecode.get_instruction(prev_step.pc)?;
2034
-
2035
- Ok(prev_inst.opcode == OpCode::EXTCODESIZE)
2036
- }
2037
-
2038
- fn get_last_instruction_with_valid_location_step_index(
2039
- trace: Either<&CallMessageTrace, &CreateMessageTrace>,
2040
- ) -> napi::Result<Option<u32>> {
2041
- let (bytecode, steps) = match &trace {
2042
- Either::A(create) => (&create.bytecode, &create.steps),
2043
- Either::B(call) => (&call.bytecode, &call.steps),
2044
- };
2045
-
2046
- let bytecode = bytecode
2047
- .as_ref()
2048
- .expect("JS code only accepted variants that had bytecode defined");
2049
-
2050
- for (i, step) in steps.iter().enumerate().rev() {
2051
- let step = match step {
2052
- Either4::A(step) => step,
2053
- _ => return Ok(None),
2054
- };
2055
-
2056
- let inst = bytecode.get_instruction(step.pc)?;
2057
-
2058
- if inst.location.is_some() {
2059
- return Ok(Some(i as u32));
2060
- }
2061
- }
2062
-
2063
- Ok(None)
2064
- }
2065
-
2066
- fn get_last_instruction_with_valid_location<'a>(
2067
- trace: Either<&'a CallMessageTrace, &'a CreateMessageTrace>,
2068
- ) -> napi::Result<Option<&'a Instruction>> {
2069
- let last_location_index = Self::get_last_instruction_with_valid_location_step_index(trace)?;
2070
-
2071
- let Some(last_location_index) = last_location_index else {
2072
- return Ok(None);
2073
- };
2074
-
2075
- let (bytecode, steps) = match &trace {
2076
- Either::A(create) => (&create.bytecode, &create.steps),
2077
- Either::B(call) => (&call.bytecode, &call.steps),
2078
- };
2079
-
2080
- let bytecode = bytecode
2081
- .as_ref()
2082
- .expect("JS code only accepted variants that had bytecode defined");
2083
-
2084
- match &steps.get(last_location_index as usize) {
2085
- Some(Either4::A(step)) => {
2086
- let inst = bytecode.get_instruction(step.pc)?;
2087
-
2088
- Ok(Some(inst))
2089
- }
2090
- _ => Ok(None),
2091
- }
2092
- }
2093
- }
2094
-
2095
- fn source_location_to_source_reference(
2096
- bytecode: &Bytecode,
2097
- location: Option<&SourceLocation>,
2098
- ) -> napi::Result<Option<SourceReference>> {
2099
- let Some(location) = location else {
2100
- return Ok(None);
2101
- };
2102
- let Some(func) = location.get_containing_function() else {
2103
- return Ok(None);
2104
- };
2105
-
2106
- let func_name = match func.r#type {
2107
- ContractFunctionType::Constructor => CONSTRUCTOR_FUNCTION_NAME.to_string(),
2108
- ContractFunctionType::Fallback => FALLBACK_FUNCTION_NAME.to_string(),
2109
- ContractFunctionType::Receive => RECEIVE_FUNCTION_NAME.to_string(),
2110
- _ => func.name.clone(),
2111
- };
2112
-
2113
- let func_location_file = func.location.file();
2114
- let func_location_file = func_location_file.borrow();
2115
-
2116
- Ok(Some(SourceReference {
2117
- function: Some(func_name.clone()),
2118
- contract: if func.r#type == ContractFunctionType::FreeFunction {
2119
- None
2120
- } else {
2121
- Some(bytecode.contract.borrow().name.clone())
2122
- },
2123
- source_name: func_location_file.source_name.clone(),
2124
- source_content: func_location_file.content.clone(),
2125
- line: location.get_starting_line_number(),
2126
- range: [location.offset, location.offset + location.length].to_vec(),
2127
- }))
2128
- }
2129
-
2130
- pub fn instruction_to_callstack_stack_trace_entry(
2131
- bytecode: &Bytecode,
2132
- inst: &Instruction,
2133
- ) -> napi::Result<Either<CallstackEntryStackTraceEntry, InternalFunctionCallStackEntry>> {
2134
- let contract = bytecode.contract.borrow();
2135
-
2136
- // This means that a jump is made from within an internal solc function.
2137
- // These are normally made from yul code, so they don't map to any Solidity
2138
- // function
2139
- let inst_location = match &inst.location {
2140
- None => {
2141
- let location = &contract.location;
2142
- let file = location.file();
2143
- let file = file.borrow();
2144
-
2145
- return Ok(Either::B(InternalFunctionCallStackEntry {
2146
- type_: StackTraceEntryTypeConst,
2147
- pc: inst.pc,
2148
- source_reference: SourceReference {
2149
- source_name: file.source_name.clone(),
2150
- source_content: file.content.clone(),
2151
- contract: Some(contract.name.clone()),
2152
- function: None,
2153
- line: location.get_starting_line_number(),
2154
- range: [location.offset, location.offset + location.length].to_vec(),
2155
- },
2156
- }));
2157
- }
2158
- Some(inst_location) => inst_location,
2159
- };
2160
-
2161
- if let Some(func) = inst_location.get_containing_function() {
2162
- let source_reference = source_location_to_source_reference(bytecode, Some(inst_location))?
2163
- .expect("Expected source reference to be defined");
2164
-
2165
- return Ok(Either::A(CallstackEntryStackTraceEntry {
2166
- type_: StackTraceEntryTypeConst,
2167
- source_reference,
2168
- function_type: func.r#type.into(),
2169
- }));
2170
- };
2171
-
2172
- let file = inst_location.file();
2173
- let file = file.borrow();
2174
-
2175
- Ok(Either::A(CallstackEntryStackTraceEntry {
2176
- type_: StackTraceEntryTypeConst,
2177
- source_reference: SourceReference {
2178
- function: None,
2179
- contract: Some(contract.name.clone()),
2180
- source_name: file.source_name.clone(),
2181
- source_content: file.content.clone(),
2182
- line: inst_location.get_starting_line_number(),
2183
- range: [
2184
- inst_location.offset,
2185
- inst_location.offset + inst_location.length,
2186
- ]
2187
- .to_vec(),
2188
- },
2189
- function_type: ContractFunctionType::Function.into(),
2190
- }))
2191
- }
2192
-
2193
- // Rewrite of `AbiHelpers.formatValues` from Hardhat
2194
- fn format_dyn_sol_value(val: &DynSolValue) -> String {
2195
- match val {
2196
- // print nested values as [value1, value2, ...]
2197
- DynSolValue::Array(items)
2198
- | DynSolValue::Tuple(items)
2199
- | DynSolValue::FixedArray(items)
2200
- | DynSolValue::CustomStruct { tuple: items, .. } => {
2201
- let mut result = String::from("[");
2202
- for (i, val) in items.iter().enumerate() {
2203
- if i > 0 {
2204
- result.push_str(", ");
2205
- }
2206
- result.push_str(&format_dyn_sol_value(val));
2207
- }
2208
-
2209
- result.push(']');
2210
- result
2211
- }
2212
- // surround string values with quotes
2213
- DynSolValue::String(s) => format!("\"{s}\""),
2214
-
2215
- DynSolValue::Address(address) => format!("\"{address}\""),
2216
- DynSolValue::Bytes(bytes) => format!("\"{}\"", hex::encode_prefixed(bytes)),
2217
- DynSolValue::FixedBytes(word, size) => {
2218
- format!("\"{}\"", hex::encode_prefixed(&word.0.as_slice()[..*size]))
2219
- }
2220
- DynSolValue::Bool(b) => b.to_string(),
2221
- DynSolValue::Function(_) => "<function>".to_string(),
2222
- DynSolValue::Int(int, _bits) => int.to_string(),
2223
- DynSolValue::Uint(uint, _bits) => uint.to_string(),
2224
- }
2225
- }
2226
-
2227
- #[cfg(test)]
2228
- mod tests {
2229
- use super::*;
2230
-
2231
- #[test]
2232
- fn test_sol_value_to_string() {
2233
- assert_eq!(
2234
- format_dyn_sol_value(&DynSolValue::String("hello".to_string())),
2235
- "\"hello\""
2236
- );
2237
- // Uniform, 0-prefixed hex strings
2238
- assert_eq!(
2239
- format_dyn_sol_value(&DynSolValue::Address([0u8; 20].into())),
2240
- format!(r#""0x{}""#, "0".repeat(2 * 20))
2241
- );
2242
- assert_eq!(
2243
- format_dyn_sol_value(&DynSolValue::Bytes(vec![0u8; 32])),
2244
- format!(r#""0x{}""#, "0".repeat(2 * 32))
2245
- );
2246
- assert_eq!(
2247
- format_dyn_sol_value(&DynSolValue::FixedBytes([0u8; 32].into(), 10)),
2248
- format!(r#""0x{}""#, "0".repeat(2 * 10))
2249
- );
2250
- assert_eq!(
2251
- format_dyn_sol_value(&DynSolValue::FixedBytes([0u8; 32].into(), 32)),
2252
- format!(r#""0x{}""#, "0".repeat(2 * 32))
2253
- );
2254
- }
2255
- }