foundry_compilers_artifacts_solc/
lib.rs

1//! Solc artifact types.
2
3#![cfg_attr(not(test), warn(unused_crate_dependencies))]
4#![allow(ambiguous_glob_reexports)]
5#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
6
7#[macro_use]
8extern crate tracing;
9
10use semver::Version;
11use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
12use serde_repr::{Deserialize_repr, Serialize_repr};
13use std::{
14    collections::{BTreeMap, HashSet},
15    fmt,
16    path::{Path, PathBuf},
17    str::FromStr,
18};
19
20pub mod error;
21pub use error::*;
22pub mod ast;
23pub use ast::*;
24pub mod remappings;
25pub use remappings::*;
26pub mod bytecode;
27pub use bytecode::*;
28pub mod contract;
29pub use contract::*;
30pub mod configurable;
31pub mod hh;
32pub use configurable::*;
33pub mod output_selection;
34pub mod serde_helpers;
35pub mod sourcemap;
36pub mod sources;
37use crate::output_selection::{ContractOutputSelection, OutputSelection};
38use foundry_compilers_core::{
39    error::SolcError,
40    utils::{
41        strip_prefix_owned, BERLIN_SOLC, BYZANTIUM_SOLC, CANCUN_SOLC, CONSTANTINOPLE_SOLC,
42        ISTANBUL_SOLC, LONDON_SOLC, OSAKA_SOLC, PARIS_SOLC, PETERSBURG_SOLC, PRAGUE_SOLC,
43        SHANGHAI_SOLC,
44    },
45};
46pub use serde_helpers::{deserialize_bytes, deserialize_opt_bytes};
47pub use sources::*;
48
49/// Solidity files are made up of multiple `source units`, a solidity contract is such a `source
50/// unit`, therefore a solidity file can contain multiple contracts: (1-N*) relationship.
51///
52/// This types represents this mapping as `file name -> (contract name -> T)`, where the generic is
53/// intended to represent contract specific information, like [`Contract`] itself, See [`Contracts`]
54pub type FileToContractsMap<T> = BTreeMap<PathBuf, BTreeMap<String, T>>;
55
56/// file -> (contract name -> Contract)
57pub type Contracts = FileToContractsMap<Contract>;
58
59pub const SOLIDITY: &str = "Solidity";
60pub const YUL: &str = "Yul";
61
62/// Languages supported by the Solc compiler.
63#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
64#[non_exhaustive]
65pub enum SolcLanguage {
66    Solidity,
67    Yul,
68}
69
70impl fmt::Display for SolcLanguage {
71    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72        match self {
73            Self::Solidity => write!(f, "Solidity"),
74            Self::Yul => write!(f, "Yul"),
75        }
76    }
77}
78
79/// Input type `solc` expects.
80#[derive(Clone, Debug, Serialize, Deserialize)]
81pub struct SolcInput {
82    pub language: SolcLanguage,
83    pub sources: Sources,
84    pub settings: Settings,
85}
86
87/// Default `language` field is set to `"Solidity"`.
88impl Default for SolcInput {
89    fn default() -> Self {
90        Self {
91            language: SolcLanguage::Solidity,
92            sources: Sources::default(),
93            settings: Settings::default(),
94        }
95    }
96}
97
98impl SolcInput {
99    pub fn new(language: SolcLanguage, sources: Sources, mut settings: Settings) -> Self {
100        if language == SolcLanguage::Yul && !settings.remappings.is_empty() {
101            warn!("omitting remappings supplied for the yul sources");
102            settings.remappings = vec![];
103        }
104        Self { language, sources, settings }
105    }
106
107    /// Builds one or two inputs from given sources set. Returns two inputs in cases when there are
108    /// both Solidity and Yul sources.
109    pub fn resolve_and_build(sources: Sources, settings: Settings) -> Vec<Self> {
110        let mut solidity_sources = Sources::new();
111        let mut yul_sources = Sources::new();
112
113        for (file, source) in sources {
114            if file.extension().is_some_and(|e| e == "yul") {
115                yul_sources.insert(file, source);
116            } else if file.extension().is_some_and(|e| e == "sol") {
117                solidity_sources.insert(file, source);
118            }
119        }
120
121        let mut res = Vec::new();
122
123        if !solidity_sources.is_empty() {
124            res.push(Self::new(SolcLanguage::Solidity, solidity_sources, settings.clone()))
125        }
126
127        if !yul_sources.is_empty() {
128            res.push(Self::new(SolcLanguage::Yul, yul_sources, settings))
129        }
130
131        res
132    }
133
134    /// This will remove/adjust values in the [`SolcInput`] that are not compatible with this
135    /// version
136    pub fn sanitize(&mut self, version: &Version) {
137        self.settings.sanitize(version, self.language);
138    }
139
140    /// Consumes the type and returns a [SolcInput::sanitized] version
141    pub fn sanitized(mut self, version: &Version) -> Self {
142        self.settings.sanitize(version, self.language);
143        self
144    }
145
146    /// Sets the EVM version for compilation
147    #[must_use]
148    pub fn evm_version(mut self, version: EvmVersion) -> Self {
149        self.settings.evm_version = Some(version);
150        self
151    }
152
153    /// Sets the optimizer runs (default = 200)
154    #[must_use]
155    pub fn optimizer(mut self, runs: usize) -> Self {
156        self.settings.optimizer.runs(runs);
157        self
158    }
159
160    /// Sets the path of the source files to `root` adjoined to the existing path
161    #[must_use]
162    pub fn join_path(mut self, root: &Path) -> Self {
163        self.sources = self.sources.into_iter().map(|(path, s)| (root.join(path), s)).collect();
164        self
165    }
166
167    /// Removes the `base` path from all source files
168    pub fn strip_prefix(&mut self, base: &Path) {
169        self.sources = std::mem::take(&mut self.sources)
170            .into_iter()
171            .map(|(path, s)| (strip_prefix_owned(path, base), s))
172            .collect();
173
174        self.settings.strip_prefix(base);
175    }
176
177    /// The flag indicating whether the current [SolcInput] is
178    /// constructed for the yul sources
179    pub fn is_yul(&self) -> bool {
180        self.language == SolcLanguage::Yul
181    }
182}
183
184/// A `CompilerInput` representation used for verify
185///
186/// This type is an alternative `CompilerInput` but uses non-alphabetic ordering of the `sources`
187/// and instead emits the (Path -> Source) path in the same order as the pairs in the `sources`
188/// `Vec`. This is used over a map, so we can determine the order in which etherscan will display
189/// the verified contracts
190#[derive(Clone, Debug, Serialize, Deserialize)]
191pub struct StandardJsonCompilerInput {
192    pub language: SolcLanguage,
193    #[serde(with = "serde_helpers::tuple_vec_map")]
194    pub sources: Vec<(PathBuf, Source)>,
195    pub settings: Settings,
196}
197
198// === impl StandardJsonCompilerInput ===
199
200impl StandardJsonCompilerInput {
201    pub fn new(sources: Vec<(PathBuf, Source)>, settings: Settings) -> Self {
202        Self { language: SolcLanguage::Solidity, sources, settings }
203    }
204
205    /// Normalizes the EVM version used in the settings to be up to the latest one
206    /// supported by the provided compiler version.
207    #[must_use]
208    pub fn normalize_evm_version(mut self, version: &Version) -> Self {
209        if let Some(evm_version) = &mut self.settings.evm_version {
210            self.settings.evm_version = evm_version.normalize_version_solc(version);
211        }
212        self
213    }
214}
215
216impl From<StandardJsonCompilerInput> for SolcInput {
217    fn from(input: StandardJsonCompilerInput) -> Self {
218        let StandardJsonCompilerInput { language, sources, settings } = input;
219        Self { language, sources: sources.into_iter().collect(), settings }
220    }
221}
222
223impl From<SolcInput> for StandardJsonCompilerInput {
224    fn from(input: SolcInput) -> Self {
225        let SolcInput { language, sources, settings, .. } = input;
226        Self { language, sources: sources.into_iter().collect(), settings }
227    }
228}
229
230#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
231#[serde(rename_all = "camelCase")]
232pub struct Settings {
233    /// Stop compilation after the given stage.
234    /// since 0.8.11: only "parsing" is valid here
235    #[serde(default, skip_serializing_if = "Option::is_none")]
236    pub stop_after: Option<String>,
237    #[serde(default, skip_serializing_if = "Vec::is_empty")]
238    pub remappings: Vec<Remapping>,
239    /// Custom Optimizer settings
240    #[serde(default)]
241    pub optimizer: Optimizer,
242    /// Model Checker options.
243    #[serde(default, skip_serializing_if = "Option::is_none")]
244    pub model_checker: Option<ModelCheckerSettings>,
245    /// Metadata settings
246    #[serde(default, skip_serializing_if = "Option::is_none")]
247    pub metadata: Option<SettingsMetadata>,
248    /// This field can be used to select desired outputs based
249    /// on file and contract names.
250    /// If this field is omitted, then the compiler loads and does type
251    /// checking, but will not generate any outputs apart from errors.
252    #[serde(default)]
253    pub output_selection: OutputSelection,
254    #[serde(
255        default,
256        with = "serde_helpers::display_from_str_opt",
257        skip_serializing_if = "Option::is_none"
258    )]
259    pub evm_version: Option<EvmVersion>,
260    /// Change compilation pipeline to go through the Yul intermediate representation. This is
261    /// false by default.
262    #[serde(rename = "viaIR", default, skip_serializing_if = "Option::is_none")]
263    pub via_ir: Option<bool>,
264    #[serde(default, skip_serializing_if = "Option::is_none")]
265    pub debug: Option<DebuggingSettings>,
266    /// Addresses of the libraries. If not all libraries are given here,
267    /// it can result in unlinked objects whose output data is different.
268    ///
269    /// The top level key is the name of the source file where the library is used.
270    /// If remappings are used, this source file should match the global path
271    /// after remappings were applied.
272    /// If this key is an empty string, that refers to a global level.
273    #[serde(default)]
274    pub libraries: Libraries,
275    /// Specify EOF version to produce.
276    #[serde(default, skip_serializing_if = "Option::is_none")]
277    pub eof_version: Option<EofVersion>,
278}
279
280/// Available EOF versions.
281#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize_repr, Deserialize_repr)]
282#[repr(u8)]
283pub enum EofVersion {
284    V1 = 1,
285}
286
287impl Settings {
288    /// Creates a new `Settings` instance with the given `output_selection`
289    pub fn new(output_selection: impl Into<OutputSelection>) -> Self {
290        Self { output_selection: output_selection.into(), ..Default::default() }
291    }
292
293    /// Consumes the type and returns a [Settings::sanitize] version
294    pub fn sanitized(mut self, version: &Version, language: SolcLanguage) -> Self {
295        self.sanitize(version, language);
296        self
297    }
298
299    /// This will remove/adjust values in the settings that are not compatible with this version.
300    pub fn sanitize(&mut self, version: &Version, language: SolcLanguage) {
301        if *version < Version::new(0, 6, 0) {
302            if let Some(meta) = &mut self.metadata {
303                // introduced in <https://docs.soliditylang.org/en/v0.6.0/using-the-compiler.html#compiler-api>
304                // missing in <https://docs.soliditylang.org/en/v0.5.17/using-the-compiler.html#compiler-api>
305                meta.bytecode_hash = None;
306            }
307            // introduced in <https://docs.soliditylang.org/en/v0.6.0/using-the-compiler.html#compiler-api>
308            self.debug = None;
309        }
310
311        if *version < Version::new(0, 7, 5) {
312            // introduced in 0.7.5 <https://github.com/ethereum/solidity/releases/tag/v0.7.5>
313            self.via_ir = None;
314        }
315
316        if *version < Version::new(0, 8, 5) {
317            // introduced in 0.8.5 <https://github.com/ethereum/solidity/releases/tag/v0.8.5>
318            if let Some(optimizer_details) = &mut self.optimizer.details {
319                optimizer_details.inliner = None;
320            }
321        }
322
323        if *version < Version::new(0, 8, 7) {
324            // lower the disable version from 0.8.10 to 0.8.7, due to `divModNoSlacks`,
325            // `showUnproved` and `solvers` are implemented
326            // introduced in <https://github.com/ethereum/solidity/releases/tag/v0.8.7>
327            self.model_checker = None;
328        }
329
330        if *version < Version::new(0, 8, 10) {
331            if let Some(debug) = &mut self.debug {
332                // introduced in <https://docs.soliditylang.org/en/v0.8.10/using-the-compiler.html#compiler-api>
333                // <https://github.com/ethereum/solidity/releases/tag/v0.8.10>
334                debug.debug_info.clear();
335            }
336
337            if let Some(model_checker) = &mut self.model_checker {
338                // introduced in <https://github.com/ethereum/solidity/releases/tag/v0.8.10>
339                model_checker.invariants = None;
340            }
341        }
342
343        if *version < Version::new(0, 8, 18) {
344            // introduced in 0.8.18 <https://github.com/ethereum/solidity/releases/tag/v0.8.18>
345            if let Some(meta) = &mut self.metadata {
346                meta.cbor_metadata = None;
347            }
348
349            if let Some(model_checker) = &mut self.model_checker {
350                if let Some(solvers) = &mut model_checker.solvers {
351                    // elf solver introduced in 0.8.18 <https://github.com/ethereum/solidity/releases/tag/v0.8.18>
352                    solvers.retain(|solver| *solver != ModelCheckerSolver::Eld);
353                }
354            }
355        }
356
357        if *version < Version::new(0, 8, 20) {
358            // introduced in 0.8.20 <https://github.com/ethereum/solidity/releases/tag/v0.8.20>
359            if let Some(model_checker) = &mut self.model_checker {
360                model_checker.show_proved_safe = None;
361                model_checker.show_unsupported = None;
362            }
363        }
364
365        if let Some(evm_version) = self.evm_version {
366            self.evm_version = evm_version.normalize_version_solc(version);
367        }
368
369        match language {
370            SolcLanguage::Solidity => {}
371            SolcLanguage::Yul => {
372                if !self.remappings.is_empty() {
373                    warn!("omitting remappings supplied for the yul sources");
374                }
375                self.remappings = Vec::new();
376            }
377        }
378    }
379
380    /// Inserts a set of `ContractOutputSelection`
381    pub fn push_all(&mut self, settings: impl IntoIterator<Item = ContractOutputSelection>) {
382        for value in settings {
383            self.push_output_selection(value)
384        }
385    }
386
387    /// Inserts a set of `ContractOutputSelection`
388    #[must_use]
389    pub fn with_extra_output(
390        mut self,
391        settings: impl IntoIterator<Item = ContractOutputSelection>,
392    ) -> Self {
393        for value in settings {
394            self.push_output_selection(value)
395        }
396        self
397    }
398
399    /// Inserts the value for all files and contracts
400    ///
401    /// ```
402    /// use foundry_compilers_artifacts_solc::{output_selection::ContractOutputSelection, Settings};
403    /// let mut selection = Settings::default();
404    /// selection.push_output_selection(ContractOutputSelection::Metadata);
405    /// ```
406    pub fn push_output_selection(&mut self, value: impl ToString) {
407        self.push_contract_output_selection("*", value)
408    }
409
410    /// Inserts the `key` `value` pair to the `output_selection` for all files
411    ///
412    /// If the `key` already exists, then the value is added to the existing list
413    pub fn push_contract_output_selection(
414        &mut self,
415        contracts: impl Into<String>,
416        value: impl ToString,
417    ) {
418        let value = value.to_string();
419        let values = self
420            .output_selection
421            .as_mut()
422            .entry("*".to_string())
423            .or_default()
424            .entry(contracts.into())
425            .or_default();
426        if !values.contains(&value) {
427            values.push(value)
428        }
429    }
430
431    /// Sets the value for all files and contracts
432    pub fn set_output_selection(&mut self, values: impl IntoIterator<Item = impl ToString>) {
433        self.set_contract_output_selection("*", values)
434    }
435
436    /// Sets the `key` to the `values` pair to the `output_selection` for all files
437    ///
438    /// This will replace the existing values for `key` if they're present
439    pub fn set_contract_output_selection(
440        &mut self,
441        key: impl Into<String>,
442        values: impl IntoIterator<Item = impl ToString>,
443    ) {
444        self.output_selection
445            .as_mut()
446            .entry("*".to_string())
447            .or_default()
448            .insert(key.into(), values.into_iter().map(|s| s.to_string()).collect());
449    }
450
451    /// Sets the `viaIR` value.
452    #[must_use]
453    pub fn set_via_ir(mut self, via_ir: bool) -> Self {
454        self.via_ir = Some(via_ir);
455        self
456    }
457
458    /// Enables `viaIR`.
459    #[must_use]
460    pub fn with_via_ir(self) -> Self {
461        self.set_via_ir(true)
462    }
463
464    /// Enable `viaIR` and use the minimum optimization settings.
465    ///
466    /// This is useful in the following scenarios:
467    /// - When compiling for test coverage, this can resolve the "stack too deep" error while still
468    ///   giving a relatively accurate source mapping
469    /// - When compiling for test, this can reduce the compilation time
470    pub fn with_via_ir_minimum_optimization(mut self) -> Self {
471        // https://github.com/foundry-rs/foundry/pull/5349
472        // https://github.com/ethereum/solidity/issues/12533#issuecomment-1013073350
473        self.via_ir = Some(true);
474        self.optimizer.details = Some(OptimizerDetails {
475            peephole: Some(false),
476            inliner: Some(false),
477            jumpdest_remover: Some(false),
478            order_literals: Some(false),
479            deduplicate: Some(false),
480            cse: Some(false),
481            constant_optimizer: Some(false),
482            yul: Some(true), // enable yul optimizer
483            yul_details: Some(YulDetails {
484                stack_allocation: Some(true),
485                // with only unused prunner step
486                optimizer_steps: Some("u".to_string()),
487            }),
488            // Set to None as it is only supported for solc starting from 0.8.22.
489            simple_counter_for_loop_unchecked_increment: None,
490        });
491        self
492    }
493
494    /// Adds `ast` to output
495    #[must_use]
496    pub fn with_ast(mut self) -> Self {
497        let output = self.output_selection.as_mut().entry("*".to_string()).or_default();
498        output.insert(String::new(), vec!["ast".to_string()]);
499        self
500    }
501
502    pub fn strip_prefix(&mut self, base: &Path) {
503        self.remappings.iter_mut().for_each(|r| {
504            r.strip_prefix(base);
505        });
506
507        self.libraries.libs = std::mem::take(&mut self.libraries.libs)
508            .into_iter()
509            .map(|(file, libs)| (file.strip_prefix(base).map(Into::into).unwrap_or(file), libs))
510            .collect();
511
512        self.output_selection = OutputSelection(
513            std::mem::take(&mut self.output_selection.0)
514                .into_iter()
515                .map(|(file, selection)| {
516                    (
517                        Path::new(&file)
518                            .strip_prefix(base)
519                            .map(|p| p.display().to_string())
520                            .unwrap_or(file),
521                        selection,
522                    )
523                })
524                .collect(),
525        );
526
527        if let Some(mut model_checker) = self.model_checker.take() {
528            model_checker.contracts = model_checker
529                .contracts
530                .into_iter()
531                .map(|(path, contracts)| {
532                    (
533                        Path::new(&path)
534                            .strip_prefix(base)
535                            .map(|p| p.display().to_string())
536                            .unwrap_or(path),
537                        contracts,
538                    )
539                })
540                .collect();
541            self.model_checker = Some(model_checker);
542        }
543    }
544
545    /// Strips `base` from all paths
546    pub fn with_base_path(mut self, base: &Path) -> Self {
547        self.strip_prefix(base);
548        self
549    }
550}
551
552impl Default for Settings {
553    fn default() -> Self {
554        Self {
555            stop_after: None,
556            optimizer: Default::default(),
557            metadata: None,
558            output_selection: OutputSelection::default_output_selection(),
559            evm_version: Some(EvmVersion::default()),
560            via_ir: None,
561            debug: None,
562            libraries: Default::default(),
563            remappings: Default::default(),
564            model_checker: None,
565            eof_version: None,
566        }
567        .with_ast()
568    }
569}
570
571/// A wrapper type for all libraries in the form of `<file>:<lib>:<addr>`
572#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
573#[serde(transparent)]
574pub struct Libraries {
575    /// All libraries, `(file path -> (Lib name -> Address))`.
576    pub libs: BTreeMap<PathBuf, BTreeMap<String, String>>,
577}
578
579// === impl Libraries ===
580
581impl Libraries {
582    /// Parses all libraries in the form of
583    /// `<file>:<lib>:<addr>`
584    ///
585    /// # Examples
586    ///
587    /// ```
588    /// use foundry_compilers_artifacts_solc::Libraries;
589    ///
590    /// let libs = Libraries::parse(&[
591    ///     "src/DssSpell.sol:DssExecLib:0xfD88CeE74f7D78697775aBDAE53f9Da1559728E4".to_string(),
592    /// ])?;
593    /// # Ok::<(), Box<dyn std::error::Error>>(())
594    /// ```
595    pub fn parse(libs: &[String]) -> Result<Self, SolcError> {
596        let mut libraries = BTreeMap::default();
597        for lib in libs {
598            let mut items = lib.split(':');
599            let file = items.next().ok_or_else(|| {
600                SolcError::msg(format!("failed to parse path to library file: {lib}"))
601            })?;
602            let lib = items
603                .next()
604                .ok_or_else(|| SolcError::msg(format!("failed to parse library name: {lib}")))?;
605            let addr = items
606                .next()
607                .ok_or_else(|| SolcError::msg(format!("failed to parse library address: {lib}")))?;
608            if items.next().is_some() {
609                return Err(SolcError::msg(format!(
610                    "failed to parse, too many arguments passed: {lib}"
611                )));
612            }
613            libraries
614                .entry(file.into())
615                .or_insert_with(BTreeMap::default)
616                .insert(lib.to_string(), addr.to_string());
617        }
618        Ok(Self { libs: libraries })
619    }
620
621    pub fn is_empty(&self) -> bool {
622        self.libs.is_empty()
623    }
624
625    pub fn len(&self) -> usize {
626        self.libs.len()
627    }
628
629    /// Applies the given function to [Self] and returns the result.
630    pub fn apply<F: FnOnce(Self) -> Self>(self, f: F) -> Self {
631        f(self)
632    }
633
634    /// Strips the given prefix from all library file paths to make them relative to the given
635    /// `base` argument
636    pub fn with_stripped_file_prefixes(mut self, base: &Path) -> Self {
637        self.libs = self
638            .libs
639            .into_iter()
640            .map(|(f, l)| (f.strip_prefix(base).unwrap_or(&f).to_path_buf(), l))
641            .collect();
642        self
643    }
644
645    /// Converts all `\\` separators in _all_ paths to `/`
646    pub fn slash_paths(&mut self) {
647        #[cfg(windows)]
648        {
649            use path_slash::PathBufExt;
650
651            self.libs = std::mem::take(&mut self.libs)
652                .into_iter()
653                .map(|(path, libs)| (PathBuf::from(path.to_slash_lossy().as_ref()), libs))
654                .collect()
655        }
656    }
657}
658
659impl From<BTreeMap<PathBuf, BTreeMap<String, String>>> for Libraries {
660    fn from(libs: BTreeMap<PathBuf, BTreeMap<String, String>>) -> Self {
661        Self { libs }
662    }
663}
664
665impl AsRef<BTreeMap<PathBuf, BTreeMap<String, String>>> for Libraries {
666    fn as_ref(&self) -> &BTreeMap<PathBuf, BTreeMap<String, String>> {
667        &self.libs
668    }
669}
670
671impl AsMut<BTreeMap<PathBuf, BTreeMap<String, String>>> for Libraries {
672    fn as_mut(&mut self) -> &mut BTreeMap<PathBuf, BTreeMap<String, String>> {
673        &mut self.libs
674    }
675}
676
677#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
678pub struct Optimizer {
679    #[serde(default, skip_serializing_if = "Option::is_none")]
680    pub enabled: Option<bool>,
681    #[serde(default, skip_serializing_if = "Option::is_none")]
682    pub runs: Option<usize>,
683    /// Switch optimizer components on or off in detail.
684    /// The "enabled" switch above provides two defaults which can be
685    /// tweaked here. If "details" is given, "enabled" can be omitted.
686    #[serde(default, skip_serializing_if = "Option::is_none")]
687    pub details: Option<OptimizerDetails>,
688}
689
690impl Optimizer {
691    pub fn runs(&mut self, runs: usize) {
692        self.runs = Some(runs);
693    }
694
695    pub fn disable(&mut self) {
696        self.enabled.take();
697    }
698
699    pub fn enable(&mut self) {
700        self.enabled = Some(true)
701    }
702}
703
704impl Default for Optimizer {
705    fn default() -> Self {
706        Self { enabled: Some(false), runs: Some(200), details: None }
707    }
708}
709
710#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
711#[serde(rename_all = "camelCase")]
712pub struct OptimizerDetails {
713    /// The peephole optimizer is always on if no details are given,
714    /// use details to switch it off.
715    #[serde(default, skip_serializing_if = "Option::is_none")]
716    pub peephole: Option<bool>,
717    /// The inliner is always on if no details are given,
718    /// use details to switch it off.
719    #[serde(default, skip_serializing_if = "Option::is_none")]
720    pub inliner: Option<bool>,
721    /// The unused jumpdest remover is always on if no details are given,
722    /// use details to switch it off.
723    #[serde(default, skip_serializing_if = "Option::is_none")]
724    pub jumpdest_remover: Option<bool>,
725    /// Sometimes re-orders literals in commutative operations.
726    #[serde(default, skip_serializing_if = "Option::is_none")]
727    pub order_literals: Option<bool>,
728    /// Removes duplicate code blocks
729    #[serde(default, skip_serializing_if = "Option::is_none")]
730    pub deduplicate: Option<bool>,
731    /// Common subexpression elimination, this is the most complicated step but
732    /// can also provide the largest gain.
733    #[serde(default, skip_serializing_if = "Option::is_none")]
734    pub cse: Option<bool>,
735    /// Optimize representation of literal numbers and strings in code.
736    #[serde(default, skip_serializing_if = "Option::is_none")]
737    pub constant_optimizer: Option<bool>,
738    /// The new Yul optimizer. Mostly operates on the code of ABI coder v2
739    /// and inline assembly.
740    /// It is activated together with the global optimizer setting
741    /// and can be deactivated here.
742    /// Before Solidity 0.6.0 it had to be activated through this switch.
743    #[serde(default, skip_serializing_if = "Option::is_none")]
744    pub yul: Option<bool>,
745    /// Tuning options for the Yul optimizer.
746    #[serde(default, skip_serializing_if = "Option::is_none")]
747    pub yul_details: Option<YulDetails>,
748    /// Use unchecked arithmetic when incrementing the counter of for loops
749    /// under certain circumstances. It is always on if no details are given.
750    #[serde(default, skip_serializing_if = "Option::is_none")]
751    pub simple_counter_for_loop_unchecked_increment: Option<bool>,
752}
753
754// === impl OptimizerDetails ===
755
756impl OptimizerDetails {
757    /// Returns true if no settings are set.
758    pub fn is_empty(&self) -> bool {
759        self.peephole.is_none()
760            && self.inliner.is_none()
761            && self.jumpdest_remover.is_none()
762            && self.order_literals.is_none()
763            && self.deduplicate.is_none()
764            && self.cse.is_none()
765            && self.constant_optimizer.is_none()
766            && self.yul.is_none()
767            && self.yul_details.as_ref().map(|yul| yul.is_empty()).unwrap_or(true)
768            && self.simple_counter_for_loop_unchecked_increment.is_none()
769    }
770}
771
772#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
773#[serde(rename_all = "camelCase")]
774pub struct YulDetails {
775    /// Improve allocation of stack slots for variables, can free up stack slots early.
776    /// Activated by default if the Yul optimizer is activated.
777    #[serde(default, skip_serializing_if = "Option::is_none")]
778    pub stack_allocation: Option<bool>,
779    /// Select optimization steps to be applied.
780    /// Optional, the optimizer will use the default sequence if omitted.
781    #[serde(default, skip_serializing_if = "Option::is_none")]
782    pub optimizer_steps: Option<String>,
783}
784
785// === impl YulDetails ===
786
787impl YulDetails {
788    /// Returns true if no settings are set.
789    pub fn is_empty(&self) -> bool {
790        self.stack_allocation.is_none() && self.optimizer_steps.is_none()
791    }
792}
793
794/// EVM versions.
795///
796/// Default is `Cancun`, since 0.8.25
797///
798/// Kept in sync with: <https://github.com/ethereum/solidity/blob/develop/liblangutil/EVMVersion.h>
799// When adding new EVM versions (see a previous attempt at https://github.com/foundry-rs/compilers/pull/51):
800// - add the version to the end of the enum
801// - update the default variant to `m_version` default: https://github.com/ethereum/solidity/blob/develop/liblangutil/EVMVersion.h#L122
802// - create a constant for the Solc version that introduced it in `../compile/mod.rs`
803// - add the version to the top of `normalize_version` and wherever else the compiler complains
804// - update `FromStr` impl
805// - write a test case in `test_evm_version_normalization` at the bottom of this file.
806#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
807pub enum EvmVersion {
808    Homestead,
809    TangerineWhistle,
810    SpuriousDragon,
811    Byzantium,
812    Constantinople,
813    Petersburg,
814    Istanbul,
815    Berlin,
816    London,
817    Paris,
818    Shanghai,
819    #[default]
820    Cancun,
821    Prague,
822    Osaka,
823}
824
825impl EvmVersion {
826    /// Find the default EVM version for the given compiler version.
827    pub fn default_version_solc(version: &Version) -> Option<Self> {
828        // In most cases, Solc compilers use the highest EVM version available at the time.
829        let default = Self::default().normalize_version_solc(version)?;
830
831        // However, there are some exceptions where the default is lower than the highest available.
832        match default {
833            Self::Constantinople => {
834                // Actually, Constantinople is never used as the default EVM version by Solidity
835                // compilers.
836                Some(Self::Byzantium)
837            }
838            Self::Cancun if *version == Version::new(0, 8, 24) => {
839                // While Cancun is introduced at the time of releasing 0.8.24, it has not been
840                // supported by the mainnet. So, the default EVM version of Solc 0.8.24 remains as
841                // Shanghai.
842                //
843                // <https://soliditylang.org/blog/2024/01/26/solidity-0.8.24-release-announcement/>
844                Some(Self::Shanghai)
845            }
846            Self::Prague if *version == Version::new(0, 8, 27) => {
847                // Prague was not set as default EVM version in 0.8.27.
848                Some(Self::Cancun)
849            }
850            _ => Some(default),
851        }
852    }
853
854    /// Normalizes this EVM version by checking against the given Solc [`Version`].
855    pub fn normalize_version_solc(self, version: &Version) -> Option<Self> {
856        // The EVM version flag was only added in 0.4.21; we work our way backwards
857        if *version >= BYZANTIUM_SOLC {
858            // If the Solc version is the latest, it supports all EVM versions.
859            // For all other cases, cap at the at-the-time highest possible fork.
860            let normalized = if *version >= OSAKA_SOLC {
861                self
862            } else if self >= Self::Prague && *version >= PRAGUE_SOLC {
863                Self::Prague
864            } else if self >= Self::Cancun && *version >= CANCUN_SOLC {
865                Self::Cancun
866            } else if self >= Self::Shanghai && *version >= SHANGHAI_SOLC {
867                Self::Shanghai
868            } else if self >= Self::Paris && *version >= PARIS_SOLC {
869                Self::Paris
870            } else if self >= Self::London && *version >= LONDON_SOLC {
871                Self::London
872            } else if self >= Self::Berlin && *version >= BERLIN_SOLC {
873                Self::Berlin
874            } else if self >= Self::Istanbul && *version >= ISTANBUL_SOLC {
875                Self::Istanbul
876            } else if self >= Self::Petersburg && *version >= PETERSBURG_SOLC {
877                Self::Petersburg
878            } else if self >= Self::Constantinople && *version >= CONSTANTINOPLE_SOLC {
879                Self::Constantinople
880            } else if self >= Self::Byzantium {
881                Self::Byzantium
882            } else {
883                self
884            };
885            Some(normalized)
886        } else {
887            None
888        }
889    }
890
891    /// Returns the EVM version as a string.
892    pub const fn as_str(&self) -> &'static str {
893        match self {
894            Self::Homestead => "homestead",
895            Self::TangerineWhistle => "tangerineWhistle",
896            Self::SpuriousDragon => "spuriousDragon",
897            Self::Byzantium => "byzantium",
898            Self::Constantinople => "constantinople",
899            Self::Petersburg => "petersburg",
900            Self::Istanbul => "istanbul",
901            Self::Berlin => "berlin",
902            Self::London => "london",
903            Self::Paris => "paris",
904            Self::Shanghai => "shanghai",
905            Self::Cancun => "cancun",
906            Self::Prague => "prague",
907            Self::Osaka => "osaka",
908        }
909    }
910
911    /// Has the `RETURNDATACOPY` and `RETURNDATASIZE` opcodes.
912    pub fn supports_returndata(&self) -> bool {
913        *self >= Self::Byzantium
914    }
915
916    pub fn has_static_call(&self) -> bool {
917        *self >= Self::Byzantium
918    }
919
920    pub fn has_bitwise_shifting(&self) -> bool {
921        *self >= Self::Constantinople
922    }
923
924    pub fn has_create2(&self) -> bool {
925        *self >= Self::Constantinople
926    }
927
928    pub fn has_ext_code_hash(&self) -> bool {
929        *self >= Self::Constantinople
930    }
931
932    pub fn has_chain_id(&self) -> bool {
933        *self >= Self::Istanbul
934    }
935
936    pub fn has_self_balance(&self) -> bool {
937        *self >= Self::Istanbul
938    }
939
940    pub fn has_base_fee(&self) -> bool {
941        *self >= Self::London
942    }
943
944    pub fn has_prevrandao(&self) -> bool {
945        *self >= Self::Paris
946    }
947
948    pub fn has_push0(&self) -> bool {
949        *self >= Self::Shanghai
950    }
951}
952
953impl fmt::Display for EvmVersion {
954    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
955        f.write_str(self.as_str())
956    }
957}
958
959impl FromStr for EvmVersion {
960    type Err = String;
961
962    fn from_str(s: &str) -> Result<Self, Self::Err> {
963        match s {
964            "homestead" => Ok(Self::Homestead),
965            "tangerineWhistle" | "tangerinewhistle" => Ok(Self::TangerineWhistle),
966            "spuriousDragon" | "spuriousdragon" => Ok(Self::SpuriousDragon),
967            "byzantium" => Ok(Self::Byzantium),
968            "constantinople" => Ok(Self::Constantinople),
969            "petersburg" => Ok(Self::Petersburg),
970            "istanbul" => Ok(Self::Istanbul),
971            "berlin" => Ok(Self::Berlin),
972            "london" => Ok(Self::London),
973            "paris" => Ok(Self::Paris),
974            "shanghai" => Ok(Self::Shanghai),
975            "cancun" => Ok(Self::Cancun),
976            "prague" => Ok(Self::Prague),
977            "osaka" => Ok(Self::Osaka),
978            s => Err(format!("Unknown evm version: {s}")),
979        }
980    }
981}
982
983/// Debugging settings for solc
984#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
985#[serde(rename_all = "camelCase")]
986pub struct DebuggingSettings {
987    #[serde(
988        default,
989        with = "serde_helpers::display_from_str_opt",
990        skip_serializing_if = "Option::is_none"
991    )]
992    pub revert_strings: Option<RevertStrings>,
993    /// How much extra debug information to include in comments in the produced EVM assembly and
994    /// Yul code.
995    /// Available components are:
996    // - `location`: Annotations of the form `@src <index>:<start>:<end>` indicating the location of
997    //   the corresponding element in the original Solidity file, where:
998    //     - `<index>` is the file index matching the `@use-src` annotation,
999    //     - `<start>` is the index of the first byte at that location,
1000    //     - `<end>` is the index of the first byte after that location.
1001    // - `snippet`: A single-line code snippet from the location indicated by `@src`. The snippet is
1002    //   quoted and follows the corresponding `@src` annotation.
1003    // - `*`: Wildcard value that can be used to request everything.
1004    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1005    pub debug_info: Vec<String>,
1006}
1007
1008/// How to treat revert (and require) reason strings.
1009#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
1010pub enum RevertStrings {
1011    /// "default" does not inject compiler-generated revert strings and keeps user-supplied ones.
1012    #[default]
1013    Default,
1014    /// "strip" removes all revert strings (if possible, i.e. if literals are used) keeping
1015    /// side-effects
1016    Strip,
1017    /// "debug" injects strings for compiler-generated internal reverts, implemented for ABI
1018    /// encoders V1 and V2 for now.
1019    Debug,
1020    /// "verboseDebug" even appends further information to user-supplied revert strings (not yet
1021    /// implemented)
1022    VerboseDebug,
1023}
1024
1025impl fmt::Display for RevertStrings {
1026    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1027        let string = match self {
1028            Self::Default => "default",
1029            Self::Strip => "strip",
1030            Self::Debug => "debug",
1031            Self::VerboseDebug => "verboseDebug",
1032        };
1033        write!(f, "{string}")
1034    }
1035}
1036
1037impl FromStr for RevertStrings {
1038    type Err = String;
1039
1040    fn from_str(s: &str) -> Result<Self, Self::Err> {
1041        match s {
1042            "default" => Ok(Self::Default),
1043            "strip" => Ok(Self::Strip),
1044            "debug" => Ok(Self::Debug),
1045            "verboseDebug" | "verbosedebug" => Ok(Self::VerboseDebug),
1046            s => Err(format!("Unknown revert string mode: {s}")),
1047        }
1048    }
1049}
1050
1051#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1052pub struct SettingsMetadata {
1053    /// Use only literal content and not URLs (false by default)
1054    #[serde(default, rename = "useLiteralContent", skip_serializing_if = "Option::is_none")]
1055    pub use_literal_content: Option<bool>,
1056    /// Use the given hash method for the metadata hash that is appended to the bytecode.
1057    /// The metadata hash can be removed from the bytecode via option "none".
1058    /// The other options are "ipfs" and "bzzr1".
1059    /// If the option is omitted, "ipfs" is used by default.
1060    #[serde(
1061        default,
1062        rename = "bytecodeHash",
1063        skip_serializing_if = "Option::is_none",
1064        with = "serde_helpers::display_from_str_opt"
1065    )]
1066    pub bytecode_hash: Option<BytecodeHash>,
1067    #[serde(default, rename = "appendCBOR", skip_serializing_if = "Option::is_none")]
1068    pub cbor_metadata: Option<bool>,
1069}
1070
1071impl SettingsMetadata {
1072    pub fn new(hash: BytecodeHash, cbor: bool) -> Self {
1073        Self { use_literal_content: None, bytecode_hash: Some(hash), cbor_metadata: Some(cbor) }
1074    }
1075}
1076
1077impl From<BytecodeHash> for SettingsMetadata {
1078    fn from(hash: BytecodeHash) -> Self {
1079        Self { use_literal_content: None, bytecode_hash: Some(hash), cbor_metadata: None }
1080    }
1081}
1082
1083/// Determines the hash method for the metadata hash that is appended to the bytecode.
1084///
1085/// Solc's default is `Ipfs`, see <https://docs.soliditylang.org/en/latest/using-the-compiler.html#compiler-api>.
1086#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1087pub enum BytecodeHash {
1088    #[default]
1089    Ipfs,
1090    None,
1091    Bzzr1,
1092}
1093
1094impl FromStr for BytecodeHash {
1095    type Err = String;
1096
1097    fn from_str(s: &str) -> Result<Self, Self::Err> {
1098        match s {
1099            "none" => Ok(Self::None),
1100            "ipfs" => Ok(Self::Ipfs),
1101            "bzzr1" => Ok(Self::Bzzr1),
1102            s => Err(format!("Unknown bytecode hash: {s}")),
1103        }
1104    }
1105}
1106
1107impl fmt::Display for BytecodeHash {
1108    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1109        let s = match self {
1110            Self::Ipfs => "ipfs",
1111            Self::None => "none",
1112            Self::Bzzr1 => "bzzr1",
1113        };
1114        f.write_str(s)
1115    }
1116}
1117
1118/// Bindings for [`solc` contract metadata](https://docs.soliditylang.org/en/latest/metadata.html)
1119#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1120pub struct Metadata {
1121    pub compiler: Compiler,
1122    pub language: String,
1123    pub output: Output,
1124    pub settings: MetadataSettings,
1125    pub sources: MetadataSources,
1126    pub version: i64,
1127}
1128
1129/// A helper type that ensures lossless (de)serialisation so we can preserve the exact String
1130/// metadata value that's being hashed by solc
1131#[derive(Clone, Debug, PartialEq, Eq)]
1132pub struct LosslessMetadata {
1133    /// The complete abi as json value
1134    pub raw_metadata: String,
1135    /// The deserialised metadata of `raw_metadata`
1136    pub metadata: Metadata,
1137}
1138
1139// === impl LosslessMetadata ===
1140
1141impl LosslessMetadata {
1142    /// Returns the whole string raw metadata as `serde_json::Value`
1143    pub fn raw_json(&self) -> serde_json::Result<serde_json::Value> {
1144        serde_json::from_str(&self.raw_metadata)
1145    }
1146}
1147
1148impl Serialize for LosslessMetadata {
1149    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1150    where
1151        S: Serializer,
1152    {
1153        serializer.serialize_str(&self.raw_metadata)
1154    }
1155}
1156
1157impl<'de> Deserialize<'de> for LosslessMetadata {
1158    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1159    where
1160        D: Deserializer<'de>,
1161    {
1162        struct LosslessMetadataVisitor;
1163
1164        impl Visitor<'_> for LosslessMetadataVisitor {
1165            type Value = LosslessMetadata;
1166
1167            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1168                write!(formatter, "metadata string")
1169            }
1170
1171            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
1172            where
1173                E: serde::de::Error,
1174            {
1175                let metadata = serde_json::from_str(value).map_err(serde::de::Error::custom)?;
1176                let raw_metadata = value.to_string();
1177                Ok(LosslessMetadata { raw_metadata, metadata })
1178            }
1179        }
1180        deserializer.deserialize_str(LosslessMetadataVisitor)
1181    }
1182}
1183
1184/// Compiler settings
1185#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1186pub struct MetadataSettings {
1187    #[serde(default)]
1188    pub remappings: Vec<Remapping>,
1189    pub optimizer: Optimizer,
1190    #[serde(default, skip_serializing_if = "Option::is_none")]
1191    pub metadata: Option<SettingsMetadata>,
1192    /// Required for Solidity: File and name of the contract or library this metadata is created
1193    /// for.
1194    #[serde(default, rename = "compilationTarget")]
1195    pub compilation_target: BTreeMap<String, String>,
1196    // Introduced in 0.8.20
1197    #[serde(
1198        default,
1199        rename = "evmVersion",
1200        with = "serde_helpers::display_from_str_opt",
1201        skip_serializing_if = "Option::is_none"
1202    )]
1203    pub evm_version: Option<EvmVersion>,
1204    /// Metadata settings
1205    ///
1206    /// Note: this differs from `Libraries` and does not require another mapping for file name
1207    /// since metadata is per file
1208    #[serde(default)]
1209    pub libraries: BTreeMap<String, String>,
1210    /// Change compilation pipeline to go through the Yul intermediate representation. This is
1211    /// false by default.
1212    #[serde(rename = "viaIR", default, skip_serializing_if = "Option::is_none")]
1213    pub via_ir: Option<bool>,
1214}
1215
1216/// Compilation source files/source units, keys are file names
1217#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1218pub struct MetadataSources {
1219    #[serde(flatten)]
1220    pub inner: BTreeMap<String, MetadataSource>,
1221}
1222
1223#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1224pub struct MetadataSource {
1225    /// Required: keccak256 hash of the source file
1226    pub keccak256: String,
1227    /// Required (unless "content" is used, see below): Sorted URL(s)
1228    /// to the source file, protocol is more or less arbitrary, but a
1229    /// Swarm URL is recommended
1230    #[serde(default)]
1231    pub urls: Vec<String>,
1232    /// Required (unless "url" is used): literal contents of the source file
1233    #[serde(default, skip_serializing_if = "Option::is_none")]
1234    pub content: Option<String>,
1235    /// Optional: SPDX license identifier as given in the source file
1236    pub license: Option<String>,
1237}
1238
1239/// Model checker settings for solc
1240#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1241#[serde(rename_all = "camelCase")]
1242pub struct ModelCheckerSettings {
1243    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
1244    pub contracts: BTreeMap<String, Vec<String>>,
1245    #[serde(
1246        default,
1247        with = "serde_helpers::display_from_str_opt",
1248        skip_serializing_if = "Option::is_none"
1249    )]
1250    pub engine: Option<ModelCheckerEngine>,
1251    #[serde(skip_serializing_if = "Option::is_none")]
1252    pub timeout: Option<u32>,
1253    #[serde(skip_serializing_if = "Option::is_none")]
1254    pub targets: Option<Vec<ModelCheckerTarget>>,
1255    #[serde(skip_serializing_if = "Option::is_none")]
1256    pub invariants: Option<Vec<ModelCheckerInvariant>>,
1257    #[serde(skip_serializing_if = "Option::is_none")]
1258    pub show_unproved: Option<bool>,
1259    #[serde(skip_serializing_if = "Option::is_none")]
1260    pub div_mod_with_slacks: Option<bool>,
1261    #[serde(skip_serializing_if = "Option::is_none")]
1262    pub solvers: Option<Vec<ModelCheckerSolver>>,
1263    #[serde(skip_serializing_if = "Option::is_none")]
1264    pub show_unsupported: Option<bool>,
1265    #[serde(skip_serializing_if = "Option::is_none")]
1266    pub show_proved_safe: Option<bool>,
1267}
1268
1269/// Which model checker engine to run.
1270#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1271pub enum ModelCheckerEngine {
1272    #[default]
1273    Default,
1274    All,
1275    BMC,
1276    CHC,
1277}
1278
1279impl fmt::Display for ModelCheckerEngine {
1280    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1281        let string = match self {
1282            Self::Default => "none",
1283            Self::All => "all",
1284            Self::BMC => "bmc",
1285            Self::CHC => "chc",
1286        };
1287        write!(f, "{string}")
1288    }
1289}
1290
1291impl FromStr for ModelCheckerEngine {
1292    type Err = String;
1293
1294    fn from_str(s: &str) -> Result<Self, Self::Err> {
1295        match s {
1296            "none" => Ok(Self::Default),
1297            "all" => Ok(Self::All),
1298            "bmc" => Ok(Self::BMC),
1299            "chc" => Ok(Self::CHC),
1300            s => Err(format!("Unknown model checker engine: {s}")),
1301        }
1302    }
1303}
1304
1305/// Which model checker targets to check.
1306#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1307#[serde(rename_all = "camelCase")]
1308pub enum ModelCheckerTarget {
1309    Assert,
1310    Underflow,
1311    Overflow,
1312    DivByZero,
1313    ConstantCondition,
1314    PopEmptyArray,
1315    OutOfBounds,
1316    Balance,
1317}
1318
1319impl fmt::Display for ModelCheckerTarget {
1320    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1321        let string = match self {
1322            Self::Assert => "assert",
1323            Self::Underflow => "underflow",
1324            Self::Overflow => "overflow",
1325            Self::DivByZero => "divByZero",
1326            Self::ConstantCondition => "constantCondition",
1327            Self::PopEmptyArray => "popEmptyArray",
1328            Self::OutOfBounds => "outOfBounds",
1329            Self::Balance => "balance",
1330        };
1331        write!(f, "{string}")
1332    }
1333}
1334
1335impl FromStr for ModelCheckerTarget {
1336    type Err = String;
1337
1338    fn from_str(s: &str) -> Result<Self, Self::Err> {
1339        match s {
1340            "assert" => Ok(Self::Assert),
1341            "underflow" => Ok(Self::Underflow),
1342            "overflow" => Ok(Self::Overflow),
1343            "divByZero" => Ok(Self::DivByZero),
1344            "constantCondition" => Ok(Self::ConstantCondition),
1345            "popEmptyArray" => Ok(Self::PopEmptyArray),
1346            "outOfBounds" => Ok(Self::OutOfBounds),
1347            "balance" => Ok(Self::Balance),
1348            s => Err(format!("Unknown model checker target: {s}")),
1349        }
1350    }
1351}
1352
1353/// Which model checker invariants to check.
1354#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1355#[serde(rename_all = "camelCase")]
1356pub enum ModelCheckerInvariant {
1357    Contract,
1358    Reentrancy,
1359}
1360
1361impl fmt::Display for ModelCheckerInvariant {
1362    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1363        let string = match self {
1364            Self::Contract => "contract",
1365            Self::Reentrancy => "reentrancy",
1366        };
1367        write!(f, "{string}")
1368    }
1369}
1370
1371impl FromStr for ModelCheckerInvariant {
1372    type Err = String;
1373
1374    fn from_str(s: &str) -> Result<Self, Self::Err> {
1375        match s {
1376            "contract" => Ok(Self::Contract),
1377            "reentrancy" => Ok(Self::Reentrancy),
1378            s => Err(format!("Unknown model checker invariant: {s}")),
1379        }
1380    }
1381}
1382
1383/// Which model checker solvers to check.
1384#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1385#[serde(rename_all = "camelCase")]
1386pub enum ModelCheckerSolver {
1387    Cvc4,
1388    Eld,
1389    Smtlib2,
1390    Z3,
1391}
1392
1393impl fmt::Display for ModelCheckerSolver {
1394    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1395        let string = match self {
1396            Self::Cvc4 => "cvc4",
1397            Self::Eld => "eld",
1398            Self::Smtlib2 => "smtlib2",
1399            Self::Z3 => "z3",
1400        };
1401        write!(f, "{string}")
1402    }
1403}
1404
1405impl FromStr for ModelCheckerSolver {
1406    type Err = String;
1407
1408    fn from_str(s: &str) -> Result<Self, Self::Err> {
1409        match s {
1410            "cvc4" => Ok(Self::Cvc4),
1411            "eld" => Ok(Self::Cvc4),
1412            "smtlib2" => Ok(Self::Smtlib2),
1413            "z3" => Ok(Self::Z3),
1414            s => Err(format!("Unknown model checker invariant: {s}")),
1415        }
1416    }
1417}
1418
1419#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1420pub struct Compiler {
1421    pub version: String,
1422}
1423
1424#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1425pub struct Output {
1426    pub abi: Vec<SolcAbi>,
1427    pub devdoc: Option<Doc>,
1428    pub userdoc: Option<Doc>,
1429}
1430
1431#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1432pub struct SolcAbi {
1433    #[serde(default)]
1434    pub inputs: Vec<Item>,
1435    #[serde(rename = "stateMutability", skip_serializing_if = "Option::is_none")]
1436    pub state_mutability: Option<String>,
1437    #[serde(rename = "type")]
1438    pub abi_type: String,
1439    #[serde(default, skip_serializing_if = "Option::is_none")]
1440    pub name: Option<String>,
1441    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1442    pub outputs: Vec<Item>,
1443    // required to satisfy solidity events
1444    #[serde(default, skip_serializing_if = "Option::is_none")]
1445    pub anonymous: Option<bool>,
1446}
1447
1448#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1449pub struct Item {
1450    #[serde(rename = "internalType")]
1451    pub internal_type: Option<String>,
1452    pub name: String,
1453    #[serde(rename = "type")]
1454    pub put_type: String,
1455    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1456    pub components: Vec<Item>,
1457    /// Indexed flag. for solidity events
1458    #[serde(default, skip_serializing_if = "Option::is_none")]
1459    pub indexed: Option<bool>,
1460}
1461
1462#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1463pub struct Doc {
1464    #[serde(default, skip_serializing_if = "Option::is_none")]
1465    pub kind: Option<String>,
1466    #[serde(default, skip_serializing_if = "Option::is_none")]
1467    pub methods: Option<DocLibraries>,
1468    #[serde(default, skip_serializing_if = "Option::is_none")]
1469    pub version: Option<u32>,
1470}
1471
1472#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1473pub struct DocLibraries {
1474    #[serde(flatten)]
1475    pub libs: BTreeMap<String, serde_json::Value>,
1476}
1477
1478/// Output type `solc` produces
1479#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1480pub struct CompilerOutput {
1481    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1482    pub errors: Vec<Error>,
1483    #[serde(default)]
1484    pub sources: BTreeMap<PathBuf, SourceFile>,
1485    #[serde(default)]
1486    pub contracts: Contracts,
1487}
1488
1489impl CompilerOutput {
1490    /// Whether the output contains a compiler error
1491    pub fn has_error(&self) -> bool {
1492        self.errors.iter().any(|err| err.severity.is_error())
1493    }
1494
1495    /// Finds the _first_ contract with the given name
1496    pub fn find(&self, contract_name: &str) -> Option<CompactContractRef<'_>> {
1497        self.contracts_iter().find_map(|(name, contract)| {
1498            (name == contract_name).then(|| CompactContractRef::from(contract))
1499        })
1500    }
1501
1502    /// Finds the first contract with the given name and removes it from the set
1503    pub fn remove(&mut self, contract_name: &str) -> Option<Contract> {
1504        self.contracts.values_mut().find_map(|c| c.remove(contract_name))
1505    }
1506
1507    /// Iterate over all contracts and their names
1508    pub fn contracts_iter(&self) -> impl Iterator<Item = (&String, &Contract)> {
1509        self.contracts.values().flatten()
1510    }
1511
1512    /// Iterate over all contracts and their names
1513    pub fn contracts_into_iter(self) -> impl Iterator<Item = (String, Contract)> {
1514        self.contracts.into_values().flatten()
1515    }
1516
1517    /// Given the contract file's path and the contract's name, tries to return the contract's
1518    /// bytecode, runtime bytecode, and abi
1519    pub fn get(&self, path: &Path, contract: &str) -> Option<CompactContractRef<'_>> {
1520        self.contracts
1521            .get(path)
1522            .and_then(|contracts| contracts.get(contract))
1523            .map(CompactContractRef::from)
1524    }
1525
1526    /// Returns the output's source files and contracts separately, wrapped in helper types that
1527    /// provide several helper methods
1528    pub fn split(self) -> (SourceFiles, OutputContracts) {
1529        (SourceFiles(self.sources), OutputContracts(self.contracts))
1530    }
1531
1532    /// Retains only those files the given iterator yields
1533    ///
1534    /// In other words, removes all contracts for files not included in the iterator
1535    pub fn retain_files<'a, I>(&mut self, files: I)
1536    where
1537        I: IntoIterator<Item = &'a Path>,
1538    {
1539        // Note: use `to_lowercase` here because solc not necessarily emits the exact file name,
1540        // e.g. `src/utils/upgradeProxy.sol` is emitted as `src/utils/UpgradeProxy.sol`
1541        let files: HashSet<_> =
1542            files.into_iter().map(|s| s.to_string_lossy().to_lowercase()).collect();
1543        self.contracts.retain(|f, _| files.contains(&f.to_string_lossy().to_lowercase()));
1544        self.sources.retain(|f, _| files.contains(&f.to_string_lossy().to_lowercase()));
1545    }
1546
1547    pub fn merge(&mut self, other: Self) {
1548        self.errors.extend(other.errors);
1549        self.contracts.extend(other.contracts);
1550        self.sources.extend(other.sources);
1551    }
1552}
1553
1554/// A wrapper helper type for the `Contracts` type alias
1555#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1556pub struct OutputContracts(pub Contracts);
1557
1558impl OutputContracts {
1559    /// Returns an iterator over all contracts and their source names.
1560    pub fn into_contracts(self) -> impl Iterator<Item = (String, Contract)> {
1561        self.0.into_values().flatten()
1562    }
1563
1564    /// Iterate over all contracts and their names
1565    pub fn contracts_iter(&self) -> impl Iterator<Item = (&String, &Contract)> {
1566        self.0.values().flatten()
1567    }
1568
1569    /// Finds the _first_ contract with the given name
1570    pub fn find(&self, contract_name: &str) -> Option<CompactContractRef<'_>> {
1571        self.contracts_iter().find_map(|(name, contract)| {
1572            (name == contract_name).then(|| CompactContractRef::from(contract))
1573        })
1574    }
1575
1576    /// Finds the first contract with the given name and removes it from the set
1577    pub fn remove(&mut self, contract_name: &str) -> Option<Contract> {
1578        self.0.values_mut().find_map(|c| c.remove(contract_name))
1579    }
1580}
1581
1582#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1583pub struct UserDoc {
1584    #[serde(default, skip_serializing_if = "Option::is_none")]
1585    pub version: Option<u32>,
1586    #[serde(default, skip_serializing_if = "Option::is_none")]
1587    pub kind: Option<String>,
1588    #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1589    pub methods: BTreeMap<String, UserDocNotice>,
1590    #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1591    pub events: BTreeMap<String, UserDocNotice>,
1592    #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1593    pub errors: BTreeMap<String, Vec<UserDocNotice>>,
1594    #[serde(default, skip_serializing_if = "Option::is_none")]
1595    pub notice: Option<String>,
1596}
1597
1598#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1599#[serde(untagged)]
1600pub enum UserDocNotice {
1601    // NOTE: this a variant used for constructors on older solc versions
1602    Constructor(String),
1603    Notice { notice: String },
1604}
1605
1606#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1607pub struct DevDoc {
1608    #[serde(default, skip_serializing_if = "Option::is_none")]
1609    pub version: Option<u32>,
1610    #[serde(default, skip_serializing_if = "Option::is_none")]
1611    pub kind: Option<String>,
1612    #[serde(default, skip_serializing_if = "Option::is_none")]
1613    pub author: Option<String>,
1614    #[serde(default, skip_serializing_if = "Option::is_none")]
1615    pub details: Option<String>,
1616    #[serde(default, rename = "custom:experimental", skip_serializing_if = "Option::is_none")]
1617    pub custom_experimental: Option<String>,
1618    #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1619    pub methods: BTreeMap<String, MethodDoc>,
1620    #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1621    pub events: BTreeMap<String, EventDoc>,
1622    #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1623    pub errors: BTreeMap<String, Vec<ErrorDoc>>,
1624    #[serde(default, skip_serializing_if = "Option::is_none")]
1625    pub title: Option<String>,
1626}
1627
1628#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1629pub struct MethodDoc {
1630    #[serde(default, skip_serializing_if = "Option::is_none")]
1631    pub details: Option<String>,
1632    #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1633    pub params: BTreeMap<String, String>,
1634    #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1635    pub returns: BTreeMap<String, String>,
1636}
1637
1638#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1639pub struct EventDoc {
1640    #[serde(default, skip_serializing_if = "Option::is_none")]
1641    pub details: Option<String>,
1642    #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1643    pub params: BTreeMap<String, String>,
1644}
1645
1646#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1647pub struct ErrorDoc {
1648    #[serde(default, skip_serializing_if = "Option::is_none")]
1649    pub details: Option<String>,
1650    #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1651    pub params: BTreeMap<String, String>,
1652}
1653
1654#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1655#[serde(rename_all = "camelCase")]
1656pub struct Evm {
1657    #[serde(default, skip_serializing_if = "Option::is_none")]
1658    pub assembly: Option<String>,
1659    #[serde(default, skip_serializing_if = "Option::is_none")]
1660    pub legacy_assembly: Option<serde_json::Value>,
1661    pub bytecode: Option<Bytecode>,
1662    #[serde(default, skip_serializing_if = "Option::is_none")]
1663    pub deployed_bytecode: Option<DeployedBytecode>,
1664    /// The list of function hashes
1665    #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1666    pub method_identifiers: BTreeMap<String, String>,
1667    /// Function gas estimates
1668    #[serde(default, skip_serializing_if = "Option::is_none")]
1669    pub gas_estimates: Option<GasEstimates>,
1670}
1671
1672impl Evm {
1673    /// Crate internal helper do transform the underlying bytecode artifacts into a more convenient
1674    /// structure
1675    pub(crate) fn into_compact(self) -> CompactEvm {
1676        let Self {
1677            assembly,
1678            legacy_assembly,
1679            bytecode,
1680            deployed_bytecode,
1681            method_identifiers,
1682            gas_estimates,
1683        } = self;
1684
1685        let (bytecode, deployed_bytecode) = match (bytecode, deployed_bytecode) {
1686            (Some(bcode), Some(dbcode)) => (Some(bcode.into()), Some(dbcode.into())),
1687            (None, Some(dbcode)) => (None, Some(dbcode.into())),
1688            (Some(bcode), None) => (Some(bcode.into()), None),
1689            (None, None) => (None, None),
1690        };
1691
1692        CompactEvm {
1693            assembly,
1694            legacy_assembly,
1695            bytecode,
1696            deployed_bytecode,
1697            method_identifiers,
1698            gas_estimates,
1699        }
1700    }
1701}
1702
1703#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1704#[serde(rename_all = "camelCase")]
1705pub(crate) struct CompactEvm {
1706    #[serde(default, skip_serializing_if = "Option::is_none")]
1707    pub assembly: Option<String>,
1708    #[serde(default, skip_serializing_if = "Option::is_none")]
1709    pub legacy_assembly: Option<serde_json::Value>,
1710    pub bytecode: Option<CompactBytecode>,
1711    #[serde(default, skip_serializing_if = "Option::is_none")]
1712    pub deployed_bytecode: Option<CompactDeployedBytecode>,
1713    /// The list of function hashes
1714    #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1715    pub method_identifiers: BTreeMap<String, String>,
1716    /// Function gas estimates
1717    #[serde(default, skip_serializing_if = "Option::is_none")]
1718    pub gas_estimates: Option<GasEstimates>,
1719}
1720
1721#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1722#[serde(rename_all = "camelCase")]
1723pub struct FunctionDebugData {
1724    pub entry_point: Option<u32>,
1725    pub id: Option<u32>,
1726    pub parameter_slots: Option<u32>,
1727    pub return_slots: Option<u32>,
1728}
1729
1730#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1731pub struct GeneratedSource {
1732    pub ast: serde_json::Value,
1733    pub contents: String,
1734    pub id: u32,
1735    pub language: String,
1736    pub name: String,
1737}
1738
1739/// Byte offsets into the bytecode.
1740/// Linking replaces the 20 bytes located there.
1741#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1742pub struct Offsets {
1743    pub start: u32,
1744    pub length: u32,
1745}
1746
1747#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1748pub struct GasEstimates {
1749    pub creation: Creation,
1750    #[serde(default)]
1751    pub external: BTreeMap<String, String>,
1752    #[serde(default)]
1753    pub internal: BTreeMap<String, String>,
1754}
1755
1756#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1757#[serde(rename_all = "camelCase")]
1758pub struct Creation {
1759    pub code_deposit_cost: String,
1760    pub execution_cost: String,
1761    pub total_cost: String,
1762}
1763
1764#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1765pub struct Ewasm {
1766    #[serde(default, skip_serializing_if = "Option::is_none")]
1767    pub wast: Option<String>,
1768    pub wasm: String,
1769}
1770
1771/// Represents the `storage-layout` section of the `CompilerOutput` if selected.
1772#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1773pub struct StorageLayout {
1774    pub storage: Vec<Storage>,
1775    #[serde(default, deserialize_with = "serde_helpers::default_for_null")]
1776    pub types: BTreeMap<String, StorageType>,
1777}
1778
1779impl StorageLayout {
1780    fn is_empty(&self) -> bool {
1781        self.storage.is_empty() && self.types.is_empty()
1782    }
1783}
1784
1785#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1786pub struct Storage {
1787    #[serde(rename = "astId")]
1788    pub ast_id: u64,
1789    pub contract: String,
1790    pub label: String,
1791    pub offset: i64,
1792    pub slot: String,
1793    #[serde(rename = "type")]
1794    pub storage_type: String,
1795}
1796
1797#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1798pub struct StorageType {
1799    pub encoding: String,
1800    #[serde(default, skip_serializing_if = "Option::is_none")]
1801    pub key: Option<String>,
1802    pub label: String,
1803    #[serde(rename = "numberOfBytes")]
1804    pub number_of_bytes: String,
1805    #[serde(default, skip_serializing_if = "Option::is_none")]
1806    pub value: Option<String>,
1807    /// additional fields
1808    #[serde(flatten)]
1809    pub other: BTreeMap<String, serde_json::Value>,
1810}
1811
1812#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1813pub struct SourceFile {
1814    pub id: u32,
1815    #[serde(default, with = "serde_helpers::empty_json_object_opt")]
1816    pub ast: Option<Ast>,
1817}
1818
1819impl SourceFile {
1820    /// Returns `true` if the source file contains at least 1 `ContractDefinition` such as
1821    /// `contract`, `abstract contract`, `interface` or `library`.
1822    pub fn contains_contract_definition(&self) -> bool {
1823        self.ast.as_ref().is_some_and(|ast| {
1824            ast.nodes.iter().any(|node| matches!(node.node_type, NodeType::ContractDefinition))
1825        })
1826    }
1827}
1828
1829/// A wrapper type for a list of source files: `path -> SourceFile`.
1830#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1831pub struct SourceFiles(pub BTreeMap<PathBuf, SourceFile>);
1832
1833impl SourceFiles {
1834    /// Returns an iterator over the source files' IDs and path.
1835    pub fn into_ids(self) -> impl Iterator<Item = (u32, PathBuf)> {
1836        self.0.into_iter().map(|(k, v)| (v.id, k))
1837    }
1838
1839    /// Returns an iterator over the source files' paths and IDs.
1840    pub fn into_paths(self) -> impl Iterator<Item = (PathBuf, u32)> {
1841        self.0.into_iter().map(|(k, v)| (k, v.id))
1842    }
1843}
1844
1845#[cfg(test)]
1846mod tests {
1847    use super::*;
1848    use alloy_primitives::Address;
1849    use similar_asserts::assert_eq;
1850    use std::fs;
1851
1852    #[test]
1853    fn can_link_bytecode() {
1854        // test cases taken from <https://github.com/ethereum/solc-js/blob/master/test/linker.ts>
1855
1856        #[derive(Serialize, Deserialize)]
1857        struct Mockject {
1858            object: BytecodeObject,
1859        }
1860        fn parse_bytecode(bytecode: &str) -> BytecodeObject {
1861            let object: Mockject =
1862                serde_json::from_value(serde_json::json!({ "object": bytecode })).unwrap();
1863            object.object
1864        }
1865
1866        let bytecode =  "6060604052341561000f57600080fd5b60f48061001d6000396000f300606060405260043610603e5763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166326121ff081146043575b600080fd5b3415604d57600080fd5b60536055565b005b73__lib2.sol:L____________________________6326121ff06040518163ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040160006040518083038186803b151560b357600080fd5b6102c65a03f4151560c357600080fd5b5050505600a165627a7a723058207979b30bd4a07c77b02774a511f2a1dd04d7e5d65b5c2735b5fc96ad61d43ae40029";
1867
1868        let mut object = parse_bytecode(bytecode);
1869        assert!(object.is_unlinked());
1870        assert!(object.contains_placeholder("lib2.sol", "L"));
1871        assert!(object.contains_fully_qualified_placeholder("lib2.sol:L"));
1872        assert!(object.link("lib2.sol", "L", Address::random()).resolve().is_some());
1873        assert!(!object.is_unlinked());
1874
1875        let mut code = Bytecode {
1876            function_debug_data: Default::default(),
1877            object: parse_bytecode(bytecode),
1878            opcodes: None,
1879            source_map: None,
1880            generated_sources: vec![],
1881            link_references: BTreeMap::from([(
1882                "lib2.sol".to_string(),
1883                BTreeMap::from([("L".to_string(), vec![])]),
1884            )]),
1885        };
1886
1887        assert!(!code.link("lib2.sol", "Y", Address::random()));
1888        assert!(code.link("lib2.sol", "L", Address::random()));
1889        assert!(code.link("lib2.sol", "L", Address::random()));
1890
1891        let hashed_placeholder = "6060604052341561000f57600080fd5b60f48061001d6000396000f300606060405260043610603e5763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166326121ff081146043575b600080fd5b3415604d57600080fd5b60536055565b005b73__$cb901161e812ceb78cfe30ca65050c4337$__6326121ff06040518163ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040160006040518083038186803b151560b357600080fd5b6102c65a03f4151560c357600080fd5b5050505600a165627a7a723058207979b30bd4a07c77b02774a511f2a1dd04d7e5d65b5c2735b5fc96ad61d43ae40029";
1892        let mut object = parse_bytecode(hashed_placeholder);
1893        assert!(object.is_unlinked());
1894        assert!(object.contains_placeholder("lib2.sol", "L"));
1895        assert!(object.contains_fully_qualified_placeholder("lib2.sol:L"));
1896        assert!(object.link("lib2.sol", "L", Address::default()).resolve().is_some());
1897        assert!(!object.is_unlinked());
1898    }
1899
1900    #[test]
1901    fn can_parse_compiler_output() {
1902        let dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../../test-data/out");
1903
1904        for path in fs::read_dir(dir).unwrap() {
1905            let path = path.unwrap().path();
1906            let compiler_output = fs::read_to_string(&path).unwrap();
1907            serde_json::from_str::<CompilerOutput>(&compiler_output).unwrap_or_else(|err| {
1908                panic!("Failed to read compiler output of {} {}", path.display(), err)
1909            });
1910        }
1911    }
1912
1913    #[test]
1914    fn can_parse_compiler_input() {
1915        let dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../../test-data/in");
1916
1917        for path in fs::read_dir(dir).unwrap() {
1918            let path = path.unwrap().path();
1919            let compiler_input = fs::read_to_string(&path).unwrap();
1920            serde_json::from_str::<SolcInput>(&compiler_input).unwrap_or_else(|err| {
1921                panic!("Failed to read compiler input of {} {}", path.display(), err)
1922            });
1923        }
1924    }
1925
1926    #[test]
1927    fn can_parse_standard_json_compiler_input() {
1928        let dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../../test-data/in");
1929
1930        for path in fs::read_dir(dir).unwrap() {
1931            let path = path.unwrap().path();
1932            let compiler_input = fs::read_to_string(&path).unwrap();
1933            let val = serde_json::from_str::<StandardJsonCompilerInput>(&compiler_input)
1934                .unwrap_or_else(|err| {
1935                    panic!("Failed to read compiler output of {} {}", path.display(), err)
1936                });
1937
1938            let pretty = serde_json::to_string_pretty(&val).unwrap();
1939            serde_json::from_str::<SolcInput>(&pretty).unwrap_or_else(|err| {
1940                panic!("Failed to read converted compiler input of {} {}", path.display(), err)
1941            });
1942        }
1943    }
1944
1945    #[test]
1946    fn test_evm_version_default() {
1947        for &(solc_version, expected) in &[
1948            // Everything before 0.4.21 should always return None
1949            ("0.4.20", None),
1950            // Byzantium clipping
1951            ("0.4.21", Some(EvmVersion::Byzantium)),
1952            // Constantinople bug fix
1953            ("0.4.22", Some(EvmVersion::Byzantium)),
1954            // Petersburg
1955            ("0.5.5", Some(EvmVersion::Petersburg)),
1956            // Istanbul
1957            ("0.5.14", Some(EvmVersion::Istanbul)),
1958            // Berlin
1959            ("0.8.5", Some(EvmVersion::Berlin)),
1960            // London
1961            ("0.8.7", Some(EvmVersion::London)),
1962            // Paris
1963            ("0.8.18", Some(EvmVersion::Paris)),
1964            // Shanghai
1965            ("0.8.20", Some(EvmVersion::Shanghai)),
1966            // Cancun
1967            ("0.8.24", Some(EvmVersion::Shanghai)),
1968            ("0.8.25", Some(EvmVersion::Cancun)),
1969        ] {
1970            let version = Version::from_str(solc_version).unwrap();
1971            assert_eq!(
1972                EvmVersion::default_version_solc(&version),
1973                expected,
1974                "({version}, {expected:?})"
1975            )
1976        }
1977    }
1978
1979    #[test]
1980    fn test_evm_version_normalization() {
1981        for &(solc_version, evm_version, expected) in &[
1982            // Everything before 0.4.21 should always return None
1983            ("0.4.20", EvmVersion::Homestead, None),
1984            // Byzantium clipping
1985            ("0.4.21", EvmVersion::Homestead, Some(EvmVersion::Homestead)),
1986            ("0.4.21", EvmVersion::Constantinople, Some(EvmVersion::Byzantium)),
1987            ("0.4.21", EvmVersion::London, Some(EvmVersion::Byzantium)),
1988            // Constantinople bug fix
1989            ("0.4.22", EvmVersion::Homestead, Some(EvmVersion::Homestead)),
1990            ("0.4.22", EvmVersion::Constantinople, Some(EvmVersion::Constantinople)),
1991            ("0.4.22", EvmVersion::London, Some(EvmVersion::Constantinople)),
1992            // Petersburg
1993            ("0.5.5", EvmVersion::Homestead, Some(EvmVersion::Homestead)),
1994            ("0.5.5", EvmVersion::Petersburg, Some(EvmVersion::Petersburg)),
1995            ("0.5.5", EvmVersion::London, Some(EvmVersion::Petersburg)),
1996            // Istanbul
1997            ("0.5.14", EvmVersion::Homestead, Some(EvmVersion::Homestead)),
1998            ("0.5.14", EvmVersion::Istanbul, Some(EvmVersion::Istanbul)),
1999            ("0.5.14", EvmVersion::London, Some(EvmVersion::Istanbul)),
2000            // Berlin
2001            ("0.8.5", EvmVersion::Homestead, Some(EvmVersion::Homestead)),
2002            ("0.8.5", EvmVersion::Berlin, Some(EvmVersion::Berlin)),
2003            ("0.8.5", EvmVersion::London, Some(EvmVersion::Berlin)),
2004            // London
2005            ("0.8.7", EvmVersion::Homestead, Some(EvmVersion::Homestead)),
2006            ("0.8.7", EvmVersion::London, Some(EvmVersion::London)),
2007            ("0.8.7", EvmVersion::Paris, Some(EvmVersion::London)),
2008            // Paris
2009            ("0.8.18", EvmVersion::Homestead, Some(EvmVersion::Homestead)),
2010            ("0.8.18", EvmVersion::Paris, Some(EvmVersion::Paris)),
2011            ("0.8.18", EvmVersion::Shanghai, Some(EvmVersion::Paris)),
2012            // Shanghai
2013            ("0.8.20", EvmVersion::Homestead, Some(EvmVersion::Homestead)),
2014            ("0.8.20", EvmVersion::Paris, Some(EvmVersion::Paris)),
2015            ("0.8.20", EvmVersion::Shanghai, Some(EvmVersion::Shanghai)),
2016            ("0.8.20", EvmVersion::Cancun, Some(EvmVersion::Shanghai)),
2017            // Cancun
2018            ("0.8.24", EvmVersion::Homestead, Some(EvmVersion::Homestead)),
2019            ("0.8.24", EvmVersion::Shanghai, Some(EvmVersion::Shanghai)),
2020            ("0.8.24", EvmVersion::Cancun, Some(EvmVersion::Cancun)),
2021            // Prague
2022            ("0.8.26", EvmVersion::Homestead, Some(EvmVersion::Homestead)),
2023            ("0.8.26", EvmVersion::Shanghai, Some(EvmVersion::Shanghai)),
2024            ("0.8.26", EvmVersion::Cancun, Some(EvmVersion::Cancun)),
2025            ("0.8.26", EvmVersion::Prague, Some(EvmVersion::Cancun)),
2026            ("0.8.27", EvmVersion::Prague, Some(EvmVersion::Prague)),
2027            ("0.8.29", EvmVersion::Osaka, Some(EvmVersion::Osaka)),
2028        ] {
2029            let version = Version::from_str(solc_version).unwrap();
2030            assert_eq!(
2031                evm_version.normalize_version_solc(&version),
2032                expected,
2033                "({version}, {evm_version:?})"
2034            )
2035        }
2036    }
2037
2038    #[test]
2039    fn can_sanitize_byte_code_hash() {
2040        let settings = Settings { metadata: Some(BytecodeHash::Ipfs.into()), ..Default::default() };
2041
2042        let input =
2043            SolcInput { language: SolcLanguage::Solidity, sources: Default::default(), settings };
2044
2045        let i = input.clone().sanitized(&Version::new(0, 6, 0));
2046        assert_eq!(i.settings.metadata.unwrap().bytecode_hash, Some(BytecodeHash::Ipfs));
2047
2048        let i = input.sanitized(&Version::new(0, 5, 17));
2049        assert!(i.settings.metadata.unwrap().bytecode_hash.is_none());
2050    }
2051
2052    #[test]
2053    fn can_sanitize_cbor_metadata() {
2054        let settings = Settings {
2055            metadata: Some(SettingsMetadata::new(BytecodeHash::Ipfs, true)),
2056            ..Default::default()
2057        };
2058
2059        let input =
2060            SolcInput { language: SolcLanguage::Solidity, sources: Default::default(), settings };
2061
2062        let i = input.clone().sanitized(&Version::new(0, 8, 18));
2063        assert_eq!(i.settings.metadata.unwrap().cbor_metadata, Some(true));
2064
2065        let i = input.sanitized(&Version::new(0, 8, 0));
2066        assert!(i.settings.metadata.unwrap().cbor_metadata.is_none());
2067    }
2068
2069    #[test]
2070    fn can_parse_libraries() {
2071        let libraries = ["./src/lib/LibraryContract.sol:Library:0xaddress".to_string()];
2072
2073        let libs = Libraries::parse(&libraries[..]).unwrap().libs;
2074
2075        assert_eq!(
2076            libs,
2077            BTreeMap::from([(
2078                PathBuf::from("./src/lib/LibraryContract.sol"),
2079                BTreeMap::from([("Library".to_string(), "0xaddress".to_string())])
2080            )])
2081        );
2082    }
2083
2084    #[test]
2085    fn can_strip_libraries_path_prefixes() {
2086        let libraries= [
2087            "/global/root/src/FileInSrc.sol:Chainlink:0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string(),
2088            "src/deep/DeepFileInSrc.sol:ChainlinkTWAP:0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c".to_string(),
2089            "/global/GlobalFile.sol:Math:0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c".to_string(),
2090            "/global/root/test/ChainlinkTWAP.t.sol:ChainlinkTWAP:0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string(),
2091            "test/SizeAuctionDiscount.sol:Math:0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c".to_string(),
2092        ];
2093
2094        let libs = Libraries::parse(&libraries[..])
2095            .unwrap()
2096            .with_stripped_file_prefixes("/global/root".as_ref())
2097            .libs;
2098
2099        assert_eq!(
2100            libs,
2101            BTreeMap::from([
2102                (
2103                    PathBuf::from("/global/GlobalFile.sol"),
2104                    BTreeMap::from([(
2105                        "Math".to_string(),
2106                        "0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c".to_string()
2107                    )])
2108                ),
2109                (
2110                    PathBuf::from("src/FileInSrc.sol"),
2111                    BTreeMap::from([(
2112                        "Chainlink".to_string(),
2113                        "0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string()
2114                    )])
2115                ),
2116                (
2117                    PathBuf::from("src/deep/DeepFileInSrc.sol"),
2118                    BTreeMap::from([(
2119                        "ChainlinkTWAP".to_string(),
2120                        "0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c".to_string()
2121                    )])
2122                ),
2123                (
2124                    PathBuf::from("test/SizeAuctionDiscount.sol"),
2125                    BTreeMap::from([(
2126                        "Math".to_string(),
2127                        "0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c".to_string()
2128                    )])
2129                ),
2130                (
2131                    PathBuf::from("test/ChainlinkTWAP.t.sol"),
2132                    BTreeMap::from([(
2133                        "ChainlinkTWAP".to_string(),
2134                        "0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string()
2135                    )])
2136                ),
2137            ])
2138        );
2139    }
2140
2141    #[test]
2142    fn can_parse_many_libraries() {
2143        let libraries= [
2144            "./src/SizeAuctionDiscount.sol:Chainlink:0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string(),
2145            "./src/SizeAuction.sol:ChainlinkTWAP:0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string(),
2146            "./src/SizeAuction.sol:Math:0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c".to_string(),
2147            "./src/test/ChainlinkTWAP.t.sol:ChainlinkTWAP:0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string(),
2148            "./src/SizeAuctionDiscount.sol:Math:0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c".to_string(),
2149        ];
2150
2151        let libs = Libraries::parse(&libraries[..]).unwrap().libs;
2152
2153        assert_eq!(
2154            libs,
2155            BTreeMap::from([
2156                (
2157                    PathBuf::from("./src/SizeAuctionDiscount.sol"),
2158                    BTreeMap::from([
2159                        (
2160                            "Chainlink".to_string(),
2161                            "0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string()
2162                        ),
2163                        (
2164                            "Math".to_string(),
2165                            "0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c".to_string()
2166                        )
2167                    ])
2168                ),
2169                (
2170                    PathBuf::from("./src/SizeAuction.sol"),
2171                    BTreeMap::from([
2172                        (
2173                            "ChainlinkTWAP".to_string(),
2174                            "0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string()
2175                        ),
2176                        (
2177                            "Math".to_string(),
2178                            "0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c".to_string()
2179                        )
2180                    ])
2181                ),
2182                (
2183                    PathBuf::from("./src/test/ChainlinkTWAP.t.sol"),
2184                    BTreeMap::from([(
2185                        "ChainlinkTWAP".to_string(),
2186                        "0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string()
2187                    )])
2188                ),
2189            ])
2190        );
2191    }
2192
2193    #[test]
2194    fn test_lossless_metadata() {
2195        #[derive(Debug, Serialize, Deserialize)]
2196        #[serde(rename_all = "camelCase")]
2197        pub struct Contract {
2198            #[serde(
2199                default,
2200                skip_serializing_if = "Option::is_none",
2201                with = "serde_helpers::json_string_opt"
2202            )]
2203            pub metadata: Option<LosslessMetadata>,
2204        }
2205
2206        let s = r#"{"metadata":"{\"compiler\":{\"version\":\"0.4.18+commit.9cf6e910\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"constant\":true,\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"}],\"devdoc\":{\"methods\":{\"transferOwnership(address)\":{\"details\":\"Allows the current owner to transfer control of the contract to a newOwner.\",\"params\":{\"newOwner\":\"The address to transfer ownership to.\"}}},\"title\":\"Ownable\"},\"userdoc\":{\"methods\":{}}},\"settings\":{\"compilationTarget\":{\"src/Contract.sol\":\"Ownable\"},\"libraries\":{},\"optimizer\":{\"enabled\":true,\"runs\":1000000},\"remappings\":[\":src/=src/\"]},\"sources\":{\"src/Contract.sol\":{\"keccak256\":\"0x3e0d611f53491f313ae035797ed7ecfd1dfd8db8fef8f82737e6f0cd86d71de7\",\"urls\":[\"bzzr://9c33025fa9d1b8389e4c7c9534a1d70fad91c6c2ad70eb5e4b7dc3a701a5f892\"]}},\"version\":1}"}"#;
2207
2208        let value: serde_json::Value = serde_json::from_str(s).unwrap();
2209        let c: Contract = serde_json::from_value(value).unwrap();
2210        assert_eq!(c.metadata.as_ref().unwrap().raw_metadata, "{\"compiler\":{\"version\":\"0.4.18+commit.9cf6e910\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"constant\":true,\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"}],\"devdoc\":{\"methods\":{\"transferOwnership(address)\":{\"details\":\"Allows the current owner to transfer control of the contract to a newOwner.\",\"params\":{\"newOwner\":\"The address to transfer ownership to.\"}}},\"title\":\"Ownable\"},\"userdoc\":{\"methods\":{}}},\"settings\":{\"compilationTarget\":{\"src/Contract.sol\":\"Ownable\"},\"libraries\":{},\"optimizer\":{\"enabled\":true,\"runs\":1000000},\"remappings\":[\":src/=src/\"]},\"sources\":{\"src/Contract.sol\":{\"keccak256\":\"0x3e0d611f53491f313ae035797ed7ecfd1dfd8db8fef8f82737e6f0cd86d71de7\",\"urls\":[\"bzzr://9c33025fa9d1b8389e4c7c9534a1d70fad91c6c2ad70eb5e4b7dc3a701a5f892\"]}},\"version\":1}");
2211
2212        let value = serde_json::to_string(&c).unwrap();
2213        assert_eq!(s, value);
2214    }
2215
2216    #[test]
2217    fn test_lossless_storage_layout() {
2218        let input = include_str!("../../../../test-data/foundryissue2462.json").trim();
2219        let layout: StorageLayout = serde_json::from_str(input).unwrap();
2220        assert_eq!(input, &serde_json::to_string(&layout).unwrap());
2221    }
2222
2223    // <https://github.com/foundry-rs/foundry/issues/3012>
2224    #[test]
2225    fn can_parse_compiler_output_spells_0_6_12() {
2226        let path =
2227            Path::new(env!("CARGO_MANIFEST_DIR")).join("../../../test-data/0.6.12-with-libs.json");
2228        let content = fs::read_to_string(path).unwrap();
2229        let _output: CompilerOutput = serde_json::from_str(&content).unwrap();
2230    }
2231
2232    // <https://github.com/foundry-rs/foundry/issues/9322>
2233    #[test]
2234    fn can_sanitize_optimizer_inliner() {
2235        let settings = Settings::default().with_via_ir_minimum_optimization();
2236
2237        let input =
2238            SolcInput { language: SolcLanguage::Solidity, sources: Default::default(), settings };
2239
2240        let i = input.clone().sanitized(&Version::new(0, 8, 4));
2241        assert!(i.settings.optimizer.details.unwrap().inliner.is_none());
2242
2243        let i = input.sanitized(&Version::new(0, 8, 5));
2244        assert_eq!(i.settings.optimizer.details.unwrap().inliner, Some(false));
2245    }
2246}