@nomicfoundation/edr 0.2.0-alpha.2
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/config.toml +8 -0
- package/.mocharc.json +4 -0
- package/Cargo.toml +50 -0
- package/LICENSE +1 -0
- package/artifacts/bindings-aarch64-apple-darwin/edr.darwin-arm64.node +0 -0
- package/artifacts/bindings-aarch64-pc-windows-msvc/edr.win32-arm64-msvc.node +0 -0
- package/artifacts/bindings-aarch64-unknown-linux-gnu/edr.linux-arm64-gnu.node +0 -0
- package/artifacts/bindings-aarch64-unknown-linux-musl/edr.linux-arm64-musl.node +0 -0
- package/artifacts/bindings-i686-pc-windows-msvc/edr.win32-ia32-msvc.node +0 -0
- package/artifacts/bindings-x86_64-apple-darwin/edr.darwin-x64.node +0 -0
- package/artifacts/bindings-x86_64-pc-windows-msvc/edr.win32-x64-msvc.node +0 -0
- package/artifacts/bindings-x86_64-unknown-linux-gnu/edr.linux-x64-gnu.node +0 -0
- package/artifacts/bindings-x86_64-unknown-linux-musl/edr.linux-x64-musl.node +0 -0
- package/build.rs +3 -0
- package/index.d.ts +383 -0
- package/index.js +264 -0
- package/npm/darwin-arm64/README.md +3 -0
- package/npm/darwin-arm64/edr.darwin-arm64.node +0 -0
- package/npm/darwin-arm64/package.json +22 -0
- package/npm/darwin-x64/README.md +3 -0
- package/npm/darwin-x64/edr.darwin-x64.node +0 -0
- package/npm/darwin-x64/package.json +22 -0
- package/npm/linux-arm64-gnu/README.md +3 -0
- package/npm/linux-arm64-gnu/edr.linux-arm64-gnu.node +0 -0
- package/npm/linux-arm64-gnu/package.json +25 -0
- package/npm/linux-arm64-musl/README.md +3 -0
- package/npm/linux-arm64-musl/edr.linux-arm64-musl.node +0 -0
- package/npm/linux-arm64-musl/package.json +25 -0
- package/npm/linux-x64-gnu/README.md +3 -0
- package/npm/linux-x64-gnu/edr.linux-x64-gnu.node +0 -0
- package/npm/linux-x64-gnu/package.json +25 -0
- package/npm/linux-x64-musl/README.md +3 -0
- package/npm/linux-x64-musl/edr.linux-x64-musl.node +0 -0
- package/npm/linux-x64-musl/package.json +25 -0
- package/npm/win32-arm64-msvc/README.md +3 -0
- package/npm/win32-arm64-msvc/edr.win32-arm64-msvc.node +0 -0
- package/npm/win32-arm64-msvc/package.json +22 -0
- package/npm/win32-ia32-msvc/README.md +3 -0
- package/npm/win32-ia32-msvc/edr.win32-ia32-msvc.node +0 -0
- package/npm/win32-ia32-msvc/package.json +22 -0
- package/npm/win32-x64-msvc/README.md +3 -0
- package/npm/win32-x64-msvc/edr.win32-x64-msvc.node +0 -0
- package/npm/win32-x64-msvc/package.json +22 -0
- package/package.json +61 -0
- package/src/account.rs +28 -0
- package/src/block.rs +110 -0
- package/src/cast.rs +119 -0
- package/src/config.rs +70 -0
- package/src/context.rs +74 -0
- package/src/debug_trace.rs +38 -0
- package/src/lib.rs +18 -0
- package/src/log.rs +41 -0
- package/src/logger.rs +1277 -0
- package/src/provider/config.rs +271 -0
- package/src/provider.rs +185 -0
- package/src/result.rs +254 -0
- package/src/subscribe.rs +60 -0
- package/src/sync.rs +85 -0
- package/src/threadsafe_function.rs +305 -0
- package/src/trace.rs +168 -0
- package/test/provider.ts +104 -0
- package/tsconfig.json +17 -0
package/src/logger.rs
ADDED
|
@@ -0,0 +1,1277 @@
|
|
|
1
|
+
use std::{
|
|
2
|
+
fmt::Display,
|
|
3
|
+
sync::mpsc::{channel, Sender},
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
use ansi_term::{Color, Style};
|
|
7
|
+
use edr_eth::{Bytes, B256, U256};
|
|
8
|
+
use edr_evm::{
|
|
9
|
+
blockchain::BlockchainError,
|
|
10
|
+
precompile::{self, Precompiles},
|
|
11
|
+
trace::TraceMessage,
|
|
12
|
+
ExecutableTransaction, ExecutionResult, SyncBlock,
|
|
13
|
+
};
|
|
14
|
+
use edr_provider::{ProviderError, TransactionFailure};
|
|
15
|
+
use itertools::izip;
|
|
16
|
+
use napi::{Env, JsFunction, NapiRaw, Status};
|
|
17
|
+
use napi_derive::napi;
|
|
18
|
+
|
|
19
|
+
use crate::{
|
|
20
|
+
cast::TryCast,
|
|
21
|
+
sync::{await_promise, handle_error},
|
|
22
|
+
threadsafe_function::{ThreadSafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode},
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
#[napi(object)]
|
|
26
|
+
pub struct ContractAndFunctionName {
|
|
27
|
+
/// The contract name.
|
|
28
|
+
pub contract_name: String,
|
|
29
|
+
/// The function name. Only present for calls.
|
|
30
|
+
pub function_name: Option<String>,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
impl TryCast<(String, Option<String>)> for ContractAndFunctionName {
|
|
34
|
+
type Error = napi::Error;
|
|
35
|
+
|
|
36
|
+
fn try_cast(self) -> std::result::Result<(String, Option<String>), Self::Error> {
|
|
37
|
+
Ok((self.contract_name, self.function_name))
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
struct DecodeConsoleLogInputsCall {
|
|
42
|
+
inputs: Vec<Bytes>,
|
|
43
|
+
sender: Sender<napi::Result<Vec<String>>>,
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
struct ContractAndFunctionNameCall {
|
|
47
|
+
code: Bytes,
|
|
48
|
+
/// Only present for calls.
|
|
49
|
+
calldata: Option<Bytes>,
|
|
50
|
+
sender: Sender<napi::Result<(String, Option<String>)>>,
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
#[napi(object)]
|
|
54
|
+
pub struct LoggerConfig {
|
|
55
|
+
/// Whether to enable the logger.
|
|
56
|
+
pub enable: bool,
|
|
57
|
+
#[napi(ts_type = "(inputs: Buffer[]) => string[]")]
|
|
58
|
+
pub decode_console_log_inputs_callback: JsFunction,
|
|
59
|
+
#[napi(ts_type = "(code: Buffer, calldata?: Buffer) => ContractAndFunctionName")]
|
|
60
|
+
pub get_contract_and_function_name_callback: JsFunction,
|
|
61
|
+
#[napi(ts_type = "(message: string, replace: boolean) => void")]
|
|
62
|
+
pub print_line_callback: JsFunction,
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
#[derive(Clone)]
|
|
66
|
+
pub enum LoggingState {
|
|
67
|
+
CollapsingMethod(CollapsedMethod),
|
|
68
|
+
HardhatMinining {
|
|
69
|
+
empty_blocks_range_start: Option<u64>,
|
|
70
|
+
},
|
|
71
|
+
IntervalMining {
|
|
72
|
+
empty_blocks_range_start: Option<u64>,
|
|
73
|
+
},
|
|
74
|
+
Empty,
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
impl LoggingState {
|
|
78
|
+
/// Converts the state into a hardhat mining state.
|
|
79
|
+
pub fn into_hardhat_mining(self) -> Option<u64> {
|
|
80
|
+
match self {
|
|
81
|
+
Self::HardhatMinining {
|
|
82
|
+
empty_blocks_range_start,
|
|
83
|
+
} => empty_blocks_range_start,
|
|
84
|
+
_ => None,
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/// Converts the state into an interval mining state.
|
|
89
|
+
pub fn into_interval_mining(self) -> Option<u64> {
|
|
90
|
+
match self {
|
|
91
|
+
Self::IntervalMining {
|
|
92
|
+
empty_blocks_range_start,
|
|
93
|
+
} => empty_blocks_range_start,
|
|
94
|
+
_ => None,
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
impl Default for LoggingState {
|
|
100
|
+
fn default() -> Self {
|
|
101
|
+
Self::Empty
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
#[derive(Clone)]
|
|
106
|
+
enum LogLine {
|
|
107
|
+
Single(String),
|
|
108
|
+
WithTitle(String, String),
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
#[derive(Debug, thiserror::Error)]
|
|
112
|
+
pub enum LoggerError {
|
|
113
|
+
#[error("Failed to print line")]
|
|
114
|
+
PrintLine,
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
#[derive(Clone)]
|
|
118
|
+
pub struct Logger {
|
|
119
|
+
collector: LogCollector,
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
impl Logger {
|
|
123
|
+
pub fn new(env: &Env, config: LoggerConfig) -> napi::Result<Self> {
|
|
124
|
+
Ok(Self {
|
|
125
|
+
collector: LogCollector::new(env, config)?,
|
|
126
|
+
})
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
impl edr_provider::Logger for Logger {
|
|
131
|
+
type BlockchainError = BlockchainError;
|
|
132
|
+
|
|
133
|
+
type LoggerError = LoggerError;
|
|
134
|
+
|
|
135
|
+
fn is_enabled(&self) -> bool {
|
|
136
|
+
self.collector.is_enabled
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
fn set_is_enabled(&mut self, is_enabled: bool) {
|
|
140
|
+
self.collector.is_enabled = is_enabled;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
fn log_call(
|
|
144
|
+
&mut self,
|
|
145
|
+
spec_id: edr_eth::SpecId,
|
|
146
|
+
transaction: &ExecutableTransaction,
|
|
147
|
+
result: &edr_provider::CallResult,
|
|
148
|
+
) -> Result<(), Self::LoggerError> {
|
|
149
|
+
self.collector.log_call(spec_id, transaction, result);
|
|
150
|
+
|
|
151
|
+
Ok(())
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
fn log_estimate_gas_failure(
|
|
155
|
+
&mut self,
|
|
156
|
+
spec_id: edr_eth::SpecId,
|
|
157
|
+
transaction: &ExecutableTransaction,
|
|
158
|
+
failure: &edr_provider::EstimateGasFailure,
|
|
159
|
+
) -> Result<(), Self::LoggerError> {
|
|
160
|
+
self.collector
|
|
161
|
+
.log_estimate_gas(spec_id, transaction, failure);
|
|
162
|
+
|
|
163
|
+
Ok(())
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
fn log_interval_mined(
|
|
167
|
+
&mut self,
|
|
168
|
+
spec_id: edr_eth::SpecId,
|
|
169
|
+
mining_result: &edr_provider::DebugMineBlockResult<Self::BlockchainError>,
|
|
170
|
+
) -> Result<(), Self::LoggerError> {
|
|
171
|
+
self.collector.log_interval_mined(spec_id, mining_result)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
fn log_mined_block(
|
|
175
|
+
&mut self,
|
|
176
|
+
spec_id: edr_eth::SpecId,
|
|
177
|
+
mining_results: &[edr_provider::DebugMineBlockResult<Self::BlockchainError>],
|
|
178
|
+
) -> Result<(), Self::LoggerError> {
|
|
179
|
+
self.collector.log_mined_blocks(spec_id, mining_results);
|
|
180
|
+
|
|
181
|
+
Ok(())
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
fn log_send_transaction(
|
|
185
|
+
&mut self,
|
|
186
|
+
spec_id: edr_eth::SpecId,
|
|
187
|
+
transaction: &edr_evm::ExecutableTransaction,
|
|
188
|
+
mining_results: &[edr_provider::DebugMineBlockResult<Self::BlockchainError>],
|
|
189
|
+
) -> Result<(), Self::LoggerError> {
|
|
190
|
+
self.collector
|
|
191
|
+
.log_send_transaction(spec_id, transaction, mining_results);
|
|
192
|
+
|
|
193
|
+
Ok(())
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
fn print_method_logs(
|
|
197
|
+
&mut self,
|
|
198
|
+
method: &str,
|
|
199
|
+
error: Option<&ProviderError<LoggerError>>,
|
|
200
|
+
) -> Result<(), Self::LoggerError> {
|
|
201
|
+
if let Some(error) = error {
|
|
202
|
+
self.collector.state = LoggingState::Empty;
|
|
203
|
+
|
|
204
|
+
if matches!(error, ProviderError::UnsupportedMethod { .. }) {
|
|
205
|
+
self.collector
|
|
206
|
+
.print::<false>(Color::Red.paint(error.to_string()))?;
|
|
207
|
+
} else {
|
|
208
|
+
self.collector.print::<false>(Color::Red.paint(method))?;
|
|
209
|
+
self.collector.print_logs()?;
|
|
210
|
+
|
|
211
|
+
if !matches!(error, ProviderError::TransactionFailed(_)) {
|
|
212
|
+
self.collector.print_empty_line()?;
|
|
213
|
+
|
|
214
|
+
let error_message = error.to_string();
|
|
215
|
+
self.collector
|
|
216
|
+
.try_indented(|logger| logger.print::<false>(&error_message))?;
|
|
217
|
+
|
|
218
|
+
if matches!(error, ProviderError::InvalidEip155TransactionChainId) {
|
|
219
|
+
self.collector.try_indented(|logger| {
|
|
220
|
+
logger.print::<false>(Color::Yellow.paint(
|
|
221
|
+
"If you are using MetaMask, you can learn how to fix this error here: https://hardhat.org/metamask-issue"
|
|
222
|
+
))
|
|
223
|
+
})?;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
self.collector.print_empty_line()?;
|
|
228
|
+
}
|
|
229
|
+
} else {
|
|
230
|
+
self.collector.print_method(method)?;
|
|
231
|
+
|
|
232
|
+
let printed = self.collector.print_logs()?;
|
|
233
|
+
if printed {
|
|
234
|
+
self.collector.print_empty_line()?;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
Ok(())
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
#[derive(Clone)]
|
|
243
|
+
pub struct CollapsedMethod {
|
|
244
|
+
count: usize,
|
|
245
|
+
method: String,
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
#[derive(Clone)]
|
|
249
|
+
struct LogCollector {
|
|
250
|
+
decode_console_log_inputs_fn: ThreadsafeFunction<DecodeConsoleLogInputsCall>,
|
|
251
|
+
get_contract_and_function_name_fn: ThreadsafeFunction<ContractAndFunctionNameCall>,
|
|
252
|
+
indentation: usize,
|
|
253
|
+
is_enabled: bool,
|
|
254
|
+
logs: Vec<LogLine>,
|
|
255
|
+
print_line_fn: ThreadsafeFunction<(String, bool)>,
|
|
256
|
+
state: LoggingState,
|
|
257
|
+
title_length: usize,
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
impl LogCollector {
|
|
261
|
+
pub fn new(env: &Env, config: LoggerConfig) -> napi::Result<Self> {
|
|
262
|
+
let decode_console_log_inputs_fn = ThreadsafeFunction::create(
|
|
263
|
+
env.raw(),
|
|
264
|
+
// SAFETY: The callback is guaranteed to be valid for the lifetime of the tracer.
|
|
265
|
+
unsafe { config.decode_console_log_inputs_callback.raw() },
|
|
266
|
+
0,
|
|
267
|
+
|ctx: ThreadSafeCallContext<DecodeConsoleLogInputsCall>| {
|
|
268
|
+
// Bytes[]
|
|
269
|
+
let inputs = ctx
|
|
270
|
+
.env
|
|
271
|
+
.create_array_with_length(ctx.value.inputs.len())
|
|
272
|
+
.and_then(|mut inputs| {
|
|
273
|
+
for (idx, input) in ctx.value.inputs.into_iter().enumerate() {
|
|
274
|
+
// SAFETY: The input is guaranteed to be valid for the lifetime of the
|
|
275
|
+
// JS buffer.
|
|
276
|
+
unsafe {
|
|
277
|
+
ctx.env.create_buffer_with_borrowed_data(
|
|
278
|
+
input.as_ptr(),
|
|
279
|
+
input.len(),
|
|
280
|
+
input,
|
|
281
|
+
|input: Bytes, _env| {
|
|
282
|
+
std::mem::drop(input);
|
|
283
|
+
},
|
|
284
|
+
)
|
|
285
|
+
}
|
|
286
|
+
.and_then(|input| inputs.set_element(idx as u32, input.into_raw()))?;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
Ok(inputs)
|
|
290
|
+
})?;
|
|
291
|
+
|
|
292
|
+
let sender = ctx.value.sender.clone();
|
|
293
|
+
|
|
294
|
+
let promise = ctx.callback.call(None, &[inputs])?;
|
|
295
|
+
let result =
|
|
296
|
+
await_promise::<Vec<String>, Vec<String>>(ctx.env, promise, ctx.value.sender);
|
|
297
|
+
|
|
298
|
+
handle_error(sender, result)
|
|
299
|
+
},
|
|
300
|
+
)?;
|
|
301
|
+
|
|
302
|
+
let get_contract_and_function_name_fn = ThreadsafeFunction::create(
|
|
303
|
+
env.raw(),
|
|
304
|
+
// SAFETY: The callback is guaranteed to be valid for the lifetime of the tracer.
|
|
305
|
+
unsafe { config.get_contract_and_function_name_callback.raw() },
|
|
306
|
+
0,
|
|
307
|
+
|ctx: ThreadSafeCallContext<ContractAndFunctionNameCall>| {
|
|
308
|
+
// Buffer
|
|
309
|
+
let code = ctx.value.code;
|
|
310
|
+
// SAFETY: The code is guaranteed to be valid for the lifetime of the
|
|
311
|
+
// JS buffer.
|
|
312
|
+
let code = unsafe {
|
|
313
|
+
ctx.env.create_buffer_with_borrowed_data(
|
|
314
|
+
code.as_ptr(),
|
|
315
|
+
code.len(),
|
|
316
|
+
code,
|
|
317
|
+
|code: Bytes, _env| {
|
|
318
|
+
std::mem::drop(code);
|
|
319
|
+
},
|
|
320
|
+
)
|
|
321
|
+
}?
|
|
322
|
+
.into_unknown();
|
|
323
|
+
|
|
324
|
+
// Option<Buffer>
|
|
325
|
+
let calldata = if let Some(calldata) = ctx.value.calldata {
|
|
326
|
+
// SAFETY: The calldata is guaranteed to be valid for the lifetime of the
|
|
327
|
+
// JS buffer.
|
|
328
|
+
unsafe {
|
|
329
|
+
ctx.env.create_buffer_with_borrowed_data(
|
|
330
|
+
calldata.as_ptr(),
|
|
331
|
+
calldata.len(),
|
|
332
|
+
calldata,
|
|
333
|
+
|calldata: Bytes, _env| {
|
|
334
|
+
std::mem::drop(calldata);
|
|
335
|
+
},
|
|
336
|
+
)
|
|
337
|
+
}?
|
|
338
|
+
.into_unknown()
|
|
339
|
+
} else {
|
|
340
|
+
ctx.env.get_undefined()?.into_unknown()
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
let sender = ctx.value.sender.clone();
|
|
344
|
+
|
|
345
|
+
let promise = ctx.callback.call(None, &[code, calldata])?;
|
|
346
|
+
let result = await_promise::<ContractAndFunctionName, (String, Option<String>)>(
|
|
347
|
+
ctx.env,
|
|
348
|
+
promise,
|
|
349
|
+
ctx.value.sender,
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
handle_error(sender, result)
|
|
353
|
+
},
|
|
354
|
+
)?;
|
|
355
|
+
|
|
356
|
+
let print_line_fn = ThreadsafeFunction::create(
|
|
357
|
+
env.raw(),
|
|
358
|
+
// SAFETY: The callback is guaranteed to be valid for the lifetime of the tracer.
|
|
359
|
+
unsafe { config.print_line_callback.raw() },
|
|
360
|
+
0,
|
|
361
|
+
|ctx: ThreadSafeCallContext<(String, bool)>| {
|
|
362
|
+
// String
|
|
363
|
+
let message = ctx.env.create_string_from_std(ctx.value.0)?;
|
|
364
|
+
|
|
365
|
+
// bool
|
|
366
|
+
let replace = ctx.env.get_boolean(ctx.value.1)?;
|
|
367
|
+
|
|
368
|
+
ctx.callback
|
|
369
|
+
.call(None, &[message.into_unknown(), replace.into_unknown()])?;
|
|
370
|
+
Ok(())
|
|
371
|
+
},
|
|
372
|
+
)?;
|
|
373
|
+
|
|
374
|
+
Ok(Self {
|
|
375
|
+
decode_console_log_inputs_fn,
|
|
376
|
+
get_contract_and_function_name_fn,
|
|
377
|
+
indentation: 0,
|
|
378
|
+
is_enabled: config.enable,
|
|
379
|
+
logs: Vec::new(),
|
|
380
|
+
print_line_fn,
|
|
381
|
+
state: LoggingState::default(),
|
|
382
|
+
title_length: 0,
|
|
383
|
+
})
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
pub fn log_call(
|
|
387
|
+
&mut self,
|
|
388
|
+
spec_id: edr_eth::SpecId,
|
|
389
|
+
transaction: &ExecutableTransaction,
|
|
390
|
+
result: &edr_provider::CallResult,
|
|
391
|
+
) {
|
|
392
|
+
let edr_provider::CallResult {
|
|
393
|
+
console_log_inputs,
|
|
394
|
+
execution_result,
|
|
395
|
+
trace,
|
|
396
|
+
} = result;
|
|
397
|
+
|
|
398
|
+
self.state = LoggingState::Empty;
|
|
399
|
+
|
|
400
|
+
self.indented(|logger| {
|
|
401
|
+
logger.log_contract_and_function_name::<true>(spec_id, trace);
|
|
402
|
+
|
|
403
|
+
logger.log_with_title("From", format!("0x{:x}", transaction.caller()));
|
|
404
|
+
if let Some(to) = transaction.to() {
|
|
405
|
+
logger.log_with_title("To", format!("0x{to:x}"));
|
|
406
|
+
}
|
|
407
|
+
if transaction.value() > U256::ZERO {
|
|
408
|
+
logger.log_with_title("Value", wei_to_human_readable(transaction.value()));
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
logger.log_console_log_messages(console_log_inputs);
|
|
412
|
+
|
|
413
|
+
if let Some(transaction_failure) = TransactionFailure::from_execution_result(
|
|
414
|
+
execution_result,
|
|
415
|
+
transaction.hash(),
|
|
416
|
+
trace,
|
|
417
|
+
) {
|
|
418
|
+
logger.log_transaction_failure(&transaction_failure);
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
pub fn log_estimate_gas(
|
|
424
|
+
&mut self,
|
|
425
|
+
spec_id: edr_eth::SpecId,
|
|
426
|
+
transaction: &ExecutableTransaction,
|
|
427
|
+
result: &edr_provider::EstimateGasFailure,
|
|
428
|
+
) {
|
|
429
|
+
let edr_provider::EstimateGasFailure {
|
|
430
|
+
console_log_inputs,
|
|
431
|
+
transaction_failure,
|
|
432
|
+
} = result;
|
|
433
|
+
|
|
434
|
+
self.state = LoggingState::Empty;
|
|
435
|
+
|
|
436
|
+
self.indented(|logger| {
|
|
437
|
+
logger.log_contract_and_function_name::<true>(
|
|
438
|
+
spec_id,
|
|
439
|
+
&transaction_failure.failure.solidity_trace,
|
|
440
|
+
);
|
|
441
|
+
|
|
442
|
+
logger.log_with_title("From", format!("0x{:x}", transaction.caller()));
|
|
443
|
+
if let Some(to) = transaction.to() {
|
|
444
|
+
logger.log_with_title("To", format!("0x{to:x}"));
|
|
445
|
+
}
|
|
446
|
+
logger.log_with_title("Value", wei_to_human_readable(transaction.value()));
|
|
447
|
+
|
|
448
|
+
logger.log_console_log_messages(console_log_inputs);
|
|
449
|
+
|
|
450
|
+
logger.log_transaction_failure(&transaction_failure.failure);
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
fn log_transaction_failure(&mut self, failure: &edr_provider::TransactionFailure) {
|
|
455
|
+
let is_revert_error = matches!(
|
|
456
|
+
failure.reason,
|
|
457
|
+
edr_provider::TransactionFailureReason::Revert(_)
|
|
458
|
+
);
|
|
459
|
+
|
|
460
|
+
let error_type = if is_revert_error {
|
|
461
|
+
"Error"
|
|
462
|
+
} else {
|
|
463
|
+
"TransactionExecutionError"
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
self.log_empty_line();
|
|
467
|
+
self.log(format!("{error_type}: {failure}"));
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
pub fn log_mined_blocks(
|
|
471
|
+
&mut self,
|
|
472
|
+
spec_id: edr_eth::SpecId,
|
|
473
|
+
mining_results: &[edr_provider::DebugMineBlockResult<BlockchainError>],
|
|
474
|
+
) {
|
|
475
|
+
let num_results = mining_results.len();
|
|
476
|
+
for (idx, mining_result) in mining_results.iter().enumerate() {
|
|
477
|
+
let state = std::mem::take(&mut self.state);
|
|
478
|
+
let empty_blocks_range_start = state.into_hardhat_mining();
|
|
479
|
+
|
|
480
|
+
if mining_result.block.transactions().is_empty() {
|
|
481
|
+
self.log_hardhat_mined_empty_block(&mining_result.block, empty_blocks_range_start);
|
|
482
|
+
|
|
483
|
+
let block_number = mining_result.block.header().number;
|
|
484
|
+
self.state = LoggingState::HardhatMinining {
|
|
485
|
+
empty_blocks_range_start: Some(
|
|
486
|
+
empty_blocks_range_start.unwrap_or(block_number),
|
|
487
|
+
),
|
|
488
|
+
};
|
|
489
|
+
} else {
|
|
490
|
+
self.log_hardhat_mined_block(spec_id, mining_result);
|
|
491
|
+
|
|
492
|
+
if idx < num_results - 1 {
|
|
493
|
+
self.log_empty_line();
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
pub fn log_interval_mined(
|
|
500
|
+
&mut self,
|
|
501
|
+
spec_id: edr_eth::SpecId,
|
|
502
|
+
mining_result: &edr_provider::DebugMineBlockResult<BlockchainError>,
|
|
503
|
+
) -> Result<(), LoggerError> {
|
|
504
|
+
let block_header = mining_result.block.header();
|
|
505
|
+
let block_number = block_header.number;
|
|
506
|
+
|
|
507
|
+
if mining_result.block.transactions().is_empty() {
|
|
508
|
+
let state = std::mem::take(&mut self.state);
|
|
509
|
+
let empty_blocks_range_start = state.into_interval_mining();
|
|
510
|
+
|
|
511
|
+
if let Some(empty_blocks_range_start) = empty_blocks_range_start {
|
|
512
|
+
self.print::<true>(format!(
|
|
513
|
+
"Mined empty block range #{empty_blocks_range_start} to #{block_number}"
|
|
514
|
+
))?;
|
|
515
|
+
} else {
|
|
516
|
+
let base_fee = if let Some(base_fee) = block_header.base_fee_per_gas.as_ref() {
|
|
517
|
+
format!(" with base fee {base_fee}")
|
|
518
|
+
} else {
|
|
519
|
+
String::new()
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
self.print::<false>(format!("Mined empty block #{block_number}{base_fee}"))?;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
self.state = LoggingState::IntervalMining {
|
|
526
|
+
empty_blocks_range_start: Some(
|
|
527
|
+
empty_blocks_range_start.unwrap_or(block_header.number),
|
|
528
|
+
),
|
|
529
|
+
};
|
|
530
|
+
} else {
|
|
531
|
+
self.log_interval_mined_block(spec_id, mining_result);
|
|
532
|
+
|
|
533
|
+
self.print::<false>(format!("Mined block #{block_number}"))?;
|
|
534
|
+
|
|
535
|
+
let printed = self.print_logs()?;
|
|
536
|
+
if printed {
|
|
537
|
+
self.print_empty_line()?;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
Ok(())
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
pub fn log_send_transaction(
|
|
545
|
+
&mut self,
|
|
546
|
+
spec_id: edr_eth::SpecId,
|
|
547
|
+
transaction: &edr_evm::ExecutableTransaction,
|
|
548
|
+
mining_results: &[edr_provider::DebugMineBlockResult<BlockchainError>],
|
|
549
|
+
) {
|
|
550
|
+
if !mining_results.is_empty() {
|
|
551
|
+
self.state = LoggingState::Empty;
|
|
552
|
+
|
|
553
|
+
let (sent_block_result, sent_transaction_result, sent_trace) = mining_results
|
|
554
|
+
.iter()
|
|
555
|
+
.find_map(|result| {
|
|
556
|
+
izip!(
|
|
557
|
+
result.block.transactions(),
|
|
558
|
+
result.transaction_results.iter(),
|
|
559
|
+
result.transaction_traces.iter()
|
|
560
|
+
)
|
|
561
|
+
.find(|(block_transaction, _, _)| {
|
|
562
|
+
*block_transaction.hash() == *transaction.hash()
|
|
563
|
+
})
|
|
564
|
+
.map(|(_, transaction_result, trace)| (result, transaction_result, trace))
|
|
565
|
+
})
|
|
566
|
+
.expect("Transaction result not found");
|
|
567
|
+
|
|
568
|
+
if mining_results.len() > 1 {
|
|
569
|
+
self.log_multiple_blocks_warning();
|
|
570
|
+
self.log_auto_mined_block_results(spec_id, mining_results, transaction.hash());
|
|
571
|
+
self.log_currently_sent_transaction(
|
|
572
|
+
spec_id,
|
|
573
|
+
sent_block_result,
|
|
574
|
+
transaction,
|
|
575
|
+
sent_transaction_result,
|
|
576
|
+
sent_trace,
|
|
577
|
+
);
|
|
578
|
+
} else if let Some(result) = mining_results.first() {
|
|
579
|
+
let transactions = result.block.transactions();
|
|
580
|
+
if transactions.len() > 1 {
|
|
581
|
+
self.log_multiple_transactions_warning();
|
|
582
|
+
self.log_auto_mined_block_results(spec_id, mining_results, transaction.hash());
|
|
583
|
+
self.log_currently_sent_transaction(
|
|
584
|
+
spec_id,
|
|
585
|
+
sent_block_result,
|
|
586
|
+
transaction,
|
|
587
|
+
sent_transaction_result,
|
|
588
|
+
sent_trace,
|
|
589
|
+
);
|
|
590
|
+
} else if let Some(transaction) = transactions.first() {
|
|
591
|
+
self.log_single_transaction_mining_result(spec_id, result, transaction);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
fn contract_and_function_name(
|
|
598
|
+
&self,
|
|
599
|
+
code: Bytes,
|
|
600
|
+
calldata: Option<Bytes>,
|
|
601
|
+
) -> (String, Option<String>) {
|
|
602
|
+
let (sender, receiver) = channel();
|
|
603
|
+
|
|
604
|
+
let status = self.get_contract_and_function_name_fn.call(
|
|
605
|
+
ContractAndFunctionNameCall {
|
|
606
|
+
code,
|
|
607
|
+
calldata,
|
|
608
|
+
sender,
|
|
609
|
+
},
|
|
610
|
+
ThreadsafeFunctionCallMode::Blocking,
|
|
611
|
+
);
|
|
612
|
+
assert_eq!(status, Status::Ok);
|
|
613
|
+
|
|
614
|
+
receiver
|
|
615
|
+
.recv()
|
|
616
|
+
.unwrap()
|
|
617
|
+
.expect("Failed call to get_contract_and_function_name")
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
fn format(&self, message: impl ToString) -> String {
|
|
621
|
+
let message = message.to_string();
|
|
622
|
+
|
|
623
|
+
if message.is_empty() {
|
|
624
|
+
message
|
|
625
|
+
} else {
|
|
626
|
+
message
|
|
627
|
+
.split('\n')
|
|
628
|
+
.map(|line| format!("{:indent$}{line}", "", indent = self.indentation))
|
|
629
|
+
.collect::<Vec<_>>()
|
|
630
|
+
.join("\n")
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
fn indented(&mut self, display_fn: impl FnOnce(&mut Self)) {
|
|
635
|
+
self.indentation += 2;
|
|
636
|
+
display_fn(self);
|
|
637
|
+
self.indentation -= 2;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
fn try_indented(
|
|
641
|
+
&mut self,
|
|
642
|
+
display_fn: impl FnOnce(&mut Self) -> Result<(), LoggerError>,
|
|
643
|
+
) -> Result<(), LoggerError> {
|
|
644
|
+
self.indentation += 2;
|
|
645
|
+
let result = display_fn(self);
|
|
646
|
+
self.indentation -= 2;
|
|
647
|
+
|
|
648
|
+
result
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
fn log(&mut self, message: impl ToString) {
|
|
652
|
+
let formatted = self.format(message);
|
|
653
|
+
|
|
654
|
+
self.logs.push(LogLine::Single(formatted));
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
fn log_auto_mined_block_results(
|
|
658
|
+
&mut self,
|
|
659
|
+
spec_id: edr_eth::SpecId,
|
|
660
|
+
results: &[edr_provider::DebugMineBlockResult<BlockchainError>],
|
|
661
|
+
sent_transaction_hash: &B256,
|
|
662
|
+
) {
|
|
663
|
+
for result in results {
|
|
664
|
+
self.log_block_from_auto_mine(spec_id, result, sent_transaction_hash);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
fn log_base_fee(&mut self, base_fee: Option<&U256>) {
|
|
669
|
+
if let Some(base_fee) = base_fee {
|
|
670
|
+
self.log(format!("Base fee: {base_fee}"));
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
fn log_block_from_auto_mine(
|
|
675
|
+
&mut self,
|
|
676
|
+
spec_id: edr_eth::SpecId,
|
|
677
|
+
result: &edr_provider::DebugMineBlockResult<BlockchainError>,
|
|
678
|
+
transaction_hash_to_highlight: &edr_eth::B256,
|
|
679
|
+
) {
|
|
680
|
+
let edr_provider::DebugMineBlockResult {
|
|
681
|
+
block,
|
|
682
|
+
transaction_results,
|
|
683
|
+
transaction_traces,
|
|
684
|
+
console_log_inputs,
|
|
685
|
+
} = result;
|
|
686
|
+
|
|
687
|
+
let transactions = block.transactions();
|
|
688
|
+
let num_transactions = transactions.len();
|
|
689
|
+
|
|
690
|
+
debug_assert_eq!(num_transactions, transaction_results.len());
|
|
691
|
+
debug_assert_eq!(num_transactions, transaction_traces.len());
|
|
692
|
+
|
|
693
|
+
let block_header = block.header();
|
|
694
|
+
|
|
695
|
+
self.indented(|logger| {
|
|
696
|
+
logger.log_block_id(block);
|
|
697
|
+
|
|
698
|
+
logger.indented(|logger| {
|
|
699
|
+
logger.log_base_fee(block_header.base_fee_per_gas.as_ref());
|
|
700
|
+
|
|
701
|
+
for (idx, transaction, result, trace) in izip!(
|
|
702
|
+
0..num_transactions,
|
|
703
|
+
transactions,
|
|
704
|
+
transaction_results,
|
|
705
|
+
transaction_traces
|
|
706
|
+
) {
|
|
707
|
+
let should_highlight_hash =
|
|
708
|
+
*transaction.hash() == *transaction_hash_to_highlight;
|
|
709
|
+
logger.log_block_transaction(
|
|
710
|
+
spec_id,
|
|
711
|
+
transaction,
|
|
712
|
+
result,
|
|
713
|
+
trace,
|
|
714
|
+
console_log_inputs,
|
|
715
|
+
should_highlight_hash,
|
|
716
|
+
);
|
|
717
|
+
|
|
718
|
+
logger.log_empty_line_between_transactions(idx, num_transactions);
|
|
719
|
+
}
|
|
720
|
+
});
|
|
721
|
+
});
|
|
722
|
+
|
|
723
|
+
self.log_empty_line();
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
fn log_block_hash(&mut self, block: &dyn SyncBlock<Error = BlockchainError>) {
|
|
727
|
+
let block_hash = block.hash();
|
|
728
|
+
|
|
729
|
+
self.log(format!("Block: {block_hash}"));
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
fn log_block_id(&mut self, block: &dyn SyncBlock<Error = BlockchainError>) {
|
|
733
|
+
let block_number = block.header().number;
|
|
734
|
+
let block_hash = block.hash();
|
|
735
|
+
|
|
736
|
+
self.log(format!("Block #{block_number}: {block_hash}"));
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
fn log_block_number(&mut self, block: &dyn SyncBlock<Error = BlockchainError>) {
|
|
740
|
+
let block_number = block.header().number;
|
|
741
|
+
|
|
742
|
+
self.log(format!("Mined block #{block_number}"));
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
/// Logs a transaction that's part of a block.
|
|
746
|
+
fn log_block_transaction(
|
|
747
|
+
&mut self,
|
|
748
|
+
spec_id: edr_eth::SpecId,
|
|
749
|
+
transaction: &edr_evm::ExecutableTransaction,
|
|
750
|
+
result: &edr_evm::ExecutionResult,
|
|
751
|
+
trace: &edr_evm::trace::Trace,
|
|
752
|
+
console_log_inputs: &[Bytes],
|
|
753
|
+
should_highlight_hash: bool,
|
|
754
|
+
) {
|
|
755
|
+
let transaction_hash = transaction.hash();
|
|
756
|
+
if should_highlight_hash {
|
|
757
|
+
self.log_with_title(
|
|
758
|
+
"Transaction",
|
|
759
|
+
Style::new().bold().paint(transaction_hash.to_string()),
|
|
760
|
+
);
|
|
761
|
+
} else {
|
|
762
|
+
self.log_with_title("Transaction", transaction_hash.to_string());
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
self.indented(|logger| {
|
|
766
|
+
logger.log_contract_and_function_name::<false>(spec_id, trace);
|
|
767
|
+
logger.log_with_title("From", format!("0x{:x}", transaction.caller()));
|
|
768
|
+
if let Some(to) = transaction.to() {
|
|
769
|
+
logger.log_with_title("To", format!("0x{to:x}"));
|
|
770
|
+
}
|
|
771
|
+
logger.log_with_title("Value", wei_to_human_readable(transaction.value()));
|
|
772
|
+
logger.log_with_title(
|
|
773
|
+
"Gas used",
|
|
774
|
+
format!(
|
|
775
|
+
"{gas_used} of {gas_limit}",
|
|
776
|
+
gas_used = result.gas_used(),
|
|
777
|
+
gas_limit = transaction.gas_limit()
|
|
778
|
+
),
|
|
779
|
+
);
|
|
780
|
+
|
|
781
|
+
logger.log_console_log_messages(console_log_inputs);
|
|
782
|
+
|
|
783
|
+
let transaction_failure = edr_provider::TransactionFailure::from_execution_result(
|
|
784
|
+
result,
|
|
785
|
+
transaction_hash,
|
|
786
|
+
trace,
|
|
787
|
+
);
|
|
788
|
+
|
|
789
|
+
if let Some(transaction_failure) = transaction_failure {
|
|
790
|
+
logger.log_transaction_failure(&transaction_failure);
|
|
791
|
+
}
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
fn log_console_log_messages(&mut self, console_log_inputs: &[Bytes]) {
|
|
796
|
+
let (sender, receiver) = channel();
|
|
797
|
+
|
|
798
|
+
let status = self.decode_console_log_inputs_fn.call(
|
|
799
|
+
DecodeConsoleLogInputsCall {
|
|
800
|
+
inputs: console_log_inputs.to_vec(),
|
|
801
|
+
sender,
|
|
802
|
+
},
|
|
803
|
+
ThreadsafeFunctionCallMode::Blocking,
|
|
804
|
+
);
|
|
805
|
+
assert_eq!(status, Status::Ok);
|
|
806
|
+
|
|
807
|
+
let console_log_inputs = receiver
|
|
808
|
+
.recv()
|
|
809
|
+
.unwrap()
|
|
810
|
+
.expect("Failed call to decode_console_log_inputs");
|
|
811
|
+
// This is a special case, as we always want to print the console.log messages.
|
|
812
|
+
// The difference is how. If we have a logger, we should use that, so that logs
|
|
813
|
+
// are printed in order. If we don't, we just print the messages here.
|
|
814
|
+
if self.is_enabled {
|
|
815
|
+
if !console_log_inputs.is_empty() {
|
|
816
|
+
self.log_empty_line();
|
|
817
|
+
self.log("console.log:");
|
|
818
|
+
|
|
819
|
+
self.indented(|logger| {
|
|
820
|
+
for input in console_log_inputs {
|
|
821
|
+
logger.log(input);
|
|
822
|
+
}
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
} else {
|
|
826
|
+
for input in console_log_inputs {
|
|
827
|
+
let status = self
|
|
828
|
+
.print_line_fn
|
|
829
|
+
.call((input, false), ThreadsafeFunctionCallMode::Blocking);
|
|
830
|
+
|
|
831
|
+
assert_eq!(status, napi::Status::Ok);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
fn log_contract_and_function_name<const PRINT_INVALID_CONTRACT_WARNING: bool>(
|
|
837
|
+
&mut self,
|
|
838
|
+
spec_id: edr_eth::SpecId,
|
|
839
|
+
trace: &edr_evm::trace::Trace,
|
|
840
|
+
) {
|
|
841
|
+
if let Some(TraceMessage::Before(before_message)) = trace.messages.first() {
|
|
842
|
+
if let Some(to) = before_message.to {
|
|
843
|
+
// Call
|
|
844
|
+
let is_precompile = {
|
|
845
|
+
let num_precompiles =
|
|
846
|
+
Precompiles::new(precompile::SpecId::from_spec_id(spec_id)).len();
|
|
847
|
+
precompile::is_precompile(to, num_precompiles)
|
|
848
|
+
};
|
|
849
|
+
|
|
850
|
+
if is_precompile {
|
|
851
|
+
let precompile = u16::from_be_bytes([to[18], to[19]]);
|
|
852
|
+
self.log_with_title(
|
|
853
|
+
"Precompile call",
|
|
854
|
+
format!("<PrecompileContract {precompile}>"),
|
|
855
|
+
);
|
|
856
|
+
} else {
|
|
857
|
+
let is_code_empty = before_message
|
|
858
|
+
.code
|
|
859
|
+
.as_ref()
|
|
860
|
+
.map_or(true, edr_evm::Bytecode::is_empty);
|
|
861
|
+
|
|
862
|
+
if is_code_empty {
|
|
863
|
+
if PRINT_INVALID_CONTRACT_WARNING {
|
|
864
|
+
self.log("WARNING: Calling an account which is not a contract");
|
|
865
|
+
}
|
|
866
|
+
} else {
|
|
867
|
+
let (contract_name, function_name) = self.contract_and_function_name(
|
|
868
|
+
before_message
|
|
869
|
+
.code
|
|
870
|
+
.as_ref()
|
|
871
|
+
.map(edr_evm::Bytecode::original_bytes)
|
|
872
|
+
.expect("Call must be defined"),
|
|
873
|
+
Some(before_message.data.clone()),
|
|
874
|
+
);
|
|
875
|
+
|
|
876
|
+
let function_name = function_name.expect("Function name must be defined");
|
|
877
|
+
self.log_with_title(
|
|
878
|
+
"Contract call",
|
|
879
|
+
if function_name.is_empty() {
|
|
880
|
+
contract_name
|
|
881
|
+
} else {
|
|
882
|
+
format!("{contract_name}#{function_name}")
|
|
883
|
+
},
|
|
884
|
+
);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
} else {
|
|
888
|
+
let result = if let Some(TraceMessage::After(result)) = trace.messages.last() {
|
|
889
|
+
result
|
|
890
|
+
} else {
|
|
891
|
+
unreachable!("Before messages must have an after message")
|
|
892
|
+
};
|
|
893
|
+
|
|
894
|
+
// Create
|
|
895
|
+
let (contract_name, _) =
|
|
896
|
+
self.contract_and_function_name(before_message.data.clone(), None);
|
|
897
|
+
|
|
898
|
+
self.log_with_title("Contract deployment", contract_name);
|
|
899
|
+
|
|
900
|
+
if let ExecutionResult::Success { output, .. } = result {
|
|
901
|
+
if let edr_evm::Output::Create(_, address) = output {
|
|
902
|
+
if let Some(deployed_address) = address {
|
|
903
|
+
self.log_with_title(
|
|
904
|
+
"Contract address",
|
|
905
|
+
format!("0x{deployed_address:x}"),
|
|
906
|
+
);
|
|
907
|
+
}
|
|
908
|
+
} else {
|
|
909
|
+
unreachable!("Create calls must return a Create output")
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
fn log_empty_block(&mut self, block: &dyn SyncBlock<Error = BlockchainError>) {
|
|
917
|
+
let block_header = block.header();
|
|
918
|
+
let block_number = block_header.number;
|
|
919
|
+
|
|
920
|
+
let base_fee = if let Some(base_fee) = block_header.base_fee_per_gas.as_ref() {
|
|
921
|
+
format!(" with base fee {base_fee}")
|
|
922
|
+
} else {
|
|
923
|
+
String::new()
|
|
924
|
+
};
|
|
925
|
+
|
|
926
|
+
self.log(format!("Mined empty block #{block_number}{base_fee}",));
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
fn log_empty_line(&mut self) {
|
|
930
|
+
self.log("");
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
fn log_empty_line_between_transactions(&mut self, idx: usize, num_transactions: usize) {
|
|
934
|
+
if num_transactions > 1 && idx < num_transactions - 1 {
|
|
935
|
+
self.log_empty_line();
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
fn log_hardhat_mined_empty_block(
|
|
940
|
+
&mut self,
|
|
941
|
+
block: &dyn SyncBlock<Error = BlockchainError>,
|
|
942
|
+
empty_blocks_range_start: Option<u64>,
|
|
943
|
+
) {
|
|
944
|
+
self.indented(|logger| {
|
|
945
|
+
if let Some(empty_blocks_range_start) = empty_blocks_range_start {
|
|
946
|
+
logger.replace_last_log_line(format!(
|
|
947
|
+
"Mined empty block range #{empty_blocks_range_start} to #{block_number}",
|
|
948
|
+
block_number = block.header().number
|
|
949
|
+
));
|
|
950
|
+
} else {
|
|
951
|
+
logger.log_empty_block(block);
|
|
952
|
+
}
|
|
953
|
+
});
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
/// Logs the result of interval mining a block.
|
|
957
|
+
fn log_interval_mined_block(
|
|
958
|
+
&mut self,
|
|
959
|
+
spec_id: edr_eth::SpecId,
|
|
960
|
+
result: &edr_provider::DebugMineBlockResult<BlockchainError>,
|
|
961
|
+
) {
|
|
962
|
+
let edr_provider::DebugMineBlockResult {
|
|
963
|
+
block,
|
|
964
|
+
transaction_results,
|
|
965
|
+
transaction_traces,
|
|
966
|
+
console_log_inputs,
|
|
967
|
+
} = result;
|
|
968
|
+
|
|
969
|
+
let transactions = block.transactions();
|
|
970
|
+
let num_transactions = transactions.len();
|
|
971
|
+
|
|
972
|
+
debug_assert_eq!(num_transactions, transaction_results.len());
|
|
973
|
+
debug_assert_eq!(num_transactions, transaction_traces.len());
|
|
974
|
+
|
|
975
|
+
let block_header = block.header();
|
|
976
|
+
|
|
977
|
+
self.indented(|logger| {
|
|
978
|
+
logger.log_block_hash(block);
|
|
979
|
+
|
|
980
|
+
logger.indented(|logger| {
|
|
981
|
+
logger.log_base_fee(block_header.base_fee_per_gas.as_ref());
|
|
982
|
+
|
|
983
|
+
for (idx, transaction, result, trace) in izip!(
|
|
984
|
+
0..num_transactions,
|
|
985
|
+
transactions,
|
|
986
|
+
transaction_results,
|
|
987
|
+
transaction_traces
|
|
988
|
+
) {
|
|
989
|
+
logger.log_block_transaction(
|
|
990
|
+
spec_id,
|
|
991
|
+
transaction,
|
|
992
|
+
result,
|
|
993
|
+
trace,
|
|
994
|
+
console_log_inputs,
|
|
995
|
+
false,
|
|
996
|
+
);
|
|
997
|
+
|
|
998
|
+
logger.log_empty_line_between_transactions(idx, num_transactions);
|
|
999
|
+
}
|
|
1000
|
+
});
|
|
1001
|
+
});
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
fn log_hardhat_mined_block(
|
|
1005
|
+
&mut self,
|
|
1006
|
+
spec_id: edr_eth::SpecId,
|
|
1007
|
+
result: &edr_provider::DebugMineBlockResult<BlockchainError>,
|
|
1008
|
+
) {
|
|
1009
|
+
let edr_provider::DebugMineBlockResult {
|
|
1010
|
+
block,
|
|
1011
|
+
transaction_results,
|
|
1012
|
+
transaction_traces,
|
|
1013
|
+
console_log_inputs,
|
|
1014
|
+
} = result;
|
|
1015
|
+
|
|
1016
|
+
let transactions = block.transactions();
|
|
1017
|
+
let num_transactions = transactions.len();
|
|
1018
|
+
|
|
1019
|
+
debug_assert_eq!(num_transactions, transaction_results.len());
|
|
1020
|
+
debug_assert_eq!(num_transactions, transaction_traces.len());
|
|
1021
|
+
|
|
1022
|
+
self.indented(|logger| {
|
|
1023
|
+
if transactions.is_empty() {
|
|
1024
|
+
logger.log_empty_block(block);
|
|
1025
|
+
} else {
|
|
1026
|
+
logger.log_block_number(block);
|
|
1027
|
+
|
|
1028
|
+
logger.indented(|logger| {
|
|
1029
|
+
logger.log_block_hash(block);
|
|
1030
|
+
|
|
1031
|
+
logger.indented(|logger| {
|
|
1032
|
+
logger.log_base_fee(block.header().base_fee_per_gas.as_ref());
|
|
1033
|
+
|
|
1034
|
+
for (idx, transaction, result, trace) in izip!(
|
|
1035
|
+
0..num_transactions,
|
|
1036
|
+
transactions,
|
|
1037
|
+
transaction_results,
|
|
1038
|
+
transaction_traces
|
|
1039
|
+
) {
|
|
1040
|
+
logger.log_block_transaction(
|
|
1041
|
+
spec_id,
|
|
1042
|
+
transaction,
|
|
1043
|
+
result,
|
|
1044
|
+
trace,
|
|
1045
|
+
console_log_inputs,
|
|
1046
|
+
false,
|
|
1047
|
+
);
|
|
1048
|
+
|
|
1049
|
+
logger.log_empty_line_between_transactions(idx, num_transactions);
|
|
1050
|
+
}
|
|
1051
|
+
});
|
|
1052
|
+
});
|
|
1053
|
+
}
|
|
1054
|
+
});
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
/// Logs a warning about multiple blocks being mined.
|
|
1058
|
+
fn log_multiple_blocks_warning(&mut self) {
|
|
1059
|
+
self.indented(|logger| {
|
|
1060
|
+
logger
|
|
1061
|
+
.log("There were other pending transactions. More than one block had to be mined:");
|
|
1062
|
+
});
|
|
1063
|
+
self.log_empty_line();
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
/// Logs a warning about multiple transactions being mined.
|
|
1067
|
+
fn log_multiple_transactions_warning(&mut self) {
|
|
1068
|
+
self.indented(|logger| {
|
|
1069
|
+
logger.log("There were other pending transactions mined in the same block:");
|
|
1070
|
+
});
|
|
1071
|
+
self.log_empty_line();
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
fn log_with_title(&mut self, title: impl Into<String>, message: impl Display) {
|
|
1075
|
+
// repeat whitespace self.indentation times and concatenate with title
|
|
1076
|
+
let title = format!("{:indent$}{}", "", title.into(), indent = self.indentation);
|
|
1077
|
+
if title.len() > self.title_length {
|
|
1078
|
+
self.title_length = title.len();
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
let message = format!("{message}");
|
|
1082
|
+
self.logs.push(LogLine::WithTitle(title, message));
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
fn log_currently_sent_transaction(
|
|
1086
|
+
&mut self,
|
|
1087
|
+
spec_id: edr_eth::SpecId,
|
|
1088
|
+
block_result: &edr_provider::DebugMineBlockResult<BlockchainError>,
|
|
1089
|
+
transaction: &ExecutableTransaction,
|
|
1090
|
+
transaction_result: &edr_evm::ExecutionResult,
|
|
1091
|
+
trace: &edr_evm::trace::Trace,
|
|
1092
|
+
) {
|
|
1093
|
+
self.indented(|logger| {
|
|
1094
|
+
logger.log("Currently sent transaction:");
|
|
1095
|
+
logger.log("");
|
|
1096
|
+
});
|
|
1097
|
+
|
|
1098
|
+
self.log_transaction(
|
|
1099
|
+
spec_id,
|
|
1100
|
+
block_result,
|
|
1101
|
+
transaction,
|
|
1102
|
+
transaction_result,
|
|
1103
|
+
trace,
|
|
1104
|
+
);
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
fn log_single_transaction_mining_result(
|
|
1108
|
+
&mut self,
|
|
1109
|
+
spec_id: edr_eth::SpecId,
|
|
1110
|
+
result: &edr_provider::DebugMineBlockResult<BlockchainError>,
|
|
1111
|
+
transaction: &ExecutableTransaction,
|
|
1112
|
+
) {
|
|
1113
|
+
let trace = result
|
|
1114
|
+
.transaction_traces
|
|
1115
|
+
.first()
|
|
1116
|
+
.expect("A transaction exists, so the trace must exist as well.");
|
|
1117
|
+
|
|
1118
|
+
let transaction_result = result
|
|
1119
|
+
.transaction_results
|
|
1120
|
+
.first()
|
|
1121
|
+
.expect("A transaction exists, so the result must exist as well.");
|
|
1122
|
+
|
|
1123
|
+
self.log_transaction(spec_id, result, transaction, transaction_result, trace);
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
fn log_transaction(
|
|
1127
|
+
&mut self,
|
|
1128
|
+
spec_id: edr_eth::SpecId,
|
|
1129
|
+
block_result: &edr_provider::DebugMineBlockResult<BlockchainError>,
|
|
1130
|
+
transaction: &ExecutableTransaction,
|
|
1131
|
+
transaction_result: &edr_evm::ExecutionResult,
|
|
1132
|
+
trace: &edr_evm::trace::Trace,
|
|
1133
|
+
) {
|
|
1134
|
+
self.indented(|logger| {
|
|
1135
|
+
logger.log_contract_and_function_name::<false>(spec_id, trace);
|
|
1136
|
+
|
|
1137
|
+
let transaction_hash = transaction.hash();
|
|
1138
|
+
logger.log_with_title("Transaction", transaction_hash);
|
|
1139
|
+
|
|
1140
|
+
logger.log_with_title("From", format!("0x{:x}", transaction.caller()));
|
|
1141
|
+
if let Some(to) = transaction.to() {
|
|
1142
|
+
logger.log_with_title("To", format!("0x{to:x}"));
|
|
1143
|
+
}
|
|
1144
|
+
logger.log_with_title("Value", wei_to_human_readable(transaction.value()));
|
|
1145
|
+
logger.log_with_title(
|
|
1146
|
+
"Gas used",
|
|
1147
|
+
format!(
|
|
1148
|
+
"{gas_used} of {gas_limit}",
|
|
1149
|
+
gas_used = transaction_result.gas_used(),
|
|
1150
|
+
gas_limit = transaction.gas_limit()
|
|
1151
|
+
),
|
|
1152
|
+
);
|
|
1153
|
+
|
|
1154
|
+
let block_number = block_result.block.header().number;
|
|
1155
|
+
logger.log_with_title(format!("Block #{block_number}"), block_result.block.hash());
|
|
1156
|
+
|
|
1157
|
+
logger.log_console_log_messages(&block_result.console_log_inputs);
|
|
1158
|
+
|
|
1159
|
+
let transaction_failure = edr_provider::TransactionFailure::from_execution_result(
|
|
1160
|
+
transaction_result,
|
|
1161
|
+
transaction_hash,
|
|
1162
|
+
trace,
|
|
1163
|
+
);
|
|
1164
|
+
|
|
1165
|
+
if let Some(transaction_failure) = transaction_failure {
|
|
1166
|
+
logger.log_transaction_failure(&transaction_failure);
|
|
1167
|
+
}
|
|
1168
|
+
});
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
fn print<const REPLACE: bool>(&mut self, message: impl ToString) -> Result<(), LoggerError> {
|
|
1172
|
+
if !self.is_enabled {
|
|
1173
|
+
return Ok(());
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
let formatted = self.format(message);
|
|
1177
|
+
|
|
1178
|
+
let status = self
|
|
1179
|
+
.print_line_fn
|
|
1180
|
+
.call((formatted, REPLACE), ThreadsafeFunctionCallMode::Blocking);
|
|
1181
|
+
|
|
1182
|
+
if status == napi::Status::Ok {
|
|
1183
|
+
Ok(())
|
|
1184
|
+
} else {
|
|
1185
|
+
Err(LoggerError::PrintLine)
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
fn print_empty_line(&mut self) -> Result<(), LoggerError> {
|
|
1190
|
+
self.print::<false>("")
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
fn print_logs(&mut self) -> Result<bool, LoggerError> {
|
|
1194
|
+
let logs = std::mem::take(&mut self.logs);
|
|
1195
|
+
if logs.is_empty() {
|
|
1196
|
+
return Ok(false);
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
for log in logs {
|
|
1200
|
+
let line = match log {
|
|
1201
|
+
LogLine::Single(message) => message,
|
|
1202
|
+
LogLine::WithTitle(title, message) => {
|
|
1203
|
+
let title = format!("{title}:");
|
|
1204
|
+
format!("{title:indent$} {message}", indent = self.title_length + 1)
|
|
1205
|
+
}
|
|
1206
|
+
};
|
|
1207
|
+
|
|
1208
|
+
self.print::<false>(line)?;
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
Ok(true)
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
fn print_method(&mut self, method: &str) -> Result<(), LoggerError> {
|
|
1215
|
+
if let Some(collapsed_method) = self.collapsed_method(method) {
|
|
1216
|
+
collapsed_method.count += 1;
|
|
1217
|
+
|
|
1218
|
+
let line = format!("{method} ({count})", count = collapsed_method.count);
|
|
1219
|
+
self.print::<true>(Color::Green.paint(line))
|
|
1220
|
+
} else {
|
|
1221
|
+
self.state = LoggingState::CollapsingMethod(CollapsedMethod {
|
|
1222
|
+
count: 1,
|
|
1223
|
+
method: method.to_string(),
|
|
1224
|
+
});
|
|
1225
|
+
|
|
1226
|
+
self.print::<false>(Color::Green.paint(method))
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
/// Retrieves the collapsed method with the provided name, if it exists.
|
|
1231
|
+
fn collapsed_method(&mut self, method: &str) -> Option<&mut CollapsedMethod> {
|
|
1232
|
+
if let LoggingState::CollapsingMethod(collapsed_method) = &mut self.state {
|
|
1233
|
+
if collapsed_method.method == method {
|
|
1234
|
+
return Some(collapsed_method);
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
None
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
fn replace_last_log_line(&mut self, message: impl ToString) {
|
|
1242
|
+
let formatted = self.format(message);
|
|
1243
|
+
|
|
1244
|
+
*self.logs.last_mut().expect("There must be a log line") = LogLine::Single(formatted);
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
fn wei_to_human_readable(wei: U256) -> String {
|
|
1249
|
+
if wei == U256::ZERO {
|
|
1250
|
+
"0 ETH".to_string()
|
|
1251
|
+
} else if wei < U256::from(100_000u64) {
|
|
1252
|
+
format!("{wei} wei")
|
|
1253
|
+
} else if wei < U256::from(100_000_000_000_000u64) {
|
|
1254
|
+
let mut decimal = to_decimal_string(wei, 9);
|
|
1255
|
+
decimal.push_str(" gwei");
|
|
1256
|
+
decimal
|
|
1257
|
+
} else {
|
|
1258
|
+
let mut decimal = to_decimal_string(wei, 18);
|
|
1259
|
+
decimal.push_str(" ETH");
|
|
1260
|
+
decimal
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
/// Converts the provided `value` to a decimal string after dividing it by
|
|
1265
|
+
/// `10^exponent`. The returned string will have at most `MAX_DECIMALS`
|
|
1266
|
+
/// decimals.
|
|
1267
|
+
fn to_decimal_string(value: U256, exponent: u8) -> String {
|
|
1268
|
+
const MAX_DECIMALS: u8 = 4;
|
|
1269
|
+
|
|
1270
|
+
let (integer, remainder) = value.div_rem(U256::from(10).pow(U256::from(exponent)));
|
|
1271
|
+
let decimal = remainder / U256::from(10).pow(U256::from(exponent - MAX_DECIMALS));
|
|
1272
|
+
|
|
1273
|
+
// Remove trailing zeros
|
|
1274
|
+
let decimal = decimal.to_string().trim_end_matches('0').to_string();
|
|
1275
|
+
|
|
1276
|
+
format!("{integer}.{decimal}")
|
|
1277
|
+
}
|