foundry_compilers/compilers/solc/
compiler.rs

1use crate::resolver::parse::SolData;
2use foundry_compilers_artifacts::{sources::Source, CompilerOutput, SolcInput};
3use foundry_compilers_core::{
4    error::{Result, SolcError},
5    utils::{SUPPORTS_BASE_PATH, SUPPORTS_INCLUDE_PATH},
6};
7use itertools::Itertools;
8use semver::{Version, VersionReq};
9use serde::{de::DeserializeOwned, Deserialize, Serialize};
10use std::{
11    collections::BTreeSet,
12    io::{self, Write},
13    path::{Path, PathBuf},
14    process::{Command, Output, Stdio},
15    str::FromStr,
16};
17
18/// Extensions acceptable by solc compiler.
19pub const SOLC_EXTENSIONS: &[&str] = &["sol", "yul"];
20
21/// take the lock in tests, we use this to enforce that
22/// a test does not run while a compiler version is being installed
23///
24/// This ensures that only one thread installs a missing `solc` exe.
25/// Instead of taking this lock in `Solc::blocking_install`, the lock should be taken before
26/// installation is detected.
27#[cfg(feature = "svm-solc")]
28#[cfg(any(test, feature = "test-utils"))]
29#[macro_export]
30macro_rules! take_solc_installer_lock {
31    ($lock:ident) => {
32        let lock_path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join(".lock");
33        let lock_file = std::fs::OpenOptions::new()
34            .read(true)
35            .write(true)
36            .create(true)
37            .truncate(false)
38            .open(lock_path)
39            .unwrap();
40        let mut lock = fd_lock::RwLock::new(lock_file);
41        let $lock = lock.write().unwrap();
42    };
43}
44
45/// A list of upstream Solc releases, used to check which version
46/// we should download.
47/// The boolean value marks whether there was an error accessing the release list
48#[cfg(feature = "svm-solc")]
49pub static RELEASES: std::sync::LazyLock<(svm::Releases, Vec<Version>, bool)> =
50    std::sync::LazyLock::new(|| {
51        match serde_json::from_str::<svm::Releases>(svm_builds::RELEASE_LIST_JSON) {
52            Ok(releases) => {
53                let sorted_versions = releases.clone().into_versions();
54                (releases, sorted_versions, true)
55            }
56            Err(err) => {
57                error!("failed to deserialize SVM static RELEASES JSON: {err}");
58                Default::default()
59            }
60        }
61    });
62
63/// Abstraction over `solc` command line utility
64///
65/// Supports sync and async functions.
66///
67/// By default the solc path is configured as follows, with descending priority:
68///   1. `SOLC_PATH` environment variable
69///   2. [svm](https://github.com/roynalnaruto/svm-rs)'s  `global_version` (set via `svm use
70///      <version>`), stored at `<svm_home>/.global_version`
71///   3. `solc` otherwise
72#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
73pub struct Solc {
74    /// Path to the `solc` executable
75    pub solc: PathBuf,
76    /// Compiler version.
77    pub version: Version,
78    /// Value for --base-path arg.
79    pub base_path: Option<PathBuf>,
80    /// Value for --allow-paths arg.
81    pub allow_paths: BTreeSet<PathBuf>,
82    /// Value for --include-paths arg.
83    pub include_paths: BTreeSet<PathBuf>,
84    /// Additional arbitrary arguments.
85    pub extra_args: Vec<String>,
86}
87
88impl Solc {
89    /// A new instance which points to `solc`. Invokes `solc --version` to determine the version.
90    ///
91    /// Returns error if `solc` is not found in the system or if the version cannot be retrieved.
92    #[instrument(name = "Solc::new", skip_all)]
93    pub fn new(path: impl Into<PathBuf>) -> Result<Self> {
94        Self::new_with_args(path, Vec::<String>::new())
95    }
96
97    /// A new instance which points to `solc` with additional cli arguments. Invokes `solc
98    /// --version` to determine the version.
99    ///
100    /// Returns error if `solc` is not found in the system or if the version cannot be retrieved.
101    pub fn new_with_args(
102        path: impl Into<PathBuf>,
103        extra_args: impl IntoIterator<Item: Into<String>>,
104    ) -> Result<Self> {
105        let path = path.into();
106        let extra_args = extra_args.into_iter().map(Into::into).collect::<Vec<_>>();
107        let version = Self::version_with_args(path.clone(), &extra_args)?;
108        Ok(Self::_new(path, version, extra_args))
109    }
110
111    /// A new instance which points to `solc` with the given version
112    pub fn new_with_version(path: impl Into<PathBuf>, version: Version) -> Self {
113        Self::_new(path.into(), version, Default::default())
114    }
115
116    fn _new(path: PathBuf, version: Version, extra_args: Vec<String>) -> Self {
117        let this = Self {
118            solc: path,
119            version,
120            base_path: None,
121            allow_paths: Default::default(),
122            include_paths: Default::default(),
123            extra_args,
124        };
125        this.debug_assert();
126        this
127    }
128
129    fn debug_assert(&self) {
130        if !cfg!(debug_assertions) {
131            return;
132        }
133        if let Ok(v) = Self::version_with_args(&self.solc, &self.extra_args) {
134            assert_eq!(v.major, self.version.major);
135            assert_eq!(v.minor, self.version.minor);
136            assert_eq!(v.patch, self.version.patch);
137        }
138    }
139
140    /// Parses the given source looking for the `pragma` definition and
141    /// returns the corresponding SemVer version requirement.
142    pub fn source_version_req(source: &Source) -> Result<VersionReq> {
143        Ok(SolData::parse_version_pragma(&source.content).ok_or(SolcError::PragmaNotFound)??)
144    }
145
146    /// Given a Solidity source, it detects the latest compiler version which can be used
147    /// to build it, and returns it.
148    ///
149    /// If the required compiler version is not installed, it also proceeds to install it.
150    #[cfg(feature = "svm-solc")]
151    pub fn detect_version(source: &Source) -> Result<Version> {
152        // detects the required solc version
153        let sol_version = Self::source_version_req(source)?;
154        Self::ensure_installed(&sol_version)
155    }
156
157    /// Given a Solidity version requirement, it detects the latest compiler version which can be
158    /// used to build it, and returns it.
159    ///
160    /// If the required compiler version is not installed, it also proceeds to install it.
161    #[cfg(feature = "svm-solc")]
162    pub fn ensure_installed(sol_version: &VersionReq) -> Result<Version> {
163        #[cfg(test)]
164        take_solc_installer_lock!(_lock);
165
166        // load the local / remote versions
167        let versions = Self::installed_versions();
168
169        let local_versions = Self::find_matching_installation(&versions, sol_version);
170        let remote_versions = Self::find_matching_installation(&RELEASES.1, sol_version);
171
172        // if there's a better upstream version than the one we have, install it
173        Ok(match (local_versions, remote_versions) {
174            (Some(local), None) => local,
175            (Some(local), Some(remote)) => {
176                if remote > local {
177                    Self::blocking_install(&remote)?;
178                    remote
179                } else {
180                    local
181                }
182            }
183            (None, Some(version)) => {
184                Self::blocking_install(&version)?;
185                version
186            }
187            // do nothing otherwise
188            _ => return Err(SolcError::VersionNotFound),
189        })
190    }
191
192    /// Assuming the `versions` array is sorted, it returns the first element which satisfies
193    /// the provided [`VersionReq`]
194    pub fn find_matching_installation(
195        versions: &[Version],
196        required_version: &VersionReq,
197    ) -> Option<Version> {
198        // iterate in reverse to find the last match
199        versions.iter().rev().find(|version| required_version.matches(version)).cloned()
200    }
201
202    /// Returns the path for a [svm](https://github.com/roynalnaruto/svm-rs) installed version.
203    ///
204    /// # Examples
205    ///
206    /// ```no_run
207    /// use foundry_compilers::solc::Solc;
208    /// use semver::Version;
209    ///
210    /// let solc = Solc::find_svm_installed_version(&Version::new(0, 8, 9))?;
211    /// assert_eq!(solc, Some(Solc::new("~/.svm/0.8.9/solc-0.8.9")?));
212    ///
213    /// Ok::<_, Box<dyn std::error::Error>>(())
214    /// ```
215    #[instrument(skip_all)]
216    #[cfg(feature = "svm-solc")]
217    pub fn find_svm_installed_version(version: &Version) -> Result<Option<Self>> {
218        let version = if version.pre.is_empty() {
219            Version::new(version.major, version.minor, version.patch)
220        } else {
221            // Preserve version if it is a prerelease.
222            version.clone()
223        };
224        let solc = svm::version_binary(&version.to_string());
225        if !solc.is_file() {
226            return Ok(None);
227        }
228        Ok(Some(Self::new_with_version(&solc, version)))
229    }
230
231    /// Returns the directory in which [svm](https://github.com/roynalnaruto/svm-rs) stores all versions
232    ///
233    /// This will be:
234    /// - `~/.svm` on unix, if it exists
235    /// - $XDG_DATA_HOME (~/.local/share/svm) if the svm folder does not exist.
236    #[cfg(feature = "svm-solc")]
237    pub fn svm_home() -> Option<PathBuf> {
238        Some(svm::data_dir().to_path_buf())
239    }
240
241    /// Returns the `semver::Version` [svm](https://github.com/roynalnaruto/svm-rs)'s `.global_version` is currently set to.
242    ///  `global_version` is configured with (`svm use <version>`)
243    ///
244    /// This will read the version string (eg: "0.8.9") that the  `~/.svm/.global_version` file
245    /// contains
246    #[cfg(feature = "svm-solc")]
247    pub fn svm_global_version() -> Option<Version> {
248        svm::get_global_version().ok().flatten()
249    }
250
251    /// Returns the list of all solc instances installed at `SVM_HOME`
252    #[cfg(feature = "svm-solc")]
253    pub fn installed_versions() -> Vec<Version> {
254        svm::installed_versions().unwrap_or_default()
255    }
256
257    /// Returns the list of all versions that are available to download
258    #[cfg(feature = "svm-solc")]
259    pub fn released_versions() -> Vec<Version> {
260        RELEASES.1.clone()
261    }
262
263    /// Installs the provided version of Solc in the machine under the svm dir and returns the
264    /// [Solc] instance pointing to the installation.
265    ///
266    /// # Examples
267    ///
268    /// ```no_run
269    /// use foundry_compilers::{solc::Solc, utils::ISTANBUL_SOLC};
270    ///
271    /// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
272    /// let solc = Solc::install(&ISTANBUL_SOLC).await?;
273    /// # Ok(())
274    /// # }
275    /// ```
276    #[cfg(feature = "svm-solc")]
277    #[instrument(name = "Solc::install", skip_all)]
278    pub async fn install(version: &Version) -> std::result::Result<Self, svm::SvmError> {
279        trace!("installing solc version \"{}\"", version);
280        crate::report::solc_installation_start(version);
281        match svm::install(version).await {
282            Ok(path) => {
283                crate::report::solc_installation_success(version);
284                Ok(Self::new_with_version(path, version.clone()))
285            }
286            Err(err) => {
287                crate::report::solc_installation_error(version, &err.to_string());
288                Err(err)
289            }
290        }
291    }
292
293    /// Blocking version of `Self::install`
294    #[cfg(feature = "svm-solc")]
295    #[instrument(name = "Solc::blocking_install", skip_all)]
296    pub fn blocking_install(version: &Version) -> std::result::Result<Self, svm::SvmError> {
297        use foundry_compilers_core::utils::RuntimeOrHandle;
298
299        #[cfg(test)]
300        crate::take_solc_installer_lock!(_lock);
301
302        let version = if version.pre.is_empty() {
303            Version::new(version.major, version.minor, version.patch)
304        } else {
305            // Preserve version if it is a prerelease.
306            version.clone()
307        };
308
309        trace!("blocking installing solc version \"{}\"", version);
310        crate::report::solc_installation_start(&version);
311        // The async version `svm::install` is used instead of `svm::blocking_install`
312        // because the underlying `reqwest::blocking::Client` does not behave well
313        // inside of a Tokio runtime. See: https://github.com/seanmonstar/reqwest/issues/1017
314        match RuntimeOrHandle::new().block_on(svm::install(&version)) {
315            Ok(path) => {
316                crate::report::solc_installation_success(&version);
317                Ok(Self::new_with_version(path, version.clone()))
318            }
319            Err(err) => {
320                crate::report::solc_installation_error(&version, &err.to_string());
321                Err(err)
322            }
323        }
324    }
325
326    /// Verify that the checksum for this version of solc is correct. We check against the SHA256
327    /// checksum from the build information published by [binaries.soliditylang.org](https://binaries.soliditylang.org/)
328    #[cfg(feature = "svm-solc")]
329    #[instrument(name = "Solc::verify_checksum", skip_all)]
330    pub fn verify_checksum(&self) -> Result<()> {
331        let version = self.version_short();
332        let mut version_path = svm::version_path(version.to_string().as_str());
333        version_path.push(format!("solc-{}", version.to_string().as_str()));
334        trace!(target:"solc", "reading solc binary for checksum {:?}", version_path);
335        let content =
336            std::fs::read(&version_path).map_err(|err| SolcError::io(err, version_path.clone()))?;
337
338        if !RELEASES.2 {
339            // we skip checksum verification because the underlying request to fetch release info
340            // failed so we have nothing to compare against
341            return Ok(());
342        }
343
344        #[cfg(windows)]
345        {
346            // Prior to 0.7.2, binaries are released as exe files which are hard to verify: <https://github.com/foundry-rs/foundry/issues/5601>
347            // <https://binaries.soliditylang.org/windows-amd64/list.json>
348            const V0_7_2: Version = Version::new(0, 7, 2);
349            if version < V0_7_2 {
350                return Ok(());
351            }
352        }
353
354        use sha2::Digest;
355        let mut hasher = sha2::Sha256::new();
356        hasher.update(content);
357        let checksum_calc = &hasher.finalize()[..];
358
359        let checksum_found = &RELEASES
360            .0
361            .get_checksum(&version)
362            .ok_or_else(|| SolcError::ChecksumNotFound { version: version.clone() })?;
363
364        if checksum_calc == checksum_found {
365            Ok(())
366        } else {
367            use alloy_primitives::hex;
368            let expected = hex::encode(checksum_found);
369            let detected = hex::encode(checksum_calc);
370            warn!(target: "solc", "checksum mismatch for {:?}, expected {}, but found {} for file {:?}", version, expected, detected, version_path);
371            Err(SolcError::ChecksumMismatch { version, expected, detected, file: version_path })
372        }
373    }
374
375    /// Convenience function for compiling all sources under the given path
376    pub fn compile_source(&self, path: &Path) -> Result<CompilerOutput> {
377        let mut res: CompilerOutput = Default::default();
378        for input in
379            SolcInput::resolve_and_build(Source::read_sol_yul_from(path)?, Default::default())
380        {
381            let input = input.sanitized(&self.version);
382            let output = self.compile(&input)?;
383            res.merge(output)
384        }
385
386        Ok(res)
387    }
388
389    /// Same as [`Self::compile()`], but only returns those files which are included in the
390    /// `CompilerInput`.
391    ///
392    /// In other words, this removes those files from the `CompilerOutput` that are __not__ included
393    /// in the provided `CompilerInput`.
394    ///
395    /// # Examples
396    pub fn compile_exact(&self, input: &SolcInput) -> Result<CompilerOutput> {
397        let mut out = self.compile(input)?;
398        out.retain_files(input.sources.keys().map(|p| p.as_path()));
399        Ok(out)
400    }
401
402    /// Compiles with `--standard-json` and deserializes the output as [`CompilerOutput`].
403    ///
404    /// # Examples
405    ///
406    /// ```no_run
407    /// use foundry_compilers::{
408    ///     artifacts::{SolcInput, Source},
409    ///     compilers::{Compiler, CompilerInput},
410    ///     solc::Solc,
411    /// };
412    ///
413    /// let solc = Solc::new("solc")?;
414    /// let input = SolcInput::resolve_and_build(
415    ///     Source::read_sol_yul_from("./contracts".as_ref()).unwrap(),
416    ///     Default::default(),
417    /// );
418    /// let output = solc.compile(&input)?;
419    /// # Ok::<_, Box<dyn std::error::Error>>(())
420    /// ```
421    pub fn compile<T: Serialize>(&self, input: &T) -> Result<CompilerOutput> {
422        self.compile_as(input)
423    }
424
425    /// Compiles with `--standard-json` and deserializes the output as the given `D`.
426    #[instrument(name = "Solc::compile", skip_all)]
427    pub fn compile_as<T: Serialize, D: DeserializeOwned>(&self, input: &T) -> Result<D> {
428        let output = self.compile_output(input)?;
429
430        // Only run UTF-8 validation once.
431        let output = std::str::from_utf8(&output).map_err(|_| SolcError::InvalidUtf8)?;
432
433        Ok(serde_json::from_str(output)?)
434    }
435
436    /// Compiles with `--standard-json` and returns the raw `stdout` output.
437    #[instrument(name = "Solc::compile_raw", skip_all)]
438    pub fn compile_output<T: Serialize>(&self, input: &T) -> Result<Vec<u8>> {
439        let mut cmd = self.configure_cmd();
440
441        trace!(input=%serde_json::to_string(input).unwrap_or_else(|e| e.to_string()));
442        debug!(?cmd, "compiling");
443
444        let mut child = cmd.spawn().map_err(self.map_io_err())?;
445        debug!("spawned");
446
447        {
448            let mut stdin = io::BufWriter::new(child.stdin.take().unwrap());
449            serde_json::to_writer(&mut stdin, input)?;
450            stdin.flush().map_err(self.map_io_err())?;
451        }
452        debug!("wrote JSON input to stdin");
453
454        let output = child.wait_with_output().map_err(self.map_io_err())?;
455        debug!(%output.status, output.stderr = ?String::from_utf8_lossy(&output.stderr), "finished");
456
457        compile_output(output)
458    }
459
460    /// Returns the SemVer [`Version`], stripping the pre-release and build metadata.
461    pub fn version_short(&self) -> Version {
462        Version::new(self.version.major, self.version.minor, self.version.patch)
463    }
464
465    /// Invokes `solc --version` and parses the output as a SemVer [`Version`].
466    pub fn version(solc: impl Into<PathBuf>) -> Result<Version> {
467        Self::version_with_args(solc, &[])
468    }
469
470    /// Invokes `solc --version` and parses the output as a SemVer [`Version`].
471    pub fn version_with_args(solc: impl Into<PathBuf>, args: &[String]) -> Result<Version> {
472        crate::cache_version(solc.into(), args, |solc| Self::version_impl(solc, args))
473    }
474
475    fn version_impl(solc: &Path, args: &[String]) -> Result<Version> {
476        let mut cmd = Command::new(solc);
477        cmd.args(args)
478            .arg("--version")
479            .stdin(Stdio::piped())
480            .stderr(Stdio::piped())
481            .stdout(Stdio::piped());
482        debug!(?cmd, "getting Solc version");
483        let output = cmd.output().map_err(|e| SolcError::io(e, solc))?;
484        trace!(?output);
485        let version = version_from_output(output)?;
486        debug!(%version);
487        Ok(version)
488    }
489
490    fn map_io_err(&self) -> impl FnOnce(std::io::Error) -> SolcError + '_ {
491        move |err| SolcError::io(err, &self.solc)
492    }
493
494    /// Configures [Command] object depending on settings and solc version used.
495    /// Some features are only supported by newer versions of solc, so we have to disable them for
496    /// older ones.
497    pub fn configure_cmd(&self) -> Command {
498        let mut cmd = Command::new(&self.solc);
499        cmd.stdin(Stdio::piped()).stderr(Stdio::piped()).stdout(Stdio::piped());
500        cmd.args(&self.extra_args);
501
502        if !self.allow_paths.is_empty() {
503            cmd.arg("--allow-paths");
504            cmd.arg(self.allow_paths.iter().map(|p| p.display()).join(","));
505        }
506        if let Some(base_path) = &self.base_path {
507            if SUPPORTS_BASE_PATH.matches(&self.version) {
508                if SUPPORTS_INCLUDE_PATH.matches(&self.version) {
509                    // `--base-path` and `--include-path` conflict if set to the same path, so
510                    // as a precaution, we ensure here that the `--base-path` is not also used
511                    // for `--include-path`
512                    for path in
513                        self.include_paths.iter().filter(|p| p.as_path() != base_path.as_path())
514                    {
515                        cmd.arg("--include-path").arg(path);
516                    }
517                }
518
519                cmd.arg("--base-path").arg(base_path);
520            }
521
522            cmd.current_dir(base_path);
523        }
524
525        cmd.arg("--standard-json");
526
527        cmd
528    }
529
530    /// Either finds an installed Solc version or installs it if it's not found.
531    #[cfg(feature = "svm-solc")]
532    pub fn find_or_install(version: &Version) -> Result<Self> {
533        let solc = if let Some(solc) = Self::find_svm_installed_version(version)? {
534            solc
535        } else {
536            Self::blocking_install(version)?
537        };
538
539        Ok(solc)
540    }
541}
542
543#[cfg(feature = "async")]
544impl Solc {
545    /// Convenience function for compiling all sources under the given path
546    pub async fn async_compile_source(&self, path: &Path) -> Result<CompilerOutput> {
547        self.async_compile(&SolcInput::resolve_and_build(
548            Source::async_read_all_from(path, SOLC_EXTENSIONS).await?,
549            Default::default(),
550        ))
551        .await
552    }
553
554    /// Run `solc --stand-json` and return the `solc`'s output as
555    /// `CompilerOutput`
556    pub async fn async_compile<T: Serialize>(&self, input: &T) -> Result<CompilerOutput> {
557        self.async_compile_as(input).await
558    }
559
560    /// Run `solc --stand-json` and return the `solc`'s output as the given json
561    /// output
562    pub async fn async_compile_as<T: Serialize, D: DeserializeOwned>(
563        &self,
564        input: &T,
565    ) -> Result<D> {
566        let output = self.async_compile_output(input).await?;
567        Ok(serde_json::from_slice(&output)?)
568    }
569
570    pub async fn async_compile_output<T: Serialize>(&self, input: &T) -> Result<Vec<u8>> {
571        use tokio::{io::AsyncWriteExt, process::Command};
572
573        let mut cmd: Command = self.configure_cmd().into();
574        let mut child = cmd.spawn().map_err(self.map_io_err())?;
575        let stdin = child.stdin.as_mut().unwrap();
576
577        let content = serde_json::to_vec(input)?;
578
579        stdin.write_all(&content).await.map_err(self.map_io_err())?;
580        stdin.flush().await.map_err(self.map_io_err())?;
581
582        compile_output(child.wait_with_output().await.map_err(self.map_io_err())?)
583    }
584
585    pub async fn async_version(solc: &Path) -> Result<Version> {
586        let mut cmd = tokio::process::Command::new(solc);
587        cmd.arg("--version").stdin(Stdio::piped()).stderr(Stdio::piped()).stdout(Stdio::piped());
588        debug!(?cmd, "getting version");
589        let output = cmd.output().await.map_err(|e| SolcError::io(e, solc))?;
590        let version = version_from_output(output)?;
591        debug!(%version);
592        Ok(version)
593    }
594
595    /// Compiles all `CompilerInput`s with their associated `Solc`.
596    ///
597    /// This will buffer up to `n` `solc` processes and then return the `CompilerOutput`s in the
598    /// order in which they complete. No more than `n` futures will be buffered at any point in
599    /// time, and less than `n` may also be buffered depending on the state of each future.
600    pub async fn compile_many<I>(jobs: I, n: usize) -> crate::many::CompiledMany
601    where
602        I: IntoIterator<Item = (Self, SolcInput)>,
603    {
604        use futures_util::stream::StreamExt;
605
606        let outputs = futures_util::stream::iter(
607            jobs.into_iter()
608                .map(|(solc, input)| async { (solc.async_compile(&input).await, solc, input) }),
609        )
610        .buffer_unordered(n)
611        .collect::<Vec<_>>()
612        .await;
613
614        crate::many::CompiledMany::new(outputs)
615    }
616}
617
618fn compile_output(output: Output) -> Result<Vec<u8>> {
619    if output.status.success() {
620        Ok(output.stdout)
621    } else {
622        Err(SolcError::solc_output(&output))
623    }
624}
625
626fn version_from_output(output: Output) -> Result<Version> {
627    if output.status.success() {
628        let stdout = String::from_utf8_lossy(&output.stdout);
629        let version = stdout
630            .lines()
631            .rfind(|l| !l.trim().is_empty())
632            .ok_or_else(|| SolcError::msg("Version not found in Solc output"))?;
633        // NOTE: semver doesn't like `+` in g++ in build metadata which is invalid semver
634        Ok(Version::from_str(&version.trim_start_matches("Version: ").replace(".g++", ".gcc"))?)
635    } else {
636        Err(SolcError::solc_output(&output))
637    }
638}
639
640impl AsRef<Path> for Solc {
641    fn as_ref(&self) -> &Path {
642        &self.solc
643    }
644}
645
646#[cfg(test)]
647#[cfg(feature = "svm-solc")]
648mod tests {
649    use super::*;
650    use crate::{resolver::parse::SolData, Artifact};
651
652    #[test]
653    fn test_version_parse() {
654        let req = SolData::parse_version_req(">=0.6.2 <0.8.21").unwrap();
655        let semver_req: VersionReq = ">=0.6.2,<0.8.21".parse().unwrap();
656        assert_eq!(req, semver_req);
657    }
658
659    fn solc() -> Solc {
660        if let Some(solc) = Solc::find_svm_installed_version(&Version::new(0, 8, 18)).unwrap() {
661            solc
662        } else {
663            Solc::blocking_install(&Version::new(0, 8, 18)).unwrap()
664        }
665    }
666
667    #[test]
668    fn solc_version_works() {
669        Solc::version(solc().solc).unwrap();
670    }
671
672    #[test]
673    fn can_parse_version_metadata() {
674        let _version = Version::from_str("0.6.6+commit.6c089d02.Linux.gcc").unwrap();
675    }
676
677    #[cfg(feature = "async")]
678    #[tokio::test(flavor = "multi_thread")]
679    async fn async_solc_version_works() {
680        Solc::async_version(&solc().solc).await.unwrap();
681    }
682
683    #[test]
684    fn solc_compile_works() {
685        let input = include_str!("../../../../../test-data/in/compiler-in-1.json");
686        let input: SolcInput = serde_json::from_str(input).unwrap();
687        let out = solc().compile(&input).unwrap();
688        let other = solc().compile(&serde_json::json!(input)).unwrap();
689        assert_eq!(out, other);
690    }
691
692    #[test]
693    fn solc_metadata_works() {
694        let input = include_str!("../../../../../test-data/in/compiler-in-1.json");
695        let mut input: SolcInput = serde_json::from_str(input).unwrap();
696        input.settings.push_output_selection("metadata");
697        let out = solc().compile(&input).unwrap();
698        for (_, c) in out.split().1.contracts_iter() {
699            assert!(c.metadata.is_some());
700        }
701    }
702
703    #[test]
704    fn can_compile_with_remapped_links() {
705        let input: SolcInput = serde_json::from_str(include_str!(
706            "../../../../../test-data/library-remapping-in.json"
707        ))
708        .unwrap();
709        let out = solc().compile(&input).unwrap();
710        let (_, mut contracts) = out.split();
711        let contract = contracts.remove("LinkTest").unwrap();
712        let bytecode = &contract.get_bytecode().unwrap().object;
713        assert!(!bytecode.is_unlinked());
714    }
715
716    #[test]
717    fn can_compile_with_remapped_links_temp_dir() {
718        let input: SolcInput = serde_json::from_str(include_str!(
719            "../../../../../test-data/library-remapping-in-2.json"
720        ))
721        .unwrap();
722        let out = solc().compile(&input).unwrap();
723        let (_, mut contracts) = out.split();
724        let contract = contracts.remove("LinkTest").unwrap();
725        let bytecode = &contract.get_bytecode().unwrap().object;
726        assert!(!bytecode.is_unlinked());
727    }
728
729    #[cfg(feature = "async")]
730    #[tokio::test(flavor = "multi_thread")]
731    async fn async_solc_compile_works() {
732        let input = include_str!("../../../../../test-data/in/compiler-in-1.json");
733        let input: SolcInput = serde_json::from_str(input).unwrap();
734        let out = solc().async_compile(&input).await.unwrap();
735        let other = solc().async_compile(&serde_json::json!(input)).await.unwrap();
736        assert_eq!(out, other);
737    }
738
739    #[cfg(feature = "async")]
740    #[tokio::test(flavor = "multi_thread")]
741    async fn async_solc_compile_works2() {
742        let input = include_str!("../../../../../test-data/in/compiler-in-2.json");
743        let input: SolcInput = serde_json::from_str(input).unwrap();
744        let out = solc().async_compile(&input).await.unwrap();
745        let other = solc().async_compile(&serde_json::json!(input)).await.unwrap();
746        assert_eq!(out, other);
747        let sync_out = solc().compile(&input).unwrap();
748        assert_eq!(out, sync_out);
749    }
750
751    #[test]
752    fn test_version_req() {
753        let versions = ["=0.1.2", "^0.5.6", ">=0.7.1", ">0.8.0"];
754
755        versions.iter().for_each(|version| {
756            let version_req = SolData::parse_version_req(version).unwrap();
757            assert_eq!(version_req, VersionReq::from_str(version).unwrap());
758        });
759
760        // Solidity defines version ranges with a space, whereas the semver package
761        // requires them to be separated with a comma
762        let version_range = ">=0.8.0 <0.9.0";
763        let version_req = SolData::parse_version_req(version_range).unwrap();
764        assert_eq!(version_req, VersionReq::from_str(">=0.8.0,<0.9.0").unwrap());
765    }
766
767    #[test]
768    #[cfg(feature = "full")]
769    fn test_find_installed_version_path() {
770        // This test does not take the lock by default, so we need to manually add it here.
771        take_solc_installer_lock!(_lock);
772        let version = Version::new(0, 8, 6);
773        if svm::installed_versions()
774            .map(|versions| !versions.contains(&version))
775            .unwrap_or_default()
776        {
777            Solc::blocking_install(&version).unwrap();
778        }
779        drop(_lock);
780        let res = Solc::find_svm_installed_version(&version).unwrap().unwrap();
781        let expected = svm::data_dir().join(version.to_string()).join(format!("solc-{version}"));
782        assert_eq!(res.solc, expected);
783    }
784
785    #[test]
786    #[cfg(feature = "svm-solc")]
787    fn can_install_solc_in_tokio_rt() {
788        let version = Version::from_str("0.8.6").unwrap();
789        let rt = tokio::runtime::Runtime::new().unwrap();
790        let result = rt.block_on(async { Solc::blocking_install(&version) });
791        assert!(result.is_ok());
792    }
793
794    #[test]
795    fn does_not_find_not_installed_version() {
796        let ver = Version::new(1, 1, 1);
797        let res = Solc::find_svm_installed_version(&ver).unwrap();
798        assert!(res.is_none());
799    }
800}