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