@nomicfoundation/edr 0.6.5 → 0.8.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/index.d.ts +42 -90
- package/index.js +4 -9
- package/package.json +8 -8
- package/src/logger.rs +34 -67
- package/src/provider.rs +132 -16
- package/src/trace/debug.rs +2 -262
- package/src/trace/exit.rs +3 -3
- package/src/trace/model.rs +5 -5
- package/src/trace/solidity_stack_trace.rs +220 -76
- package/src/trace.rs +11 -11
- package/src/trace/compiler.rs +0 -27
- package/src/trace/error_inferrer.rs +0 -2255
- package/src/trace/mapped_inlined_internal_functions_heuristics.rs +0 -180
- package/src/trace/message_trace.rs +0 -179
- package/src/trace/solidity_tracer.rs +0 -315
- package/src/trace/vm_trace_decoder.rs +0 -234
- package/src/trace/vm_tracer.rs +0 -71
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
//! This file includes Solidity tracing heuristics for solc starting with
|
|
2
|
-
//! version 0.6.9.
|
|
3
|
-
//!
|
|
4
|
-
//! This solc version introduced a significant change to how sourcemaps are
|
|
5
|
-
//! handled for inline yul/internal functions. These were mapped to the
|
|
6
|
-
//! unmapped/-1 file before, which lead to many unmapped reverts. Now, they are
|
|
7
|
-
//! mapped to the part of the Solidity source that lead to their inlining.
|
|
8
|
-
//!
|
|
9
|
-
//! This change is a very positive change, as errors would point to the correct
|
|
10
|
-
//! line by default. The only problem is that we used to rely very heavily on
|
|
11
|
-
//! unmapped reverts to decide when our error detection heuristics were to be
|
|
12
|
-
//! run. In fact, these heuristics were first introduced because of unmapped
|
|
13
|
-
//! reverts.
|
|
14
|
-
//!
|
|
15
|
-
//! Instead of synthetically completing stack traces when unmapped reverts
|
|
16
|
-
//! occur, we now start from complete stack traces and adjust them if we can
|
|
17
|
-
//! provide more meaningful errors.
|
|
18
|
-
|
|
19
|
-
use edr_evm::interpreter::OpCode;
|
|
20
|
-
use napi::{
|
|
21
|
-
bindgen_prelude::{Either24, Either4},
|
|
22
|
-
Either,
|
|
23
|
-
};
|
|
24
|
-
use semver::Version;
|
|
25
|
-
|
|
26
|
-
use super::{
|
|
27
|
-
message_trace::{CallMessageTrace, CreateMessageTrace, EvmStep},
|
|
28
|
-
solidity_stack_trace::{
|
|
29
|
-
InvalidParamsErrorStackTraceEntry, NonContractAccountCalledErrorStackTraceEntry,
|
|
30
|
-
RevertErrorStackTraceEntry, SolidityStackTrace, StackTraceEntryTypeConst,
|
|
31
|
-
},
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
const FIRST_SOLC_VERSION_WITH_MAPPED_SMALL_INTERNAL_FUNCTIONS: Version = Version::new(0, 6, 9);
|
|
35
|
-
|
|
36
|
-
pub fn stack_trace_may_require_adjustments(
|
|
37
|
-
stacktrace: &SolidityStackTrace,
|
|
38
|
-
decoded_trace: Either<&CallMessageTrace, &CreateMessageTrace>,
|
|
39
|
-
) -> bool {
|
|
40
|
-
let bytecode = match &decoded_trace {
|
|
41
|
-
Either::A(create) => &create.bytecode,
|
|
42
|
-
Either::B(call) => &call.bytecode,
|
|
43
|
-
};
|
|
44
|
-
let bytecode = bytecode.as_ref().expect("JS code asserts");
|
|
45
|
-
|
|
46
|
-
let Some(last_frame) = stacktrace.last() else {
|
|
47
|
-
return false;
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
if let Either24::E(last_frame @ RevertErrorStackTraceEntry { .. }) = last_frame {
|
|
51
|
-
return !last_frame.is_invalid_opcode_error
|
|
52
|
-
&& last_frame.return_data.is_empty()
|
|
53
|
-
&& Version::parse(&bytecode.compiler_version)
|
|
54
|
-
.map(|version| version >= FIRST_SOLC_VERSION_WITH_MAPPED_SMALL_INTERNAL_FUNCTIONS)
|
|
55
|
-
.unwrap_or(false);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
false
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
pub fn adjust_stack_trace(
|
|
62
|
-
mut stacktrace: SolidityStackTrace,
|
|
63
|
-
decoded_trace: Either<&CallMessageTrace, &CreateMessageTrace>,
|
|
64
|
-
) -> napi::Result<SolidityStackTrace> {
|
|
65
|
-
let Some(Either24::E(revert @ RevertErrorStackTraceEntry { .. })) = stacktrace.last() else {
|
|
66
|
-
unreachable!("JS code asserts that; it's only used immediately after we check with `stack_trace_may_require_adjustments` that the last frame is a revert frame");
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
// Replace the last revert frame with an adjusted frame if needed
|
|
70
|
-
if is_non_contract_account_called_error(decoded_trace)? {
|
|
71
|
-
let last_revert_frame_source_reference = revert.source_reference.clone();
|
|
72
|
-
stacktrace.pop();
|
|
73
|
-
stacktrace.push(
|
|
74
|
-
NonContractAccountCalledErrorStackTraceEntry {
|
|
75
|
-
type_: StackTraceEntryTypeConst,
|
|
76
|
-
source_reference: last_revert_frame_source_reference,
|
|
77
|
-
}
|
|
78
|
-
.into(),
|
|
79
|
-
);
|
|
80
|
-
return Ok(stacktrace);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if is_constructor_invalid_params_error(decoded_trace)? {
|
|
84
|
-
let last_revert_frame_source_reference = revert.source_reference.clone();
|
|
85
|
-
stacktrace.pop();
|
|
86
|
-
stacktrace.push(
|
|
87
|
-
InvalidParamsErrorStackTraceEntry {
|
|
88
|
-
type_: StackTraceEntryTypeConst,
|
|
89
|
-
source_reference: last_revert_frame_source_reference,
|
|
90
|
-
}
|
|
91
|
-
.into(),
|
|
92
|
-
);
|
|
93
|
-
return Ok(stacktrace);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if is_call_invalid_params_error(decoded_trace)? {
|
|
97
|
-
let last_revert_frame_source_reference = revert.source_reference.clone();
|
|
98
|
-
stacktrace.pop();
|
|
99
|
-
stacktrace.push(
|
|
100
|
-
InvalidParamsErrorStackTraceEntry {
|
|
101
|
-
type_: StackTraceEntryTypeConst,
|
|
102
|
-
source_reference: last_revert_frame_source_reference,
|
|
103
|
-
}
|
|
104
|
-
.into(),
|
|
105
|
-
);
|
|
106
|
-
|
|
107
|
-
return Ok(stacktrace);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
Ok(stacktrace)
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
fn is_non_contract_account_called_error(
|
|
114
|
-
decoded_trace: Either<&CallMessageTrace, &CreateMessageTrace>,
|
|
115
|
-
) -> napi::Result<bool> {
|
|
116
|
-
match_opcodes(
|
|
117
|
-
decoded_trace,
|
|
118
|
-
-9,
|
|
119
|
-
&[
|
|
120
|
-
OpCode::EXTCODESIZE,
|
|
121
|
-
OpCode::ISZERO,
|
|
122
|
-
OpCode::DUP1,
|
|
123
|
-
OpCode::ISZERO,
|
|
124
|
-
],
|
|
125
|
-
)
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
fn is_constructor_invalid_params_error(
|
|
129
|
-
decoded_trace: Either<&CallMessageTrace, &CreateMessageTrace>,
|
|
130
|
-
) -> napi::Result<bool> {
|
|
131
|
-
Ok(match_opcodes(decoded_trace, -20, &[OpCode::CODESIZE])?
|
|
132
|
-
&& match_opcodes(decoded_trace, -15, &[OpCode::CODECOPY])?
|
|
133
|
-
&& match_opcodes(decoded_trace, -7, &[OpCode::LT, OpCode::ISZERO])?)
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
fn is_call_invalid_params_error(
|
|
137
|
-
decoded_trace: Either<&CallMessageTrace, &CreateMessageTrace>,
|
|
138
|
-
) -> napi::Result<bool> {
|
|
139
|
-
Ok(match_opcodes(decoded_trace, -11, &[OpCode::CALLDATASIZE])?
|
|
140
|
-
&& match_opcodes(decoded_trace, -7, &[OpCode::LT, OpCode::ISZERO])?)
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
fn match_opcodes(
|
|
144
|
-
decoded_trace: Either<&CallMessageTrace, &CreateMessageTrace>,
|
|
145
|
-
first_step_index: i32,
|
|
146
|
-
opcodes: &[OpCode],
|
|
147
|
-
) -> napi::Result<bool> {
|
|
148
|
-
let (bytecode, steps) = match &decoded_trace {
|
|
149
|
-
Either::A(call) => (&call.bytecode, &call.steps),
|
|
150
|
-
Either::B(create) => (&create.bytecode, &create.steps),
|
|
151
|
-
};
|
|
152
|
-
let bytecode = bytecode.as_ref().expect("JS code asserts");
|
|
153
|
-
|
|
154
|
-
// If the index is negative, we start from the end of the trace,
|
|
155
|
-
// just like in the original JS code
|
|
156
|
-
let mut index = match first_step_index {
|
|
157
|
-
0.. => first_step_index as usize,
|
|
158
|
-
..=-1 if first_step_index.abs() < steps.len() as i32 => {
|
|
159
|
-
(steps.len() as i32 + first_step_index) as usize
|
|
160
|
-
}
|
|
161
|
-
// Out of bounds
|
|
162
|
-
_ => return Ok(false),
|
|
163
|
-
};
|
|
164
|
-
|
|
165
|
-
for opcode in opcodes {
|
|
166
|
-
let Some(Either4::A(EvmStep { pc })) = steps.get(index) else {
|
|
167
|
-
return Ok(false);
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
let instruction = bytecode.get_instruction(*pc)?;
|
|
171
|
-
|
|
172
|
-
if instruction.opcode != *opcode {
|
|
173
|
-
return Ok(false);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
index += 1;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
Ok(true)
|
|
180
|
-
}
|
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
//! Bridging type for the existing `MessageTrace` interface in Hardhat.
|
|
2
|
-
|
|
3
|
-
use napi::{
|
|
4
|
-
bindgen_prelude::{BigInt, ClassInstance, Either3, Either4, Uint8Array, Undefined},
|
|
5
|
-
Either, Env,
|
|
6
|
-
};
|
|
7
|
-
use napi_derive::napi;
|
|
8
|
-
|
|
9
|
-
use super::{exit::Exit, model::BytecodeWrapper};
|
|
10
|
-
|
|
11
|
-
#[napi(object)]
|
|
12
|
-
pub struct EvmStep {
|
|
13
|
-
pub pc: u32,
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
#[napi(object)]
|
|
17
|
-
pub struct PrecompileMessageTrace {
|
|
18
|
-
// `BaseMessageTrace`
|
|
19
|
-
pub value: BigInt,
|
|
20
|
-
pub return_data: Uint8Array,
|
|
21
|
-
pub exit: ClassInstance<Exit>,
|
|
22
|
-
pub gas_used: BigInt,
|
|
23
|
-
pub depth: u32,
|
|
24
|
-
// `PrecompileMessageTrace`
|
|
25
|
-
pub precompile: u32,
|
|
26
|
-
pub calldata: Uint8Array,
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// NOTE: Because of the hack below for `deployed_contract`, now the
|
|
30
|
-
// `CallMessageTrace` is a strict superset of `CreateMessageTrace`, so we need
|
|
31
|
-
// to take care to keep the order consistent from most-specific to
|
|
32
|
-
// least-specific in the `Either{3,4}` type when converting to or from N-API.
|
|
33
|
-
#[napi(object)]
|
|
34
|
-
pub struct CreateMessageTrace {
|
|
35
|
-
// `BaseMessageTrace`
|
|
36
|
-
pub value: BigInt,
|
|
37
|
-
pub return_data: Uint8Array,
|
|
38
|
-
pub exit: ClassInstance<Exit>,
|
|
39
|
-
pub gas_used: BigInt,
|
|
40
|
-
pub depth: u32,
|
|
41
|
-
// `BaseEvmMessageTrace`
|
|
42
|
-
pub code: Uint8Array,
|
|
43
|
-
pub steps: Vec<Either4<EvmStep, PrecompileMessageTrace, CallMessageTrace, CreateMessageTrace>>,
|
|
44
|
-
/// Reference to the resolved `Bytecode` EDR data.
|
|
45
|
-
/// Only used on the JS side by the `VmTraceDecoder` class.
|
|
46
|
-
pub bytecode: Option<ClassInstance<BytecodeWrapper>>,
|
|
47
|
-
pub number_of_subtraces: u32,
|
|
48
|
-
// `CreateMessageTrace`
|
|
49
|
-
// HACK: It seems that `Either<Uint8Array, Undefined>` means exactly what we
|
|
50
|
-
// want (a required property but can be explicitly `undefined`) but internally
|
|
51
|
-
// the napi-rs treats an encountered `Undefined` like a missing property
|
|
52
|
-
// and it throws a validation error. While not 100% backwards compatible, we
|
|
53
|
-
// work around using an optional type.
|
|
54
|
-
// See https://github.com/napi-rs/napi-rs/issues/1986 for context on the PR
|
|
55
|
-
// that introduced this behavior.
|
|
56
|
-
pub deployed_contract: Option<Either<Uint8Array, Undefined>>,
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
#[napi(object)]
|
|
60
|
-
pub struct CallMessageTrace {
|
|
61
|
-
// `BaseMessageTrace`
|
|
62
|
-
pub value: BigInt,
|
|
63
|
-
pub return_data: Uint8Array,
|
|
64
|
-
pub exit: ClassInstance<Exit>,
|
|
65
|
-
pub gas_used: BigInt,
|
|
66
|
-
pub depth: u32,
|
|
67
|
-
// `BaseEvmMessageTrace`
|
|
68
|
-
pub code: Uint8Array,
|
|
69
|
-
pub steps: Vec<Either4<EvmStep, PrecompileMessageTrace, CallMessageTrace, CreateMessageTrace>>,
|
|
70
|
-
/// Reference to the resolved `Bytecode` EDR data.
|
|
71
|
-
/// Only used on the JS side by the `VmTraceDecoder` class.
|
|
72
|
-
pub bytecode: Option<ClassInstance<BytecodeWrapper>>,
|
|
73
|
-
pub number_of_subtraces: u32,
|
|
74
|
-
// `CallMessageTrace`
|
|
75
|
-
pub calldata: Uint8Array,
|
|
76
|
-
pub address: Uint8Array,
|
|
77
|
-
pub code_address: Uint8Array,
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/// Converts [`edr_solidity::message_trace::MessageTraceStep`] to the N-API
|
|
81
|
-
/// representation.
|
|
82
|
-
///
|
|
83
|
-
/// # Panics
|
|
84
|
-
/// This function will panic if the value is mutably borrowed.
|
|
85
|
-
pub fn message_trace_step_to_napi(
|
|
86
|
-
value: edr_solidity::message_trace::MessageTraceStep,
|
|
87
|
-
env: Env,
|
|
88
|
-
) -> napi::Result<Either4<EvmStep, PrecompileMessageTrace, CallMessageTrace, CreateMessageTrace>> {
|
|
89
|
-
Ok(match value {
|
|
90
|
-
edr_solidity::message_trace::MessageTraceStep::Evm(step) => {
|
|
91
|
-
Either4::A(EvmStep { pc: step.pc as u32 })
|
|
92
|
-
}
|
|
93
|
-
edr_solidity::message_trace::MessageTraceStep::Message(msg) => {
|
|
94
|
-
// Immediately drop the borrow lock to err on the safe side as we
|
|
95
|
-
// may be recursing.
|
|
96
|
-
let owned = msg.borrow().clone();
|
|
97
|
-
match message_trace_to_napi(owned, env)? {
|
|
98
|
-
Either3::A(precompile) => Either4::B(precompile),
|
|
99
|
-
Either3::B(call) => Either4::C(call),
|
|
100
|
-
Either3::C(create) => Either4::D(create),
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
})
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/// Converts the Rust representation of a `MessageTrace` to the N-API
|
|
107
|
-
/// representation.
|
|
108
|
-
pub fn message_trace_to_napi(
|
|
109
|
-
value: edr_solidity::message_trace::MessageTrace,
|
|
110
|
-
env: Env,
|
|
111
|
-
) -> napi::Result<Either3<PrecompileMessageTrace, CallMessageTrace, CreateMessageTrace>> {
|
|
112
|
-
Ok(match value {
|
|
113
|
-
edr_solidity::message_trace::MessageTrace::Precompile(precompile) => {
|
|
114
|
-
Either3::A(PrecompileMessageTrace {
|
|
115
|
-
value: BigInt {
|
|
116
|
-
sign_bit: false,
|
|
117
|
-
words: precompile.base.value.as_limbs().to_vec(),
|
|
118
|
-
},
|
|
119
|
-
return_data: Uint8Array::from(precompile.base.return_data.as_ref()),
|
|
120
|
-
exit: Exit(precompile.base.exit.into()).into_instance(env)?,
|
|
121
|
-
gas_used: BigInt::from(precompile.base.gas_used),
|
|
122
|
-
depth: precompile.base.depth as u32,
|
|
123
|
-
|
|
124
|
-
precompile: precompile.precompile,
|
|
125
|
-
calldata: Uint8Array::from(precompile.calldata.as_ref()),
|
|
126
|
-
})
|
|
127
|
-
}
|
|
128
|
-
edr_solidity::message_trace::MessageTrace::Call(call) => Either3::B(CallMessageTrace {
|
|
129
|
-
value: BigInt {
|
|
130
|
-
sign_bit: false,
|
|
131
|
-
words: call.base.base.value.as_limbs().to_vec(),
|
|
132
|
-
},
|
|
133
|
-
return_data: Uint8Array::from(call.base.base.return_data.as_ref()),
|
|
134
|
-
exit: Exit(call.base.base.exit.into()).into_instance(env)?,
|
|
135
|
-
gas_used: BigInt::from(call.base.base.gas_used),
|
|
136
|
-
depth: call.base.base.depth as u32,
|
|
137
|
-
code: Uint8Array::from(call.base.code.as_ref()),
|
|
138
|
-
steps: call
|
|
139
|
-
.base
|
|
140
|
-
.steps
|
|
141
|
-
.into_iter()
|
|
142
|
-
.map(|step| message_trace_step_to_napi(step, env))
|
|
143
|
-
.collect::<napi::Result<Vec<_>>>()?,
|
|
144
|
-
// NOTE: We specifically use None as that will be later filled on the JS side
|
|
145
|
-
bytecode: None,
|
|
146
|
-
number_of_subtraces: call.base.number_of_subtraces,
|
|
147
|
-
|
|
148
|
-
address: Uint8Array::from(call.address.as_slice()),
|
|
149
|
-
calldata: Uint8Array::from(call.calldata.as_ref()),
|
|
150
|
-
code_address: Uint8Array::from(call.code_address.as_slice()),
|
|
151
|
-
}),
|
|
152
|
-
edr_solidity::message_trace::MessageTrace::Create(create) => {
|
|
153
|
-
Either3::C(CreateMessageTrace {
|
|
154
|
-
value: BigInt {
|
|
155
|
-
sign_bit: false,
|
|
156
|
-
words: create.base.base.value.as_limbs().to_vec(),
|
|
157
|
-
},
|
|
158
|
-
return_data: Uint8Array::from(create.base.base.return_data.as_ref()),
|
|
159
|
-
exit: Exit(create.base.base.exit.into()).into_instance(env)?,
|
|
160
|
-
gas_used: BigInt::from(create.base.base.gas_used),
|
|
161
|
-
depth: create.base.base.depth as u32,
|
|
162
|
-
code: Uint8Array::from(create.base.code.as_ref()),
|
|
163
|
-
steps: create
|
|
164
|
-
.base
|
|
165
|
-
.steps
|
|
166
|
-
.into_iter()
|
|
167
|
-
.map(|step| message_trace_step_to_napi(step, env))
|
|
168
|
-
.collect::<napi::Result<Vec<_>>>()?,
|
|
169
|
-
// NOTE: We specifically use None as that will be later filled on the JS side
|
|
170
|
-
bytecode: None,
|
|
171
|
-
|
|
172
|
-
number_of_subtraces: create.base.number_of_subtraces,
|
|
173
|
-
deployed_contract: create
|
|
174
|
-
.deployed_contract
|
|
175
|
-
.map(|contract| Either::A(Uint8Array::from(contract.as_ref()))),
|
|
176
|
-
})
|
|
177
|
-
}
|
|
178
|
-
})
|
|
179
|
-
}
|
|
@@ -1,315 +0,0 @@
|
|
|
1
|
-
use edr_evm::interpreter::OpCode;
|
|
2
|
-
use edr_solidity::build_model::{Instruction, JumpType};
|
|
3
|
-
use napi::{
|
|
4
|
-
bindgen_prelude::{Either3, Either4},
|
|
5
|
-
Either,
|
|
6
|
-
};
|
|
7
|
-
use napi_derive::napi;
|
|
8
|
-
|
|
9
|
-
use super::{
|
|
10
|
-
error_inferrer::{
|
|
11
|
-
instruction_to_callstack_stack_trace_entry, ErrorInferrer, SubmessageDataRef,
|
|
12
|
-
},
|
|
13
|
-
mapped_inlined_internal_functions_heuristics::{
|
|
14
|
-
adjust_stack_trace, stack_trace_may_require_adjustments,
|
|
15
|
-
},
|
|
16
|
-
message_trace::{CallMessageTrace, CreateMessageTrace, EvmStep, PrecompileMessageTrace},
|
|
17
|
-
solidity_stack_trace::{PrecompileErrorStackTraceEntry, SolidityStackTrace},
|
|
18
|
-
};
|
|
19
|
-
use crate::trace::{
|
|
20
|
-
exit::ExitCode,
|
|
21
|
-
solidity_stack_trace::{
|
|
22
|
-
ContractTooLargeErrorStackTraceEntry, SolidityStackTraceEntry, StackTraceEntryTypeConst,
|
|
23
|
-
UnrecognizedContractCallstackEntryStackTraceEntry,
|
|
24
|
-
UnrecognizedContractErrorStackTraceEntry, UnrecognizedCreateCallstackEntryStackTraceEntry,
|
|
25
|
-
UnrecognizedCreateErrorStackTraceEntry,
|
|
26
|
-
},
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
#[napi(constructor)]
|
|
30
|
-
pub struct SolidityTracer;
|
|
31
|
-
|
|
32
|
-
#[allow(clippy::unused_self)] // we allow this for convenience for now
|
|
33
|
-
#[napi]
|
|
34
|
-
impl SolidityTracer {
|
|
35
|
-
#[napi(catch_unwind)]
|
|
36
|
-
pub fn get_stack_trace(
|
|
37
|
-
&self,
|
|
38
|
-
trace: Either3<PrecompileMessageTrace, CallMessageTrace, CreateMessageTrace>,
|
|
39
|
-
) -> napi::Result<SolidityStackTrace> {
|
|
40
|
-
let trace = match &trace {
|
|
41
|
-
Either3::A(precompile) => Either3::A(precompile),
|
|
42
|
-
Either3::B(call) => Either3::B(call),
|
|
43
|
-
Either3::C(create) => Either3::C(create),
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
self.get_stack_trace_inner(trace)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
pub fn get_stack_trace_inner(
|
|
50
|
-
&self,
|
|
51
|
-
trace: Either3<&PrecompileMessageTrace, &CallMessageTrace, &CreateMessageTrace>,
|
|
52
|
-
) -> napi::Result<SolidityStackTrace> {
|
|
53
|
-
let exit = match &trace {
|
|
54
|
-
Either3::A(precompile) => &precompile.exit,
|
|
55
|
-
Either3::B(call) => &call.exit,
|
|
56
|
-
Either3::C(create) => &create.exit,
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
if !exit.is_error() {
|
|
60
|
-
return Ok(vec![]);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
match trace {
|
|
64
|
-
Either3::A(precompile) => Ok(self.get_precompile_message_stack_trace(precompile)?),
|
|
65
|
-
Either3::B(call) if call.bytecode.is_some() => {
|
|
66
|
-
Ok(self.get_call_message_stack_trace(call)?)
|
|
67
|
-
}
|
|
68
|
-
Either3::C(create) if create.bytecode.is_some() => {
|
|
69
|
-
Ok(self.get_create_message_stack_trace(create)?)
|
|
70
|
-
}
|
|
71
|
-
// No bytecode is present
|
|
72
|
-
Either3::B(call) => Ok(self.get_unrecognized_message_stack_trace(Either::A(call))?),
|
|
73
|
-
Either3::C(create) => Ok(self.get_unrecognized_message_stack_trace(Either::B(create))?),
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
fn get_last_subtrace<'a>(
|
|
78
|
-
&self,
|
|
79
|
-
trace: Either<&'a CallMessageTrace, &'a CreateMessageTrace>,
|
|
80
|
-
) -> Option<Either3<&'a PrecompileMessageTrace, &'a CallMessageTrace, &'a CreateMessageTrace>>
|
|
81
|
-
{
|
|
82
|
-
let (number_of_subtraces, steps) = match trace {
|
|
83
|
-
Either::A(create) => (create.number_of_subtraces, &create.steps),
|
|
84
|
-
Either::B(call) => (call.number_of_subtraces, &call.steps),
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
if number_of_subtraces == 0 {
|
|
88
|
-
return None;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
steps.iter().rev().find_map(|step| match step {
|
|
92
|
-
Either4::A(EvmStep { .. }) => None,
|
|
93
|
-
Either4::B(precompile) => Some(Either3::A(precompile)),
|
|
94
|
-
Either4::C(call) => Some(Either3::B(call)),
|
|
95
|
-
Either4::D(create) => Some(Either3::C(create)),
|
|
96
|
-
})
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
fn get_precompile_message_stack_trace(
|
|
100
|
-
&self,
|
|
101
|
-
trace: &PrecompileMessageTrace,
|
|
102
|
-
) -> napi::Result<SolidityStackTrace> {
|
|
103
|
-
Ok(vec![PrecompileErrorStackTraceEntry {
|
|
104
|
-
type_: StackTraceEntryTypeConst,
|
|
105
|
-
precompile: trace.precompile,
|
|
106
|
-
source_reference: None,
|
|
107
|
-
}
|
|
108
|
-
.into()])
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
fn get_create_message_stack_trace(
|
|
112
|
-
&self,
|
|
113
|
-
trace: &CreateMessageTrace,
|
|
114
|
-
) -> napi::Result<SolidityStackTrace> {
|
|
115
|
-
let inferred_error = ErrorInferrer::infer_before_tracing_create_message(trace)?;
|
|
116
|
-
|
|
117
|
-
if let Some(inferred_error) = inferred_error {
|
|
118
|
-
return Ok(inferred_error);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
self.trace_evm_execution(Either::B(trace))
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
fn get_call_message_stack_trace(
|
|
125
|
-
&self,
|
|
126
|
-
trace: &CallMessageTrace,
|
|
127
|
-
) -> napi::Result<SolidityStackTrace> {
|
|
128
|
-
let inferred_error = ErrorInferrer::infer_before_tracing_call_message(trace)?;
|
|
129
|
-
|
|
130
|
-
if let Some(inferred_error) = inferred_error {
|
|
131
|
-
return Ok(inferred_error);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
self.trace_evm_execution(Either::A(trace))
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
fn get_unrecognized_message_stack_trace(
|
|
138
|
-
&self,
|
|
139
|
-
trace: Either<&CallMessageTrace, &CreateMessageTrace>,
|
|
140
|
-
) -> napi::Result<SolidityStackTrace> {
|
|
141
|
-
let (trace_exit_kind, trace_return_data) = match &trace {
|
|
142
|
-
Either::A(call) => (call.exit.kind(), &call.return_data),
|
|
143
|
-
Either::B(create) => (create.exit.kind(), &create.return_data),
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
let subtrace = self.get_last_subtrace(trace);
|
|
147
|
-
|
|
148
|
-
if let Some(subtrace) = subtrace {
|
|
149
|
-
let (is_error, return_data) = match subtrace {
|
|
150
|
-
Either3::A(precompile) => {
|
|
151
|
-
(precompile.exit.is_error(), precompile.return_data.clone())
|
|
152
|
-
}
|
|
153
|
-
Either3::B(call) => (call.exit.is_error(), call.return_data.clone()),
|
|
154
|
-
Either3::C(create) => (create.exit.is_error(), create.return_data.clone()),
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
// This is not a very exact heuristic, but most of the time it will be right, as
|
|
158
|
-
// solidity reverts if a call fails, and most contracts are in
|
|
159
|
-
// solidity
|
|
160
|
-
if is_error && trace_return_data.as_ref() == return_data.as_ref() {
|
|
161
|
-
let unrecognized_entry: SolidityStackTraceEntry = match trace {
|
|
162
|
-
Either::A(CallMessageTrace { address, .. }) => {
|
|
163
|
-
UnrecognizedContractCallstackEntryStackTraceEntry {
|
|
164
|
-
type_: StackTraceEntryTypeConst,
|
|
165
|
-
address: address.clone(),
|
|
166
|
-
source_reference: None,
|
|
167
|
-
}
|
|
168
|
-
.into()
|
|
169
|
-
}
|
|
170
|
-
Either::B(CreateMessageTrace { .. }) => {
|
|
171
|
-
UnrecognizedCreateCallstackEntryStackTraceEntry {
|
|
172
|
-
type_: StackTraceEntryTypeConst,
|
|
173
|
-
source_reference: None,
|
|
174
|
-
}
|
|
175
|
-
.into()
|
|
176
|
-
}
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
let mut stacktrace = vec![unrecognized_entry];
|
|
180
|
-
stacktrace.extend(self.get_stack_trace_inner(subtrace)?);
|
|
181
|
-
|
|
182
|
-
return Ok(stacktrace);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
if trace_exit_kind == ExitCode::CODESIZE_EXCEEDS_MAXIMUM {
|
|
187
|
-
return Ok(vec![ContractTooLargeErrorStackTraceEntry {
|
|
188
|
-
type_: StackTraceEntryTypeConst,
|
|
189
|
-
source_reference: None,
|
|
190
|
-
}
|
|
191
|
-
.into()]);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
let is_invalid_opcode_error = trace_exit_kind == ExitCode::INVALID_OPCODE;
|
|
195
|
-
|
|
196
|
-
match trace {
|
|
197
|
-
Either::A(trace @ CallMessageTrace { .. }) => {
|
|
198
|
-
Ok(vec![UnrecognizedContractErrorStackTraceEntry {
|
|
199
|
-
type_: StackTraceEntryTypeConst,
|
|
200
|
-
address: trace.address.clone(),
|
|
201
|
-
return_data: trace.return_data.clone(),
|
|
202
|
-
is_invalid_opcode_error,
|
|
203
|
-
source_reference: None,
|
|
204
|
-
}
|
|
205
|
-
.into()])
|
|
206
|
-
}
|
|
207
|
-
Either::B(trace @ CreateMessageTrace { .. }) => {
|
|
208
|
-
Ok(vec![UnrecognizedCreateErrorStackTraceEntry {
|
|
209
|
-
type_: StackTraceEntryTypeConst,
|
|
210
|
-
return_data: trace.return_data.clone(),
|
|
211
|
-
is_invalid_opcode_error,
|
|
212
|
-
source_reference: None,
|
|
213
|
-
}
|
|
214
|
-
.into()])
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
fn trace_evm_execution(
|
|
220
|
-
&self,
|
|
221
|
-
trace: Either<&CallMessageTrace, &CreateMessageTrace>,
|
|
222
|
-
) -> napi::Result<SolidityStackTrace> {
|
|
223
|
-
let stack_trace = self.raw_trace_evm_execution(trace)?;
|
|
224
|
-
|
|
225
|
-
if stack_trace_may_require_adjustments(&stack_trace, trace) {
|
|
226
|
-
return adjust_stack_trace(stack_trace, trace);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
Ok(stack_trace)
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
fn raw_trace_evm_execution(
|
|
233
|
-
&self,
|
|
234
|
-
trace: Either<&CallMessageTrace, &CreateMessageTrace>,
|
|
235
|
-
) -> napi::Result<SolidityStackTrace> {
|
|
236
|
-
let (bytecode, steps, number_of_subtraces) = match &trace {
|
|
237
|
-
Either::A(call) => (&call.bytecode, &call.steps, call.number_of_subtraces),
|
|
238
|
-
Either::B(create) => (&create.bytecode, &create.steps, create.number_of_subtraces),
|
|
239
|
-
};
|
|
240
|
-
let bytecode = bytecode.as_ref().expect("JS code asserts");
|
|
241
|
-
|
|
242
|
-
let mut stacktrace: SolidityStackTrace = vec![];
|
|
243
|
-
|
|
244
|
-
let mut subtraces_seen = 0;
|
|
245
|
-
|
|
246
|
-
// There was a jump into a function according to the sourcemaps
|
|
247
|
-
let mut jumped_into_function = false;
|
|
248
|
-
|
|
249
|
-
let mut function_jumpdests: Vec<&Instruction> = vec![];
|
|
250
|
-
|
|
251
|
-
let mut last_submessage_data: Option<SubmessageDataRef<'_>> = None;
|
|
252
|
-
|
|
253
|
-
let mut iter = steps.iter().enumerate().peekable();
|
|
254
|
-
while let Some((step_index, step)) = iter.next() {
|
|
255
|
-
if let Either4::A(EvmStep { pc }) = step {
|
|
256
|
-
let inst = bytecode.get_instruction(*pc)?;
|
|
257
|
-
|
|
258
|
-
if inst.jump_type == JumpType::IntoFunction && iter.peek().is_some() {
|
|
259
|
-
let (_, next_step) = iter.peek().unwrap();
|
|
260
|
-
let Either4::A(next_evm_step) = next_step else {
|
|
261
|
-
unreachable!("JS code asserted that");
|
|
262
|
-
};
|
|
263
|
-
let next_inst = bytecode.get_instruction(next_evm_step.pc)?;
|
|
264
|
-
|
|
265
|
-
if next_inst.opcode == OpCode::JUMPDEST {
|
|
266
|
-
let frame = instruction_to_callstack_stack_trace_entry(bytecode, inst)?;
|
|
267
|
-
stacktrace.push(match frame {
|
|
268
|
-
Either::A(frame) => frame.into(),
|
|
269
|
-
Either::B(frame) => frame.into(),
|
|
270
|
-
});
|
|
271
|
-
if next_inst.location.is_some() {
|
|
272
|
-
jumped_into_function = true;
|
|
273
|
-
}
|
|
274
|
-
function_jumpdests.push(next_inst);
|
|
275
|
-
}
|
|
276
|
-
} else if inst.jump_type == JumpType::OutofFunction {
|
|
277
|
-
stacktrace.pop();
|
|
278
|
-
function_jumpdests.pop();
|
|
279
|
-
}
|
|
280
|
-
} else {
|
|
281
|
-
let message_trace = match step {
|
|
282
|
-
Either4::A(_) => unreachable!("branch is taken above"),
|
|
283
|
-
Either4::B(precompile) => Either3::A(precompile),
|
|
284
|
-
Either4::C(call) => Either3::B(call),
|
|
285
|
-
Either4::D(create) => Either3::C(create),
|
|
286
|
-
};
|
|
287
|
-
|
|
288
|
-
subtraces_seen += 1;
|
|
289
|
-
|
|
290
|
-
// If there are more subtraces, this one didn't terminate the execution
|
|
291
|
-
if subtraces_seen < number_of_subtraces {
|
|
292
|
-
continue;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
let submessage_trace = self.get_stack_trace_inner(message_trace)?;
|
|
296
|
-
|
|
297
|
-
last_submessage_data = Some(SubmessageDataRef {
|
|
298
|
-
message_trace,
|
|
299
|
-
step_index: step_index as u32,
|
|
300
|
-
stacktrace: submessage_trace,
|
|
301
|
-
});
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
let stacktrace_with_inferred_error = ErrorInferrer::infer_after_tracing(
|
|
306
|
-
trace,
|
|
307
|
-
stacktrace,
|
|
308
|
-
&function_jumpdests,
|
|
309
|
-
jumped_into_function,
|
|
310
|
-
last_submessage_data,
|
|
311
|
-
)?;
|
|
312
|
-
|
|
313
|
-
ErrorInferrer::filter_redundant_frames(stacktrace_with_inferred_error)
|
|
314
|
-
}
|
|
315
|
-
}
|