ethers_solc/artifacts/
mod.rs

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