Skip to main content

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