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 link(unlinked, name, addr);
308 }
309 self
310 }
311
312 pub fn link(&mut self, file: &str, library: &str, addr: Address) -> &mut Self {
316 self.link_fully_qualified(&format!("{file}:{library}"), addr)
317 }
318
319 pub fn link_all<I, S, T>(&mut self, libs: I) -> &mut Self
321 where
322 I: IntoIterator<Item = (S, T, Address)>,
323 S: AsRef<str>,
324 T: AsRef<str>,
325 {
326 for (file, lib, addr) in libs.into_iter() {
327 self.link(file.as_ref(), lib.as_ref(), addr);
328 }
329 self
330 }
331
332 pub fn contains_fully_qualified_placeholder(&self, name: &str) -> bool {
334 if let Self::Unlinked(unlinked) = self {
335 unlinked.contains(&utils::library_hash_placeholder(name))
336 || unlinked.contains(&utils::library_fully_qualified_placeholder(name))
337 } else {
338 false
339 }
340 }
341
342 pub fn contains_placeholder(&self, file: &str, library: &str) -> bool {
344 self.contains_fully_qualified_placeholder(&format!("{file}:{library}"))
345 }
346
347 pub fn strip_bytecode_placeholders(&self) -> Option<Bytes> {
352 match &self {
353 Self::Bytecode(bytes) => Some(bytes.clone()),
354 Self::Unlinked(s) => {
355 let bytes = replace_placeholders_and_decode(s).ok()?;
357 Some(bytes.into())
358 }
359 }
360 }
361}
362
363impl Default for BytecodeObject {
365 fn default() -> Self {
366 Self::Bytecode(Default::default())
367 }
368}
369
370impl AsRef<[u8]> for BytecodeObject {
371 fn as_ref(&self) -> &[u8] {
372 match self {
373 Self::Bytecode(code) => code.as_ref(),
374 Self::Unlinked(code) => code.as_bytes(),
375 }
376 }
377}
378
379fn link(unlinked: &mut String, name: &str, addr: Address) {
381 const LEN: usize = 40;
382
383 let mut refs = vec![];
384 let mut find = |needle: &str| {
385 assert_eq!(needle.len(), LEN, "{needle:?}");
386 refs.extend(memchr::memmem::find_iter(unlinked.as_bytes(), needle));
387 };
388
389 let placeholder = utils::library_hash_placeholder(name);
390 find(&format!("__{placeholder}__"));
391
392 let fully_qualified_placeholder = utils::library_fully_qualified_placeholder(name);
395 find(&format!("__{fully_qualified_placeholder}__"));
396
397 if refs.is_empty() {
398 debug!("no references found while linking {name} -> {addr}");
399 return;
400 }
401
402 let mut buffer = hex::Buffer::<20, false>::new();
404 let hex_addr = &*buffer.format(&addr);
405 assert_eq!(hex_addr.len(), LEN, "{hex_addr:?}");
406
407 let unlinked = unsafe { unlinked.as_bytes_mut() };
411 for &idx in &refs {
412 unlinked[idx..idx + LEN].copy_from_slice(hex_addr.as_bytes());
413 }
414}
415
416pub fn serialize_bytecode_without_prefix<S>(
421 bytecode: &BytecodeObject,
422 s: S,
423) -> Result<S::Ok, S::Error>
424where
425 S: Serializer,
426{
427 match bytecode {
428 BytecodeObject::Bytecode(code) => s.serialize_str(&hex::encode(code)),
429 BytecodeObject::Unlinked(code) => s.serialize_str(code.strip_prefix("0x").unwrap_or(code)),
430 }
431}
432
433pub fn replace_placeholders_and_decode(s: &str) -> Result<Vec<u8>, hex::FromHexError> {
435 let re = regex::Regex::new(r"_\$.{34}\$_").expect("invalid regex");
436 let s = re.replace_all(s, "00".repeat(40));
437 hex::decode(s.as_bytes())
438}
439
440#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
441pub struct DeployedBytecode {
442 #[serde(flatten)]
443 pub bytecode: Option<Bytecode>,
444 #[serde(
445 default,
446 rename = "immutableReferences",
447 skip_serializing_if = "::std::collections::BTreeMap::is_empty"
448 )]
449 pub immutable_references: BTreeMap<String, Vec<Offsets>>,
450}
451
452impl DeployedBytecode {
453 pub fn bytes(&self) -> Option<&Bytes> {
455 self.bytecode.as_ref().and_then(|bytecode| bytecode.object.as_bytes())
456 }
457
458 pub fn into_bytes(self) -> Option<Bytes> {
460 self.bytecode.and_then(|bytecode| bytecode.object.into_bytes())
461 }
462}
463
464impl From<Bytecode> for DeployedBytecode {
465 fn from(bcode: Bytecode) -> Self {
466 Self { bytecode: Some(bcode), immutable_references: Default::default() }
467 }
468}
469
470#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
471#[serde(rename_all = "camelCase")]
472pub struct CompactDeployedBytecode {
473 #[serde(flatten)]
474 pub bytecode: Option<CompactBytecode>,
475 #[serde(
476 default,
477 rename = "immutableReferences",
478 skip_serializing_if = "::std::collections::BTreeMap::is_empty"
479 )]
480 pub immutable_references: BTreeMap<String, Vec<Offsets>>,
481}
482
483impl CompactDeployedBytecode {
484 pub fn empty() -> Self {
487 Self { bytecode: Some(CompactBytecode::empty()), immutable_references: Default::default() }
488 }
489
490 pub fn bytes(&self) -> Option<&Bytes> {
492 self.bytecode.as_ref().and_then(|bytecode| bytecode.object.as_bytes())
493 }
494
495 pub fn into_bytes(self) -> Option<Bytes> {
497 self.bytecode.and_then(|bytecode| bytecode.object.into_bytes())
498 }
499
500 pub fn source_map(&self) -> Option<Result<SourceMap, SyntaxError>> {
504 self.bytecode.as_ref().and_then(|bytecode| bytecode.source_map())
505 }
506}
507
508impl From<DeployedBytecode> for CompactDeployedBytecode {
509 fn from(bcode: DeployedBytecode) -> Self {
510 Self {
511 bytecode: bcode.bytecode.map(|d_bcode| d_bcode.into()),
512 immutable_references: bcode.immutable_references,
513 }
514 }
515}
516
517impl From<CompactDeployedBytecode> for DeployedBytecode {
518 fn from(bcode: CompactDeployedBytecode) -> Self {
519 Self {
520 bytecode: bcode.bytecode.map(|d_bcode| d_bcode.into()),
521 immutable_references: bcode.immutable_references,
522 }
523 }
524}
525
526#[cfg(test)]
527mod tests {
528 use crate::{ConfigurableContractArtifact, ContractBytecode};
529
530 #[test]
531 fn test_empty_bytecode() {
532 let empty = r#"
533 {
534 "abi": [],
535 "bytecode": {
536 "object": "0x",
537 "linkReferences": {}
538 },
539 "deployedBytecode": {
540 "object": "0x",
541 "linkReferences": {}
542 }
543 }
544 "#;
545
546 let artifact: ConfigurableContractArtifact = serde_json::from_str(empty).unwrap();
547 let contract = artifact.into_contract_bytecode();
548 let bytecode: ContractBytecode = contract.into();
549 let bytecode = bytecode.unwrap();
550 assert!(!bytecode.bytecode.object.is_unlinked());
551 }
552}