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