@nomicfoundation/edr 0.11.0 → 0.12.0-alpha.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.
@@ -0,0 +1,22 @@
1
+ use std::sync::Arc;
2
+
3
+ use edr_napi_core::provider::SyncProviderFactory;
4
+ use napi_derive::napi;
5
+
6
+ #[napi]
7
+ pub struct ProviderFactory {
8
+ inner: Arc<dyn SyncProviderFactory>,
9
+ }
10
+
11
+ impl ProviderFactory {
12
+ /// Returns a reference to the inner provider factory.
13
+ pub fn as_inner(&self) -> &Arc<dyn SyncProviderFactory> {
14
+ &self.inner
15
+ }
16
+ }
17
+
18
+ impl From<Arc<dyn SyncProviderFactory>> for ProviderFactory {
19
+ fn from(inner: Arc<dyn SyncProviderFactory>) -> Self {
20
+ Self { inner }
21
+ }
22
+ }
@@ -0,0 +1,70 @@
1
+ use edr_eth::l1;
2
+ use edr_napi_core::spec::SolidityTraceData;
3
+ use napi::Either;
4
+ use napi_derive::napi;
5
+
6
+ use crate::{
7
+ cast::TryCast,
8
+ trace::{RawTrace, solidity_stack_trace::SolidityStackTrace},
9
+ };
10
+
11
+ #[napi]
12
+ pub struct Response {
13
+ inner: edr_napi_core::spec::Response<l1::HaltReason>,
14
+ }
15
+
16
+ impl From<edr_napi_core::spec::Response<l1::HaltReason>> for Response {
17
+ fn from(value: edr_napi_core::spec::Response<l1::HaltReason>) -> Self {
18
+ Self { inner: value }
19
+ }
20
+ }
21
+
22
+ #[napi]
23
+ impl Response {
24
+ #[doc = "Returns the response data as a JSON string or a JSON object."]
25
+ #[napi(getter)]
26
+ pub fn data(&self) -> Either<String, serde_json::Value> {
27
+ self.inner.data.clone()
28
+ }
29
+
30
+ // Rust port of https://github.com/NomicFoundation/hardhat/blob/c20bf195a6efdc2d74e778b7a4a7799aac224841/packages/hardhat-core/src/internal/hardhat-network/provider/provider.ts#L590
31
+ #[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."]
32
+ #[napi]
33
+ pub fn stack_trace(&self) -> napi::Result<Option<SolidityStackTrace>> {
34
+ let Some(SolidityTraceData {
35
+ trace,
36
+ contract_decoder,
37
+ }) = &self.inner.solidity_trace
38
+ else {
39
+ return Ok(None);
40
+ };
41
+ let nested_trace = edr_solidity::nested_tracer::convert_trace_messages_to_nested_trace(
42
+ trace.as_ref().clone(),
43
+ )
44
+ .map_err(|err| napi::Error::from_reason(err.to_string()))?;
45
+
46
+ if let Some(vm_trace) = nested_trace {
47
+ let decoded_trace = contract_decoder.try_to_decode_message_trace(vm_trace);
48
+ let stack_trace = edr_solidity::solidity_tracer::get_stack_trace(decoded_trace)
49
+ .map_err(|err| napi::Error::from_reason(err.to_string()))?;
50
+ let stack_trace = stack_trace
51
+ .into_iter()
52
+ .map(TryCast::try_cast)
53
+ .collect::<Result<Vec<_>, _>>()?;
54
+
55
+ Ok(Some(stack_trace))
56
+ } else {
57
+ Ok(None)
58
+ }
59
+ }
60
+
61
+ #[doc = "Returns the raw traces of executed contracts. This maybe contain zero or more traces."]
62
+ #[napi(getter)]
63
+ pub fn traces(&self) -> Vec<RawTrace> {
64
+ self.inner
65
+ .traces
66
+ .iter()
67
+ .map(|trace| RawTrace::from(trace.clone()))
68
+ .collect()
69
+ }
70
+ }
package/src/provider.rs CHANGED
@@ -1,223 +1,70 @@
1
- mod config;
1
+ /// Types related to provider factories.
2
+ pub mod factory;
3
+ mod response;
2
4
 
3
5
  use std::sync::Arc;
4
6
 
5
- use edr_provider::{time::CurrentTime, InvalidRequestReason};
6
- use edr_rpc_eth::jsonrpc;
7
+ use edr_napi_core::provider::SyncProvider;
7
8
  use edr_solidity::contract_decoder::ContractDecoder;
8
- use napi::{
9
- bindgen_prelude::Uint8Array, tokio::runtime, Either, Env, JsFunction, JsObject, Status,
10
- };
9
+ use napi::{Env, JsFunction, JsObject, Status, tokio::runtime};
11
10
  use napi_derive::napi;
12
11
 
13
- use self::config::ProviderConfig;
14
- use crate::{
15
- call_override::CallOverrideCallback,
16
- context::EdrContext,
17
- logger::{Logger, LoggerConfig, LoggerError},
18
- subscribe::SubscriberCallback,
19
- trace::{solidity_stack_trace::SolidityStackTrace, RawTrace},
20
- };
12
+ pub use self::factory::ProviderFactory;
13
+ use self::response::Response;
14
+ use crate::call_override::CallOverrideCallback;
21
15
 
22
16
  /// A JSON-RPC provider for Ethereum.
23
17
  #[napi]
24
18
  pub struct Provider {
25
- provider: Arc<edr_provider::Provider<LoggerError>>,
26
- runtime: runtime::Handle,
27
19
  contract_decoder: Arc<ContractDecoder>,
20
+ provider: Arc<dyn SyncProvider>,
21
+ runtime: runtime::Handle,
28
22
  #[cfg(feature = "scenarios")]
29
23
  scenario_file: Option<napi::tokio::sync::Mutex<napi::tokio::fs::File>>,
30
24
  }
31
25
 
32
- #[napi]
33
26
  impl Provider {
34
- #[doc = "Constructs a new provider with the provided configuration."]
35
- #[napi(ts_return_type = "Promise<Provider>")]
36
- pub fn with_config(
37
- env: Env,
38
- // We take the context as argument to ensure that tracing is initialized properly.
39
- _context: &EdrContext,
40
- config: ProviderConfig,
41
- logger_config: LoggerConfig,
42
- tracing_config: TracingConfigWithBuffers,
43
- #[napi(ts_arg_type = "(event: SubscriptionEvent) => void")] subscriber_callback: JsFunction,
44
- ) -> napi::Result<JsObject> {
45
- let runtime = runtime::Handle::current();
46
-
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
- )?);
62
- let subscriber_callback = SubscriberCallback::new(&env, subscriber_callback)?;
63
- let subscriber_callback = Box::new(move |event| subscriber_callback.call(event));
64
-
65
- let (deferred, promise) = env.create_deferred()?;
66
- runtime.clone().spawn_blocking(move || {
27
+ /// Constructs a new instance.
28
+ pub fn new(
29
+ provider: Arc<dyn SyncProvider>,
30
+ runtime: runtime::Handle,
31
+ contract_decoder: Arc<ContractDecoder>,
32
+ #[cfg(feature = "scenarios")] scenario_file: Option<
33
+ napi::tokio::sync::Mutex<napi::tokio::fs::File>,
34
+ >,
35
+ ) -> Self {
36
+ Self {
37
+ contract_decoder,
38
+ provider,
39
+ runtime,
67
40
  #[cfg(feature = "scenarios")]
68
- let scenario_file =
69
- runtime::Handle::current().block_on(crate::scenarios::scenario_file(
70
- &config,
71
- edr_provider::Logger::is_enabled(&*logger),
72
- ))?;
73
-
74
- let result = edr_provider::Provider::new(
75
- runtime.clone(),
76
- logger,
77
- subscriber_callback,
78
- config,
79
- Arc::clone(&contract_decoder),
80
- CurrentTime,
81
- )
82
- .map_or_else(
83
- |error| Err(napi::Error::new(Status::GenericFailure, error.to_string())),
84
- |provider| {
85
- Ok(Provider {
86
- provider: Arc::new(provider),
87
- runtime,
88
- contract_decoder,
89
- #[cfg(feature = "scenarios")]
90
- scenario_file,
91
- })
92
- },
93
- );
94
-
95
- deferred.resolve(|_env| result);
96
- Ok::<_, napi::Error>(())
97
- });
98
-
99
- Ok(promise)
41
+ scenario_file,
42
+ }
100
43
  }
44
+ }
101
45
 
46
+ #[napi]
47
+ impl Provider {
102
48
  #[doc = "Handles a JSON-RPC request and returns a JSON-RPC response."]
103
49
  #[napi]
104
- pub async fn handle_request(&self, json_request: String) -> napi::Result<Response> {
50
+ pub async fn handle_request(&self, request: String) -> napi::Result<Response> {
105
51
  let provider = self.provider.clone();
106
- let request = match serde_json::from_str(&json_request) {
107
- Ok(request) => request,
108
- Err(error) => {
109
- let message = error.to_string();
110
- let reason = InvalidRequestReason::new(&json_request, &message);
111
-
112
- // HACK: We need to log failed deserialization attempts when they concern input
113
- // validation.
114
- if let Some((method_name, provider_error)) = reason.provider_error() {
115
- // Ignore potential failure of logging, as returning the original error is more
116
- // important
117
- let _result = runtime::Handle::current()
118
- .spawn_blocking(move || {
119
- provider.log_failed_deserialization(&method_name, &provider_error)
120
- })
121
- .await
122
- .map_err(|error| {
123
- napi::Error::new(Status::GenericFailure, error.to_string())
124
- })?;
125
- }
126
-
127
- let data = serde_json::from_str(&json_request).ok();
128
- let response = jsonrpc::ResponseData::<()>::Error {
129
- error: jsonrpc::Error {
130
- code: reason.error_code(),
131
- message: reason.error_message(),
132
- data,
133
- },
134
- };
135
-
136
- return serde_json::to_string(&response)
137
- .map_err(|error| {
138
- napi::Error::new(
139
- Status::InvalidArg,
140
- format!("Invalid JSON `{json_request}` due to: {error}"),
141
- )
142
- })
143
- .map(|json| Response {
144
- solidity_trace: None,
145
- data: Either::A(json),
146
- traces: Vec::new(),
147
- });
148
- }
149
- };
150
52
 
151
53
  #[cfg(feature = "scenarios")]
152
54
  if let Some(scenario_file) = &self.scenario_file {
153
55
  crate::scenarios::write_request(scenario_file, &request).await?;
154
56
  }
155
57
 
156
- let mut response = runtime::Handle::current()
157
- .spawn_blocking(move || provider.handle_request(request))
158
- .await
159
- .map_err(|e| napi::Error::new(Status::GenericFailure, e.to_string()))?;
160
-
161
- // We can take the solidity trace as it won't be used for anything else
162
- let solidity_trace = response.as_mut().err().and_then(|error| {
163
- if let edr_provider::ProviderError::TransactionFailed(failure) = error {
164
- if matches!(
165
- failure.failure.reason,
166
- edr_provider::TransactionFailureReason::OutOfGas(_)
167
- ) {
168
- None
169
- } else {
170
- Some(Arc::new(std::mem::take(
171
- &mut failure.failure.solidity_trace,
172
- )))
173
- }
174
- } else {
175
- None
176
- }
177
- });
178
-
179
- // We can take the traces as they won't be used for anything else
180
- let traces = match &mut response {
181
- Ok(response) => std::mem::take(&mut response.traces),
182
- Err(edr_provider::ProviderError::TransactionFailed(failure)) => {
183
- std::mem::take(&mut failure.traces)
184
- }
185
- Err(_) => Vec::new(),
186
- };
187
-
188
- let response = jsonrpc::ResponseData::from(response.map(|response| response.result));
58
+ let contract_decoder = Arc::clone(&self.contract_decoder);
189
59
 
190
- serde_json::to_string(&response)
191
- .and_then(|json| {
192
- // We experimentally determined that 500_000_000 was the maximum string length
193
- // that can be returned without causing the error:
194
- //
195
- // > Failed to convert rust `String` into napi `string`
196
- //
197
- // To be safe, we're limiting string lengths to half of that.
198
- const MAX_STRING_LENGTH: usize = 250_000_000;
199
-
200
- if json.len() <= MAX_STRING_LENGTH {
201
- Ok(Either::A(json))
202
- } else {
203
- serde_json::to_value(response).map(Either::B)
204
- }
205
- })
206
- .map_err(|error| napi::Error::new(Status::GenericFailure, error.to_string()))
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
- }
217
- })
60
+ self.runtime
61
+ .spawn_blocking(move || provider.handle_request(request, contract_decoder))
62
+ .await
63
+ .map_err(|error| napi::Error::new(Status::GenericFailure, error.to_string()))?
64
+ .map(Response::from)
218
65
  }
219
66
 
220
- #[napi(ts_return_type = "void")]
67
+ #[napi(ts_return_type = "Promise<void>")]
221
68
  pub fn set_call_override_callback(
222
69
  &self,
223
70
  env: Env,
@@ -225,152 +72,38 @@ impl Provider {
225
72
  ts_arg_type = "(contract_address: Buffer, data: Buffer) => Promise<CallOverrideResult | undefined>"
226
73
  )]
227
74
  call_override_callback: JsFunction,
228
- ) -> napi::Result<()> {
229
- let provider = self.provider.clone();
230
-
75
+ ) -> napi::Result<JsObject> {
231
76
  let call_override_callback =
232
77
  CallOverrideCallback::new(&env, call_override_callback, self.runtime.clone())?;
78
+
233
79
  let call_override_callback =
234
80
  Arc::new(move |address, data| call_override_callback.call_override(address, data));
235
81
 
236
- provider.set_call_override_callback(Some(call_override_callback));
82
+ let provider = self.provider.clone();
83
+
84
+ let (deferred, promise) = env.create_deferred()?;
85
+ self.runtime.spawn_blocking(move || {
86
+ provider.set_call_override_callback(call_override_callback);
87
+
88
+ deferred.resolve(|_env| Ok(()));
89
+ });
237
90
 
238
- Ok(())
91
+ Ok(promise)
239
92
  }
240
93
 
241
94
  /// Set to `true` to make the traces returned with `eth_call`,
242
95
  /// `eth_estimateGas`, `eth_sendRawTransaction`, `eth_sendTransaction`,
243
96
  /// `evm_mine`, `hardhat_mine` include the full stack and memory. Set to
244
97
  /// `false` to disable this.
245
- #[napi(ts_return_type = "void")]
246
- pub fn set_verbose_tracing(&self, verbose_tracing: bool) {
247
- self.provider.set_verbose_tracing(verbose_tracing);
248
- }
249
- }
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
-
317
- #[napi]
318
- pub struct Response {
319
- // N-API is known to be slow when marshalling `serde_json::Value`s, so we try to return a
320
- // `String`. If the object is too large to be represented as a `String`, we return a `Buffer`
321
- // instead.
322
- data: Either<String, serde_json::Value>,
323
- /// When a transaction fails to execute, the provider returns a trace of the
324
- /// transaction.
325
- solidity_trace: Option<SolidityTraceData>,
326
- /// This may contain zero or more traces, depending on the (batch) request
327
- traces: Vec<Arc<edr_evm::trace::Trace>>,
328
- }
329
-
330
- #[napi]
331
- impl Response {
332
- /// Returns the response data as a JSON string or a JSON object.
333
- #[napi(getter)]
334
- pub fn data(&self) -> Either<String, serde_json::Value> {
335
- self.data.clone()
336
- }
337
-
338
- #[napi(getter)]
339
- pub fn traces(&self) -> Vec<RawTrace> {
340
- self.traces
341
- .iter()
342
- .map(|trace| RawTrace::new(trace.clone()))
343
- .collect()
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
98
  #[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<_>, _>>()?;
99
+ pub async fn set_verbose_tracing(&self, verbose_tracing: bool) -> napi::Result<()> {
100
+ let provider = self.provider.clone();
370
101
 
371
- Ok(Some(stack_trace))
372
- } else {
373
- Ok(None)
374
- }
102
+ self.runtime
103
+ .spawn_blocking(move || {
104
+ provider.set_verbose_tracing(verbose_tracing);
105
+ })
106
+ .await
107
+ .map_err(|error| napi::Error::new(Status::GenericFailure, error.to_string()))
375
108
  }
376
109
  }