ethers_solc/artifacts/
bytecode.rs

1//! Bytecode related types
2
3use crate::{
4    artifacts::{serde_helpers, FunctionDebugData, GeneratedSource, Offsets},
5    sourcemap::{self, SourceMap, SyntaxError},
6    utils,
7};
8use ethers_core::{abi::Address, types::Bytes};
9use serde::{Deserialize, Serialize, Serializer};
10use std::collections::BTreeMap;
11
12#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
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, Serialize, Deserialize, Eq, PartialEq)]
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    /// Returns the parsed source map
56    ///
57    /// See also <https://docs.soliditylang.org/en/v0.8.10/internals/source_mappings.html>
58    pub fn source_map(&self) -> Option<Result<SourceMap, SyntaxError>> {
59        self.source_map.as_ref().map(|map| sourcemap::parse(map))
60    }
61
62    /// Tries to link the bytecode object with the `file` and `library` name.
63    /// Replaces all library placeholders with the given address.
64    ///
65    /// Returns true if the bytecode object is fully linked, false otherwise
66    /// This is a noop if the bytecode object is already fully linked.
67    pub fn link(
68        &mut self,
69        file: impl AsRef<str>,
70        library: impl AsRef<str>,
71        address: Address,
72    ) -> bool {
73        if !self.object.is_unlinked() {
74            return true
75        }
76
77        let file = file.as_ref();
78        let library = library.as_ref();
79        if let Some((key, mut contracts)) = self.link_references.remove_entry(file) {
80            if contracts.remove(library).is_some() {
81                self.object.link(file, library, address);
82            }
83            if !contracts.is_empty() {
84                self.link_references.insert(key, contracts);
85            }
86            if self.link_references.is_empty() {
87                return self.object.resolve().is_some()
88            }
89        }
90        false
91    }
92}
93
94impl From<Bytecode> for CompactBytecode {
95    fn from(bcode: Bytecode) -> CompactBytecode {
96        CompactBytecode {
97            object: bcode.object,
98            source_map: bcode.source_map,
99            link_references: bcode.link_references,
100        }
101    }
102}
103
104impl From<CompactBytecode> for Bytecode {
105    fn from(bcode: CompactBytecode) -> Bytecode {
106        Bytecode {
107            object: bcode.object,
108            source_map: bcode.source_map,
109            link_references: bcode.link_references,
110            function_debug_data: Default::default(),
111            opcodes: Default::default(),
112            generated_sources: Default::default(),
113        }
114    }
115}
116
117impl From<BytecodeObject> for Bytecode {
118    fn from(object: BytecodeObject) -> Bytecode {
119        Bytecode {
120            object,
121            function_debug_data: Default::default(),
122            opcodes: Default::default(),
123            source_map: Default::default(),
124            generated_sources: Default::default(),
125            link_references: Default::default(),
126        }
127    }
128}
129
130impl Bytecode {
131    /// Returns the parsed source map
132    ///
133    /// See also <https://docs.soliditylang.org/en/v0.8.10/internals/source_mappings.html>
134    pub fn source_map(&self) -> Option<Result<SourceMap, SyntaxError>> {
135        self.source_map.as_ref().map(|map| sourcemap::parse(map))
136    }
137
138    /// Same as `Bytecode::link` but with fully qualified name (`file.sol:Math`)
139    pub fn link_fully_qualified(&mut self, name: impl AsRef<str>, addr: Address) -> bool {
140        if let Some((file, lib)) = name.as_ref().split_once(':') {
141            self.link(file, lib, addr)
142        } else {
143            false
144        }
145    }
146
147    /// Tries to link the bytecode object with the `file` and `library` name.
148    /// Replaces all library placeholders with the given address.
149    ///
150    /// Returns true if the bytecode object is fully linked, false otherwise
151    /// This is a noop if the bytecode object is already fully linked.
152    pub fn link(
153        &mut self,
154        file: impl AsRef<str>,
155        library: impl AsRef<str>,
156        address: Address,
157    ) -> bool {
158        if !self.object.is_unlinked() {
159            return true
160        }
161
162        let file = file.as_ref();
163        let library = library.as_ref();
164        if let Some((key, mut contracts)) = self.link_references.remove_entry(file) {
165            if contracts.remove(library).is_some() {
166                self.object.link(file, library, address);
167            }
168            if !contracts.is_empty() {
169                self.link_references.insert(key, contracts);
170            }
171            if self.link_references.is_empty() {
172                return self.object.resolve().is_some()
173            }
174        }
175        false
176    }
177
178    /// Links the bytecode object with all provided `(file, lib, addr)`
179    pub fn link_all<I, S, T>(&mut self, libs: I) -> bool
180    where
181        I: IntoIterator<Item = (S, T, Address)>,
182        S: AsRef<str>,
183        T: AsRef<str>,
184    {
185        for (file, lib, addr) in libs.into_iter() {
186            if self.link(file, lib, addr) {
187                return true
188            }
189        }
190        false
191    }
192
193    /// Links the bytecode object with all provided `(fully_qualified, addr)`
194    pub fn link_all_fully_qualified<I, S>(&mut self, libs: I) -> bool
195    where
196        I: IntoIterator<Item = (S, Address)>,
197        S: AsRef<str>,
198    {
199        for (name, addr) in libs.into_iter() {
200            if self.link_fully_qualified(name, addr) {
201                return true
202            }
203        }
204        false
205    }
206}
207
208/// Represents the bytecode of a contracts that might be not fully linked yet.
209#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
210#[serde(untagged)]
211pub enum BytecodeObject {
212    /// Fully linked bytecode object
213    #[serde(deserialize_with = "serde_helpers::deserialize_bytes")]
214    Bytecode(Bytes),
215    /// Bytecode as hex string that's not fully linked yet and contains library placeholders
216    #[serde(with = "serde_helpers::string_bytes")]
217    Unlinked(String),
218}
219
220impl BytecodeObject {
221    /// Returns the underlying `Bytes` if the object is a valid bytecode, and not empty
222    pub fn into_bytes(self) -> Option<Bytes> {
223        match self {
224            BytecodeObject::Bytecode(bytes) => Some(bytes),
225            BytecodeObject::Unlinked(_) => None,
226        }
227    }
228
229    /// Returns a reference to the underlying `Bytes` if the object is a valid bytecode, and not
230    /// empty
231    pub fn as_bytes(&self) -> Option<&Bytes> {
232        match self {
233            BytecodeObject::Bytecode(bytes) => Some(bytes),
234            BytecodeObject::Unlinked(_) => None,
235        }
236    }
237
238    /// Returns the number of bytes of the fully linked bytecode
239    ///
240    /// Returns `0` if this object is unlinked.
241    pub fn bytes_len(&self) -> usize {
242        self.as_bytes().map(|b| b.as_ref().len()).unwrap_or_default()
243    }
244
245    /// Returns a reference to the underlying `String` if the object is unlinked
246    pub fn as_str(&self) -> Option<&str> {
247        match self {
248            BytecodeObject::Bytecode(_) => None,
249            BytecodeObject::Unlinked(s) => Some(s.as_str()),
250        }
251    }
252
253    /// Returns the unlinked `String` if the object is unlinked or empty
254    pub fn into_unlinked(self) -> Option<String> {
255        match self {
256            BytecodeObject::Bytecode(_) => None,
257            BytecodeObject::Unlinked(code) => Some(code),
258        }
259    }
260
261    /// Whether this object is still unlinked
262    pub fn is_unlinked(&self) -> bool {
263        matches!(self, BytecodeObject::Unlinked(_))
264    }
265
266    /// Whether this object a valid bytecode
267    pub fn is_bytecode(&self) -> bool {
268        matches!(self, BytecodeObject::Bytecode(_))
269    }
270
271    /// Returns `true` if the object is a valid bytecode and not empty.
272    /// Returns false the object is a valid but empty bytecode or unlinked.
273    pub fn is_non_empty_bytecode(&self) -> bool {
274        self.as_bytes().map(|c| !c.0.is_empty()).unwrap_or_default()
275    }
276
277    /// Tries to resolve the unlinked string object a valid bytecode object in place
278    ///
279    /// Returns the string if it is a valid
280    pub fn resolve(&mut self) -> Option<&Bytes> {
281        if let BytecodeObject::Unlinked(unlinked) = self {
282            if let Ok(linked) = hex::decode(unlinked) {
283                *self = BytecodeObject::Bytecode(linked.into());
284            }
285        }
286        self.as_bytes()
287    }
288
289    /// Link using the fully qualified name of a library
290    /// The fully qualified library name is the path of its source file and the library name
291    /// separated by `:` like `file.sol:Math`
292    ///
293    /// This will replace all occurrences of the library placeholder with the given address.
294    ///
295    /// See also: <https://docs.soliditylang.org/en/develop/using-the-compiler.html#library-linking>
296    pub fn link_fully_qualified(&mut self, name: impl AsRef<str>, addr: Address) -> &mut Self {
297        if let BytecodeObject::Unlinked(ref mut unlinked) = self {
298            let name = name.as_ref();
299            let place_holder = utils::library_hash_placeholder(name);
300            // the address as hex without prefix
301            let hex_addr = hex::encode(addr);
302
303            // the library placeholder used to be the fully qualified name of the library instead of
304            // the hash. This is also still supported by `solc` so we handle this as well
305            let fully_qualified_placeholder = utils::library_fully_qualified_placeholder(name);
306
307            *unlinked = unlinked
308                .replace(&format!("__{fully_qualified_placeholder}__"), &hex_addr)
309                .replace(&format!("__{place_holder}__"), &hex_addr)
310        }
311        self
312    }
313
314    /// Link using the `file` and `library` names as fully qualified name `<file>:<library>`
315    /// See `BytecodeObject::link_fully_qualified`
316    pub fn link(
317        &mut self,
318        file: impl AsRef<str>,
319        library: impl AsRef<str>,
320        addr: Address,
321    ) -> &mut Self {
322        self.link_fully_qualified(format!("{}:{}", file.as_ref(), library.as_ref(),), addr)
323    }
324
325    /// Links the bytecode object with all provided `(file, lib, addr)`
326    pub fn link_all<I, S, T>(&mut self, libs: I) -> &mut Self
327    where
328        I: IntoIterator<Item = (S, T, Address)>,
329        S: AsRef<str>,
330        T: AsRef<str>,
331    {
332        for (file, lib, addr) in libs.into_iter() {
333            self.link(file, lib, addr);
334        }
335        self
336    }
337
338    /// Whether the bytecode contains a matching placeholder using the qualified name
339    pub fn contains_fully_qualified_placeholder(&self, name: impl AsRef<str>) -> bool {
340        if let BytecodeObject::Unlinked(unlinked) = self {
341            let name = name.as_ref();
342            unlinked.contains(&utils::library_hash_placeholder(name)) ||
343                unlinked.contains(&utils::library_fully_qualified_placeholder(name))
344        } else {
345            false
346        }
347    }
348
349    /// Whether the bytecode contains a matching placeholder
350    pub fn contains_placeholder(&self, file: impl AsRef<str>, library: impl AsRef<str>) -> bool {
351        self.contains_fully_qualified_placeholder(format!("{}:{}", file.as_ref(), library.as_ref()))
352    }
353}
354
355// Returns an empty bytecode object
356impl Default for BytecodeObject {
357    fn default() -> Self {
358        BytecodeObject::Bytecode(Default::default())
359    }
360}
361
362impl AsRef<[u8]> for BytecodeObject {
363    fn as_ref(&self) -> &[u8] {
364        match self {
365            BytecodeObject::Bytecode(code) => code.as_ref(),
366            BytecodeObject::Unlinked(code) => code.as_bytes(),
367        }
368    }
369}
370
371/// This will serialize the bytecode data without a `0x` prefix, which the `ethers::types::Bytes`
372/// adds by default.
373///
374/// This ensures that we serialize bytecode data in the same way as solc does, See also <https://github.com/gakonst/ethers-rs/issues/1422>
375pub fn serialize_bytecode_without_prefix<S>(
376    bytecode: &BytecodeObject,
377    s: S,
378) -> Result<S::Ok, S::Error>
379where
380    S: Serializer,
381{
382    match bytecode {
383        BytecodeObject::Bytecode(code) => s.serialize_str(&hex::encode(code)),
384        BytecodeObject::Unlinked(code) => {
385            s.serialize_str(code.strip_prefix("0x").unwrap_or(code.as_str()))
386        }
387    }
388}
389
390#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
391pub struct DeployedBytecode {
392    #[serde(flatten)]
393    pub bytecode: Option<Bytecode>,
394    #[serde(
395        default,
396        rename = "immutableReferences",
397        skip_serializing_if = "::std::collections::BTreeMap::is_empty"
398    )]
399    pub immutable_references: BTreeMap<String, Vec<Offsets>>,
400}
401
402impl DeployedBytecode {
403    /// Returns the underlying `Bytes` if the object is a valid bytecode, and not empty
404    pub fn into_bytes(self) -> Option<Bytes> {
405        self.bytecode?.object.into_bytes()
406    }
407}
408
409impl From<Bytecode> for DeployedBytecode {
410    fn from(bcode: Bytecode) -> DeployedBytecode {
411        DeployedBytecode { bytecode: Some(bcode), immutable_references: Default::default() }
412    }
413}
414
415#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
416#[serde(rename_all = "camelCase")]
417pub struct CompactDeployedBytecode {
418    #[serde(flatten)]
419    pub bytecode: Option<CompactBytecode>,
420    #[serde(
421        default,
422        rename = "immutableReferences",
423        skip_serializing_if = "::std::collections::BTreeMap::is_empty"
424    )]
425    pub immutable_references: BTreeMap<String, Vec<Offsets>>,
426}
427
428impl CompactDeployedBytecode {
429    /// Returns a new `CompactDeployedBytecode` object that contains nothing, as it's the case for
430    /// interfaces and standalone solidity files that don't contain any contract definitions
431    pub fn empty() -> Self {
432        Self { bytecode: Some(CompactBytecode::empty()), immutable_references: Default::default() }
433    }
434
435    /// Returns the parsed source map
436    ///
437    /// See also <https://docs.soliditylang.org/en/v0.8.10/internals/source_mappings.html>
438    pub fn source_map(&self) -> Option<Result<SourceMap, SyntaxError>> {
439        self.bytecode.as_ref().and_then(|bytecode| bytecode.source_map())
440    }
441}
442
443impl From<DeployedBytecode> for CompactDeployedBytecode {
444    fn from(bcode: DeployedBytecode) -> CompactDeployedBytecode {
445        CompactDeployedBytecode {
446            bytecode: bcode.bytecode.map(|d_bcode| d_bcode.into()),
447            immutable_references: bcode.immutable_references,
448        }
449    }
450}
451
452impl From<CompactDeployedBytecode> for DeployedBytecode {
453    fn from(bcode: CompactDeployedBytecode) -> DeployedBytecode {
454        DeployedBytecode {
455            bytecode: bcode.bytecode.map(|d_bcode| d_bcode.into()),
456            immutable_references: bcode.immutable_references,
457        }
458    }
459}
460
461#[cfg(test)]
462mod tests {
463    use crate::{artifacts::ContractBytecode, ConfigurableContractArtifact};
464
465    #[test]
466    fn test_empty_bytecode() {
467        let empty = r#"
468        {
469  "abi": [],
470  "bytecode": {
471    "object": "0x",
472    "linkReferences": {}
473  },
474  "deployedBytecode": {
475    "object": "0x",
476    "linkReferences": {}
477  }
478  }
479        "#;
480
481        let artifact: ConfigurableContractArtifact = serde_json::from_str(empty).unwrap();
482        let contract = artifact.into_contract_bytecode();
483        let bytecode: ContractBytecode = contract.into();
484        let bytecode = bytecode.unwrap();
485        assert!(!bytecode.bytecode.object.is_unlinked());
486    }
487}