@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.
package/index.d.ts CHANGED
@@ -149,8 +149,6 @@ export interface LoggerConfig {
149
149
  /** Whether to enable the logger. */
150
150
  enable: boolean
151
151
  decodeConsoleLogInputsCallback: (inputs: Buffer[]) => string[]
152
- /** Used to resolve the contract and function name when logging. */
153
- getContractAndFunctionNameCallback: (code: Buffer, calldata?: Buffer) => ContractAndFunctionName
154
152
  printLineCallback: (message: string, replace: boolean) => void
155
153
  }
156
154
  /** Configuration for a chain */
@@ -346,18 +344,7 @@ export interface SubscriptionEvent {
346
344
  filterId: bigint
347
345
  result: any
348
346
  }
349
- export declare function createModelsAndDecodeBytecodes(solcVersion: string, compilerInput: any, compilerOutput: any): Array<BytecodeWrapper>
350
347
  export declare function linkHexStringBytecode(code: string, address: string, position: number): string
351
- export const enum ContractFunctionType {
352
- CONSTRUCTOR = 0,
353
- FUNCTION = 1,
354
- FALLBACK = 2,
355
- RECEIVE = 3,
356
- GETTER = 4,
357
- MODIFIER = 5,
358
- FREE_FUNCTION = 6
359
- }
360
- export declare function printMessageTrace(trace: PrecompileMessageTrace | CallMessageTrace | CreateMessageTrace, depth?: number | undefined | null): void
361
348
  export declare function printStackTrace(trace: SolidityStackTrace): void
362
349
  /** Represents the exit code of the EVM. */
363
350
  export const enum ExitCode {
@@ -376,53 +363,18 @@ export const enum ExitCode {
376
363
  /** Create init code size exceeds limit (runtime). */
377
364
  CODESIZE_EXCEEDS_MAXIMUM = 6,
378
365
  /** Create collision. */
379
- CREATE_COLLISION = 7
380
- }
381
- export interface EvmStep {
382
- pc: number
366
+ CREATE_COLLISION = 7,
367
+ /** Unknown halt reason. */
368
+ UNKNOWN_HALT_REASON = 8
383
369
  }
384
- export interface PrecompileMessageTrace {
385
- value: bigint
386
- returnData: Uint8Array
387
- exit: Exit
388
- gasUsed: bigint
389
- depth: number
390
- precompile: number
391
- calldata: Uint8Array
392
- }
393
- export interface CreateMessageTrace {
394
- value: bigint
395
- returnData: Uint8Array
396
- exit: Exit
397
- gasUsed: bigint
398
- depth: number
399
- code: Uint8Array
400
- steps: Array<EvmStep | PrecompileMessageTrace | CallMessageTrace | CreateMessageTrace>
401
- /**
402
- * Reference to the resolved `Bytecode` EDR data.
403
- * Only used on the JS side by the `VmTraceDecoder` class.
404
- */
405
- bytecode?: BytecodeWrapper
406
- numberOfSubtraces: number
407
- deployedContract?: Uint8Array | undefined
408
- }
409
- export interface CallMessageTrace {
410
- value: bigint
411
- returnData: Uint8Array
412
- exit: Exit
413
- gasUsed: bigint
414
- depth: number
415
- code: Uint8Array
416
- steps: Array<EvmStep | PrecompileMessageTrace | CallMessageTrace | CreateMessageTrace>
417
- /**
418
- * Reference to the resolved `Bytecode` EDR data.
419
- * Only used on the JS side by the `VmTraceDecoder` class.
420
- */
421
- bytecode?: BytecodeWrapper
422
- numberOfSubtraces: number
423
- calldata: Uint8Array
424
- address: Uint8Array
425
- codeAddress: Uint8Array
370
+ export const enum ContractFunctionType {
371
+ CONSTRUCTOR = 0,
372
+ FUNCTION = 1,
373
+ FALLBACK = 2,
374
+ RECEIVE = 3,
375
+ GETTER = 4,
376
+ MODIFIER = 5,
377
+ FREE_FUNCTION = 6
426
378
  }
427
379
  export const enum StackTraceEntryType {
428
380
  CALLSTACK_ENTRY = 0,
@@ -578,11 +530,6 @@ export interface ContractCallRunOutOfGasError {
578
530
  type: StackTraceEntryType.CONTRACT_CALL_RUN_OUT_OF_GAS_ERROR
579
531
  sourceReference?: SourceReference
580
532
  }
581
- export interface ContractAndFunctionName {
582
- contractName: string
583
- functionName: string | undefined
584
- }
585
- export declare function initializeVmTraceDecoder(vmTraceDecoder: VmTraceDecoder, tracingConfig: any): void
586
533
  export interface TracingMessage {
587
534
  /** Sender address */
588
535
  readonly caller: Buffer
@@ -626,6 +573,11 @@ export interface TracingMessageResult {
626
573
  /** Execution result */
627
574
  readonly executionResult: ExecutionResult
628
575
  }
576
+ /**
577
+ * Returns the latest version of solc that EDR officially
578
+ * supports and is tested against.
579
+ */
580
+ export declare function getLatestSupportedSolcVersion(): string
629
581
  export interface Withdrawal {
630
582
  /** The index of withdrawal */
631
583
  index: bigint
@@ -643,7 +595,7 @@ export declare class EdrContext {
643
595
  /** A JSON-RPC provider for Ethereum. */
644
596
  export declare class Provider {
645
597
  /**Constructs a new provider with the provided configuration. */
646
- static withConfig(context: EdrContext, config: ProviderConfig, loggerConfig: LoggerConfig, subscriberCallback: (event: SubscriptionEvent) => void): Promise<Provider>
598
+ static withConfig(context: EdrContext, config: ProviderConfig, loggerConfig: LoggerConfig, tracingConfig: any, subscriberCallback: (event: SubscriptionEvent) => void): Promise<Provider>
647
599
  /**Handles a JSON-RPC request and returns a JSON-RPC response. */
648
600
  handleRequest(jsonRequest: string): Promise<Response>
649
601
  setCallOverrideCallback(callOverrideCallback: (contract_address: Buffer, data: Buffer) => Promise<CallOverrideResult | undefined>): void
@@ -658,19 +610,20 @@ export declare class Provider {
658
610
  export declare class Response {
659
611
  /** Returns the response data as a JSON string or a JSON object. */
660
612
  get data(): string | any
661
- get solidityTrace(): RawTrace | null
662
613
  get traces(): Array<RawTrace>
614
+ /**Compute the error stack trace. Return the stack trace if it can be decoded, otherwise returns none. Throws if there was an error computing the stack trace. */
615
+ stackTrace(): SolidityStackTrace | null
663
616
  }
664
- /**
665
- * Opaque handle to the `Bytecode` struct.
666
- * Only used on the JS side by the `VmTraceDecoder` class.
667
- */
668
- export declare class BytecodeWrapper { }
669
617
  export declare class Exit {
670
618
  get kind(): ExitCode
671
619
  isError(): boolean
672
620
  getReason(): string
673
621
  }
622
+ /**
623
+ * Opaque handle to the `Bytecode` struct.
624
+ * Only used on the JS side by the `VmTraceDecoder` class.
625
+ */
626
+ export declare class BytecodeWrapper { }
674
627
  export declare class ReturnData {
675
628
  readonly value: Uint8Array
676
629
  constructor(value: Uint8Array)
@@ -680,26 +633,6 @@ export declare class ReturnData {
680
633
  decodeError(): string
681
634
  decodePanic(): bigint
682
635
  }
683
- export declare class SolidityTracer {
684
-
685
- constructor()
686
- getStackTrace(trace: PrecompileMessageTrace | CallMessageTrace | CreateMessageTrace): SolidityStackTrace
687
- }
688
- export declare class VmTraceDecoder {
689
- constructor()
690
- addBytecode(bytecode: BytecodeWrapper): void
691
- tryToDecodeMessageTrace(messageTrace: PrecompileMessageTrace | CallMessageTrace | CreateMessageTrace): PrecompileMessageTrace | CallMessageTrace | CreateMessageTrace
692
- getContractAndFunctionNamesForCall(code: Uint8Array, calldata: Uint8Array | undefined): ContractAndFunctionName
693
- }
694
- export type VMTracer = VmTracer
695
- /** N-API bindings for the Rust port of `VMTracer` from Hardhat. */
696
- export declare class VmTracer {
697
- constructor()
698
- /** Observes a trace, collecting information about the execution of the EVM. */
699
- observe(trace: RawTrace): void
700
- getLastTopLevelMessageTrace(): PrecompileMessageTrace | CallMessageTrace | CreateMessageTrace | undefined
701
- getLastError(): Error | undefined
702
- }
703
636
  export declare class RawTrace {
704
637
  trace(): Array<TracingMessage | TracingStep | TracingMessageResult>
705
638
  }
package/index.js CHANGED
@@ -310,7 +310,7 @@ if (!nativeBinding) {
310
310
  throw new Error(`Failed to load native binding`)
311
311
  }
312
312
 
313
- const { SpecId, EdrContext, MineOrdering, Provider, Response, SuccessReason, ExceptionalHalt, createModelsAndDecodeBytecodes, linkHexStringBytecode, BytecodeWrapper, ContractFunctionType, printMessageTrace, printStackTrace, Exit, ExitCode, ReturnData, StackTraceEntryType, stackTraceEntryTypeToString, FALLBACK_FUNCTION_NAME, RECEIVE_FUNCTION_NAME, CONSTRUCTOR_FUNCTION_NAME, UNRECOGNIZED_FUNCTION_NAME, UNKNOWN_FUNCTION_NAME, PRECOMPILE_FUNCTION_NAME, UNRECOGNIZED_CONTRACT_NAME, SolidityTracer, VmTraceDecoder, initializeVmTraceDecoder, VmTracer, RawTrace } = nativeBinding
313
+ const { SpecId, EdrContext, MineOrdering, Provider, Response, SuccessReason, ExceptionalHalt, linkHexStringBytecode, printStackTrace, Exit, ExitCode, BytecodeWrapper, ContractFunctionType, ReturnData, StackTraceEntryType, stackTraceEntryTypeToString, FALLBACK_FUNCTION_NAME, RECEIVE_FUNCTION_NAME, CONSTRUCTOR_FUNCTION_NAME, UNRECOGNIZED_FUNCTION_NAME, UNKNOWN_FUNCTION_NAME, PRECOMPILE_FUNCTION_NAME, UNRECOGNIZED_CONTRACT_NAME, RawTrace, getLatestSupportedSolcVersion } = nativeBinding
314
314
 
315
315
  module.exports.SpecId = SpecId
316
316
  module.exports.EdrContext = EdrContext
@@ -319,14 +319,12 @@ module.exports.Provider = Provider
319
319
  module.exports.Response = Response
320
320
  module.exports.SuccessReason = SuccessReason
321
321
  module.exports.ExceptionalHalt = ExceptionalHalt
322
- module.exports.createModelsAndDecodeBytecodes = createModelsAndDecodeBytecodes
323
322
  module.exports.linkHexStringBytecode = linkHexStringBytecode
324
- module.exports.BytecodeWrapper = BytecodeWrapper
325
- module.exports.ContractFunctionType = ContractFunctionType
326
- module.exports.printMessageTrace = printMessageTrace
327
323
  module.exports.printStackTrace = printStackTrace
328
324
  module.exports.Exit = Exit
329
325
  module.exports.ExitCode = ExitCode
326
+ module.exports.BytecodeWrapper = BytecodeWrapper
327
+ module.exports.ContractFunctionType = ContractFunctionType
330
328
  module.exports.ReturnData = ReturnData
331
329
  module.exports.StackTraceEntryType = StackTraceEntryType
332
330
  module.exports.stackTraceEntryTypeToString = stackTraceEntryTypeToString
@@ -337,8 +335,5 @@ module.exports.UNRECOGNIZED_FUNCTION_NAME = UNRECOGNIZED_FUNCTION_NAME
337
335
  module.exports.UNKNOWN_FUNCTION_NAME = UNKNOWN_FUNCTION_NAME
338
336
  module.exports.PRECOMPILE_FUNCTION_NAME = PRECOMPILE_FUNCTION_NAME
339
337
  module.exports.UNRECOGNIZED_CONTRACT_NAME = UNRECOGNIZED_CONTRACT_NAME
340
- module.exports.SolidityTracer = SolidityTracer
341
- module.exports.VmTraceDecoder = VmTraceDecoder
342
- module.exports.initializeVmTraceDecoder = initializeVmTraceDecoder
343
- module.exports.VmTracer = VmTracer
344
338
  module.exports.RawTrace = RawTrace
339
+ module.exports.getLatestSupportedSolcVersion = getLatestSupportedSolcVersion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nomicfoundation/edr",
3
- "version": "0.6.4",
3
+ "version": "0.7.0",
4
4
  "devDependencies": {
5
5
  "@napi-rs/cli": "^2.18.4",
6
6
  "@types/chai": "^4.2.0",
@@ -52,13 +52,13 @@
52
52
  "repository": "NomicFoundation/edr.git",
53
53
  "types": "index.d.ts",
54
54
  "dependencies": {
55
- "@nomicfoundation/edr-darwin-arm64": "0.6.4",
56
- "@nomicfoundation/edr-darwin-x64": "0.6.4",
57
- "@nomicfoundation/edr-linux-arm64-gnu": "0.6.4",
58
- "@nomicfoundation/edr-linux-arm64-musl": "0.6.4",
59
- "@nomicfoundation/edr-linux-x64-gnu": "0.6.4",
60
- "@nomicfoundation/edr-linux-x64-musl": "0.6.4",
61
- "@nomicfoundation/edr-win32-x64-msvc": "0.6.4"
55
+ "@nomicfoundation/edr-darwin-arm64": "0.7.0",
56
+ "@nomicfoundation/edr-darwin-x64": "0.7.0",
57
+ "@nomicfoundation/edr-linux-arm64-gnu": "0.7.0",
58
+ "@nomicfoundation/edr-linux-arm64-musl": "0.7.0",
59
+ "@nomicfoundation/edr-linux-x64-gnu": "0.7.0",
60
+ "@nomicfoundation/edr-linux-x64-musl": "0.7.0",
61
+ "@nomicfoundation/edr-win32-x64-msvc": "0.7.0"
62
62
  },
63
63
  "scripts": {
64
64
  "artifacts": "napi artifacts",
package/src/context.rs CHANGED
@@ -1,5 +1,6 @@
1
- use std::{io, ops::Deref, sync::Arc};
1
+ use std::{ops::Deref, sync::Arc};
2
2
 
3
+ #[cfg(feature = "tracing")]
3
4
  use napi::Status;
4
5
  use napi_derive::napi;
5
6
  use tracing_subscriber::{prelude::*, EnvFilter, Registry};
@@ -23,8 +24,7 @@ impl EdrContext {
23
24
  #[doc = "Creates a new [`EdrContext`] instance. Should only be called once!"]
24
25
  #[napi(constructor)]
25
26
  pub fn new() -> napi::Result<Self> {
26
- let context =
27
- Context::new().map_err(|e| napi::Error::new(Status::GenericFailure, e.to_string()))?;
27
+ let context = Context::new()?;
28
28
 
29
29
  Ok(Self {
30
30
  inner: Arc::new(context),
@@ -34,14 +34,13 @@ impl EdrContext {
34
34
 
35
35
  #[derive(Debug)]
36
36
  pub struct Context {
37
- _subscriber_guard: tracing::subscriber::DefaultGuard,
38
37
  #[cfg(feature = "tracing")]
39
38
  _tracing_write_guard: tracing_flame::FlushGuard<std::io::BufWriter<std::fs::File>>,
40
39
  }
41
40
 
42
41
  impl Context {
43
42
  /// Creates a new [`Context`] instance. Should only be called once!
44
- pub fn new() -> io::Result<Self> {
43
+ pub fn new() -> napi::Result<Self> {
45
44
  let fmt_layer = tracing_subscriber::fmt::layer()
46
45
  .with_file(true)
47
46
  .with_line_number(true)
@@ -54,8 +53,13 @@ impl Context {
54
53
 
55
54
  #[cfg(feature = "tracing")]
56
55
  let (flame_layer, guard) = {
57
- let (flame_layer, guard) =
58
- tracing_flame::FlameLayer::with_file("tracing.folded").unwrap();
56
+ let (flame_layer, guard) = tracing_flame::FlameLayer::with_file("tracing.folded")
57
+ .map_err(|err| {
58
+ napi::Error::new(
59
+ Status::GenericFailure,
60
+ format!("Failed to create tracing.folded file with error: {err:?}"),
61
+ )
62
+ })?;
59
63
 
60
64
  let flame_layer = flame_layer.with_empty_samples(false);
61
65
  (flame_layer, guard)
@@ -64,10 +68,14 @@ impl Context {
64
68
  #[cfg(feature = "tracing")]
65
69
  let subscriber = subscriber.with(flame_layer);
66
70
 
67
- let subscriber_guard = tracing::subscriber::set_default(subscriber);
71
+ if let Err(error) = tracing::subscriber::set_global_default(subscriber) {
72
+ println!(
73
+ "Failed to set global tracing subscriber with error: {error}\n\
74
+ Please only initialize EdrContext once per process to avoid this error."
75
+ );
76
+ }
68
77
 
69
78
  Ok(Self {
70
- _subscriber_guard: subscriber_guard,
71
79
  #[cfg(feature = "tracing")]
72
80
  _tracing_write_guard: guard,
73
81
  })
package/src/logger.rs CHANGED
@@ -1,4 +1,7 @@
1
- use std::{fmt::Display, sync::mpsc::channel};
1
+ use std::{
2
+ fmt::Display,
3
+ sync::{mpsc::channel, Arc},
4
+ };
2
5
 
3
6
  use ansi_term::{Color, Style};
4
7
  use edr_eth::{
@@ -14,6 +17,7 @@ use edr_evm::{
14
17
  ExecutionResult, SyncBlock,
15
18
  };
16
19
  use edr_provider::{ProviderError, TransactionFailure};
20
+ use edr_solidity::contract_decoder::ContractDecoder;
17
21
  use itertools::izip;
18
22
  use napi::{
19
23
  threadsafe_function::{
@@ -41,21 +45,12 @@ impl TryCast<(String, Option<String>)> for ContractAndFunctionName {
41
45
  }
42
46
  }
43
47
 
44
- struct ContractAndFunctionNameCall {
45
- code: Bytes,
46
- /// Only present for calls.
47
- calldata: Option<Bytes>,
48
- }
49
-
50
48
  #[napi(object)]
51
49
  pub struct LoggerConfig {
52
50
  /// Whether to enable the logger.
53
51
  pub enable: bool,
54
52
  #[napi(ts_type = "(inputs: Buffer[]) => string[]")]
55
53
  pub decode_console_log_inputs_callback: JsFunction,
56
- #[napi(ts_type = "(code: Buffer, calldata?: Buffer) => ContractAndFunctionName")]
57
- /// Used to resolve the contract and function name when logging.
58
- pub get_contract_and_function_name_callback: JsFunction,
59
54
  #[napi(ts_type = "(message: string, replace: boolean) => void")]
60
55
  pub print_line_callback: JsFunction,
61
56
  }
@@ -118,9 +113,13 @@ pub struct Logger {
118
113
  }
119
114
 
120
115
  impl Logger {
121
- pub fn new(env: &Env, config: LoggerConfig) -> napi::Result<Self> {
116
+ pub fn new(
117
+ env: &Env,
118
+ config: LoggerConfig,
119
+ contract_decoder: Arc<ContractDecoder>,
120
+ ) -> napi::Result<Self> {
122
121
  Ok(Self {
123
- collector: LogCollector::new(env, config)?,
122
+ collector: LogCollector::new(env, config, contract_decoder)?,
124
123
  })
125
124
  }
126
125
  }
@@ -235,6 +234,15 @@ impl edr_provider::Logger for Logger {
235
234
 
236
235
  Ok(())
237
236
  }
237
+
238
+ fn print_contract_decoding_error(&mut self, error: &str) -> Result<(), Self::LoggerError> {
239
+ self.collector.log(
240
+ "Contract decoder failed to be updated. Please report this to help us improve Hardhat.",
241
+ );
242
+ self.collector.print_empty_line()?;
243
+ self.collector.log(error);
244
+ Ok(())
245
+ }
238
246
  }
239
247
 
240
248
  #[derive(Clone)]
@@ -245,9 +253,8 @@ pub struct CollapsedMethod {
245
253
 
246
254
  #[derive(Clone)]
247
255
  struct LogCollector {
256
+ contract_decoder: Arc<ContractDecoder>,
248
257
  decode_console_log_inputs_fn: ThreadsafeFunction<Vec<Bytes>, ErrorStrategy::Fatal>,
249
- get_contract_and_function_name_fn:
250
- ThreadsafeFunction<ContractAndFunctionNameCall, ErrorStrategy::Fatal>,
251
258
  indentation: usize,
252
259
  is_enabled: bool,
253
260
  logs: Vec<LogLine>,
@@ -257,7 +264,11 @@ struct LogCollector {
257
264
  }
258
265
 
259
266
  impl LogCollector {
260
- pub fn new(env: &Env, config: LoggerConfig) -> napi::Result<Self> {
267
+ pub fn new(
268
+ env: &Env,
269
+ config: LoggerConfig,
270
+ contract_decoder: Arc<ContractDecoder>,
271
+ ) -> napi::Result<Self> {
261
272
  let mut decode_console_log_inputs_fn = config
262
273
  .decode_console_log_inputs_callback
263
274
  .create_threadsafe_function(0, |ctx: ThreadSafeCallContext<Vec<Bytes>>| {
@@ -281,34 +292,6 @@ impl LogCollector {
281
292
  // exiting.
282
293
  decode_console_log_inputs_fn.unref(env)?;
283
294
 
284
- let mut get_contract_and_function_name_fn = config
285
- .get_contract_and_function_name_callback
286
- .create_threadsafe_function(
287
- 0,
288
- |ctx: ThreadSafeCallContext<ContractAndFunctionNameCall>| {
289
- // Buffer
290
- let code = ctx
291
- .env
292
- .create_buffer_with_data(ctx.value.code.to_vec())?
293
- .into_unknown();
294
-
295
- // Option<Buffer>
296
- let calldata = if let Some(calldata) = ctx.value.calldata {
297
- ctx.env
298
- .create_buffer_with_data(calldata.to_vec())?
299
- .into_unknown()
300
- } else {
301
- ctx.env.get_undefined()?.into_unknown()
302
- };
303
-
304
- Ok(vec![code, calldata])
305
- },
306
- )?;
307
-
308
- // Maintain a weak reference to the function to avoid the event loop from
309
- // exiting.
310
- get_contract_and_function_name_fn.unref(env)?;
311
-
312
295
  let mut print_line_fn = config.print_line_callback.create_threadsafe_function(
313
296
  0,
314
297
  |ctx: ThreadSafeCallContext<(String, bool)>| {
@@ -327,8 +310,8 @@ impl LogCollector {
327
310
  print_line_fn.unref(env)?;
328
311
 
329
312
  Ok(Self {
313
+ contract_decoder,
330
314
  decode_console_log_inputs_fn,
331
- get_contract_and_function_name_fn,
332
315
  indentation: 0,
333
316
  is_enabled: config.enable,
334
317
  logs: Vec::new(),
@@ -560,29 +543,13 @@ impl LogCollector {
560
543
  code: Bytes,
561
544
  calldata: Option<Bytes>,
562
545
  ) -> (String, Option<String>) {
563
- let (sender, receiver) = channel();
564
-
565
- let status = self
566
- .get_contract_and_function_name_fn
567
- .call_with_return_value(
568
- ContractAndFunctionNameCall { code, calldata },
569
- ThreadsafeFunctionCallMode::Blocking,
570
- move |result: ContractAndFunctionName| {
571
- let contract_and_function_name = result.try_cast();
572
- sender.send(contract_and_function_name).map_err(|_error| {
573
- napi::Error::new(
574
- Status::GenericFailure,
575
- "Failed to send result from get_contract_and_function_name",
576
- )
577
- })
578
- },
579
- );
580
- assert_eq!(status, Status::Ok);
581
-
582
- receiver
583
- .recv()
584
- .unwrap()
585
- .expect("Failed call to get_contract_and_function_name")
546
+ let edr_solidity::contract_decoder::ContractAndFunctionName {
547
+ contract_name,
548
+ function_name,
549
+ } = self
550
+ .contract_decoder
551
+ .get_contract_and_function_names_for_call(&code, calldata.as_ref());
552
+ (contract_name, function_name)
586
553
  }
587
554
 
588
555
  fn format(&self, message: impl ToString) -> String {
package/src/provider.rs CHANGED
@@ -4,6 +4,7 @@ use std::sync::Arc;
4
4
 
5
5
  use edr_provider::{time::CurrentTime, InvalidRequestReason};
6
6
  use edr_rpc_eth::jsonrpc;
7
+ use edr_solidity::contract_decoder::ContractDecoder;
7
8
  use napi::{tokio::runtime, Either, Env, JsFunction, JsObject, Status};
8
9
  use napi_derive::napi;
9
10
 
@@ -13,7 +14,7 @@ use crate::{
13
14
  context::EdrContext,
14
15
  logger::{Logger, LoggerConfig, LoggerError},
15
16
  subscribe::SubscriberCallback,
16
- trace::RawTrace,
17
+ trace::{solidity_stack_trace::SolidityStackTrace, RawTrace},
17
18
  };
18
19
 
19
20
  /// A JSON-RPC provider for Ethereum.
@@ -21,6 +22,7 @@ use crate::{
21
22
  pub struct Provider {
22
23
  provider: Arc<edr_provider::Provider<LoggerError>>,
23
24
  runtime: runtime::Handle,
25
+ contract_decoder: Arc<ContractDecoder>,
24
26
  #[cfg(feature = "scenarios")]
25
27
  scenario_file: Option<napi::tokio::sync::Mutex<napi::tokio::fs::File>>,
26
28
  }
@@ -35,12 +37,25 @@ impl Provider {
35
37
  _context: &EdrContext,
36
38
  config: ProviderConfig,
37
39
  logger_config: LoggerConfig,
40
+ tracing_config: serde_json::Value,
38
41
  #[napi(ts_arg_type = "(event: SubscriptionEvent) => void")] subscriber_callback: JsFunction,
39
42
  ) -> napi::Result<JsObject> {
40
- let config = edr_provider::ProviderConfig::try_from(config)?;
41
43
  let runtime = runtime::Handle::current();
42
44
 
43
- let logger = Box::new(Logger::new(&env, logger_config)?);
45
+ let config = edr_provider::ProviderConfig::try_from(config)?;
46
+
47
+ // TODO https://github.com/NomicFoundation/edr/issues/760
48
+ let build_info_config: edr_solidity::contract_decoder::BuildInfoConfig =
49
+ serde_json::from_value(tracing_config)?;
50
+ let contract_decoder = ContractDecoder::new(&build_info_config)
51
+ .map_err(|error| napi::Error::from_reason(error.to_string()))?;
52
+ let contract_decoder = Arc::new(contract_decoder);
53
+
54
+ let logger = Box::new(Logger::new(
55
+ &env,
56
+ logger_config,
57
+ Arc::clone(&contract_decoder),
58
+ )?);
44
59
  let subscriber_callback = SubscriberCallback::new(&env, subscriber_callback)?;
45
60
  let subscriber_callback = Box::new(move |event| subscriber_callback.call(event));
46
61
 
@@ -58,6 +73,7 @@ impl Provider {
58
73
  logger,
59
74
  subscriber_callback,
60
75
  config,
76
+ Arc::clone(&contract_decoder),
61
77
  CurrentTime,
62
78
  )
63
79
  .map_or_else(
@@ -66,6 +82,7 @@ impl Provider {
66
82
  Ok(Provider {
67
83
  provider: Arc::new(provider),
68
84
  runtime,
85
+ contract_decoder,
69
86
  #[cfg(feature = "scenarios")]
70
87
  scenario_file,
71
88
  })
@@ -184,10 +201,16 @@ impl Provider {
184
201
  }
185
202
  })
186
203
  .map_err(|error| napi::Error::new(Status::GenericFailure, error.to_string()))
187
- .map(|data| Response {
188
- solidity_trace,
189
- data,
190
- traces: traces.into_iter().map(Arc::new).collect(),
204
+ .map(|data| {
205
+ let solidity_trace = solidity_trace.map(|trace| SolidityTraceData {
206
+ trace,
207
+ contract_decoder: Arc::clone(&self.contract_decoder),
208
+ });
209
+ Response {
210
+ solidity_trace,
211
+ data,
212
+ traces: traces.into_iter().map(Arc::new).collect(),
213
+ }
191
214
  })
192
215
  }
193
216
 
@@ -222,6 +245,12 @@ impl Provider {
222
245
  }
223
246
  }
224
247
 
248
+ #[derive(Debug)]
249
+ struct SolidityTraceData {
250
+ trace: Arc<edr_evm::trace::Trace>,
251
+ contract_decoder: Arc<ContractDecoder>,
252
+ }
253
+
225
254
  #[napi]
226
255
  pub struct Response {
227
256
  // N-API is known to be slow when marshalling `serde_json::Value`s, so we try to return a
@@ -230,7 +259,7 @@ pub struct Response {
230
259
  data: Either<String, serde_json::Value>,
231
260
  /// When a transaction fails to execute, the provider returns a trace of the
232
261
  /// transaction.
233
- solidity_trace: Option<Arc<edr_evm::trace::Trace>>,
262
+ solidity_trace: Option<SolidityTraceData>,
234
263
  /// This may contain zero or more traces, depending on the (batch) request
235
264
  traces: Vec<Arc<edr_evm::trace::Trace>>,
236
265
  }
@@ -243,13 +272,6 @@ impl Response {
243
272
  self.data.clone()
244
273
  }
245
274
 
246
- #[napi(getter)]
247
- pub fn solidity_trace(&self) -> Option<RawTrace> {
248
- self.solidity_trace
249
- .as_ref()
250
- .map(|trace| RawTrace::new(trace.clone()))
251
- }
252
-
253
275
  #[napi(getter)]
254
276
  pub fn traces(&self) -> Vec<RawTrace> {
255
277
  self.traces
@@ -257,4 +279,35 @@ impl Response {
257
279
  .map(|trace| RawTrace::new(trace.clone()))
258
280
  .collect()
259
281
  }
282
+
283
+ // Rust port of https://github.com/NomicFoundation/hardhat/blob/c20bf195a6efdc2d74e778b7a4a7799aac224841/packages/hardhat-core/src/internal/hardhat-network/provider/provider.ts#L590
284
+ #[doc = "Compute the error stack trace. Return the stack trace if it can be decoded, otherwise returns none. Throws if there was an error computing the stack trace."]
285
+ #[napi]
286
+ pub fn stack_trace(&self) -> napi::Result<Option<SolidityStackTrace>> {
287
+ let Some(SolidityTraceData {
288
+ trace,
289
+ contract_decoder,
290
+ }) = &self.solidity_trace
291
+ else {
292
+ return Ok(None);
293
+ };
294
+ let nested_trace = edr_solidity::nested_tracer::convert_trace_messages_to_nested_trace(
295
+ trace.as_ref().clone(),
296
+ )
297
+ .map_err(|err| napi::Error::from_reason(err.to_string()))?;
298
+
299
+ if let Some(vm_trace) = nested_trace {
300
+ let decoded_trace = contract_decoder.try_to_decode_message_trace(vm_trace);
301
+ let stack_trace = edr_solidity::solidity_tracer::get_stack_trace(decoded_trace)
302
+ .map_err(|err| napi::Error::from_reason(err.to_string()))?;
303
+ let stack_trace = stack_trace
304
+ .into_iter()
305
+ .map(super::cast::TryCast::try_cast)
306
+ .collect::<Result<Vec<_>, _>>()?;
307
+
308
+ Ok(Some(stack_trace))
309
+ } else {
310
+ Ok(None)
311
+ }
312
+ }
260
313
  }