@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 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 */
@@ -256,6 +254,27 @@ export interface ProviderConfig {
256
254
  /** The network ID of the blockchain */
257
255
  networkId: bigint
258
256
  }
257
+ /** Tracing config for Solidity stack trace generation. */
258
+ export interface TracingConfigWithBuffers {
259
+ /**
260
+ * Build information to use for decoding contracts. Either a Hardhat v2
261
+ * build info file that contains both input and output or a Hardhat v3
262
+ * build info file that doesn't contain output and a separate output file.
263
+ */
264
+ buildInfos?: Array<Uint8Array> | Array<BuildInfoAndOutput>
265
+ /** Whether to ignore contracts whose name starts with "Ignored". */
266
+ ignoreContracts?: boolean
267
+ }
268
+ /**
269
+ * Hardhat V3 build info where the compiler output is not part of the build
270
+ * info file.
271
+ */
272
+ export interface BuildInfoAndOutput {
273
+ /** The build info input file */
274
+ buildInfo: Uint8Array
275
+ /** The build info output file */
276
+ output: Uint8Array
277
+ }
259
278
  /** The possible reasons for successful termination of the EVM. */
260
279
  export const enum SuccessReason {
261
280
  /** The opcode `STOP` was called */
@@ -346,18 +365,7 @@ export interface SubscriptionEvent {
346
365
  filterId: bigint
347
366
  result: any
348
367
  }
349
- export declare function createModelsAndDecodeBytecodes(solcVersion: string, compilerInput: any, compilerOutput: any): Array<BytecodeWrapper>
350
368
  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
369
  export declare function printStackTrace(trace: SolidityStackTrace): void
362
370
  /** Represents the exit code of the EVM. */
363
371
  export const enum ExitCode {
@@ -380,51 +388,14 @@ export const enum ExitCode {
380
388
  /** Unknown halt reason. */
381
389
  UNKNOWN_HALT_REASON = 8
382
390
  }
383
- export interface EvmStep {
384
- pc: number
385
- }
386
- export interface PrecompileMessageTrace {
387
- value: bigint
388
- returnData: Uint8Array
389
- exit: Exit
390
- gasUsed: bigint
391
- depth: number
392
- precompile: number
393
- calldata: Uint8Array
394
- }
395
- export interface CreateMessageTrace {
396
- value: bigint
397
- returnData: Uint8Array
398
- exit: Exit
399
- gasUsed: bigint
400
- depth: number
401
- code: Uint8Array
402
- steps: Array<EvmStep | PrecompileMessageTrace | CallMessageTrace | CreateMessageTrace>
403
- /**
404
- * Reference to the resolved `Bytecode` EDR data.
405
- * Only used on the JS side by the `VmTraceDecoder` class.
406
- */
407
- bytecode?: BytecodeWrapper
408
- numberOfSubtraces: number
409
- deployedContract?: Uint8Array | undefined
410
- }
411
- export interface CallMessageTrace {
412
- value: bigint
413
- returnData: Uint8Array
414
- exit: Exit
415
- gasUsed: bigint
416
- depth: number
417
- code: Uint8Array
418
- steps: Array<EvmStep | PrecompileMessageTrace | CallMessageTrace | CreateMessageTrace>
419
- /**
420
- * Reference to the resolved `Bytecode` EDR data.
421
- * Only used on the JS side by the `VmTraceDecoder` class.
422
- */
423
- bytecode?: BytecodeWrapper
424
- numberOfSubtraces: number
425
- calldata: Uint8Array
426
- address: Uint8Array
427
- codeAddress: Uint8Array
391
+ export const enum ContractFunctionType {
392
+ CONSTRUCTOR = 0,
393
+ FUNCTION = 1,
394
+ FALLBACK = 2,
395
+ RECEIVE = 3,
396
+ GETTER = 4,
397
+ MODIFIER = 5,
398
+ FREE_FUNCTION = 6
428
399
  }
429
400
  export const enum StackTraceEntryType {
430
401
  CALLSTACK_ENTRY = 0,
@@ -580,11 +551,6 @@ export interface ContractCallRunOutOfGasError {
580
551
  type: StackTraceEntryType.CONTRACT_CALL_RUN_OUT_OF_GAS_ERROR
581
552
  sourceReference?: SourceReference
582
553
  }
583
- export interface ContractAndFunctionName {
584
- contractName: string
585
- functionName: string | undefined
586
- }
587
- export declare function initializeVmTraceDecoder(vmTraceDecoder: VmTraceDecoder, tracingConfig: any): void
588
554
  export interface TracingMessage {
589
555
  /** Sender address */
590
556
  readonly caller: Buffer
@@ -628,6 +594,11 @@ export interface TracingMessageResult {
628
594
  /** Execution result */
629
595
  readonly executionResult: ExecutionResult
630
596
  }
597
+ /**
598
+ * Returns the latest version of solc that EDR officially
599
+ * supports and is tested against.
600
+ */
601
+ export declare function getLatestSupportedSolcVersion(): string
631
602
  export interface Withdrawal {
632
603
  /** The index of withdrawal */
633
604
  index: bigint
@@ -645,7 +616,7 @@ export declare class EdrContext {
645
616
  /** A JSON-RPC provider for Ethereum. */
646
617
  export declare class Provider {
647
618
  /**Constructs a new provider with the provided configuration. */
648
- static withConfig(context: EdrContext, config: ProviderConfig, loggerConfig: LoggerConfig, subscriberCallback: (event: SubscriptionEvent) => void): Promise<Provider>
619
+ static withConfig(context: EdrContext, config: ProviderConfig, loggerConfig: LoggerConfig, tracingConfig: TracingConfigWithBuffers, subscriberCallback: (event: SubscriptionEvent) => void): Promise<Provider>
649
620
  /**Handles a JSON-RPC request and returns a JSON-RPC response. */
650
621
  handleRequest(jsonRequest: string): Promise<Response>
651
622
  setCallOverrideCallback(callOverrideCallback: (contract_address: Buffer, data: Buffer) => Promise<CallOverrideResult | undefined>): void
@@ -660,19 +631,20 @@ export declare class Provider {
660
631
  export declare class Response {
661
632
  /** Returns the response data as a JSON string or a JSON object. */
662
633
  get data(): string | any
663
- get solidityTrace(): RawTrace | null
664
634
  get traces(): Array<RawTrace>
635
+ /**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. */
636
+ stackTrace(): SolidityStackTrace | null
665
637
  }
666
- /**
667
- * Opaque handle to the `Bytecode` struct.
668
- * Only used on the JS side by the `VmTraceDecoder` class.
669
- */
670
- export declare class BytecodeWrapper { }
671
638
  export declare class Exit {
672
639
  get kind(): ExitCode
673
640
  isError(): boolean
674
641
  getReason(): string
675
642
  }
643
+ /**
644
+ * Opaque handle to the `Bytecode` struct.
645
+ * Only used on the JS side by the `VmTraceDecoder` class.
646
+ */
647
+ export declare class BytecodeWrapper { }
676
648
  export declare class ReturnData {
677
649
  readonly value: Uint8Array
678
650
  constructor(value: Uint8Array)
@@ -682,26 +654,6 @@ export declare class ReturnData {
682
654
  decodeError(): string
683
655
  decodePanic(): bigint
684
656
  }
685
- export declare class SolidityTracer {
686
-
687
- constructor()
688
- getStackTrace(trace: PrecompileMessageTrace | CallMessageTrace | CreateMessageTrace): SolidityStackTrace
689
- }
690
- export declare class VmTraceDecoder {
691
- constructor()
692
- addBytecode(bytecode: BytecodeWrapper): void
693
- tryToDecodeMessageTrace(messageTrace: PrecompileMessageTrace | CallMessageTrace | CreateMessageTrace): PrecompileMessageTrace | CallMessageTrace | CreateMessageTrace
694
- getContractAndFunctionNamesForCall(code: Uint8Array, calldata: Uint8Array | undefined): ContractAndFunctionName
695
- }
696
- export type VMTracer = VmTracer
697
- /** N-API bindings for the Rust port of `VMTracer` from Hardhat. */
698
- export declare class VmTracer {
699
- constructor()
700
- /** Observes a trace, collecting information about the execution of the EVM. */
701
- observe(trace: RawTrace): void
702
- getLastTopLevelMessageTrace(): PrecompileMessageTrace | CallMessageTrace | CreateMessageTrace | undefined
703
- getLastError(): Error | undefined
704
- }
705
657
  export declare class RawTrace {
706
658
  trace(): Array<TracingMessage | TracingStep | TracingMessageResult>
707
659
  }
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.5",
3
+ "version": "0.8.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.5",
56
- "@nomicfoundation/edr-darwin-x64": "0.6.5",
57
- "@nomicfoundation/edr-linux-arm64-gnu": "0.6.5",
58
- "@nomicfoundation/edr-linux-arm64-musl": "0.6.5",
59
- "@nomicfoundation/edr-linux-x64-gnu": "0.6.5",
60
- "@nomicfoundation/edr-linux-x64-musl": "0.6.5",
61
- "@nomicfoundation/edr-win32-x64-msvc": "0.6.5"
55
+ "@nomicfoundation/edr-darwin-arm64": "0.8.0",
56
+ "@nomicfoundation/edr-darwin-x64": "0.8.0",
57
+ "@nomicfoundation/edr-linux-arm64-gnu": "0.8.0",
58
+ "@nomicfoundation/edr-linux-arm64-musl": "0.8.0",
59
+ "@nomicfoundation/edr-linux-x64-gnu": "0.8.0",
60
+ "@nomicfoundation/edr-linux-x64-musl": "0.8.0",
61
+ "@nomicfoundation/edr-win32-x64-msvc": "0.8.0"
62
62
  },
63
63
  "scripts": {
64
64
  "artifacts": "napi artifacts",
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,7 +4,10 @@ use std::sync::Arc;
4
4
 
5
5
  use edr_provider::{time::CurrentTime, InvalidRequestReason};
6
6
  use edr_rpc_eth::jsonrpc;
7
- use napi::{tokio::runtime, Either, Env, JsFunction, JsObject, Status};
7
+ use edr_solidity::contract_decoder::ContractDecoder;
8
+ use napi::{
9
+ bindgen_prelude::Uint8Array, tokio::runtime, Either, Env, JsFunction, JsObject, Status,
10
+ };
8
11
  use napi_derive::napi;
9
12
 
10
13
  use self::config::ProviderConfig;
@@ -13,7 +16,7 @@ use crate::{
13
16
  context::EdrContext,
14
17
  logger::{Logger, LoggerConfig, LoggerError},
15
18
  subscribe::SubscriberCallback,
16
- trace::RawTrace,
19
+ trace::{solidity_stack_trace::SolidityStackTrace, RawTrace},
17
20
  };
18
21
 
19
22
  /// A JSON-RPC provider for Ethereum.
@@ -21,6 +24,7 @@ use crate::{
21
24
  pub struct Provider {
22
25
  provider: Arc<edr_provider::Provider<LoggerError>>,
23
26
  runtime: runtime::Handle,
27
+ contract_decoder: Arc<ContractDecoder>,
24
28
  #[cfg(feature = "scenarios")]
25
29
  scenario_file: Option<napi::tokio::sync::Mutex<napi::tokio::fs::File>>,
26
30
  }
@@ -35,12 +39,26 @@ impl Provider {
35
39
  _context: &EdrContext,
36
40
  config: ProviderConfig,
37
41
  logger_config: LoggerConfig,
42
+ tracing_config: TracingConfigWithBuffers,
38
43
  #[napi(ts_arg_type = "(event: SubscriptionEvent) => void")] subscriber_callback: JsFunction,
39
44
  ) -> napi::Result<JsObject> {
40
- let config = edr_provider::ProviderConfig::try_from(config)?;
41
45
  let runtime = runtime::Handle::current();
42
46
 
43
- let logger = Box::new(Logger::new(&env, logger_config)?);
47
+ let config = edr_provider::ProviderConfig::try_from(config)?;
48
+
49
+ // TODO https://github.com/NomicFoundation/edr/issues/760
50
+ let build_info_config =
51
+ edr_solidity::artifacts::BuildInfoConfig::parse_from_buffers((&tracing_config).into())
52
+ .map_err(|err| napi::Error::from_reason(err.to_string()))?;
53
+ let contract_decoder = ContractDecoder::new(&build_info_config)
54
+ .map_err(|error| napi::Error::from_reason(error.to_string()))?;
55
+ let contract_decoder = Arc::new(contract_decoder);
56
+
57
+ let logger = Box::new(Logger::new(
58
+ &env,
59
+ logger_config,
60
+ Arc::clone(&contract_decoder),
61
+ )?);
44
62
  let subscriber_callback = SubscriberCallback::new(&env, subscriber_callback)?;
45
63
  let subscriber_callback = Box::new(move |event| subscriber_callback.call(event));
46
64
 
@@ -58,6 +76,7 @@ impl Provider {
58
76
  logger,
59
77
  subscriber_callback,
60
78
  config,
79
+ Arc::clone(&contract_decoder),
61
80
  CurrentTime,
62
81
  )
63
82
  .map_or_else(
@@ -66,6 +85,7 @@ impl Provider {
66
85
  Ok(Provider {
67
86
  provider: Arc::new(provider),
68
87
  runtime,
88
+ contract_decoder,
69
89
  #[cfg(feature = "scenarios")]
70
90
  scenario_file,
71
91
  })
@@ -184,10 +204,16 @@ impl Provider {
184
204
  }
185
205
  })
186
206
  .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(),
207
+ .map(|data| {
208
+ let solidity_trace = solidity_trace.map(|trace| SolidityTraceData {
209
+ trace,
210
+ contract_decoder: Arc::clone(&self.contract_decoder),
211
+ });
212
+ Response {
213
+ solidity_trace,
214
+ data,
215
+ traces: traces.into_iter().map(Arc::new).collect(),
216
+ }
191
217
  })
192
218
  }
193
219
 
@@ -222,6 +248,72 @@ impl Provider {
222
248
  }
223
249
  }
224
250
 
251
+ /// Tracing config for Solidity stack trace generation.
252
+ #[napi(object)]
253
+ pub struct TracingConfigWithBuffers {
254
+ /// Build information to use for decoding contracts. Either a Hardhat v2
255
+ /// build info file that contains both input and output or a Hardhat v3
256
+ /// build info file that doesn't contain output and a separate output file.
257
+ pub build_infos: Option<Either<Vec<Uint8Array>, Vec<BuildInfoAndOutput>>>,
258
+ /// Whether to ignore contracts whose name starts with "Ignored".
259
+ pub ignore_contracts: Option<bool>,
260
+ }
261
+
262
+ /// Hardhat V3 build info where the compiler output is not part of the build
263
+ /// info file.
264
+ #[napi(object)]
265
+ pub struct BuildInfoAndOutput {
266
+ /// The build info input file
267
+ pub build_info: Uint8Array,
268
+ /// The build info output file
269
+ pub output: Uint8Array,
270
+ }
271
+
272
+ impl<'a> From<&'a BuildInfoAndOutput>
273
+ for edr_solidity::artifacts::BuildInfoBufferSeparateOutput<'a>
274
+ {
275
+ fn from(value: &'a BuildInfoAndOutput) -> Self {
276
+ Self {
277
+ build_info: value.build_info.as_ref(),
278
+ output: value.output.as_ref(),
279
+ }
280
+ }
281
+ }
282
+
283
+ impl<'a> From<&'a TracingConfigWithBuffers>
284
+ for edr_solidity::artifacts::BuildInfoConfigWithBuffers<'a>
285
+ {
286
+ fn from(value: &'a TracingConfigWithBuffers) -> Self {
287
+ use edr_solidity::artifacts::{BuildInfoBufferSeparateOutput, BuildInfoBuffers};
288
+
289
+ let build_infos = value.build_infos.as_ref().map(|infos| match infos {
290
+ Either::A(with_output) => BuildInfoBuffers::WithOutput(
291
+ with_output
292
+ .iter()
293
+ .map(std::convert::AsRef::as_ref)
294
+ .collect(),
295
+ ),
296
+ Either::B(separate_output) => BuildInfoBuffers::SeparateInputOutput(
297
+ separate_output
298
+ .iter()
299
+ .map(BuildInfoBufferSeparateOutput::from)
300
+ .collect(),
301
+ ),
302
+ });
303
+
304
+ Self {
305
+ build_infos,
306
+ ignore_contracts: value.ignore_contracts,
307
+ }
308
+ }
309
+ }
310
+
311
+ #[derive(Debug)]
312
+ struct SolidityTraceData {
313
+ trace: Arc<edr_evm::trace::Trace>,
314
+ contract_decoder: Arc<ContractDecoder>,
315
+ }
316
+
225
317
  #[napi]
226
318
  pub struct Response {
227
319
  // N-API is known to be slow when marshalling `serde_json::Value`s, so we try to return a
@@ -230,7 +322,7 @@ pub struct Response {
230
322
  data: Either<String, serde_json::Value>,
231
323
  /// When a transaction fails to execute, the provider returns a trace of the
232
324
  /// transaction.
233
- solidity_trace: Option<Arc<edr_evm::trace::Trace>>,
325
+ solidity_trace: Option<SolidityTraceData>,
234
326
  /// This may contain zero or more traces, depending on the (batch) request
235
327
  traces: Vec<Arc<edr_evm::trace::Trace>>,
236
328
  }
@@ -243,13 +335,6 @@ impl Response {
243
335
  self.data.clone()
244
336
  }
245
337
 
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
338
  #[napi(getter)]
254
339
  pub fn traces(&self) -> Vec<RawTrace> {
255
340
  self.traces
@@ -257,4 +342,35 @@ impl Response {
257
342
  .map(|trace| RawTrace::new(trace.clone()))
258
343
  .collect()
259
344
  }
345
+
346
+ // Rust port of https://github.com/NomicFoundation/hardhat/blob/c20bf195a6efdc2d74e778b7a4a7799aac224841/packages/hardhat-core/src/internal/hardhat-network/provider/provider.ts#L590
347
+ #[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."]
348
+ #[napi]
349
+ pub fn stack_trace(&self) -> napi::Result<Option<SolidityStackTrace>> {
350
+ let Some(SolidityTraceData {
351
+ trace,
352
+ contract_decoder,
353
+ }) = &self.solidity_trace
354
+ else {
355
+ return Ok(None);
356
+ };
357
+ let nested_trace = edr_solidity::nested_tracer::convert_trace_messages_to_nested_trace(
358
+ trace.as_ref().clone(),
359
+ )
360
+ .map_err(|err| napi::Error::from_reason(err.to_string()))?;
361
+
362
+ if let Some(vm_trace) = nested_trace {
363
+ let decoded_trace = contract_decoder.try_to_decode_message_trace(vm_trace);
364
+ let stack_trace = edr_solidity::solidity_tracer::get_stack_trace(decoded_trace)
365
+ .map_err(|err| napi::Error::from_reason(err.to_string()))?;
366
+ let stack_trace = stack_trace
367
+ .into_iter()
368
+ .map(super::cast::TryCast::try_cast)
369
+ .collect::<Result<Vec<_>, _>>()?;
370
+
371
+ Ok(Some(stack_trace))
372
+ } else {
373
+ Ok(None)
374
+ }
375
+ }
260
376
  }