foundry_compilers_artifacts_solc/
bytecode.rs

1//! Bytecode related types.
2
3use 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    /// Debugging information at function level
17    #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
18    pub function_debug_data: BTreeMap<String, FunctionDebugData>,
19    /// The bytecode as a hex string.
20    #[serde(serialize_with = "serialize_bytecode_without_prefix")]
21    pub object: BytecodeObject,
22    /// Opcodes list (string)
23    #[serde(default, skip_serializing_if = "Option::is_none")]
24    pub opcodes: Option<String>,
25    /// The source mapping as a string. See the source mapping definition.
26    #[serde(default, skip_serializing_if = "Option::is_none")]
27    pub source_map: Option<String>,
28    /// Array of sources generated by the compiler. Currently only contains a
29    /// single Yul file.
30    #[serde(default, skip_serializing_if = "Vec::is_empty")]
31    pub generated_sources: Vec<GeneratedSource>,
32    /// If given, this is an unlinked object.
33    #[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    /// The bytecode as a hex string.
41    pub object: BytecodeObject,
42    /// The source mapping as a string. See the source mapping definition.
43    #[serde(default, skip_serializing_if = "Option::is_none")]
44    pub source_map: Option<String>,
45    /// If given, this is an unlinked object.
46    #[serde(default)]
47    pub link_references: BTreeMap<String, BTreeMap<String, Vec<Offsets>>>,
48}
49
50impl CompactBytecode {
51    /// Returns a new `CompactBytecode` object that contains nothing, as it's the case for
52    /// interfaces and standalone solidity files that don't contain any contract definitions
53    pub fn empty() -> Self {
54        Self { object: Default::default(), source_map: None, link_references: Default::default() }
55    }
56
57    /// Returns the parsed source map
58    ///
59    /// See also <https://docs.soliditylang.org/en/v0.8.10/internals/source_mappings.html>
60    pub fn source_map(&self) -> Option<Result<SourceMap, SyntaxError>> {
61        self.source_map.as_ref().map(|map| sourcemap::parse(map))
62    }
63
64    /// Tries to link the bytecode object with the `file` and `library` name.
65    /// Replaces all library placeholders with the given address.
66    ///
67    /// Returns true if the bytecode object is fully linked, false otherwise
68    /// This is a noop if the bytecode object is already fully linked.
69    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    /// Returns the bytes of the bytecode object.
89    pub fn bytes(&self) -> Option<&Bytes> {
90        self.object.as_bytes()
91    }
92
93    /// Returns the underlying `Bytes` if the object is a valid bytecode.
94    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    /// Returns the parsed source map
137    ///
138    /// See also <https://docs.soliditylang.org/en/v0.8.10/internals/source_mappings.html>
139    pub fn source_map(&self) -> Option<Result<SourceMap, SyntaxError>> {
140        self.source_map.as_ref().map(|map| sourcemap::parse(map))
141    }
142
143    /// Same as `Bytecode::link` but with fully qualified name (`file.sol:Math`)
144    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    /// Tries to link the bytecode object with the `file` and `library` name.
153    /// Replaces all library placeholders with the given address.
154    ///
155    /// Returns true if the bytecode object is fully linked, false otherwise
156    /// This is a noop if the bytecode object is already fully linked.
157    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    /// Links the bytecode object with all provided `(file, lib, addr)`
177    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    /// Links the bytecode object with all provided `(fully_qualified, addr)`
192    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    /// Returns a reference to the underlying `Bytes` if the object is a valid bytecode.
206    pub fn bytes(&self) -> Option<&Bytes> {
207        self.object.as_bytes()
208    }
209
210    /// Returns the underlying `Bytes` if the object is a valid bytecode.
211    pub fn into_bytes(self) -> Option<Bytes> {
212        self.object.into_bytes()
213    }
214}
215
216/// Represents the bytecode of a contracts that might be not fully linked yet.
217#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
218#[serde(untagged)]
219pub enum BytecodeObject {
220    /// Fully linked bytecode object.
221    #[serde(deserialize_with = "serde_helpers::deserialize_bytes")]
222    Bytecode(Bytes),
223    /// Bytecode as hex string that's not fully linked yet and contains library placeholders.
224    #[serde(with = "serde_helpers::string_bytes")]
225    Unlinked(String),
226}
227
228impl BytecodeObject {
229    /// Returns a reference to the underlying `Bytes` if the object is a valid bytecode.
230    pub fn as_bytes(&self) -> Option<&Bytes> {
231        match self {
232            Self::Bytecode(bytes) => Some(bytes),
233            Self::Unlinked(_) => None,
234        }
235    }
236
237    /// Returns the underlying `Bytes` if the object is a valid bytecode.
238    pub fn into_bytes(self) -> Option<Bytes> {
239        match self {
240            Self::Bytecode(bytes) => Some(bytes),
241            Self::Unlinked(_) => None,
242        }
243    }
244
245    /// Returns the number of bytes of the fully linked bytecode.
246    ///
247    /// Returns `0` if this object is unlinked.
248    pub fn bytes_len(&self) -> usize {
249        self.as_bytes().map(|b| b.as_ref().len()).unwrap_or_default()
250    }
251
252    /// Returns a reference to the underlying `String` if the object is unlinked.
253    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    /// Returns the unlinked `String` if the object is unlinked.
261    pub fn into_unlinked(self) -> Option<String> {
262        match self {
263            Self::Bytecode(_) => None,
264            Self::Unlinked(code) => Some(code),
265        }
266    }
267
268    /// Whether this object is still unlinked.
269    pub fn is_unlinked(&self) -> bool {
270        matches!(self, Self::Unlinked(_))
271    }
272
273    /// Whether this object a valid bytecode.
274    pub fn is_bytecode(&self) -> bool {
275        matches!(self, Self::Bytecode(_))
276    }
277
278    /// Returns `true` if the object is a valid bytecode and not empty.
279    ///
280    /// Returns `false` if the object is a valid but empty or unlinked bytecode.
281    pub fn is_non_empty_bytecode(&self) -> bool {
282        self.as_bytes().map(|c| !c.0.is_empty()).unwrap_or_default()
283    }
284
285    /// Tries to resolve the unlinked string object a valid bytecode object in place.
286    ///
287    /// Returns the string if it is a valid
288    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    /// Links using the fully qualified name of a library.
298    ///
299    /// The fully qualified library name is the path of its source file and the library name
300    /// separated by `:` like `file.sol:Math`
301    ///
302    /// This will replace all occurrences of the library placeholder with the given address.
303    ///
304    /// See also: <https://docs.soliditylang.org/en/develop/using-the-compiler.html#library-linking>
305    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            // the address as hex without prefix
309            let hex_addr = hex::encode(addr);
310
311            // the library placeholder used to be the fully qualified name of the library instead of
312            // the hash. This is also still supported by `solc` so we handle this as well
313            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    /// Links using the `file` and `library` names as fully qualified name `<file>:<library>`.
323    ///
324    /// See [`link_fully_qualified`](Self::link_fully_qualified).
325    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    /// Links the bytecode object with all provided `(file, lib, addr)`.
330    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    /// Returns whether the bytecode contains a matching placeholder using the qualified name.
343    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    /// Returns whether the bytecode contains a matching placeholder.
353    pub fn contains_placeholder(&self, file: &str, library: &str) -> bool {
354        self.contains_fully_qualified_placeholder(&format!("{file}:{library}"))
355    }
356}
357
358// Returns an empty bytecode object
359impl 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
374/// This will serialize the bytecode data without a `0x` prefix, which the `ethers::types::Bytes`
375/// adds by default.
376///
377/// This ensures that we serialize bytecode data in the same way as solc does, See also <https://github.com/gakonst/ethers-rs/issues/1422>
378pub 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    /// Returns a reference to the underlying `Bytes` if the object is a valid bytecode.
405    pub fn bytes(&self) -> Option<&Bytes> {
406        self.bytecode.as_ref().and_then(|bytecode| bytecode.object.as_bytes())
407    }
408
409    /// Returns the underlying `Bytes` if the object is a valid bytecode.
410    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    /// Returns a new `CompactDeployedBytecode` object that contains nothing, as it's the case for
436    /// interfaces and standalone solidity files that don't contain any contract definitions
437    pub fn empty() -> Self {
438        Self { bytecode: Some(CompactBytecode::empty()), immutable_references: Default::default() }
439    }
440
441    /// Returns a reference to the underlying `Bytes` if the object is a valid bytecode.
442    pub fn bytes(&self) -> Option<&Bytes> {
443        self.bytecode.as_ref().and_then(|bytecode| bytecode.object.as_bytes())
444    }
445
446    /// Returns the underlying `Bytes` if the object is a valid bytecode.
447    pub fn into_bytes(self) -> Option<Bytes> {
448        self.bytecode.and_then(|bytecode| bytecode.object.into_bytes())
449    }
450
451    /// Returns the parsed source map
452    ///
453    /// See also <https://docs.soliditylang.org/en/v0.8.10/internals/source_mappings.html>
454    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}