@nomicfoundation/edr 0.5.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.toml +7 -0
- package/index.d.ts +277 -1
- package/index.js +23 -1
- package/package.json +44 -35
- package/src/logger.rs +1 -0
- package/src/provider.rs +0 -6
- package/src/trace/compiler.rs +27 -0
- package/src/trace/debug.rs +318 -0
- package/src/trace/error_inferrer.rs +2213 -0
- package/src/trace/exit.rs +86 -0
- package/src/trace/library_utils.rs +6 -0
- package/src/trace/mapped_inlined_internal_functions_heuristics.rs +180 -0
- package/src/trace/message_trace.rs +179 -0
- package/src/trace/model.rs +59 -0
- package/src/trace/return_data.rs +84 -0
- package/src/trace/solidity_stack_trace.rs +694 -0
- package/src/trace/solidity_tracer.rs +315 -0
- package/src/trace/vm_trace_decoder.rs +234 -0
- package/src/trace/vm_tracer.rs +71 -0
- package/src/trace.rs +23 -1
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
//! Port of `hardhat-network/stack-traces/debug.ts` from Hardhat.
|
|
2
|
+
|
|
3
|
+
use edr_eth::U256;
|
|
4
|
+
use edr_evm::{hex, interpreter::OpCode};
|
|
5
|
+
use edr_solidity::build_model::JumpType;
|
|
6
|
+
use napi::{
|
|
7
|
+
bindgen_prelude::{Either24, Either3, Either4},
|
|
8
|
+
Either, Env,
|
|
9
|
+
};
|
|
10
|
+
use napi_derive::napi;
|
|
11
|
+
|
|
12
|
+
use super::{
|
|
13
|
+
message_trace::{CallMessageTrace, CreateMessageTrace, PrecompileMessageTrace},
|
|
14
|
+
solidity_stack_trace::{RevertErrorStackTraceEntry, SolidityStackTrace},
|
|
15
|
+
};
|
|
16
|
+
use crate::trace::return_data::ReturnData;
|
|
17
|
+
|
|
18
|
+
const MARGIN_SPACE: usize = 6;
|
|
19
|
+
|
|
20
|
+
#[napi]
|
|
21
|
+
fn print_message_trace(
|
|
22
|
+
trace: Either3<PrecompileMessageTrace, CallMessageTrace, CreateMessageTrace>,
|
|
23
|
+
depth: Option<u32>,
|
|
24
|
+
env: Env,
|
|
25
|
+
) -> napi::Result<()> {
|
|
26
|
+
let trace = match &trace {
|
|
27
|
+
Either3::A(precompile) => Either3::A(precompile),
|
|
28
|
+
Either3::B(call) => Either3::B(call),
|
|
29
|
+
Either3::C(create) => Either3::C(create),
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
let depth = depth.unwrap_or(0);
|
|
33
|
+
|
|
34
|
+
print_message_trace_inner(trace, depth, env)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
fn print_message_trace_inner(
|
|
38
|
+
trace: Either3<&PrecompileMessageTrace, &CallMessageTrace, &CreateMessageTrace>,
|
|
39
|
+
depth: u32,
|
|
40
|
+
env: Env,
|
|
41
|
+
) -> napi::Result<()> {
|
|
42
|
+
println!();
|
|
43
|
+
|
|
44
|
+
match trace {
|
|
45
|
+
Either3::A(precompile) => print_precompile_trace(precompile, depth),
|
|
46
|
+
Either3::B(call) => print_call_trace(call, depth, env)?,
|
|
47
|
+
Either3::C(create) => print_create_trace(create, depth, env)?,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
println!();
|
|
51
|
+
|
|
52
|
+
Ok(())
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
fn print_precompile_trace(trace: &PrecompileMessageTrace, depth: u32) {
|
|
56
|
+
let margin = " ".repeat(depth as usize * MARGIN_SPACE);
|
|
57
|
+
|
|
58
|
+
let value = U256::from_limbs_slice(&trace.value.words);
|
|
59
|
+
|
|
60
|
+
println!("{margin}Precompile trace");
|
|
61
|
+
|
|
62
|
+
println!("{margin} precompile number: {}", trace.precompile);
|
|
63
|
+
println!("{margin} value: {value}");
|
|
64
|
+
println!(
|
|
65
|
+
"{margin} calldata: {}",
|
|
66
|
+
hex::encode_prefixed(&*trace.calldata)
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
if trace.exit.is_error() {
|
|
70
|
+
println!("{margin} error: {}", trace.exit.get_reason());
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
println!(
|
|
74
|
+
"{margin} returnData: {}",
|
|
75
|
+
hex::encode_prefixed(&*trace.return_data)
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
fn print_call_trace(trace: &CallMessageTrace, depth: u32, env: Env) -> napi::Result<()> {
|
|
80
|
+
let margin = " ".repeat(depth as usize * MARGIN_SPACE);
|
|
81
|
+
|
|
82
|
+
println!("{margin}Call trace");
|
|
83
|
+
|
|
84
|
+
if let Some(bytecode) = &trace.bytecode {
|
|
85
|
+
let contract = bytecode.contract.borrow();
|
|
86
|
+
let file = contract.location.file();
|
|
87
|
+
let file = file.borrow();
|
|
88
|
+
|
|
89
|
+
println!(
|
|
90
|
+
"{margin} calling contract: {}:{}",
|
|
91
|
+
file.source_name, contract.name
|
|
92
|
+
);
|
|
93
|
+
} else {
|
|
94
|
+
println!(
|
|
95
|
+
"{margin} unrecognized contract code: {:?}",
|
|
96
|
+
hex::encode_prefixed(&*trace.code)
|
|
97
|
+
);
|
|
98
|
+
println!(
|
|
99
|
+
"{margin} contract: {}",
|
|
100
|
+
hex::encode_prefixed(&*trace.address)
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
println!(
|
|
105
|
+
"{margin} value: {}",
|
|
106
|
+
U256::from_limbs_slice(&trace.value.words)
|
|
107
|
+
);
|
|
108
|
+
println!(
|
|
109
|
+
"{margin} calldata: {}",
|
|
110
|
+
hex::encode_prefixed(&*trace.calldata)
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
if trace.exit.is_error() {
|
|
114
|
+
println!("{margin} error: {}", trace.exit.get_reason());
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
println!(
|
|
118
|
+
"{margin} returnData: {}",
|
|
119
|
+
hex::encode_prefixed(&*trace.return_data)
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
trace_steps(Either::A(trace), depth, env)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
fn print_create_trace(trace: &CreateMessageTrace, depth: u32, env: Env) -> napi::Result<()> {
|
|
126
|
+
let margin = " ".repeat(depth as usize * MARGIN_SPACE);
|
|
127
|
+
|
|
128
|
+
println!("{margin}Create trace");
|
|
129
|
+
|
|
130
|
+
if let Some(bytecode) = &trace.bytecode {
|
|
131
|
+
let contract = bytecode.contract.borrow();
|
|
132
|
+
|
|
133
|
+
println!("{margin} deploying contract: {}", contract.name);
|
|
134
|
+
println!("{margin} code: {}", hex::encode_prefixed(&*trace.code));
|
|
135
|
+
} else {
|
|
136
|
+
println!(
|
|
137
|
+
"{margin} unrecognized deployment code: {}",
|
|
138
|
+
hex::encode_prefixed(&*trace.code)
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
println!(
|
|
143
|
+
"{margin} value: {}",
|
|
144
|
+
U256::from_limbs_slice(&trace.value.words)
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
if let Some(Either::A(deployed_contract)) = &trace.deployed_contract {
|
|
148
|
+
println!(
|
|
149
|
+
"{margin} contract address: {}",
|
|
150
|
+
hex::encode_prefixed(deployed_contract)
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if trace.exit.is_error() {
|
|
155
|
+
println!("{margin} error: {}", trace.exit.get_reason());
|
|
156
|
+
// The return data is the deployed-bytecode if there was no error, so we don't
|
|
157
|
+
// show it
|
|
158
|
+
println!(
|
|
159
|
+
"{margin} returnData: {}",
|
|
160
|
+
hex::encode_prefixed(&*trace.return_data)
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
trace_steps(Either::B(trace), depth, env)?;
|
|
165
|
+
|
|
166
|
+
Ok(())
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
fn trace_steps(
|
|
170
|
+
trace: Either<&CallMessageTrace, &CreateMessageTrace>,
|
|
171
|
+
depth: u32,
|
|
172
|
+
env: Env,
|
|
173
|
+
) -> napi::Result<()> {
|
|
174
|
+
let margin = " ".repeat(depth as usize * MARGIN_SPACE);
|
|
175
|
+
|
|
176
|
+
println!("{margin} steps:");
|
|
177
|
+
println!();
|
|
178
|
+
|
|
179
|
+
let (bytecode, steps) = match &trace {
|
|
180
|
+
Either::A(call) => (&call.bytecode, &call.steps),
|
|
181
|
+
Either::B(create) => (&create.bytecode, &create.steps),
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
for step in steps {
|
|
185
|
+
let step = match step {
|
|
186
|
+
Either4::A(step) => step,
|
|
187
|
+
trace @ (Either4::B(..) | Either4::C(..) | Either4::D(..)) => {
|
|
188
|
+
let trace = match trace {
|
|
189
|
+
Either4::A(..) => unreachable!(),
|
|
190
|
+
Either4::B(precompile) => Either3::A(precompile),
|
|
191
|
+
Either4::C(call) => Either3::B(call),
|
|
192
|
+
Either4::D(create) => Either3::C(create),
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
print_message_trace_inner(trace, depth + 1, env)?;
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
let pc = format!("{:>5}", format!("{:03}", step.pc));
|
|
201
|
+
|
|
202
|
+
if let Some(bytecode) = bytecode {
|
|
203
|
+
let inst = bytecode.get_instruction(step.pc)?;
|
|
204
|
+
|
|
205
|
+
let location = inst
|
|
206
|
+
.location
|
|
207
|
+
.as_ref()
|
|
208
|
+
.map(|inst_location| {
|
|
209
|
+
let inst_location = &inst_location;
|
|
210
|
+
let file = inst_location.file();
|
|
211
|
+
let file = file.borrow();
|
|
212
|
+
|
|
213
|
+
let mut location_str = file.source_name.clone();
|
|
214
|
+
|
|
215
|
+
if let Some(func) = inst_location.get_containing_function() {
|
|
216
|
+
let file = func.location.file();
|
|
217
|
+
let file = file.borrow();
|
|
218
|
+
|
|
219
|
+
let source_name = func
|
|
220
|
+
.contract_name
|
|
221
|
+
.as_ref()
|
|
222
|
+
.unwrap_or_else(|| &file.source_name);
|
|
223
|
+
|
|
224
|
+
location_str += &format!(":{source_name}:{}", func.name);
|
|
225
|
+
}
|
|
226
|
+
location_str +=
|
|
227
|
+
&format!(" - {}:{}", inst_location.offset, inst_location.length);
|
|
228
|
+
|
|
229
|
+
napi::Result::Ok(location_str)
|
|
230
|
+
})
|
|
231
|
+
.transpose()?
|
|
232
|
+
.unwrap_or_default();
|
|
233
|
+
|
|
234
|
+
if matches!(inst.opcode, OpCode::JUMP | OpCode::JUMPI) {
|
|
235
|
+
let jump = if inst.jump_type == JumpType::NotJump {
|
|
236
|
+
"".to_string()
|
|
237
|
+
} else {
|
|
238
|
+
format!("({})", inst.jump_type)
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
let entry = format!("{margin} {pc} {opcode} {jump}", opcode = inst.opcode);
|
|
242
|
+
|
|
243
|
+
println!("{entry:<50}{location}");
|
|
244
|
+
} else if inst.opcode.is_push() {
|
|
245
|
+
let entry = format!(
|
|
246
|
+
"{margin} {pc} {opcode} {push_data}",
|
|
247
|
+
opcode = inst.opcode,
|
|
248
|
+
push_data = inst
|
|
249
|
+
.push_data
|
|
250
|
+
.as_deref()
|
|
251
|
+
.map(hex::encode_prefixed)
|
|
252
|
+
.unwrap_or_default()
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
println!("{entry:<50}{location}");
|
|
256
|
+
} else {
|
|
257
|
+
let entry = format!("{margin} {pc} {opcode}", opcode = inst.opcode);
|
|
258
|
+
|
|
259
|
+
println!("{entry:<50}{location}");
|
|
260
|
+
}
|
|
261
|
+
} else {
|
|
262
|
+
println!("{margin} {pc}");
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
Ok(())
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
#[napi]
|
|
270
|
+
fn print_stack_trace(trace: SolidityStackTrace) -> napi::Result<()> {
|
|
271
|
+
let entry_values = trace
|
|
272
|
+
.into_iter()
|
|
273
|
+
.map(|entry| match entry {
|
|
274
|
+
Either24::A(entry) => serde_json::to_value(entry),
|
|
275
|
+
Either24::B(entry) => serde_json::to_value(entry),
|
|
276
|
+
Either24::C(entry) => serde_json::to_value(entry),
|
|
277
|
+
Either24::D(entry) => serde_json::to_value(entry),
|
|
278
|
+
Either24::F(entry) => serde_json::to_value(entry),
|
|
279
|
+
Either24::G(entry) => serde_json::to_value(entry),
|
|
280
|
+
Either24::H(entry) => serde_json::to_value(entry),
|
|
281
|
+
Either24::I(entry) => serde_json::to_value(entry),
|
|
282
|
+
Either24::J(entry) => serde_json::to_value(entry),
|
|
283
|
+
Either24::K(entry) => serde_json::to_value(entry),
|
|
284
|
+
Either24::L(entry) => serde_json::to_value(entry),
|
|
285
|
+
Either24::M(entry) => serde_json::to_value(entry),
|
|
286
|
+
Either24::N(entry) => serde_json::to_value(entry),
|
|
287
|
+
Either24::O(entry) => serde_json::to_value(entry),
|
|
288
|
+
Either24::P(entry) => serde_json::to_value(entry),
|
|
289
|
+
Either24::Q(entry) => serde_json::to_value(entry),
|
|
290
|
+
Either24::R(entry) => serde_json::to_value(entry),
|
|
291
|
+
Either24::S(entry) => serde_json::to_value(entry),
|
|
292
|
+
Either24::T(entry) => serde_json::to_value(entry),
|
|
293
|
+
Either24::U(entry) => serde_json::to_value(entry),
|
|
294
|
+
Either24::V(entry) => serde_json::to_value(entry),
|
|
295
|
+
Either24::W(entry) => serde_json::to_value(entry),
|
|
296
|
+
Either24::X(entry) => serde_json::to_value(entry),
|
|
297
|
+
// Decode the error message from the return data
|
|
298
|
+
Either24::E(entry @ RevertErrorStackTraceEntry { .. }) => {
|
|
299
|
+
use serde::de::Error;
|
|
300
|
+
|
|
301
|
+
let decoded_error_msg = ReturnData::new(entry.return_data.clone())
|
|
302
|
+
.decode_error()
|
|
303
|
+
.map_err(|e| {
|
|
304
|
+
serde_json::Error::custom(format_args!("Error decoding return data: {e}"))
|
|
305
|
+
})?;
|
|
306
|
+
|
|
307
|
+
let mut value = serde_json::to_value(entry)?;
|
|
308
|
+
value["message"] = decoded_error_msg.into();
|
|
309
|
+
Ok(value)
|
|
310
|
+
}
|
|
311
|
+
})
|
|
312
|
+
.collect::<Result<Vec<_>, _>>()
|
|
313
|
+
.map_err(|e| napi::Error::from_reason(format!("Error converting to JSON: {e}")))?;
|
|
314
|
+
|
|
315
|
+
println!("{}", serde_json::to_string_pretty(&entry_values)?);
|
|
316
|
+
|
|
317
|
+
Ok(())
|
|
318
|
+
}
|