From f3699d08bfa1a897f72b8e1474a81e3097662e4e Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Sun, 15 May 2022 02:29:45 +0300 Subject: [PATCH] fix(solc): add `RuntimeOrHandle` & fix solc blocking installation (#1260) * rt wrapper for solc install * fix * uncomment feat flag * feature deps & comment * async it tests * use svm::block_install for wasm * hide rt or handle for wasm * hide import for wasm --- Cargo.lock | 1 + ethers-solc/Cargo.toml | 5 ++-- ethers-solc/src/compile/mod.rs | 24 +++++++++++++++- ethers-solc/src/utils.rs | 34 +++++++++++++++++++++++ ethers-solc/tests/project.rs | 51 ++++++++++++++++++++++++++++++++++ 5 files changed, 112 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8fdbc201..458ac4c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1401,6 +1401,7 @@ dependencies = [ name = "ethers-solc" version = "0.3.0" dependencies = [ + "cfg-if 1.0.0", "colored", "criterion", "dunce", diff --git a/ethers-solc/Cargo.toml b/ethers-solc/Cargo.toml index a184ea32..c0acee2a 100644 --- a/ethers-solc/Cargo.toml +++ b/ethers-solc/Cargo.toml @@ -19,7 +19,7 @@ serde_json = "1.0.68" serde = { version = "1.0.130", features = ["derive"] } semver = { version = "1.0.9", features = ["serde"] } walkdir = "2.3.2" -tokio = { version = "1.15.0", default-features = false, features = ["process", "io-util", "fs", "time"], optional = true } +tokio = { version = "1.15.0", default-features = false, features = ["rt"] } futures-util = { version = "^0.3", optional = true } once_cell = "1.10.0" regex = "1.5.5" @@ -39,6 +39,7 @@ solang-parser = { default-features = false, version = "=0.1.13" } rayon = "1.5.2" rand = { version = "0.8.5", optional = true } path-slash = "0.1.4" +cfg-if = "1.0.0" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] home = "0.5.3" @@ -80,7 +81,7 @@ required-features = ["full", "project-util"] [features] default = ["rustls"] -async = ["tokio", "futures-util"] +async = ["tokio/process", "tokio/io-util", "tokio/fs", "tokio/time", "futures-util"] full = ["async", "svm-solc"] svm-solc = ["svm/blocking", "svm-builds", "sha2"] # Utilities for creating and testing project workspaces diff --git a/ethers-solc/src/compile/mod.rs b/ethers-solc/src/compile/mod.rs index 384f9c75..92e3b9a9 100644 --- a/ethers-solc/src/compile/mod.rs +++ b/ethers-solc/src/compile/mod.rs @@ -371,9 +371,21 @@ impl Solc { /// Blocking version of `Self::install` #[cfg(all(feature = "svm-solc"))] pub fn blocking_install(version: &Version) -> std::result::Result { + use crate::utils::RuntimeOrHandle; + tracing::trace!("blocking installing solc version \"{}\"", version); crate::report::solc_installation_start(version); - match svm::blocking_install(version) { + // the async version `svm::install` is used instead of `svm::blocking_intsall` + // because the underlying `reqwest::blocking::Client` does not behave well + // in tokio rt. see https://github.com/seanmonstar/reqwest/issues/1017 + cfg_if::cfg_if! { + if #[cfg(target_arch = "wasm32")] { + let installation = svm::blocking_install(version); + } else { + let installation = RuntimeOrHandle::new().block_on(svm::install(version)); + } + }; + match installation { Ok(path) => { crate::report::solc_installation_success(version); Ok(Solc::new(path)) @@ -723,6 +735,7 @@ mod tests { let other = solc().async_compile(&serde_json::json!(input)).await.unwrap(); assert_eq!(out, other); } + #[cfg(feature = "async")] #[tokio::test] async fn async_solc_compile_works2() { @@ -799,6 +812,15 @@ mod tests { assert_eq!(res.solc, expected); } + #[test] + #[cfg(feature = "svm-solc")] + fn can_install_solc_in_tokio_rt() { + let version = Version::from_str("0.8.6").unwrap(); + let rt = tokio::runtime::Runtime::new().unwrap(); + let result = rt.block_on(async { Solc::blocking_install(&version) }); + assert!(result.is_ok()); + } + #[test] fn does_not_find_not_installed_version() { let ver = "1.1.1"; diff --git a/ethers-solc/src/utils.rs b/ethers-solc/src/utils.rs index 825107ea..a5bd3e11 100644 --- a/ethers-solc/src/utils.rs +++ b/ethers-solc/src/utils.rs @@ -330,6 +330,40 @@ pub(crate) fn find_fave_or_alt_path(root: impl AsRef, fave: &str, alt: &st p } +#[cfg(not(target_arch = "wasm32"))] +use tokio::runtime::{Handle, Runtime}; + +#[cfg(not(target_arch = "wasm32"))] +#[derive(Debug)] +pub enum RuntimeOrHandle { + Runtime(Runtime), + Handle(Handle), +} + +#[cfg(not(target_arch = "wasm32"))] +impl Default for RuntimeOrHandle { + fn default() -> Self { + Self::new() + } +} + +#[cfg(not(target_arch = "wasm32"))] +impl RuntimeOrHandle { + pub fn new() -> RuntimeOrHandle { + match Handle::try_current() { + Ok(handle) => RuntimeOrHandle::Handle(handle), + Err(_) => RuntimeOrHandle::Runtime(Runtime::new().expect("Failed to start runtime")), + } + } + + pub fn block_on(&self, f: F) -> F::Output { + match &self { + RuntimeOrHandle::Runtime(runtime) => runtime.block_on(f), + RuntimeOrHandle::Handle(handle) => tokio::task::block_in_place(|| handle.block_on(f)), + } + } +} + /// Creates a new named tempdir #[cfg(any(test, feature = "project-util"))] pub(crate) fn tempdir(name: &str) -> Result { diff --git a/ethers-solc/tests/project.rs b/ethers-solc/tests/project.rs index 5bc568e7..21698fd7 100644 --- a/ethers-solc/tests/project.rs +++ b/ethers-solc/tests/project.rs @@ -17,6 +17,7 @@ use ethers_solc::{ ProjectPathsConfig, Solc, TestFileFilter, }; use pretty_assertions::assert_eq; +use semver::Version; #[allow(unused)] fn init_tracing() { @@ -1329,3 +1330,53 @@ fn can_compile_model_checker_sample() { assert!(!compiled.has_compiler_errors()); assert!(compiled.has_compiler_warnings()); } + +fn remove_solc_if_exists(version: &Version) { + match Solc::find_svm_installed_version(version.to_string()).unwrap() { + Some(_) => svm::remove_version(&version).expect("failed to remove version"), + None => {} + }; +} + +#[tokio::test(flavor = "multi_thread")] +async fn can_install_solc_and_compile_version() { + let project = TempProject::dapptools().unwrap(); + let version = Version::new(0, 8, 10); + + project + .add_source( + "Contract", + format!( + r#" +pragma solidity {}; +contract Contract {{ }} +"#, + version.to_string() + ), + ) + .unwrap(); + + remove_solc_if_exists(&version); + + let compiled = project.compile().unwrap(); + assert!(!compiled.has_compiler_errors()); +} + +#[tokio::test(flavor = "multi_thread")] +async fn can_install_solc_and_compile_std_json_input_async() { + let tmp = TempProject::dapptools_init().unwrap(); + tmp.assert_no_errors(); + let source = tmp.list_source_files().into_iter().find(|p| p.ends_with("Dapp.t.sol")).unwrap(); + let input = tmp.project().standard_json_input(source).unwrap(); + let solc = &tmp.project().solc; + + assert!(input.settings.remappings.contains(&"ds-test/=lib/ds-test/src/".parse().unwrap())); + let input: CompilerInput = input.into(); + assert!(input.sources.contains_key(Path::new("lib/ds-test/src/test.sol"))); + + remove_solc_if_exists(&solc.version().expect("failed to get version")); + + let out = solc.async_compile(&input).await.unwrap(); + assert!(!out.has_error()); + assert!(out.sources.contains_key("lib/ds-test/src/test.sol")); +}