1use crate::{
4 serde_helpers,
5 sourcemap::{self, SourceMap, SyntaxError},
6 FunctionDebugData, GeneratedSource, Offsets,
7};
8use alloy_primitives::{hex, Address, Bytes};
9use foundry_compilers_core::utils;
10use serde::{Deserialize, Serialize, Serializer};
11use std::collections::BTreeMap;
12
13#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
14#[serde(rename_all = "camelCase")]
15pub struct Bytecode {
16 #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
18 pub function_debug_data: BTreeMap<String, FunctionDebugData>,
19 #[serde(serialize_with = "serialize_bytecode_without_prefix")]
21 pub object: BytecodeObject,
22 #[serde(default, skip_serializing_if = "Option::is_none")]
24 pub opcodes: Option<String>,
25 #[serde(default, skip_serializing_if = "Option::is_none")]
27 pub source_map: Option<String>,
28 #[serde(default, skip_serializing_if = "Vec::is_empty")]
31 pub generated_sources: Vec<GeneratedSource>,
32 #[serde(default)]
34 pub link_references: BTreeMap<String, BTreeMap<String, Vec<Offsets>>>,
35}
36
37#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
38#[serde(rename_all = "camelCase")]
39pub struct CompactBytecode {
40 pub object: BytecodeObject,
42 #[serde(default, skip_serializing_if = "Option::is_none")]
44 pub source_map: Option<String>,
45 #[serde(default)]
47 pub link_references: BTreeMap<String, BTreeMap<String, Vec<Offsets>>>,
48}
49
50impl CompactBytecode {
51 pub fn empty() -> Self {
54 Self { object: Default::default(), source_map: None, link_references: Default::default() }
55 }
56
57 pub fn source_map(&self) -> Option<Result<SourceMap, SyntaxError>> {
61 self.source_map.as_ref().map(|map| sourcemap::parse(map))
62 }
63
64 pub fn link(&mut self, file: &str, library: &str, address: Address) -> bool {
70 if !self.object.is_unlinked() {
71 return true;
72 }
73
74 if let Some((key, mut contracts)) = self.link_references.remove_entry(file) {
75 if contracts.remove(library).is_some() {
76 self.object.link(file, library, address);
77 }
78 if !contracts.is_empty() {
79 self.link_references.insert(key, contracts);
80 }
81 if self.link_references.is_empty() {
82 return self.object.resolve().is_some();
83 }
84 }
85 false
86 }
87
88 pub fn bytes(&self) -> Option<&Bytes> {
90 self.object.as_bytes()
91 }
92
93 pub fn into_bytes(self) -> Option<Bytes> {
95 self.object.into_bytes()
96 }
97}
98
99impl From<Bytecode> for CompactBytecode {
100 fn from(bcode: Bytecode) -> Self {
101 Self {
102 object: bcode.object,
103 source_map: bcode.source_map,
104 link_references: bcode.link_references,
105 }
106 }
107}
108
109impl From<CompactBytecode> for Bytecode {
110 fn from(bcode: CompactBytecode) -> Self {
111 Self {
112 object: bcode.object,
113 source_map: bcode.source_map,
114 link_references: bcode.link_references,
115 function_debug_data: Default::default(),
116 opcodes: Default::default(),
117 generated_sources: Default::default(),
118 }
119 }
120}
121
122impl From<BytecodeObject> for Bytecode {
123 fn from(object: BytecodeObject) -> Self {
124 Self {
125 object,
126 function_debug_data: Default::default(),
127 opcodes: Default::default(),
128 source_map: Default::default(),
129 generated_sources: Default::default(),
130 link_references: Default::default(),
131 }
132 }
133}
134
135impl Bytecode {
136 pub fn source_map(&self) -> Option<Result<SourceMap, SyntaxError>> {
140 self.source_map.as_ref().map(|map| sourcemap::parse(map))
141 }
142
143 pub fn link_fully_qualified(&mut self, name: &str, addr: Address) -> bool {
145 if let Some((file, lib)) = name.split_once(':') {
146 self.link(file, lib, addr)
147 } else {
148 false
149 }
150 }
151
152 pub fn link(&mut self, file: &str, library: &str, address: Address) -> bool {
158 if !self.object.is_unlinked() {
159 return true;
160 }
161
162 if let Some((key, mut contracts)) = self.link_references.remove_entry(file) {
163 if contracts.remove(library).is_some() {
164 self.object.link(file, library, address);
165 }
166 if !contracts.is_empty() {
167 self.link_references.insert(key, contracts);
168 }
169 if self.link_references.is_empty() {
170 return self.object.resolve().is_some();
171 }
172 }
173 false
174 }
175
176 pub fn link_all<I, S, T>(&mut self, libs: I) -> bool
178 where
179 I: IntoIterator<Item = (S, T, Address)>,
180 S: AsRef<str>,
181 T: AsRef<str>,
182 {
183 for (file, lib, addr) in libs.into_iter() {
184 if self.link(file.as_ref(), lib.as_ref(), addr) {
185 return true;
186 }
187 }
188 false
189 }
190
191 pub fn link_all_fully_qualified<I, S>(&mut self, libs: I) -> bool
193 where
194 I: IntoIterator<Item = (S, Address)>,
195 S: AsRef<str>,
196 {
197 for (name, addr) in libs.into_iter() {
198 if self.link_fully_qualified(name.as_ref(), addr) {
199 return true;
200 }
201 }
202 false
203 }
204
205 pub fn bytes(&self) -> Option<&Bytes> {
207 self.object.as_bytes()
208 }
209
210 pub fn into_bytes(self) -> Option<Bytes> {
212 self.object.into_bytes()
213 }
214}
215
216#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
218#[serde(untagged)]
219pub enum BytecodeObject {
220 #[serde(deserialize_with = "serde_helpers::deserialize_bytes")]
222 Bytecode(Bytes),
223 #[serde(with = "serde_helpers::string_bytes")]
225 Unlinked(String),
226}
227
228impl BytecodeObject {
229 pub fn as_bytes(&self) -> Option<&Bytes> {
231 match self {
232 Self::Bytecode(bytes) => Some(bytes),
233 Self::Unlinked(_) => None,
234 }
235 }
236
237 pub fn into_bytes(self) -> Option<Bytes> {
239 match self {
240 Self::Bytecode(bytes) => Some(bytes),
241 Self::Unlinked(_) => None,
242 }
243 }
244
245 pub fn bytes_len(&self) -> usize {
249 self.as_bytes().map(|b| b.as_ref().len()).unwrap_or_default()
250 }
251
252 pub fn as_str(&self) -> Option<&str> {
254 match self {
255 Self::Bytecode(_) => None,
256 Self::Unlinked(s) => Some(s.as_str()),
257 }
258 }
259
260 pub fn into_unlinked(self) -> Option<String> {
262 match self {
263 Self::Bytecode(_) => None,
264 Self::Unlinked(code) => Some(code),
265 }
266 }
267
268 pub fn is_unlinked(&self) -> bool {
270 matches!(self, Self::Unlinked(_))
271 }
272
273 pub fn is_bytecode(&self) -> bool {
275 matches!(self, Self::Bytecode(_))
276 }
277
278 pub fn is_non_empty_bytecode(&self) -> bool {
282 self.as_bytes().map(|c| !c.0.is_empty()).unwrap_or_default()
283 }
284
285 pub fn resolve(&mut self) -> Option<&Bytes> {
289 if let Self::Unlinked(unlinked) = self {
290 if let Ok(linked) = hex::decode(unlinked) {
291 *self = Self::Bytecode(linked.into());
292 }
293 }
294 self.as_bytes()
295 }
296
297 pub fn link_fully_qualified(&mut self, name: &str, addr: Address) -> &mut Self {
306 if let Self::Unlinked(unlinked) = self {
307 let place_holder = utils::library_hash_placeholder(name);
308 let hex_addr = hex::encode(addr);
310
311 let fully_qualified_placeholder = utils::library_fully_qualified_placeholder(name);
314
315 *unlinked = unlinked
316 .replace(&format!("__{fully_qualified_placeholder}__"), &hex_addr)
317 .replace(&format!("__{place_holder}__"), &hex_addr)
318 }
319 self
320 }
321
322 pub fn link(&mut self, file: &str, library: &str, addr: Address) -> &mut Self {
326 self.link_fully_qualified(&format!("{file}:{library}"), addr)
327 }
328
329 pub fn link_all<I, S, T>(&mut self, libs: I) -> &mut Self
331 where
332 I: IntoIterator<Item = (S, T, Address)>,
333 S: AsRef<str>,
334 T: AsRef<str>,
335 {
336 for (file, lib, addr) in libs.into_iter() {
337 self.link(file.as_ref(), lib.as_ref(), addr);
338 }
339 self
340 }
341
342 pub fn contains_fully_qualified_placeholder(&self, name: &str) -> bool {
344 if let Self::Unlinked(unlinked) = self {
345 unlinked.contains(&utils::library_hash_placeholder(name))
346 || unlinked.contains(&utils::library_fully_qualified_placeholder(name))
347 } else {
348 false
349 }
350 }
351
352 pub fn contains_placeholder(&self, file: &str, library: &str) -> bool {
354 self.contains_fully_qualified_placeholder(&format!("{file}:{library}"))
355 }
356}
357
358impl Default for BytecodeObject {
360 fn default() -> Self {
361 Self::Bytecode(Default::default())
362 }
363}
364
365impl AsRef<[u8]> for BytecodeObject {
366 fn as_ref(&self) -> &[u8] {
367 match self {
368 Self::Bytecode(code) => code.as_ref(),
369 Self::Unlinked(code) => code.as_bytes(),
370 }
371 }
372}
373
374pub fn serialize_bytecode_without_prefix<S>(
379 bytecode: &BytecodeObject,
380 s: S,
381) -> Result<S::Ok, S::Error>
382where
383 S: Serializer,
384{
385 match bytecode {
386 BytecodeObject::Bytecode(code) => s.serialize_str(&hex::encode(code)),
387 BytecodeObject::Unlinked(code) => s.serialize_str(code.strip_prefix("0x").unwrap_or(code)),
388 }
389}
390
391#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
392pub struct DeployedBytecode {
393 #[serde(flatten)]
394 pub bytecode: Option<Bytecode>,
395 #[serde(
396 default,
397 rename = "immutableReferences",
398 skip_serializing_if = "::std::collections::BTreeMap::is_empty"
399 )]
400 pub immutable_references: BTreeMap<String, Vec<Offsets>>,
401}
402
403impl DeployedBytecode {
404 pub fn bytes(&self) -> Option<&Bytes> {
406 self.bytecode.as_ref().and_then(|bytecode| bytecode.object.as_bytes())
407 }
408
409 pub fn into_bytes(self) -> Option<Bytes> {
411 self.bytecode.and_then(|bytecode| bytecode.object.into_bytes())
412 }
413}
414
415impl From<Bytecode> for DeployedBytecode {
416 fn from(bcode: Bytecode) -> Self {
417 Self { bytecode: Some(bcode), immutable_references: Default::default() }
418 }
419}
420
421#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
422#[serde(rename_all = "camelCase")]
423pub struct CompactDeployedBytecode {
424 #[serde(flatten)]
425 pub bytecode: Option<CompactBytecode>,
426 #[serde(
427 default,
428 rename = "immutableReferences",
429 skip_serializing_if = "::std::collections::BTreeMap::is_empty"
430 )]
431 pub immutable_references: BTreeMap<String, Vec<Offsets>>,
432}
433
434impl CompactDeployedBytecode {
435 pub fn empty() -> Self {
438 Self { bytecode: Some(CompactBytecode::empty()), immutable_references: Default::default() }
439 }
440
441 pub fn bytes(&self) -> Option<&Bytes> {
443 self.bytecode.as_ref().and_then(|bytecode| bytecode.object.as_bytes())
444 }
445
446 pub fn into_bytes(self) -> Option<Bytes> {
448 self.bytecode.and_then(|bytecode| bytecode.object.into_bytes())
449 }
450
451 pub fn source_map(&self) -> Option<Result<SourceMap, SyntaxError>> {
455 self.bytecode.as_ref().and_then(|bytecode| bytecode.source_map())
456 }
457}
458
459impl From<DeployedBytecode> for CompactDeployedBytecode {
460 fn from(bcode: DeployedBytecode) -> Self {
461 Self {
462 bytecode: bcode.bytecode.map(|d_bcode| d_bcode.into()),
463 immutable_references: bcode.immutable_references,
464 }
465 }
466}
467
468impl From<CompactDeployedBytecode> for DeployedBytecode {
469 fn from(bcode: CompactDeployedBytecode) -> Self {
470 Self {
471 bytecode: bcode.bytecode.map(|d_bcode| d_bcode.into()),
472 immutable_references: bcode.immutable_references,
473 }
474 }
475}
476
477#[cfg(test)]
478mod tests {
479 use crate::{ConfigurableContractArtifact, ContractBytecode};
480
481 #[test]
482 fn test_empty_bytecode() {
483 let empty = r#"
484 {
485 "abi": [],
486 "bytecode": {
487 "object": "0x",
488 "linkReferences": {}
489 },
490 "deployedBytecode": {
491 "object": "0x",
492 "linkReferences": {}
493 }
494 }
495 "#;
496
497 let artifact: ConfigurableContractArtifact = serde_json::from_str(empty).unwrap();
498 let contract = artifact.into_contract_bytecode();
499 let bytecode: ContractBytecode = contract.into();
500 let bytecode = bytecode.unwrap();
501 assert!(!bytecode.bytecode.object.is_unlinked());
502 }
503}