1use crate::{
4 artifacts::{serde_helpers, FunctionDebugData, GeneratedSource, Offsets},
5 sourcemap::{self, SourceMap, SyntaxError},
6 utils,
7};
8use ethers_core::{abi::Address, types::Bytes};
9use serde::{Deserialize, Serialize, Serializer};
10use std::collections::BTreeMap;
11
12#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
13#[serde(rename_all = "camelCase")]
14pub struct Bytecode {
15 #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
17 pub function_debug_data: BTreeMap<String, FunctionDebugData>,
18 #[serde(serialize_with = "serialize_bytecode_without_prefix")]
20 pub object: BytecodeObject,
21 #[serde(default, skip_serializing_if = "Option::is_none")]
23 pub opcodes: Option<String>,
24 #[serde(default, skip_serializing_if = "Option::is_none")]
26 pub source_map: Option<String>,
27 #[serde(default, skip_serializing_if = "Vec::is_empty")]
30 pub generated_sources: Vec<GeneratedSource>,
31 #[serde(default)]
33 pub link_references: BTreeMap<String, BTreeMap<String, Vec<Offsets>>>,
34}
35
36#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
37#[serde(rename_all = "camelCase")]
38pub struct CompactBytecode {
39 pub object: BytecodeObject,
41 #[serde(default, skip_serializing_if = "Option::is_none")]
43 pub source_map: Option<String>,
44 #[serde(default)]
46 pub link_references: BTreeMap<String, BTreeMap<String, Vec<Offsets>>>,
47}
48
49impl CompactBytecode {
50 pub fn empty() -> Self {
53 Self { object: Default::default(), source_map: None, link_references: Default::default() }
54 }
55 pub fn source_map(&self) -> Option<Result<SourceMap, SyntaxError>> {
59 self.source_map.as_ref().map(|map| sourcemap::parse(map))
60 }
61
62 pub fn link(
68 &mut self,
69 file: impl AsRef<str>,
70 library: impl AsRef<str>,
71 address: Address,
72 ) -> bool {
73 if !self.object.is_unlinked() {
74 return true
75 }
76
77 let file = file.as_ref();
78 let library = library.as_ref();
79 if let Some((key, mut contracts)) = self.link_references.remove_entry(file) {
80 if contracts.remove(library).is_some() {
81 self.object.link(file, library, address);
82 }
83 if !contracts.is_empty() {
84 self.link_references.insert(key, contracts);
85 }
86 if self.link_references.is_empty() {
87 return self.object.resolve().is_some()
88 }
89 }
90 false
91 }
92}
93
94impl From<Bytecode> for CompactBytecode {
95 fn from(bcode: Bytecode) -> CompactBytecode {
96 CompactBytecode {
97 object: bcode.object,
98 source_map: bcode.source_map,
99 link_references: bcode.link_references,
100 }
101 }
102}
103
104impl From<CompactBytecode> for Bytecode {
105 fn from(bcode: CompactBytecode) -> Bytecode {
106 Bytecode {
107 object: bcode.object,
108 source_map: bcode.source_map,
109 link_references: bcode.link_references,
110 function_debug_data: Default::default(),
111 opcodes: Default::default(),
112 generated_sources: Default::default(),
113 }
114 }
115}
116
117impl From<BytecodeObject> for Bytecode {
118 fn from(object: BytecodeObject) -> Bytecode {
119 Bytecode {
120 object,
121 function_debug_data: Default::default(),
122 opcodes: Default::default(),
123 source_map: Default::default(),
124 generated_sources: Default::default(),
125 link_references: Default::default(),
126 }
127 }
128}
129
130impl Bytecode {
131 pub fn source_map(&self) -> Option<Result<SourceMap, SyntaxError>> {
135 self.source_map.as_ref().map(|map| sourcemap::parse(map))
136 }
137
138 pub fn link_fully_qualified(&mut self, name: impl AsRef<str>, addr: Address) -> bool {
140 if let Some((file, lib)) = name.as_ref().split_once(':') {
141 self.link(file, lib, addr)
142 } else {
143 false
144 }
145 }
146
147 pub fn link(
153 &mut self,
154 file: impl AsRef<str>,
155 library: impl AsRef<str>,
156 address: Address,
157 ) -> bool {
158 if !self.object.is_unlinked() {
159 return true
160 }
161
162 let file = file.as_ref();
163 let library = library.as_ref();
164 if let Some((key, mut contracts)) = self.link_references.remove_entry(file) {
165 if contracts.remove(library).is_some() {
166 self.object.link(file, library, address);
167 }
168 if !contracts.is_empty() {
169 self.link_references.insert(key, contracts);
170 }
171 if self.link_references.is_empty() {
172 return self.object.resolve().is_some()
173 }
174 }
175 false
176 }
177
178 pub fn link_all<I, S, T>(&mut self, libs: I) -> bool
180 where
181 I: IntoIterator<Item = (S, T, Address)>,
182 S: AsRef<str>,
183 T: AsRef<str>,
184 {
185 for (file, lib, addr) in libs.into_iter() {
186 if self.link(file, lib, addr) {
187 return true
188 }
189 }
190 false
191 }
192
193 pub fn link_all_fully_qualified<I, S>(&mut self, libs: I) -> bool
195 where
196 I: IntoIterator<Item = (S, Address)>,
197 S: AsRef<str>,
198 {
199 for (name, addr) in libs.into_iter() {
200 if self.link_fully_qualified(name, addr) {
201 return true
202 }
203 }
204 false
205 }
206}
207
208#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
210#[serde(untagged)]
211pub enum BytecodeObject {
212 #[serde(deserialize_with = "serde_helpers::deserialize_bytes")]
214 Bytecode(Bytes),
215 #[serde(with = "serde_helpers::string_bytes")]
217 Unlinked(String),
218}
219
220impl BytecodeObject {
221 pub fn into_bytes(self) -> Option<Bytes> {
223 match self {
224 BytecodeObject::Bytecode(bytes) => Some(bytes),
225 BytecodeObject::Unlinked(_) => None,
226 }
227 }
228
229 pub fn as_bytes(&self) -> Option<&Bytes> {
232 match self {
233 BytecodeObject::Bytecode(bytes) => Some(bytes),
234 BytecodeObject::Unlinked(_) => None,
235 }
236 }
237
238 pub fn bytes_len(&self) -> usize {
242 self.as_bytes().map(|b| b.as_ref().len()).unwrap_or_default()
243 }
244
245 pub fn as_str(&self) -> Option<&str> {
247 match self {
248 BytecodeObject::Bytecode(_) => None,
249 BytecodeObject::Unlinked(s) => Some(s.as_str()),
250 }
251 }
252
253 pub fn into_unlinked(self) -> Option<String> {
255 match self {
256 BytecodeObject::Bytecode(_) => None,
257 BytecodeObject::Unlinked(code) => Some(code),
258 }
259 }
260
261 pub fn is_unlinked(&self) -> bool {
263 matches!(self, BytecodeObject::Unlinked(_))
264 }
265
266 pub fn is_bytecode(&self) -> bool {
268 matches!(self, BytecodeObject::Bytecode(_))
269 }
270
271 pub fn is_non_empty_bytecode(&self) -> bool {
274 self.as_bytes().map(|c| !c.0.is_empty()).unwrap_or_default()
275 }
276
277 pub fn resolve(&mut self) -> Option<&Bytes> {
281 if let BytecodeObject::Unlinked(unlinked) = self {
282 if let Ok(linked) = hex::decode(unlinked) {
283 *self = BytecodeObject::Bytecode(linked.into());
284 }
285 }
286 self.as_bytes()
287 }
288
289 pub fn link_fully_qualified(&mut self, name: impl AsRef<str>, addr: Address) -> &mut Self {
297 if let BytecodeObject::Unlinked(ref mut unlinked) = self {
298 let name = name.as_ref();
299 let place_holder = utils::library_hash_placeholder(name);
300 let hex_addr = hex::encode(addr);
302
303 let fully_qualified_placeholder = utils::library_fully_qualified_placeholder(name);
306
307 *unlinked = unlinked
308 .replace(&format!("__{fully_qualified_placeholder}__"), &hex_addr)
309 .replace(&format!("__{place_holder}__"), &hex_addr)
310 }
311 self
312 }
313
314 pub fn link(
317 &mut self,
318 file: impl AsRef<str>,
319 library: impl AsRef<str>,
320 addr: Address,
321 ) -> &mut Self {
322 self.link_fully_qualified(format!("{}:{}", file.as_ref(), library.as_ref(),), addr)
323 }
324
325 pub fn link_all<I, S, T>(&mut self, libs: I) -> &mut Self
327 where
328 I: IntoIterator<Item = (S, T, Address)>,
329 S: AsRef<str>,
330 T: AsRef<str>,
331 {
332 for (file, lib, addr) in libs.into_iter() {
333 self.link(file, lib, addr);
334 }
335 self
336 }
337
338 pub fn contains_fully_qualified_placeholder(&self, name: impl AsRef<str>) -> bool {
340 if let BytecodeObject::Unlinked(unlinked) = self {
341 let name = name.as_ref();
342 unlinked.contains(&utils::library_hash_placeholder(name)) ||
343 unlinked.contains(&utils::library_fully_qualified_placeholder(name))
344 } else {
345 false
346 }
347 }
348
349 pub fn contains_placeholder(&self, file: impl AsRef<str>, library: impl AsRef<str>) -> bool {
351 self.contains_fully_qualified_placeholder(format!("{}:{}", file.as_ref(), library.as_ref()))
352 }
353}
354
355impl Default for BytecodeObject {
357 fn default() -> Self {
358 BytecodeObject::Bytecode(Default::default())
359 }
360}
361
362impl AsRef<[u8]> for BytecodeObject {
363 fn as_ref(&self) -> &[u8] {
364 match self {
365 BytecodeObject::Bytecode(code) => code.as_ref(),
366 BytecodeObject::Unlinked(code) => code.as_bytes(),
367 }
368 }
369}
370
371pub fn serialize_bytecode_without_prefix<S>(
376 bytecode: &BytecodeObject,
377 s: S,
378) -> Result<S::Ok, S::Error>
379where
380 S: Serializer,
381{
382 match bytecode {
383 BytecodeObject::Bytecode(code) => s.serialize_str(&hex::encode(code)),
384 BytecodeObject::Unlinked(code) => {
385 s.serialize_str(code.strip_prefix("0x").unwrap_or(code.as_str()))
386 }
387 }
388}
389
390#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
391pub struct DeployedBytecode {
392 #[serde(flatten)]
393 pub bytecode: Option<Bytecode>,
394 #[serde(
395 default,
396 rename = "immutableReferences",
397 skip_serializing_if = "::std::collections::BTreeMap::is_empty"
398 )]
399 pub immutable_references: BTreeMap<String, Vec<Offsets>>,
400}
401
402impl DeployedBytecode {
403 pub fn into_bytes(self) -> Option<Bytes> {
405 self.bytecode?.object.into_bytes()
406 }
407}
408
409impl From<Bytecode> for DeployedBytecode {
410 fn from(bcode: Bytecode) -> DeployedBytecode {
411 DeployedBytecode { bytecode: Some(bcode), immutable_references: Default::default() }
412 }
413}
414
415#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
416#[serde(rename_all = "camelCase")]
417pub struct CompactDeployedBytecode {
418 #[serde(flatten)]
419 pub bytecode: Option<CompactBytecode>,
420 #[serde(
421 default,
422 rename = "immutableReferences",
423 skip_serializing_if = "::std::collections::BTreeMap::is_empty"
424 )]
425 pub immutable_references: BTreeMap<String, Vec<Offsets>>,
426}
427
428impl CompactDeployedBytecode {
429 pub fn empty() -> Self {
432 Self { bytecode: Some(CompactBytecode::empty()), immutable_references: Default::default() }
433 }
434
435 pub fn source_map(&self) -> Option<Result<SourceMap, SyntaxError>> {
439 self.bytecode.as_ref().and_then(|bytecode| bytecode.source_map())
440 }
441}
442
443impl From<DeployedBytecode> for CompactDeployedBytecode {
444 fn from(bcode: DeployedBytecode) -> CompactDeployedBytecode {
445 CompactDeployedBytecode {
446 bytecode: bcode.bytecode.map(|d_bcode| d_bcode.into()),
447 immutable_references: bcode.immutable_references,
448 }
449 }
450}
451
452impl From<CompactDeployedBytecode> for DeployedBytecode {
453 fn from(bcode: CompactDeployedBytecode) -> DeployedBytecode {
454 DeployedBytecode {
455 bytecode: bcode.bytecode.map(|d_bcode| d_bcode.into()),
456 immutable_references: bcode.immutable_references,
457 }
458 }
459}
460
461#[cfg(test)]
462mod tests {
463 use crate::{artifacts::ContractBytecode, ConfigurableContractArtifact};
464
465 #[test]
466 fn test_empty_bytecode() {
467 let empty = r#"
468 {
469 "abi": [],
470 "bytecode": {
471 "object": "0x",
472 "linkReferences": {}
473 },
474 "deployedBytecode": {
475 "object": "0x",
476 "linkReferences": {}
477 }
478 }
479 "#;
480
481 let artifact: ConfigurableContractArtifact = serde_json::from_str(empty).unwrap();
482 let contract = artifact.into_contract_bytecode();
483 let bytecode: ContractBytecode = contract.into();
484 let bytecode = bytecode.unwrap();
485 assert!(!bytecode.bytecode.object.is_unlinked());
486 }
487}