Skip to main content

foundry_compilers_artifacts_solc/
bytecode.rs

1//! Bytecode related types.
2
3use 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    /// Debugging information at function level
16    #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
17    pub function_debug_data: BTreeMap<String, FunctionDebugData>,
18    /// The bytecode as a hex string.
19    #[serde(serialize_with = "serialize_bytecode_without_prefix")]
20    pub object: BytecodeObject,
21    /// Opcodes list (string)
22    #[serde(default, skip_serializing_if = "Option::is_none")]
23    pub opcodes: Option<String>,
24    /// The source mapping as a string. See the source mapping definition.
25    #[serde(default, skip_serializing_if = "Option::is_none")]
26    pub source_map: Option<String>,
27    /// Array of sources generated by the compiler. Currently only contains a
28    /// single Yul file.
29    #[serde(default, skip_serializing_if = "Vec::is_empty")]
30    pub generated_sources: Vec<GeneratedSource>,
31    /// If given, this is an unlinked object.
32    #[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    /// The bytecode as a hex string.
40    pub object: BytecodeObject,
41    /// The source mapping as a string. See the source mapping definition.
42    #[serde(default, skip_serializing_if = "Option::is_none")]
43    pub source_map: Option<String>,
44    /// If given, this is an unlinked object.
45    #[serde(default)]
46    pub link_references: BTreeMap<String, BTreeMap<String, Vec<Offsets>>>,
47}
48
49impl CompactBytecode {
50    /// Returns a new `CompactBytecode` object that contains nothing, as it's the case for
51    /// interfaces and standalone solidity files that don't contain any contract definitions
52    pub fn empty() -> Self {
53        Self { object: Default::default(), source_map: None, link_references: Default::default() }
54    }
55
56    /// Returns the parsed source map
57    ///
58    /// See also <https://docs.soliditylang.org/en/v0.8.10/internals/source_mappings.html>
59    pub fn source_map(&self) -> Option<Result<SourceMap, SyntaxError>> {
60        self.source_map.as_ref().map(|map| sourcemap::parse(map))
61    }
62
63    /// Tries to link the bytecode object with the `file` and `library` name.
64    /// Replaces all library placeholders with the given address.
65    ///
66    /// Returns true if the bytecode object is fully linked, false otherwise
67    /// This is a noop if the bytecode object is already fully linked.
68    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    /// Returns the bytes of the bytecode object.
88    pub const fn bytes(&self) -> Option<&Bytes> {
89        self.object.as_bytes()
90    }
91
92    /// Returns the underlying `Bytes` if the object is a valid bytecode.
93    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    /// Returns the parsed source map
136    ///
137    /// See also <https://docs.soliditylang.org/en/v0.8.10/internals/source_mappings.html>
138    pub fn source_map(&self) -> Option<Result<SourceMap, SyntaxError>> {
139        self.source_map.as_ref().map(|map| sourcemap::parse(map))
140    }
141
142    /// Same as `Bytecode::link` but with fully qualified name (`file.sol:Math`)
143    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    /// Tries to link the bytecode object with the `file` and `library` name.
152    /// Replaces all library placeholders with the given address.
153    ///
154    /// Returns true if the bytecode object is fully linked, false otherwise
155    /// This is a noop if the bytecode object is already fully linked.
156    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    /// Links the bytecode object with all provided `(file, lib, addr)`
176    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    /// Links the bytecode object with all provided `(fully_qualified, addr)`
191    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    /// Returns a reference to the underlying `Bytes` if the object is a valid bytecode.
205    pub const fn bytes(&self) -> Option<&Bytes> {
206        self.object.as_bytes()
207    }
208
209    /// Returns the underlying `Bytes` if the object is a valid bytecode.
210    pub fn into_bytes(self) -> Option<Bytes> {
211        self.object.into_bytes()
212    }
213}
214
215/// Represents the bytecode of a contracts that might be not fully linked yet.
216#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
217#[serde(untagged)]
218pub enum BytecodeObject {
219    /// Fully linked bytecode object.
220    #[serde(deserialize_with = "serde_helpers::deserialize_bytes")]
221    Bytecode(Bytes),
222    /// Bytecode as hex string that's not fully linked yet and contains library placeholders.
223    #[serde(with = "serde_helpers::string_bytes")]
224    Unlinked(String),
225}
226
227impl BytecodeObject {
228    /// Returns a reference to the underlying `Bytes` if the object is a valid bytecode.
229    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    /// Returns the underlying `Bytes` if the object is a valid bytecode.
237    pub fn into_bytes(self) -> Option<Bytes> {
238        match self {
239            Self::Bytecode(bytes) => Some(bytes),
240            Self::Unlinked(_) => None,
241        }
242    }
243
244    /// Returns the number of bytes of the fully linked bytecode.
245    ///
246    /// Returns `0` if this object is unlinked.
247    pub fn bytes_len(&self) -> usize {
248        self.as_bytes().map(|b| b.as_ref().len()).unwrap_or_default()
249    }
250
251    /// Returns a reference to the underlying `String` if the object is unlinked.
252    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    /// Returns the unlinked `String` if the object is unlinked.
260    pub fn into_unlinked(self) -> Option<String> {
261        match self {
262            Self::Bytecode(_) => None,
263            Self::Unlinked(code) => Some(code),
264        }
265    }
266
267    /// Whether this object is still unlinked.
268    pub const fn is_unlinked(&self) -> bool {
269        matches!(self, Self::Unlinked(_))
270    }
271
272    /// Whether this object a valid bytecode.
273    pub const fn is_bytecode(&self) -> bool {
274        matches!(self, Self::Bytecode(_))
275    }
276
277    /// Returns `true` if the object is a valid bytecode and not empty.
278    ///
279    /// Returns `false` if the object is a valid but empty or unlinked bytecode.
280    pub fn is_non_empty_bytecode(&self) -> bool {
281        self.as_bytes().map(|c| !c.0.is_empty()).unwrap_or_default()
282    }
283
284    /// Tries to resolve the unlinked string object a valid bytecode object in place.
285    ///
286    /// Returns the string if it is a valid
287    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    /// Links using the fully qualified name of a library.
297    ///
298    /// The fully qualified library name is the path of its source file and the library name
299    /// separated by `:` like `file.sol:Math`
300    ///
301    /// This will replace all occurrences of the library placeholder with the given address.
302    ///
303    /// See also: <https://docs.soliditylang.org/en/develop/using-the-compiler.html#library-linking>
304    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    /// Links using the `file` and `library` names as fully qualified name `<file>:<library>`.
312    ///
313    /// See [`link_fully_qualified`](Self::link_fully_qualified).
314    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    /// Links the bytecode object with all provided `(file, lib, addr)`.
319    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    /// Returns whether the bytecode contains a matching placeholder using the qualified name.
332    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    /// Returns whether the bytecode contains a matching placeholder.
342    pub fn contains_placeholder(&self, file: &str, library: &str) -> bool {
343        self.contains_fully_qualified_placeholder(&format!("{file}:{library}"))
344    }
345
346    /// Strips all __$xxx$__ placeholders from the bytecode if it's an unlinked bytecode.
347    /// by replacing them with 20 zero bytes.
348    /// This is useful for matching bytecodes to a contract source, and for the source map,
349    /// in which the actual address of the placeholder isn't important.
350    pub fn strip_bytecode_placeholders(&self) -> Option<Bytes> {
351        match &self {
352            Self::Bytecode(bytes) => Some(bytes.clone()),
353            Self::Unlinked(s) => {
354                // Replace all __$xxx$__ placeholders with 32 zero bytes
355                let bytes = replace_placeholders_and_decode(s).ok()?;
356                Some(bytes.into())
357            }
358        }
359    }
360}
361
362// Returns an empty bytecode object
363impl 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
378/// Reference: <https://github.com/argotorg/solidity/blob/965166317bbc2b02067eb87f222a2dce9d24e289/libevmasm/LinkerObject.cpp#L38>
379fn 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    // The library placeholder used to be the fully qualified name of the library instead of
392    // the hash. This is also still supported by `solc` so we handle this as well.
393    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    // The address as hex without prefix.
402    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    // The ranges are non-overlapping, so we don't need to sort, and can iterate in whatever order
407    // because of equal lengths.
408    // SAFETY: We're replacing LEN bytes at a time, and all the indexes come from the same string.
409    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
415/// This will serialize the bytecode data without a `0x` prefix, which the `ethers::types::Bytes`
416/// adds by default.
417///
418/// This ensures that we serialize bytecode data in the same way as solc does, See also <https://github.com/gakonst/ethers-rs/issues/1422>
419pub 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
432// Replace all __$xxx$__ placeholders with 32 zero bytes
433pub 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    /// Returns a reference to the underlying `Bytes` if the object is a valid bytecode.
453    pub fn bytes(&self) -> Option<&Bytes> {
454        self.bytecode.as_ref().and_then(|bytecode| bytecode.object.as_bytes())
455    }
456
457    /// Returns the underlying `Bytes` if the object is a valid bytecode.
458    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    /// Returns a new `CompactDeployedBytecode` object that contains nothing, as it's the case for
484    /// interfaces and standalone solidity files that don't contain any contract definitions
485    pub fn empty() -> Self {
486        Self { bytecode: Some(CompactBytecode::empty()), immutable_references: Default::default() }
487    }
488
489    /// Returns a reference to the underlying `Bytes` if the object is a valid bytecode.
490    pub fn bytes(&self) -> Option<&Bytes> {
491        self.bytecode.as_ref().and_then(|bytecode| bytecode.object.as_bytes())
492    }
493
494    /// Returns the underlying `Bytes` if the object is a valid bytecode.
495    pub fn into_bytes(self) -> Option<Bytes> {
496        self.bytecode.and_then(|bytecode| bytecode.object.into_bytes())
497    }
498
499    /// Returns the parsed source map
500    ///
501    /// See also <https://docs.soliditylang.org/en/v0.8.10/internals/source_mappings.html>
502    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}