1use crate::{
4 FunctionDebugData, GeneratedSource, Offsets, serde_helpers,
5 sourcemap::{self, SourceMap, SyntaxError},
6};
7use alloy_primitives::{Address, Bytes, hex};
8use foundry_compilers_core::utils;
9use serde::{Deserialize, Serialize, Serializer};
10use std::collections::BTreeMap;
11
12#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
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, PartialEq, Eq, Serialize, Deserialize)]
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
56 pub fn source_map(&self) -> Option<Result<SourceMap, SyntaxError>> {
60 self.source_map.as_ref().map(|map| sourcemap::parse(map))
61 }
62
63 pub fn link(&mut self, file: &str, library: &str, address: Address) -> bool {
69 if !self.object.is_unlinked() {
70 return true;
71 }
72
73 if let Some((key, mut contracts)) = self.link_references.remove_entry(file) {
74 if contracts.remove(library).is_some() {
75 self.object.link(file, library, address);
76 }
77 if !contracts.is_empty() {
78 self.link_references.insert(key, contracts);
79 }
80 if self.link_references.is_empty() {
81 return self.object.resolve().is_some();
82 }
83 }
84 false
85 }
86
87 pub const fn bytes(&self) -> Option<&Bytes> {
89 self.object.as_bytes()
90 }
91
92 pub fn into_bytes(self) -> Option<Bytes> {
94 self.object.into_bytes()
95 }
96}
97
98impl From<Bytecode> for CompactBytecode {
99 fn from(bcode: Bytecode) -> Self {
100 Self {
101 object: bcode.object,
102 source_map: bcode.source_map,
103 link_references: bcode.link_references,
104 }
105 }
106}
107
108impl From<CompactBytecode> for Bytecode {
109 fn from(bcode: CompactBytecode) -> Self {
110 Self {
111 object: bcode.object,
112 source_map: bcode.source_map,
113 link_references: bcode.link_references,
114 function_debug_data: Default::default(),
115 opcodes: Default::default(),
116 generated_sources: Default::default(),
117 }
118 }
119}
120
121impl From<BytecodeObject> for Bytecode {
122 fn from(object: BytecodeObject) -> Self {
123 Self {
124 object,
125 function_debug_data: Default::default(),
126 opcodes: Default::default(),
127 source_map: Default::default(),
128 generated_sources: Default::default(),
129 link_references: Default::default(),
130 }
131 }
132}
133
134impl Bytecode {
135 pub fn source_map(&self) -> Option<Result<SourceMap, SyntaxError>> {
139 self.source_map.as_ref().map(|map| sourcemap::parse(map))
140 }
141
142 pub fn link_fully_qualified(&mut self, name: &str, addr: Address) -> bool {
144 if let Some((file, lib)) = name.split_once(':') {
145 self.link(file, lib, addr)
146 } else {
147 false
148 }
149 }
150
151 pub fn link(&mut self, file: &str, library: &str, address: Address) -> bool {
157 if !self.object.is_unlinked() {
158 return true;
159 }
160
161 if let Some((key, mut contracts)) = self.link_references.remove_entry(file) {
162 if contracts.remove(library).is_some() {
163 self.object.link(file, library, address);
164 }
165 if !contracts.is_empty() {
166 self.link_references.insert(key, contracts);
167 }
168 if self.link_references.is_empty() {
169 return self.object.resolve().is_some();
170 }
171 }
172 false
173 }
174
175 pub fn link_all<I, S, T>(&mut self, libs: I) -> bool
177 where
178 I: IntoIterator<Item = (S, T, Address)>,
179 S: AsRef<str>,
180 T: AsRef<str>,
181 {
182 for (file, lib, addr) in libs {
183 if self.link(file.as_ref(), lib.as_ref(), addr) {
184 return true;
185 }
186 }
187 false
188 }
189
190 pub fn link_all_fully_qualified<I, S>(&mut self, libs: I) -> bool
192 where
193 I: IntoIterator<Item = (S, Address)>,
194 S: AsRef<str>,
195 {
196 for (name, addr) in libs {
197 if self.link_fully_qualified(name.as_ref(), addr) {
198 return true;
199 }
200 }
201 false
202 }
203
204 pub const fn bytes(&self) -> Option<&Bytes> {
206 self.object.as_bytes()
207 }
208
209 pub fn into_bytes(self) -> Option<Bytes> {
211 self.object.into_bytes()
212 }
213}
214
215#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
217#[serde(untagged)]
218pub enum BytecodeObject {
219 #[serde(deserialize_with = "serde_helpers::deserialize_bytes")]
221 Bytecode(Bytes),
222 #[serde(with = "serde_helpers::string_bytes")]
224 Unlinked(String),
225}
226
227impl BytecodeObject {
228 pub const fn as_bytes(&self) -> Option<&Bytes> {
230 match self {
231 Self::Bytecode(bytes) => Some(bytes),
232 Self::Unlinked(_) => None,
233 }
234 }
235
236 pub fn into_bytes(self) -> Option<Bytes> {
238 match self {
239 Self::Bytecode(bytes) => Some(bytes),
240 Self::Unlinked(_) => None,
241 }
242 }
243
244 pub fn bytes_len(&self) -> usize {
248 self.as_bytes().map(|b| b.as_ref().len()).unwrap_or_default()
249 }
250
251 pub const fn as_str(&self) -> Option<&str> {
253 match self {
254 Self::Bytecode(_) => None,
255 Self::Unlinked(s) => Some(s.as_str()),
256 }
257 }
258
259 pub fn into_unlinked(self) -> Option<String> {
261 match self {
262 Self::Bytecode(_) => None,
263 Self::Unlinked(code) => Some(code),
264 }
265 }
266
267 pub const fn is_unlinked(&self) -> bool {
269 matches!(self, Self::Unlinked(_))
270 }
271
272 pub const fn is_bytecode(&self) -> bool {
274 matches!(self, Self::Bytecode(_))
275 }
276
277 pub fn is_non_empty_bytecode(&self) -> bool {
281 self.as_bytes().map(|c| !c.0.is_empty()).unwrap_or_default()
282 }
283
284 pub fn resolve(&mut self) -> Option<&Bytes> {
288 if let Self::Unlinked(unlinked) = self
289 && let Ok(linked) = hex::decode(unlinked)
290 {
291 *self = Self::Bytecode(linked.into());
292 }
293 self.as_bytes()
294 }
295
296 pub fn link_fully_qualified(&mut self, name: &str, addr: Address) -> &mut Self {
305 if let Self::Unlinked(unlinked) = self {
306 link(unlinked, name, addr);
307 }
308 self
309 }
310
311 pub fn link(&mut self, file: &str, library: &str, addr: Address) -> &mut Self {
315 self.link_fully_qualified(&format!("{file}:{library}"), addr)
316 }
317
318 pub fn link_all<I, S, T>(&mut self, libs: I) -> &mut Self
320 where
321 I: IntoIterator<Item = (S, T, Address)>,
322 S: AsRef<str>,
323 T: AsRef<str>,
324 {
325 for (file, lib, addr) in libs {
326 self.link(file.as_ref(), lib.as_ref(), addr);
327 }
328 self
329 }
330
331 pub fn contains_fully_qualified_placeholder(&self, name: &str) -> bool {
333 if let Self::Unlinked(unlinked) = self {
334 unlinked.contains(&utils::library_hash_placeholder(name))
335 || unlinked.contains(&utils::library_fully_qualified_placeholder(name))
336 } else {
337 false
338 }
339 }
340
341 pub fn contains_placeholder(&self, file: &str, library: &str) -> bool {
343 self.contains_fully_qualified_placeholder(&format!("{file}:{library}"))
344 }
345
346 pub fn strip_bytecode_placeholders(&self) -> Option<Bytes> {
351 match &self {
352 Self::Bytecode(bytes) => Some(bytes.clone()),
353 Self::Unlinked(s) => {
354 let bytes = replace_placeholders_and_decode(s).ok()?;
356 Some(bytes.into())
357 }
358 }
359 }
360}
361
362impl Default for BytecodeObject {
364 fn default() -> Self {
365 Self::Bytecode(Default::default())
366 }
367}
368
369impl AsRef<[u8]> for BytecodeObject {
370 fn as_ref(&self) -> &[u8] {
371 match self {
372 Self::Bytecode(code) => code.as_ref(),
373 Self::Unlinked(code) => code.as_bytes(),
374 }
375 }
376}
377
378fn link(unlinked: &mut String, name: &str, addr: Address) {
380 const LEN: usize = 40;
381
382 let mut refs = vec![];
383 let mut find = |needle: &str| {
384 assert_eq!(needle.len(), LEN, "{needle:?}");
385 refs.extend(memchr::memmem::find_iter(unlinked.as_bytes(), needle));
386 };
387
388 let placeholder = utils::library_hash_placeholder(name);
389 find(&format!("__{placeholder}__"));
390
391 let fully_qualified_placeholder = utils::library_fully_qualified_placeholder(name);
394 find(&format!("__{fully_qualified_placeholder}__"));
395
396 if refs.is_empty() {
397 debug!("no references found while linking {name} -> {addr}");
398 return;
399 }
400
401 let mut buffer = hex::Buffer::<20, false>::new();
403 let hex_addr = &*buffer.format(&addr);
404 assert_eq!(hex_addr.len(), LEN, "{hex_addr:?}");
405
406 let unlinked = unsafe { unlinked.as_bytes_mut() };
410 for &idx in &refs {
411 unlinked[idx..idx + LEN].copy_from_slice(hex_addr.as_bytes());
412 }
413}
414
415pub fn serialize_bytecode_without_prefix<S>(
420 bytecode: &BytecodeObject,
421 s: S,
422) -> Result<S::Ok, S::Error>
423where
424 S: Serializer,
425{
426 match bytecode {
427 BytecodeObject::Bytecode(code) => s.serialize_str(&hex::encode(code)),
428 BytecodeObject::Unlinked(code) => s.serialize_str(code.strip_prefix("0x").unwrap_or(code)),
429 }
430}
431
432pub fn replace_placeholders_and_decode(s: &str) -> Result<Vec<u8>, hex::FromHexError> {
434 let re = regex::Regex::new(r"_\$.{34}\$_").expect("invalid regex");
435 let s = re.replace_all(s, "00".repeat(40));
436 hex::decode(s.as_bytes())
437}
438
439#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
440pub struct DeployedBytecode {
441 #[serde(flatten)]
442 pub bytecode: Option<Bytecode>,
443 #[serde(
444 default,
445 rename = "immutableReferences",
446 skip_serializing_if = "::std::collections::BTreeMap::is_empty"
447 )]
448 pub immutable_references: BTreeMap<String, Vec<Offsets>>,
449}
450
451impl DeployedBytecode {
452 pub fn bytes(&self) -> Option<&Bytes> {
454 self.bytecode.as_ref().and_then(|bytecode| bytecode.object.as_bytes())
455 }
456
457 pub fn into_bytes(self) -> Option<Bytes> {
459 self.bytecode.and_then(|bytecode| bytecode.object.into_bytes())
460 }
461}
462
463impl From<Bytecode> for DeployedBytecode {
464 fn from(bcode: Bytecode) -> Self {
465 Self { bytecode: Some(bcode), immutable_references: Default::default() }
466 }
467}
468
469#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
470#[serde(rename_all = "camelCase")]
471pub struct CompactDeployedBytecode {
472 #[serde(flatten)]
473 pub bytecode: Option<CompactBytecode>,
474 #[serde(
475 default,
476 rename = "immutableReferences",
477 skip_serializing_if = "::std::collections::BTreeMap::is_empty"
478 )]
479 pub immutable_references: BTreeMap<String, Vec<Offsets>>,
480}
481
482impl CompactDeployedBytecode {
483 pub fn empty() -> Self {
486 Self { bytecode: Some(CompactBytecode::empty()), immutable_references: Default::default() }
487 }
488
489 pub fn bytes(&self) -> Option<&Bytes> {
491 self.bytecode.as_ref().and_then(|bytecode| bytecode.object.as_bytes())
492 }
493
494 pub fn into_bytes(self) -> Option<Bytes> {
496 self.bytecode.and_then(|bytecode| bytecode.object.into_bytes())
497 }
498
499 pub fn source_map(&self) -> Option<Result<SourceMap, SyntaxError>> {
503 self.bytecode.as_ref().and_then(|bytecode| bytecode.source_map())
504 }
505}
506
507impl From<DeployedBytecode> for CompactDeployedBytecode {
508 fn from(bcode: DeployedBytecode) -> Self {
509 Self {
510 bytecode: bcode.bytecode.map(|d_bcode| d_bcode.into()),
511 immutable_references: bcode.immutable_references,
512 }
513 }
514}
515
516impl From<CompactDeployedBytecode> for DeployedBytecode {
517 fn from(bcode: CompactDeployedBytecode) -> Self {
518 Self {
519 bytecode: bcode.bytecode.map(|d_bcode| d_bcode.into()),
520 immutable_references: bcode.immutable_references,
521 }
522 }
523}
524
525#[cfg(test)]
526mod tests {
527 use crate::{ConfigurableContractArtifact, ContractBytecode};
528
529 #[test]
530 fn test_empty_bytecode() {
531 let empty = r#"
532 {
533 "abi": [],
534 "bytecode": {
535 "object": "0x",
536 "linkReferences": {}
537 },
538 "deployedBytecode": {
539 "object": "0x",
540 "linkReferences": {}
541 }
542 }
543 "#;
544
545 let artifact: ConfigurableContractArtifact = serde_json::from_str(empty).unwrap();
546 let contract = artifact.into_contract_bytecode();
547 let bytecode: ContractBytecode = contract.into();
548 let bytecode = bytecode.unwrap();
549 assert!(!bytecode.bytecode.object.is_unlinked());
550 }
551}