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