@nomicfoundation/edr 0.12.0-alpha.0 → 0.12.0-next.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/src/mock.rs ADDED
@@ -0,0 +1,68 @@
1
+ use std::sync::Arc;
2
+
3
+ use edr_napi_core::provider::SyncProvider;
4
+ use edr_rpc_client::jsonrpc;
5
+ use edr_solidity::contract_decoder::ContractDecoder;
6
+ use napi::tokio::runtime;
7
+ use napi_derive::napi;
8
+
9
+ use crate::{context::EdrContext, provider::Provider};
10
+
11
+ /// A mock provider that always returns the given mocked response.
12
+ pub struct MockProvider {
13
+ mocked_response: serde_json::Value,
14
+ }
15
+
16
+ impl MockProvider {
17
+ pub fn new(mocked_response: serde_json::Value) -> Self {
18
+ Self { mocked_response }
19
+ }
20
+ }
21
+
22
+ impl SyncProvider for MockProvider {
23
+ fn handle_request(
24
+ &self,
25
+ _request: String,
26
+ _contract_decoder: Arc<ContractDecoder>,
27
+ ) -> napi::Result<edr_napi_core::spec::Response<edr_eth::l1::HaltReason>> {
28
+ let response = jsonrpc::ResponseData::Success {
29
+ result: self.mocked_response.clone(),
30
+ };
31
+ edr_napi_core::spec::marshal_response_data(response)
32
+ .map(|data| edr_napi_core::spec::Response {
33
+ solidity_trace: None,
34
+ data,
35
+ traces: Vec::new(),
36
+ })
37
+ .map_err(|error| napi::Error::new(napi::Status::GenericFailure, error.to_string()))
38
+ }
39
+
40
+ fn set_call_override_callback(
41
+ &self,
42
+ _call_override_callback: Arc<dyn edr_provider::SyncCallOverride>,
43
+ ) {
44
+ }
45
+
46
+ fn set_verbose_tracing(&self, _enabled: bool) {}
47
+ }
48
+
49
+ #[napi]
50
+ impl EdrContext {
51
+ #[doc = "Creates a mock provider, which always returns the given response."]
52
+ #[doc = "For testing purposes."]
53
+ #[napi]
54
+ pub fn create_mock_provider(
55
+ &self,
56
+ mocked_response: serde_json::Value,
57
+ ) -> napi::Result<Provider> {
58
+ let provider = Provider::new(
59
+ Arc::new(MockProvider::new(mocked_response)),
60
+ runtime::Handle::current(),
61
+ Arc::new(ContractDecoder::default()),
62
+ #[cfg(feature = "scenarios")]
63
+ None,
64
+ );
65
+
66
+ Ok(provider)
67
+ }
68
+ }
@@ -0,0 +1,50 @@
1
+ use edr_eth::Address;
2
+ use edr_evm::precompile::{self, PrecompileFn, PrecompileWithAddress};
3
+ use napi::bindgen_prelude::Uint8Array;
4
+ use napi_derive::napi;
5
+
6
+ #[napi]
7
+ #[derive(Clone)]
8
+ pub struct Precompile {
9
+ address: Address,
10
+ precompile_fn: PrecompileFn,
11
+ }
12
+
13
+ impl Precompile {
14
+ pub fn new(address: Address, precompile_fn: PrecompileFn) -> Self {
15
+ Self {
16
+ address,
17
+ precompile_fn,
18
+ }
19
+ }
20
+
21
+ /// Returns the address and precompile function as a tuple.
22
+ pub fn to_tuple(&self) -> (Address, PrecompileFn) {
23
+ (self.address, self.precompile_fn)
24
+ }
25
+ }
26
+
27
+ impl From<PrecompileWithAddress> for Precompile {
28
+ fn from(value: PrecompileWithAddress) -> Self {
29
+ Self {
30
+ address: value.0,
31
+ precompile_fn: value.1,
32
+ }
33
+ }
34
+ }
35
+
36
+ #[napi]
37
+ impl Precompile {
38
+ /// Returns the address of the precompile.
39
+ #[napi(catch_unwind, getter)]
40
+ pub fn address(&self) -> Uint8Array {
41
+ Uint8Array::with_data_copied(self.address)
42
+ }
43
+ }
44
+
45
+ /// [RIP-7212](https://github.com/ethereum/RIPs/blob/master/RIPS/rip-7212.md#specification)
46
+ /// secp256r1 precompile.
47
+ #[napi(catch_unwind)]
48
+ pub fn precompile_p256_verify() -> Precompile {
49
+ Precompile::from(precompile::secp256r1::P256VERIFY)
50
+ }
@@ -1,11 +1,12 @@
1
1
  use edr_eth::l1;
2
2
  use edr_napi_core::spec::SolidityTraceData;
3
+ use edr_solidity::contract_decoder::NestedTraceDecoder as _;
3
4
  use napi::Either;
4
5
  use napi_derive::napi;
5
6
 
6
7
  use crate::{
7
8
  cast::TryCast,
8
- trace::{RawTrace, solidity_stack_trace::SolidityStackTrace},
9
+ trace::{solidity_stack_trace::SolidityStackTrace, RawTrace},
9
10
  };
10
11
 
11
12
  #[napi]
@@ -22,14 +23,14 @@ impl From<edr_napi_core::spec::Response<l1::HaltReason>> for Response {
22
23
  #[napi]
23
24
  impl Response {
24
25
  #[doc = "Returns the response data as a JSON string or a JSON object."]
25
- #[napi(getter)]
26
+ #[napi(catch_unwind, getter)]
26
27
  pub fn data(&self) -> Either<String, serde_json::Value> {
27
28
  self.inner.data.clone()
28
29
  }
29
30
 
30
31
  // Rust port of https://github.com/NomicFoundation/hardhat/blob/c20bf195a6efdc2d74e778b7a4a7799aac224841/packages/hardhat-core/src/internal/hardhat-network/provider/provider.ts#L590
31
32
  #[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
+ #[napi(catch_unwind)]
33
34
  pub fn stack_trace(&self) -> napi::Result<Option<SolidityStackTrace>> {
34
35
  let Some(SolidityTraceData {
35
36
  trace,
@@ -44,7 +45,9 @@ impl Response {
44
45
  .map_err(|err| napi::Error::from_reason(err.to_string()))?;
45
46
 
46
47
  if let Some(vm_trace) = nested_trace {
47
- let decoded_trace = contract_decoder.try_to_decode_message_trace(vm_trace);
48
+ let decoded_trace = contract_decoder
49
+ .try_to_decode_nested_trace(vm_trace)
50
+ .map_err(|err| napi::Error::from_reason(err.to_string()))?;
48
51
  let stack_trace = edr_solidity::solidity_tracer::get_stack_trace(decoded_trace)
49
52
  .map_err(|err| napi::Error::from_reason(err.to_string()))?;
50
53
  let stack_trace = stack_trace
@@ -59,7 +62,7 @@ impl Response {
59
62
  }
60
63
 
61
64
  #[doc = "Returns the raw traces of executed contracts. This maybe contain zero or more traces."]
62
- #[napi(getter)]
65
+ #[napi(catch_unwind, getter)]
63
66
  pub fn traces(&self) -> Vec<RawTrace> {
64
67
  self.inner
65
68
  .traces
package/src/provider.rs CHANGED
@@ -6,7 +6,7 @@ use std::sync::Arc;
6
6
 
7
7
  use edr_napi_core::provider::SyncProvider;
8
8
  use edr_solidity::contract_decoder::ContractDecoder;
9
- use napi::{Env, JsFunction, JsObject, Status, tokio::runtime};
9
+ use napi::{tokio::runtime, Env, JsFunction, JsObject, Status};
10
10
  use napi_derive::napi;
11
11
 
12
12
  pub use self::factory::ProviderFactory;
@@ -46,7 +46,7 @@ impl Provider {
46
46
  #[napi]
47
47
  impl Provider {
48
48
  #[doc = "Handles a JSON-RPC request and returns a JSON-RPC response."]
49
- #[napi]
49
+ #[napi(catch_unwind)]
50
50
  pub async fn handle_request(&self, request: String) -> napi::Result<Response> {
51
51
  let provider = self.provider.clone();
52
52
 
@@ -64,24 +64,30 @@ impl Provider {
64
64
  .map(Response::from)
65
65
  }
66
66
 
67
- #[napi(ts_return_type = "Promise<void>")]
67
+ #[napi(catch_unwind, ts_return_type = "Promise<void>")]
68
68
  pub fn set_call_override_callback(
69
69
  &self,
70
70
  env: Env,
71
71
  #[napi(
72
- ts_arg_type = "(contract_address: Buffer, data: Buffer) => Promise<CallOverrideResult | undefined>"
72
+ ts_arg_type = "(contract_address: ArrayBuffer, data: ArrayBuffer) => Promise<CallOverrideResult | undefined>"
73
73
  )]
74
74
  call_override_callback: JsFunction,
75
75
  ) -> napi::Result<JsObject> {
76
+ let (deferred, promise) = env.create_deferred()?;
77
+
76
78
  let call_override_callback =
77
- CallOverrideCallback::new(&env, call_override_callback, self.runtime.clone())?;
79
+ match CallOverrideCallback::new(&env, call_override_callback, self.runtime.clone()) {
80
+ Ok(callback) => callback,
81
+ Err(error) => {
82
+ deferred.reject(error);
83
+ return Ok(promise);
84
+ }
85
+ };
78
86
 
79
87
  let call_override_callback =
80
88
  Arc::new(move |address, data| call_override_callback.call_override(address, data));
81
89
 
82
90
  let provider = self.provider.clone();
83
-
84
- let (deferred, promise) = env.create_deferred()?;
85
91
  self.runtime.spawn_blocking(move || {
86
92
  provider.set_call_override_callback(call_override_callback);
87
93
 
@@ -95,7 +101,7 @@ impl Provider {
95
101
  /// `eth_estimateGas`, `eth_sendRawTransaction`, `eth_sendTransaction`,
96
102
  /// `evm_mine`, `hardhat_mine` include the full stack and memory. Set to
97
103
  /// `false` to disable this.
98
- #[napi]
104
+ #[napi(catch_unwind)]
99
105
  pub async fn set_verbose_tracing(&self, verbose_tracing: bool) -> napi::Result<()> {
100
106
  let provider = self.provider.clone();
101
107
 
package/src/result.rs CHANGED
@@ -1,7 +1,7 @@
1
1
  use edr_evm::trace::AfterMessage;
2
2
  use napi::{
3
- Either, Env, JsBuffer, JsBufferValue,
4
- bindgen_prelude::{BigInt, Buffer, Either3},
3
+ bindgen_prelude::{BigInt, Either3, Uint8Array},
4
+ Either,
5
5
  };
6
6
  use napi_derive::napi;
7
7
 
@@ -44,15 +44,15 @@ impl From<SuccessReason> for edr_eth::result::SuccessReason {
44
44
  #[napi(object)]
45
45
  pub struct CallOutput {
46
46
  /// Return value
47
- pub return_value: JsBuffer,
47
+ pub return_value: Uint8Array,
48
48
  }
49
49
 
50
50
  #[napi(object)]
51
51
  pub struct CreateOutput {
52
52
  /// Return value
53
- pub return_value: JsBuffer,
53
+ pub return_value: Uint8Array,
54
54
  /// Optionally, a 160-bit address
55
- pub address: Option<Buffer>,
55
+ pub address: Option<Uint8Array>,
56
56
  }
57
57
 
58
58
  /// The result when the EVM terminates successfully.
@@ -76,7 +76,7 @@ pub struct RevertResult {
76
76
  /// The amount of gas used
77
77
  pub gas_used: BigInt,
78
78
  /// The transaction output
79
- pub output: JsBuffer,
79
+ pub output: Uint8Array,
80
80
  }
81
81
 
82
82
  /// Indicates that the EVM has experienced an exceptional halt. This causes
@@ -191,15 +191,15 @@ pub struct ExecutionResult {
191
191
  /// The transaction result
192
192
  pub result: Either3<SuccessResult, RevertResult, HaltResult>,
193
193
  /// Optional contract address if the transaction created a new contract.
194
- pub contract_address: Option<Buffer>,
194
+ pub contract_address: Option<Uint8Array>,
195
195
  }
196
196
 
197
- impl ExecutionResult {
198
- pub fn new(env: &Env, message: &AfterMessage<edr_eth::l1::HaltReason>) -> napi::Result<Self> {
197
+ impl From<&AfterMessage<edr_eth::l1::HaltReason>> for ExecutionResult {
198
+ fn from(value: &AfterMessage<edr_eth::l1::HaltReason>) -> Self {
199
199
  let AfterMessage {
200
200
  execution_result,
201
201
  contract_address,
202
- } = message;
202
+ } = value;
203
203
 
204
204
  let result = match execution_result {
205
205
  edr_eth::result::ExecutionResult::Success {
@@ -209,10 +209,7 @@ impl ExecutionResult {
209
209
  logs,
210
210
  output,
211
211
  } => {
212
- let logs = logs
213
- .iter()
214
- .map(|log| ExecutionLog::new(env, log))
215
- .collect::<napi::Result<_>>()?;
212
+ let logs = logs.iter().map(ExecutionLog::from).collect();
216
213
 
217
214
  Either3::A(SuccessResult {
218
215
  reason: SuccessReason::from(*reason),
@@ -221,29 +218,23 @@ impl ExecutionResult {
221
218
  logs,
222
219
  output: match output {
223
220
  edr_eth::result::Output::Call(return_value) => {
224
- let return_value = env
225
- .create_buffer_with_data(return_value.to_vec())
226
- .map(JsBufferValue::into_raw)?;
221
+ let return_value = Uint8Array::with_data_copied(return_value);
227
222
 
228
223
  Either::A(CallOutput { return_value })
229
224
  }
230
225
  edr_eth::result::Output::Create(return_value, address) => {
231
- let return_value = env
232
- .create_buffer_with_data(return_value.to_vec())
233
- .map(JsBufferValue::into_raw)?;
226
+ let return_value = Uint8Array::with_data_copied(return_value);
234
227
 
235
228
  Either::B(CreateOutput {
236
229
  return_value,
237
- address: address.map(|address| Buffer::from(address.as_slice())),
230
+ address: address.as_ref().map(Uint8Array::with_data_copied),
238
231
  })
239
232
  }
240
233
  },
241
234
  })
242
235
  }
243
236
  edr_eth::result::ExecutionResult::Revert { gas_used, output } => {
244
- let output = env
245
- .create_buffer_with_data(output.to_vec())
246
- .map(JsBufferValue::into_raw)?;
237
+ let output = Uint8Array::with_data_copied(output);
247
238
 
248
239
  Either3::B(RevertResult {
249
240
  gas_used: BigInt::from(*gas_used),
@@ -256,11 +247,11 @@ impl ExecutionResult {
256
247
  }),
257
248
  };
258
249
 
259
- let contract_address = contract_address.map(|address| Buffer::from(address.as_slice()));
250
+ let contract_address = contract_address.as_ref().map(Uint8Array::with_data_copied);
260
251
 
261
- Ok(Self {
252
+ Self {
262
253
  result,
263
254
  contract_address,
264
- })
255
+ }
265
256
  }
266
257
  }
package/src/scenarios.rs CHANGED
@@ -2,7 +2,7 @@ use std::time::{SystemTime, UNIX_EPOCH};
2
2
 
3
3
  use edr_scenarios::ScenarioConfig;
4
4
  use napi::tokio::{fs::File, io::AsyncWriteExt, sync::Mutex};
5
- use rand::{Rng, distributions::Alphanumeric};
5
+ use rand::{distributions::Alphanumeric, Rng};
6
6
 
7
7
  const SCENARIO_FILE_PREFIX: &str = "EDR_SCENARIO_PREFIX";
8
8
 
@@ -29,7 +29,7 @@ pub async fn scenario_file(
29
29
  let config = ScenarioConfig {
30
30
  chain_type: Some(chain_type),
31
31
  logger_enabled,
32
- provider_config: provider_config.into(),
32
+ provider_config: provider_config.try_into()?,
33
33
  };
34
34
  let mut line = serde_json::to_string(&config)?;
35
35
  line.push('\n');
package/src/serde.rs ADDED
@@ -0,0 +1,57 @@
1
+ use edr_eth::hex;
2
+ use napi::bindgen_prelude::{BigInt, Uint8Array};
3
+ use serde::Serializer;
4
+
5
+ /// Serialize a `Uint8Array` as a 0x-prefixed hex string
6
+ pub fn serialize_uint8array_as_hex<S>(buffer: &Uint8Array, serializer: S) -> Result<S::Ok, S::Error>
7
+ where
8
+ S: Serializer,
9
+ {
10
+ let hex_string = format!("0x{}", hex::encode(buffer));
11
+ serializer.serialize_str(&hex_string)
12
+ }
13
+
14
+ /// Serialize an Option<Uint8Array> as a 0x-prefixed hex string or None
15
+ pub fn serialize_optional_uint8array_as_hex<S>(
16
+ buffer: &Option<Uint8Array>,
17
+ serializer: S,
18
+ ) -> Result<S::Ok, S::Error>
19
+ where
20
+ S: Serializer,
21
+ {
22
+ match buffer {
23
+ Some(buf) => {
24
+ let hex_string = format!("0x{}", hex::encode(buf));
25
+ serializer.serialize_str(&hex_string)
26
+ }
27
+ None => serializer.serialize_none(),
28
+ }
29
+ }
30
+
31
+ /// Serialize a `BigInt` as a struct with `sign_bit` and `words` fields
32
+ pub fn serialize_bigint_as_struct<S>(value: &BigInt, serializer: S) -> Result<S::Ok, S::Error>
33
+ where
34
+ S: Serializer,
35
+ {
36
+ use serde::ser::SerializeStruct;
37
+
38
+ let mut state = serializer.serialize_struct("BigInt", 2)?;
39
+ state.serialize_field("sign_bit", &value.sign_bit)?;
40
+ state.serialize_field("words", &value.words)?;
41
+ state.end()
42
+ }
43
+
44
+ /// Serialize an Option<BigInt> as a struct with `sign_bit` and `words` fields
45
+ /// or None
46
+ pub fn serialize_optional_bigint_as_struct<S>(
47
+ value: &Option<BigInt>,
48
+ serializer: S,
49
+ ) -> Result<S::Ok, S::Error>
50
+ where
51
+ S: Serializer,
52
+ {
53
+ match value {
54
+ Some(val) => serialize_bigint_as_struct(val, serializer),
55
+ None => serializer.serialize_none(),
56
+ }
57
+ }
@@ -0,0 +1,184 @@
1
+ use std::{
2
+ borrow::Cow,
3
+ collections::{BTreeMap, HashMap},
4
+ };
5
+
6
+ use napi_derive::napi;
7
+
8
+ /// A compilation artifact.
9
+ #[derive(Clone, Debug)]
10
+ #[napi(object)]
11
+ pub struct Artifact {
12
+ /// The identifier of the artifact.
13
+ pub id: ArtifactId,
14
+ /// The test contract.
15
+ pub contract: ContractData,
16
+ }
17
+
18
+ /// The identifier of a Solidity contract.
19
+ #[derive(Clone, Debug)]
20
+ #[napi(object)]
21
+ pub struct ArtifactId {
22
+ /// The name of the contract.
23
+ pub name: String,
24
+ /// Original source file path.
25
+ pub source: String,
26
+ /// The solc semver string.
27
+ pub solc_version: String,
28
+ }
29
+
30
+ impl From<edr_solidity::artifacts::ArtifactId> for ArtifactId {
31
+ fn from(value: edr_solidity::artifacts::ArtifactId) -> Self {
32
+ Self {
33
+ name: value.name,
34
+ source: value.source.to_string_lossy().to_string(),
35
+ solc_version: value.version.to_string(),
36
+ }
37
+ }
38
+ }
39
+
40
+ impl TryFrom<ArtifactId> for edr_solidity::artifacts::ArtifactId {
41
+ type Error = napi::Error;
42
+
43
+ fn try_from(value: ArtifactId) -> napi::Result<Self> {
44
+ Ok(edr_solidity::artifacts::ArtifactId {
45
+ name: value.name,
46
+ source: value.source.parse().map_err(|_err| {
47
+ napi::Error::new(napi::Status::GenericFailure, "Invalid source path")
48
+ })?,
49
+ version: value.solc_version.parse().map_err(|_err| {
50
+ napi::Error::new(napi::Status::GenericFailure, "Invalid solc semver string")
51
+ })?,
52
+ })
53
+ }
54
+ }
55
+
56
+ /// A test contract to execute.
57
+ #[derive(Clone, Debug)]
58
+ #[napi(object)]
59
+ pub struct ContractData {
60
+ /// Contract ABI as json string.
61
+ pub abi: String,
62
+ /// Contract creation code as hex string. It can be missing if the contract
63
+ /// is ABI only.
64
+ pub bytecode: Option<String>,
65
+ /// The link references of the deployment bytecode.
66
+ pub link_references: Option<HashMap<String, HashMap<String, Vec<LinkReference>>>>,
67
+ /// Contract runtime code as hex string. It can be missing if the contract
68
+ /// is ABI only.
69
+ pub deployed_bytecode: Option<String>,
70
+ /// The link references of the deployed bytecode.
71
+ pub deployed_link_references: Option<HashMap<String, HashMap<String, Vec<LinkReference>>>>,
72
+ }
73
+
74
+ impl TryFrom<ContractData> for foundry_compilers::artifacts::CompactContractBytecode {
75
+ type Error = napi::Error;
76
+
77
+ fn try_from(contract: ContractData) -> napi::Result<Self> {
78
+ Ok(foundry_compilers::artifacts::CompactContractBytecode {
79
+ abi: Some(serde_json::from_str(&contract.abi).map_err(|_err| {
80
+ napi::Error::new(napi::Status::GenericFailure, "Invalid JSON ABI")
81
+ })?),
82
+ bytecode: contract
83
+ .bytecode
84
+ .map(|bytecode| {
85
+ let link_references =
86
+ convert_link_references(contract.link_references.unwrap_or_default());
87
+ let object = convert_bytecode(bytecode, !link_references.is_empty())?;
88
+ Ok::<_, napi::Error>(foundry_compilers::artifacts::CompactBytecode {
89
+ object,
90
+ source_map: None,
91
+ link_references,
92
+ })
93
+ })
94
+ .transpose()?,
95
+ deployed_bytecode: contract
96
+ .deployed_bytecode
97
+ .map(|deployed_bytecode| {
98
+ let link_references = convert_link_references(
99
+ contract.deployed_link_references.unwrap_or_default(),
100
+ );
101
+ let object = convert_bytecode(deployed_bytecode, !link_references.is_empty())?;
102
+ let compact_bytecode = foundry_compilers::artifacts::CompactBytecode {
103
+ object,
104
+ source_map: None,
105
+ link_references,
106
+ };
107
+ Ok::<_, napi::Error>(foundry_compilers::artifacts::CompactDeployedBytecode {
108
+ bytecode: Some(compact_bytecode),
109
+ immutable_references: BTreeMap::default(),
110
+ })
111
+ })
112
+ .transpose()?,
113
+ })
114
+ }
115
+ }
116
+
117
+ impl TryFrom<ContractData> for foundry_compilers::artifacts::CompactContractBytecodeCow<'static> {
118
+ type Error = napi::Error;
119
+
120
+ fn try_from(contract: ContractData) -> napi::Result<Self> {
121
+ let c: foundry_compilers::artifacts::CompactContractBytecode = contract.try_into()?;
122
+ Ok(foundry_compilers::artifacts::CompactContractBytecodeCow {
123
+ abi: c.abi.map(Cow::Owned),
124
+ bytecode: c.bytecode.map(Cow::Owned),
125
+ deployed_bytecode: c.deployed_bytecode.map(Cow::Owned),
126
+ })
127
+ }
128
+ }
129
+
130
+ // The order of link references as supplied through the NAPI interface doesn't
131
+ // matter, but the order can matter downstream for deterministic address
132
+ // generation.
133
+ fn convert_link_references(
134
+ link_references: HashMap<String, HashMap<String, Vec<LinkReference>>>,
135
+ ) -> BTreeMap<String, BTreeMap<String, Vec<foundry_compilers::artifacts::Offsets>>> {
136
+ link_references
137
+ .into_iter()
138
+ .map(|(file, libraries)| {
139
+ let lib_map = libraries
140
+ .into_iter()
141
+ .map(|(library_name, references)| {
142
+ let offsets = references.into_iter().map(Into::into).collect();
143
+ (library_name, offsets)
144
+ })
145
+ .collect();
146
+ (file, lib_map)
147
+ })
148
+ .collect()
149
+ }
150
+
151
+ fn convert_bytecode(
152
+ bytecode: String,
153
+ needs_linking: bool,
154
+ ) -> napi::Result<foundry_compilers::artifacts::BytecodeObject> {
155
+ if needs_linking {
156
+ Ok(foundry_compilers::artifacts::BytecodeObject::Unlinked(
157
+ bytecode,
158
+ ))
159
+ } else {
160
+ let bytes = bytecode.parse().map_err(|err| {
161
+ let message = format!("Hex decoding error while parsing bytecode: '{err}'. Maybe forgot to pass link references for a contract that needs linking?");
162
+ napi::Error::from_reason(message)
163
+ })?;
164
+ Ok(foundry_compilers::artifacts::BytecodeObject::Bytecode(
165
+ bytes,
166
+ ))
167
+ }
168
+ }
169
+
170
+ #[derive(Clone, Debug)]
171
+ #[napi(object)]
172
+ pub struct LinkReference {
173
+ pub start: u32,
174
+ pub length: u32,
175
+ }
176
+
177
+ impl From<LinkReference> for foundry_compilers::artifacts::Offsets {
178
+ fn from(value: LinkReference) -> Self {
179
+ Self {
180
+ start: value.start,
181
+ length: value.length,
182
+ }
183
+ }
184
+ }