Repository: gakonst/ethers-rs
Pull request #547 (2021-10-31 11:13:44)

feat: add solc svm find support
<!--
Thank you for your Pull Request. Please provide a description above and review
the requirements below.

Bug fixes and new features should include tests.

Contributors guide: https://github.com/gakonst/ethers-rs/blob/master/CONTRIBUTING.md

The contributors guide includes instructions for running rustfmt and building the
documentation.
-->

## Motivation

<!--
Explain the context and why you're making that change. What is the problem
you're trying to solve? In some cases there is not a problem and this can be
thought of as being the motivation for your change.
-->

## Solution

<!--
Summarize the solution and provide any necessary context needed to understand
the code change.
-->

## PR Checklist

- [ ] Added Tests
- [ ] Added Documentation
- [ ] Updated the changelog



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 93974d6d..ec383fb7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,7 @@
 
 ### Unreleased
 
+- use enumerated aliases for overloaded functions [#545](https://github.com/gakonst/ethers-rs/pull/545)
 - move `AbiEncode` `AbiDecode` trait to ethers-core and implement for core types [#531](https://github.com/gakonst/ethers-rs/pull/531)
 - add `EthCall` trait and derive macro which generates matching structs for contract calls [#517](https://github.com/gakonst/ethers-rs/pull/517)
 - `abigen!` now generates `Display` for all events using the new `EthDisplay` macro [#513](https://github.com/gakonst/ethers-rs/pull/513)
diff --git a/Cargo.lock b/Cargo.lock
index adb9af0a..9d75602d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -435,6 +435,17 @@ dependencies = [
  "generic-array 0.14.4",
 ]
 
+[[package]]
+name = "clap"
+version = "2.33.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
+dependencies = [
+ "bitflags",
+ "textwrap",
+ "unicode-width",
+]
+
 [[package]]
 name = "cmac"
 version = "0.6.0"
@@ -536,6 +547,34 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "console"
+version = "0.14.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3993e6445baa160675931ec041a5e03ca84b9c6e32a056150d3aa2bdda0a1f45"
+dependencies = [
+ "encode_unicode",
+ "lazy_static",
+ "libc",
+ "regex",
+ "terminal_size",
+ "unicode-width",
+ "winapi",
+]
+
+[[package]]
+name = "console"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a28b32d32ca44b70c3e4acd7db1babf555fa026e385fb95f18028f88848b3c31"
+dependencies = [
+ "encode_unicode",
+ "libc",
+ "once_cell",
+ "terminal_size",
+ "winapi",
+]
+
 [[package]]
 name = "const-oid"
 version = "0.6.1"
@@ -676,6 +715,18 @@ dependencies = [
  "const-oid",
 ]
 
+[[package]]
+name = "dialoguer"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c9dd058f8b65922819fabb4a41e7d1964e56344042c26efbccd465202c23fa0c"
+dependencies = [
+ "console 0.14.1",
+ "lazy_static",
+ "tempfile",
+ "zeroize",
+]
+
 [[package]]
 name = "digest"
 version = "0.8.1"
@@ -772,6 +823,12 @@ dependencies = [
  "zeroize",
 ]
 
+[[package]]
+name = "encode_unicode"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
+
 [[package]]
 name = "encoding_rs"
 version = "0.8.28"
@@ -857,6 +914,7 @@ dependencies = [
  "ethers-middleware",
  "ethers-providers",
  "ethers-signers",
+ "ethers-solc",
  "hex",
  "rand 0.8.4",
  "serde",
@@ -875,6 +933,7 @@ dependencies = [
  "ethers-middleware",
  "ethers-providers",
  "ethers-signers",
+ "ethers-solc",
  "futures-util",
  "hex",
  "once_cell",
@@ -932,7 +991,6 @@ dependencies = [
  "ethabi",
  "futures-util",
  "generic-array 0.14.4",
- "glob",
  "hex",
  "hex-literal",
  "k256",
@@ -942,7 +1000,6 @@ dependencies = [
  "rand 0.8.4",
  "rlp",
  "rlp-derive",
- "semver 1.0.4",
  "serde",
  "serde_json",
  "syn",
@@ -988,6 +1045,7 @@ dependencies = [
  "ethers-core",
  "ethers-providers",
  "ethers-signers",
+ "ethers-solc",
  "futures-util",
  "hex",
  "instant",
@@ -1074,14 +1132,17 @@ name = "ethers-solc"
 version = "0.1.0"
 dependencies = [
  "colored",
+ "ethers-core",
  "futures-util",
  "hex",
+ "home",
  "md-5",
  "once_cell",
  "regex",
  "semver 1.0.4",
  "serde",
  "serde_json",
+ "svm-rs",
  "tempdir",
  "thiserror",
  "tokio",
@@ -1308,12 +1369,6 @@ version = "0.25.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f0a01e0497841a3b2db4f8afa483cce65f7e96a3498bd6c541734792aeac8fe7"
 
-[[package]]
-name = "glob"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
-
 [[package]]
 name = "group"
 version = "0.10.0"
@@ -1356,6 +1411,15 @@ version = "0.11.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
 
+[[package]]
+name = "heck"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
+dependencies = [
+ "unicode-segmentation",
+]
+
 [[package]]
 name = "hermit-abi"
 version = "0.1.19"
@@ -1398,6 +1462,15 @@ dependencies = [
  "digest 0.9.0",
 ]
 
+[[package]]
+name = "home"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2456aef2e6b6a9784192ae780c0f15bc57df0e918585282325e8c8ac27737654"
+dependencies = [
+ "winapi",
+]
+
 [[package]]
 name = "http"
 version = "0.2.5"
@@ -1543,6 +1616,18 @@ dependencies = [
  "hashbrown",
 ]
 
+[[package]]
+name = "indicatif"
+version = "0.16.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d207dc617c7a380ab07ff572a6e52fa202a2a8f355860ac9c38e23f8196be1b"
+dependencies = [
+ "console 0.15.0",
+ "lazy_static",
+ "number_prefix",
+ "regex",
+]
+
 [[package]]
 name = "instant"
 version = "0.1.12"
@@ -1561,6 +1646,15 @@ version = "2.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9"
 
+[[package]]
+name = "itertools"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf"
+dependencies = [
+ "either",
+]
+
 [[package]]
 name = "itoa"
 version = "0.4.8"
@@ -1767,6 +1861,12 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "number_prefix"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
+
 [[package]]
 name = "object"
 version = "0.26.2"
@@ -2801,12 +2901,61 @@ version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
 
+[[package]]
+name = "structopt"
+version = "0.3.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "40b9788f4202aa75c240ecc9c15c65185e6a39ccdeb0fd5d008b98825464c87c"
+dependencies = [
+ "clap",
+ "lazy_static",
+ "structopt-derive",
+]
+
+[[package]]
+name = "structopt-derive"
+version = "0.4.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0"
+dependencies = [
+ "heck",
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "subtle"
 version = "2.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
 
+[[package]]
+name = "svm-rs"
+version = "0.1.2"
+source = "git+https://github.com/roynalnaruto/svm-rs#5d353ec236a596dbfd2b37878ee5aee53ab6eadf"
+dependencies = [
+ "anyhow",
+ "cfg-if 1.0.0",
+ "console 0.14.1",
+ "dialoguer",
+ "home",
+ "indicatif",
+ "itertools",
+ "once_cell",
+ "rand 0.8.4",
+ "reqwest",
+ "semver 1.0.4",
+ "serde",
+ "serde_json",
+ "structopt",
+ "tempfile",
+ "thiserror",
+ "tokio",
+ "url",
+]
+
 [[package]]
 name = "syn"
 version = "1.0.81"
@@ -2860,6 +3009,25 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "terminal_size"
+version = "0.1.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "textwrap"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
+dependencies = [
+ "unicode-width",
+]
+
 [[package]]
 name = "thiserror"
 version = "1.0.30"
@@ -3158,6 +3326,18 @@ dependencies = [
  "tinyvec",
 ]
 
+[[package]]
+name = "unicode-segmentation"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
+
+[[package]]
+name = "unicode-width"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
+
 [[package]]
 name = "unicode-xid"
 version = "0.2.2"
diff --git a/Cargo.toml b/Cargo.toml
index dab4edba..e2ac4802 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -61,7 +61,6 @@ legacy = [
 
 # individual features per sub-crate
 ## core
-setup = ["ethers-core/setup"]
 eip712 = ["ethers-contract/eip712", "ethers-core/eip712"]
 ## providers
 ws = ["ethers-providers/ws"]
@@ -77,10 +76,11 @@ abigen = ["ethers-contract/abigen"]
 
 [dependencies]
 ethers-contract = { version = "^0.5.0", default-features = false, path = "./ethers-contract" }
-ethers-core = { version = "^0.5.0", default-features = false, path = "./ethers-core", features = ["setup"] }
+ethers-core = { version = "^0.5.0", default-features = false, path = "./ethers-core" }
 ethers-providers = { version = "^0.5.0", default-features = false, path = "./ethers-providers" }
 ethers-signers = { version = "^0.5.0", default-features = false, path = "./ethers-signers" }
 ethers-middleware = { version = "^0.5.0", default-features = false, path = "./ethers-middleware" }
+ethers-solc = { version = "^0.1.0", default-features = false, path = "./ethers-solc" }
 
 [dev-dependencies]
 ethers-contract = { version = "^0.5.0", default-features = false, path = "./ethers-contract", features = ["abigen", "eip712"] }
diff --git a/ethers-contract/Cargo.toml b/ethers-contract/Cargo.toml
index 304b55bc..7c68a899 100644
--- a/ethers-contract/Cargo.toml
+++ b/ethers-contract/Cargo.toml
@@ -32,6 +32,7 @@ ethers-contract-abigen = { version = "^0.5.0", path = "ethers-contract-abigen" }
 ethers-contract-derive = { version = "^0.5.0", path = "ethers-contract-derive" }
 ethers-core = { version = "^0.5.0", path = "../ethers-core", default-features = false, features = ["eip712"]}
 ethers-derive-eip712 = { version = "0.1.0", path = "../ethers-core/ethers-derive-eip712"}
+ethers-solc = { version = "^0.1.0", path = "../ethers-solc", default-features = false }
 
 [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
 tokio = { version = "1.5", default-features = false, features = ["macros"] }
diff --git a/ethers-contract/ethers-contract-abigen/src/contract/methods.rs b/ethers-contract/ethers-contract-abigen/src/contract/methods.rs
index c8e9cb55..cf88ed50 100644
--- a/ethers-contract/ethers-contract-abigen/src/contract/methods.rs
+++ b/ethers-contract/ethers-contract-abigen/src/contract/methods.rs
@@ -282,15 +282,27 @@ impl Context {
             // sort functions by number of inputs asc
             let mut functions = functions.iter().collect::<Vec<_>>();
             functions.sort_by(|f1, f2| f1.inputs.len().cmp(&f2.inputs.len()));
-            let prev = functions[0];
-            for duplicate in functions.into_iter().skip(1) {
+            let first = functions[0];
+            // assuming here that if there are overloaded functions with nameless params like `log;,
+            // log(string); log(string, string)` `log()` should also be aliased with its
+            // index to `log0`
+            let mut add_alias_for_first_with_idx = false;
+            for (idx, duplicate) in functions.into_iter().enumerate().skip(1) {
                 // attempt to find diff in the input arguments
-                let diff = duplicate
-                    .inputs
-                    .iter()
-                    .filter(|i1| prev.inputs.iter().all(|i2| *i1 != i2))
-                    .collect::<Vec<_>>();
-
+                let mut diff = Vec::new();
+                let mut same_params = true;
+                for (idx, i1) in duplicate.inputs.iter().enumerate() {
+                    if first.inputs.iter().all(|i2| i1 != i2) {
+                        diff.push(i1);
+                        same_params = false;
+                    } else {
+                        // check for cases like `log(string); log(string, string)` by keep track of
+                        // same order
+                        if same_params && idx + 1 > first.inputs.len() {
+                            diff.push(i1);
+                        }
+                    }
+                }
                 let alias = match diff.len() {
                     0 => {
                         // this should not happen since functions with same name and input are
@@ -302,30 +314,46 @@ impl Context {
                     }
                     1 => {
                         // single additional input params
-                        format!(
-                            "{}_with_{}",
-                            duplicate.name.to_snake_case(),
-                            diff[0].name.to_snake_case()
-                        )
+                        if diff[0].name.is_empty() {
+                            add_alias_for_first_with_idx = true;
+                            format!("{}1", duplicate.name.to_snake_case())
+                        } else {
+                            format!(
+                                "{}_with_{}",
+                                duplicate.name.to_snake_case(),
+                                diff[0].name.to_snake_case()
+                            )
+                        }
                     }
                     _ => {
-                        // 1 + n additional input params
-                        let and = diff
-                            .iter()
-                            .skip(1)
-                            .map(|i| i.name.to_snake_case())
-                            .collect::<Vec<_>>()
-                            .join("_and_");
-                        format!(
-                            "{}_with_{}_and_{}",
-                            duplicate.name.to_snake_case(),
-                            diff[0].name.to_snake_case(),
-                            and
-                        )
+                        if diff.iter().any(|d| d.name.is_empty()) {
+                            add_alias_for_first_with_idx = true;
+                            format!("{}{}", duplicate.name.to_snake_case(), idx)
+                        } else {
+                            // 1 + n additional input params
+                            let and = diff
+                                .iter()
+                                .skip(1)
+                                .map(|i| i.name.to_snake_case())
+                                .collect::<Vec<_>>()
+                                .join("_and_");
+                            format!(
+                                "{}_with_{}_and_{}",
+                                duplicate.name.to_snake_case(),
+                                diff[0].name.to_snake_case(),
+                                and
+                            )
+                        }
                     }
                 };
                 aliases.insert(duplicate.abi_signature(), util::safe_ident(&alias));
             }
+
+            if add_alias_for_first_with_idx {
+                // insert an alias for the root duplicated call
+                let prev_alias = format!("{}0", first.name.to_snake_case());
+                aliases.insert(first.abi_signature(), util::safe_ident(&prev_alias));
+            }
         }
         Ok(aliases)
     }
diff --git a/ethers-contract/ethers-contract-derive/src/lib.rs b/ethers-contract/ethers-contract-derive/src/lib.rs
index 1393b6fb..d343be2c 100644
--- a/ethers-contract/ethers-contract-derive/src/lib.rs
+++ b/ethers-contract/ethers-contract-derive/src/lib.rs
@@ -62,6 +62,9 @@ pub(crate) mod utils;
 /// );
 /// ```
 ///
+/// Aliases for overloaded functions with no aliases provided in the `method` section are derived
+/// automatically.
+///
 /// `abigen!` supports multiple abigen definitions separated by a semicolon `;`
 /// This is useful if the contracts use ABIEncoderV2 structs. In which case
 /// `abigen!` bundles all type duplicates so that all rust contracts also use
diff --git a/ethers-contract/src/contract.rs b/ethers-contract/src/contract.rs
index f348483c..43f60c5f 100644
--- a/ethers-contract/src/contract.rs
+++ b/ethers-contract/src/contract.rs
@@ -67,7 +67,6 @@ use std::{fmt::Debug, marker::PhantomData, sync::Arc};
 /// ```no_run
 /// use ethers_core::{
 ///     abi::Abi,
-///     utils::Solc,
 ///     types::{Address, H256},
 /// };
 /// use ethers_contract::Contract;
diff --git a/ethers-contract/src/factory.rs b/ethers-contract/src/factory.rs
index a2cfb070..6be23ba8 100644
--- a/ethers-contract/src/factory.rs
+++ b/ethers-contract/src/factory.rs
@@ -90,7 +90,7 @@ impl<M: Middleware> Deployer<M> {
 /// # Example
 ///
 /// ```no_run
-/// use ethers_core::utils::Solc;
+/// use ethers_solc::Solc;
 /// use ethers_contract::ContractFactory;
 /// use ethers_providers::{Provider, Http};
 /// use ethers_signers::Wallet;
@@ -99,9 +99,9 @@ impl<M: Middleware> Deployer<M> {
 /// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
 /// // first we'll compile the contract (you can alternatively compile it yourself
 /// // and pass the ABI/Bytecode
-/// let compiled = Solc::new("./tests/contract.sol").build().unwrap();
+/// let compiled = Solc::default().compile_source("./tests/contract.sol").unwrap();
 /// let contract = compiled
-///     .get("SimpleStorage")
+///     .get("./tests/contract.sol", "SimpleStorage")
 ///     .expect("could not find contract");
 ///
 /// // connect to the network
@@ -109,7 +109,7 @@ impl<M: Middleware> Deployer<M> {
 /// let client = std::sync::Arc::new(client);
 ///
 /// // create a factory which will be used to deploy instances of the contract
-/// let factory = ContractFactory::new(contract.abi.clone(), contract.bytecode.clone(), client);
+/// let factory = ContractFactory::new(contract.abi.unwrap().clone(), contract.bin.unwrap().clone(), client);
 ///
 /// // The deployer created by the `deploy` call exposes a builder which gets consumed
 /// // by the async `send` call
diff --git a/ethers-core/Cargo.toml b/ethers-core/Cargo.toml
index 762190a9..5a094d0c 100644
--- a/ethers-core/Cargo.toml
+++ b/ethers-core/Cargo.toml
@@ -27,10 +27,8 @@ tiny-keccak = { version = "2.0.2", default-features = false }
 serde = { version = "1.0.124", default-features = false, features = ["derive"] }
 serde_json = { version = "1.0.64", default-features = false }
 thiserror = { version = "1.0.30", default-features = false }
-glob = { version = "0.3.0", default-features = false }
 bytes = { version = "1.1.0", features = ["serde"] }
 hex = { version = "0.4.3", default-features = false, features = ["std"] }
-semver = "1.0.4"
 once_cell = "1.8.0"
 
 # eip712 feature enabled dependencies
diff --git a/ethers-core/src/lib.rs b/ethers-core/src/lib.rs
index a105e58b..71dd77cf 100644
--- a/ethers-core/src/lib.rs
+++ b/ethers-core/src/lib.rs
@@ -33,8 +33,7 @@
 //! ## Utilities
 //!
 //! The crate provides utilities for launching local Ethereum testnets by using `ganache-cli`
-//! via the `GanacheBuilder` struct. In addition, you're able to compile contracts on the
-//! filesystem by providing a glob to their path, using the `Solc` struct.
+//! via the `GanacheBuilder` struct.
 //!
 //! # Features
 //!
diff --git a/ethers-core/src/utils/mod.rs b/ethers-core/src/utils/mod.rs
index 18e35adc..0099e2e4 100644
--- a/ethers-core/src/utils/mod.rs
+++ b/ethers-core/src/utils/mod.rs
@@ -10,20 +10,6 @@ mod geth;
 #[cfg(not(target_arch = "wasm32"))]
 pub use geth::{Geth, GethInstance};
 
-/// Solidity compiler bindings
-#[cfg(not(target_arch = "wasm32"))]
-pub mod solc;
-
-#[cfg(not(target_arch = "wasm32"))]
-pub use solc::{CompiledContract, Solc};
-
-#[cfg(not(target_arch = "wasm32"))]
-#[cfg(feature = "setup")]
-mod setup;
-#[cfg(not(target_arch = "wasm32"))]
-#[cfg(feature = "setup")]
-pub use setup::*;
-
 mod hash;
 pub use hash::{hash_message, id, keccak256, serialize};
 
diff --git a/ethers-core/src/utils/setup.rs b/ethers-core/src/utils/setup.rs
deleted file mode 100644
index 6c7384c6..00000000
--- a/ethers-core/src/utils/setup.rs
+++ /dev/null
@@ -1,54 +0,0 @@
-//! Setup utilities to start necessary infrastructure
-
-use crate::utils::{
-    solc::{CompiledContract, SolcError},
-    Ganache, GanacheInstance, Geth, GethInstance, Solc,
-};
-use std::collections::HashMap;
-
-/// Builds the contracts and returns a hashmap for each named contract
-///
-/// Same as [crate::utils::Solc::build] but async
-pub async fn compile(solc: Solc) -> Result<HashMap<String, CompiledContract>, SolcError> {
-    tokio::task::spawn_blocking(|| solc.build()).await.unwrap()
-}
-
-/// Launches a [crate::utils::GanacheInstance]
-///
-/// Same as [crate::utils::Ganache::spawn] but async
-pub async fn launch_ganache(ganache: Ganache) -> GanacheInstance {
-    tokio::task::spawn_blocking(|| ganache.spawn()).await.unwrap()
-}
-
-/// Compiles the contracts and launches a [crate::utils::GanacheInstance]
-///
-/// Same as [crate::utils::setup::compile] and [crate::utils::setup::launch_ganache]
-pub async fn compile_and_launch_ganache(
-    solc: Solc,
-    ganache: Ganache,
-) -> Result<(HashMap<String, CompiledContract>, GanacheInstance), SolcError> {
-    let solc_fut = compile(solc);
-    let ganache_fut = launch_ganache(ganache);
-    let (solc, ganache) = futures_util::join!(solc_fut, ganache_fut);
-    solc.map(|solc| (solc, ganache))
-}
-
-/// Launches a [crate::utils::GethInstance]
-///
-/// Same as [crate::utils::Geth::spawn] but async
-pub async fn launch_geth(geth: Geth) -> GethInstance {
-    tokio::task::spawn_blocking(|| geth.spawn()).await.unwrap()
-}
-
-/// Compiles the contracts and launches a [crate::utils::GethInstance]
-///
-/// Same as [crate::utils::setup::compile] and [crate::utils::setup::launch_geth]
-pub async fn compile_and_launch_geth(
-    solc: Solc,
-    geth: Geth,
-) -> Result<(HashMap<String, CompiledContract>, GethInstance), SolcError> {
-    let solc_fut = compile(solc);
-    let geth_fut = launch_geth(geth);
-    let (solc, geth) = futures_util::join!(solc_fut, geth_fut);
-    solc.map(|solc| (solc, geth))
-}
diff --git a/ethers-core/src/utils/solc.rs b/ethers-core/src/utils/solc.rs
deleted file mode 100644
index d89e6cd5..00000000
--- a/ethers-core/src/utils/solc.rs
+++ /dev/null
@@ -1,584 +0,0 @@
-use std::{collections::HashMap, fmt, io::BufRead, path::PathBuf, process::Command, str::FromStr};
-
-use glob::glob;
-use serde::{de::DeserializeOwned, Deserialize, Deserializer, Serialize, Serializer};
-use thiserror::Error;
-
-use crate::{abi::Abi, types::Bytes};
-use once_cell::sync::Lazy;
-use semver::Version;
-
-/// The name of the `solc` binary on the system
-const SOLC: &str = "solc";
-
-/// Support for configuring the EVM version
-/// https://blog.soliditylang.org/2018/03/08/solidity-0.4.21-release-announcement/
-static CONSTANTINOPLE_SOLC: Lazy<Version> = Lazy::new(|| Version::from_str("0.4.21").unwrap());
-
-/// Petersburg support
-/// https://blog.soliditylang.org/2019/03/05/solidity-0.5.5-release-announcement/
-static PETERSBURG_SOLC: Lazy<Version> = Lazy::new(|| Version::from_str("0.5.5").unwrap());
-
-/// Istanbul support
-/// https://blog.soliditylang.org/2019/12/09/solidity-0.5.14-release-announcement/
-static ISTANBUL_SOLC: Lazy<Version> = Lazy::new(|| Version::from_str("0.5.14").unwrap());
-
-/// Berlin support
-/// https://blog.soliditylang.org/2021/06/10/solidity-0.8.5-release-announcement/
-static BERLIN_SOLC: Lazy<Version> = Lazy::new(|| Version::from_str("0.8.5").unwrap());
-
-/// London support
-/// https://blog.soliditylang.org/2021/08/11/solidity-0.8.7-release-announcement/
-static LONDON_SOLC: Lazy<Version> = Lazy::new(|| Version::from_str("0.8.7").unwrap());
-
-type Result<T> = std::result::Result<T, SolcError>;
-
-#[derive(Debug, Error)]
-pub enum SolcError {
-    /// Internal solc error
-    #[error("Solc Error: {0}")]
-    SolcError(String),
-    #[error(transparent)]
-    SemverError(#[from] semver::Error),
-    /// Deserialization error
-    #[error(transparent)]
-    SerdeJson(#[from] serde_json::Error),
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize)]
-/// The result of a solc compilation
-pub struct CompiledContract {
-    /// The contract's ABI
-    pub abi: Abi,
-    /// The contract's bytecode
-    pub bytecode: Bytes,
-    /// The contract's runtime bytecode
-    pub runtime_bytecode: Bytes,
-}
-
-/// Solidity Compiler Bindings
-///
-/// Assumes that `solc` is installed and available in the caller's $PATH. Any calls
-/// will **panic** otherwise.
-///
-/// By default, it uses 200 optimizer runs and Istanbul as the EVM version
-///
-/// # Examples
-///
-/// ```no_run
-/// use ethers_core::utils::Solc;
-///
-/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
-/// // Give it a glob
-/// let contracts = Solc::new("./contracts/*")
-///     .optimizer(Some(200))
-///     .build()?;
-///
-/// // this will return None if the specified contract did not exist in the compiled
-/// // files
-/// let contract = contracts.get("SimpleStorage").expect("contract not found");
-/// # Ok(())
-/// # }
-/// ```
-pub struct Solc {
-    /// The path to the Solc binary
-    pub solc_path: Option<PathBuf>,
-
-    /// The path where contracts will be read from
-    pub paths: Vec<String>,
-
-    /// Number of optimizer runs. None for no optimization
-    pub optimizer: Option<usize>,
-
-    /// Evm Version
-    pub evm_version: EvmVersion,
-
-    /// Paths for importing other libraries
-    pub allowed_paths: Vec<PathBuf>,
-
-    /// Output a single json document containing the specified information.
-    /// Default is `abi,bin,bin-runtime`
-    pub combined_json: Option<String>,
-
-    /// Additional arguments to pass to solc
-    pub args: Vec<String>,
-}
-
-impl Solc {
-    /// Instantiates The Solc builder with the provided glob of Solidity files
-    pub fn new(path: &str) -> Self {
-        // Convert the glob to a vector of string paths
-        // TODO: This might not be the most robust way to do this
-        let paths = glob(path)
-            .expect("could not get glob")
-            .map(|path| path.expect("path not found").to_string_lossy().to_string())
-            .collect::<Vec<String>>();
-        Self::new_with_paths(paths)
-    }
-
-    /// Instantiates the Solc builder for the provided paths
-    pub fn new_with_paths(paths: Vec<String>) -> Self {
-        Self {
-            paths,
-            solc_path: None,
-            optimizer: Some(200), // default optimizer runs = 200
-            evm_version: EvmVersion::Istanbul,
-            allowed_paths: Vec::new(),
-            combined_json: Some("abi,bin,bin-runtime".to_string()),
-            args: Vec::new(),
-        }
-    }
-
-    /// Gets the complete solc output as json object
-    pub fn exec(self) -> Result<serde_json::Value> {
-        let path = self.solc_path.unwrap_or_else(|| PathBuf::from(SOLC));
-
-        let mut command = Command::new(&path);
-        let version = Solc::version(Some(path));
-
-        if let Some(combined_json) = self.combined_json {
-            command.arg("--combined-json").arg(combined_json);
-        }
-
-        if let Some(evm_version) = normalize_evm_version(&version, self.evm_version) {
-            command.arg("--evm-version").arg(evm_version.to_string());
-        }
-
-        if let Some(runs) = self.optimizer {
-            command.arg("--optimize").arg("--optimize-runs").arg(runs.to_string());
-        }
-
-        command.args(self.args);
-
-        for path in self.paths {
-            command.arg(path);
-        }
-
-        let command = command.output().expect("could not run `solc`");
-
-        if !command.status.success() {
-            return Err(SolcError::SolcError(String::from_utf8_lossy(&command.stderr).to_string()))
-        }
-
-        // Deserialize the output
-        Ok(serde_json::from_slice(&command.stdout)?)
-    }
-
-    /// Gets the ABI for the contracts
-    pub fn build_raw(self) -> Result<HashMap<String, CompiledContractStr>> {
-        let mut output = self.exec()?;
-        let contract_values = output["contracts"].as_object_mut().ok_or_else(|| {
-            SolcError::SolcError("no contracts found in `solc` output".to_string())
-        })?;
-
-        let mut contracts = HashMap::with_capacity(contract_values.len());
-
-        for (name, contract) in contract_values {
-            if let serde_json::Value::String(bin) = contract["bin"].take() {
-                let name = name.rsplit(':').next().expect("could not strip fname").to_owned();
-
-                // abi could be an escaped string (solc<=0.7) or an array (solc>=0.8)
-                let abi = match contract["abi"].take() {
-                    serde_json::Value::String(abi) => abi,
-                    val @ serde_json::Value::Array(_) => val.to_string(),
-                    val => {
-                        return Err(SolcError::SolcError(format!(
-                            "Expected abi in solc output, found {:?}",
-                            val
-                        )))
-                    }
-                };
-
-                let runtime_bin =
-                    if let serde_json::Value::String(bin) = contract["bin-runtime"].take() {
-                        bin
-                    } else {
-                        panic!("no runtime bytecode found")
-                    };
-                contracts.insert(name, CompiledContractStr { abi, bin, runtime_bin });
-            } else {
-                return Err(SolcError::SolcError("could not find `bin` in solc output".to_string()))
-            }
-        }
-
-        Ok(contracts)
-    }
-
-    /// Builds the contracts and returns a hashmap for each named contract
-    pub fn build(self) -> Result<HashMap<String, CompiledContract>> {
-        // Build, and then get the data in the correct format
-        let contracts = self
-            .build_raw()?
-            .into_iter()
-            .map(|(name, contract)| {
-                // parse the ABI
-                let abi = serde_json::from_str(&contract.abi)
-                    .expect("could not parse `solc` abi, this should never happen");
-
-                // parse the bytecode
-                let bytecode =
-                    hex::decode(contract.bin).expect("solc did not produce valid bytecode").into();
-
-                // parse the runtime bytecode
-                let runtime_bytecode = hex::decode(contract.runtime_bin)
-                    .expect("solc did not produce valid runtime-bytecode")
-                    .into();
-                (name, CompiledContract { abi, bytecode, runtime_bytecode })
-            })
-            .collect::<HashMap<String, CompiledContract>>();
-
-        Ok(contracts)
-    }
-
-    /// Returns the output of `solc --version`
-    ///
-    /// # Panics
-    ///
-    /// If `solc` is not found
-    pub fn version(solc_path: Option<PathBuf>) -> Version {
-        let solc_path = solc_path.unwrap_or_else(|| PathBuf::from(SOLC));
-        let command_output = Command::new(&solc_path)
-            .arg("--version")
-            .output()
-            .unwrap_or_else(|_| panic!("`{:?}` not found", solc_path));
-
-        let version = command_output
-            .stdout
-            .lines()
-            .last()
-            .expect("expected version in solc output")
-            .expect("could not get solc version");
-
-        // Return the version trimmed
-        let version = version.replace("Version: ", "");
-        Version::from_str(&version[0..5]).expect("not a version")
-    }
-
-    /// Sets the EVM version for compilation
-    pub fn evm_version(mut self, version: EvmVersion) -> Self {
-        self.evm_version = version;
-        self
-    }
-
-    /// Sets the path to the solc binary
-    pub fn solc_path(mut self, path: PathBuf) -> Self {
-        self.solc_path = Some(std::fs::canonicalize(path).unwrap());
-        self
-    }
-
-    /// Sets the `combined-json` option, by default this is set to `abi,bin,bin-runtime`
-    /// NOTE: In order to get the `CompiledContract` from `Self::build`, this _must_ contain
-    /// `abi,bin`.
-    pub fn combined_json(mut self, combined_json: impl Into<String>) -> Self {
-        self.combined_json = Some(combined_json.into());
-        self
-    }
-
-    /// Sets the optimizer runs (default = 200). None indicates no optimization
-    ///
-    /// ```rust,no_run
-    /// use ethers_core::utils::Solc;
-    ///
-    /// // No optimization
-    /// let contracts = Solc::new("./contracts/*")
-    ///     .optimizer(None)
-    ///     .build().unwrap();
-    ///
-    /// // Some(200) is default, optimizer on with 200 runs
-    /// // .arg() allows passing arbitrary args to solc command
-    /// let optimized_contracts = Solc::new("./contracts/*")
-    ///     .optimizer(Some(200))
-    ///     .arg("--metadata-hash=none")
-    ///     .build().unwrap();
-    /// ```
-    pub fn optimizer(mut self, runs: Option<usize>) -> Self {
-        self.optimizer = runs;
-        self
-    }
-
-    /// Sets the allowed paths for using files from outside the same directory
-    // TODO: Test this
-    pub fn allowed_paths(mut self, paths: Vec<PathBuf>) -> Self {
-        self.allowed_paths = paths;
-        self
-    }
-
-    /// Adds an argument to pass to solc
-    pub fn arg<T: Into<String>>(mut self, arg: T) -> Self {
-        self.args.push(arg.into());
-        self
-    }
-
-    /// Adds multiple arguments to pass to solc
-    pub fn args<I, S>(mut self, args: I) -> Self
-    where
-        I: IntoIterator<Item = S>,
-        S: Into<String>,
-    {
-        for arg in args {
-            self = self.arg(arg);
-        }
-        self
-    }
-}
-
-#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
-pub enum EvmVersion {
-    Homestead,
-    TangerineWhistle,
-    SpuriusDragon,
-    Constantinople,
-    Petersburg,
-    Istanbul,
-    Berlin,
-    London,
-}
-
-impl fmt::Display for EvmVersion {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        let string = match self {
-            EvmVersion::Homestead => "homestead",
-            EvmVersion::TangerineWhistle => "tangerineWhistle",
-            EvmVersion::SpuriusDragon => "spuriusDragon",
-            EvmVersion::Constantinople => "constantinople",
-            EvmVersion::Petersburg => "petersburg",
-            EvmVersion::Istanbul => "istanbul",
-            EvmVersion::Berlin => "berlin",
-            EvmVersion::London => "london",
-        };
-        write!(f, "{}", string)
-    }
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize)]
-// Helper struct for deserializing the solc string outputs
-struct SolcOutput {
-    contracts: HashMap<String, CompiledContractStr>,
-    version: String,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize)]
-/// Helper struct for deserializing the solc string outputs
-pub struct CompiledContractStr {
-    /// The contract's raw ABI
-    pub abi: String,
-    /// The contract's bytecode in hex
-    pub bin: String,
-    /// The contract's runtime bytecode in hex
-    pub runtime_bin: String,
-}
-
-fn normalize_evm_version(version: &Version, evm_version: EvmVersion) -> Option<EvmVersion> {
-    // the EVM version flag was only added at 0.4.21
-    // we work our way backwards
-    if version >= &CONSTANTINOPLE_SOLC {
-        // If the Solc is at least at london, it supports all EVM versions
-        Some(if version >= &LONDON_SOLC {
-            evm_version
-        // For all other cases, cap at the at-the-time highest possible fork
-        } else if version >= &BERLIN_SOLC && evm_version >= EvmVersion::Berlin {
-            EvmVersion::Berlin
-        } else if version >= &ISTANBUL_SOLC && evm_version >= EvmVersion::Istanbul {
-            EvmVersion::Istanbul
-        } else if version >= &PETERSBURG_SOLC && evm_version >= EvmVersion::Petersburg {
-            EvmVersion::Petersburg
-        } else if evm_version >= EvmVersion::Constantinople {
-            EvmVersion::Constantinople
-        } else {
-            evm_version
-        })
-    } else {
-        None
-    }
-}
-
-/// General `solc` contract output
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct Contract {
-    pub abi: Abi,
-    pub evm: Evm,
-    #[serde(
-        deserialize_with = "de_from_json_opt",
-        serialize_with = "ser_to_inner_json",
-        skip_serializing_if = "Option::is_none"
-    )]
-    pub metadata: Option<Metadata>,
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct Evm {
-    pub bytecode: Bytecode,
-    pub deployed_bytecode: Bytecode,
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct Bytecode {
-    #[serde(deserialize_with = "deserialize_bytes")]
-    pub object: Bytes,
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct Metadata {
-    pub compiler: Compiler,
-    pub language: String,
-    pub output: Output,
-    pub settings: Settings,
-    pub sources: Sources,
-    pub version: i64,
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct Compiler {
-    pub version: String,
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct Output {
-    pub abi: Vec<SolcAbi>,
-    pub devdoc: Option<Doc>,
-    pub userdoc: Option<Doc>,
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct SolcAbi {
-    pub inputs: Vec<Item>,
-    #[serde(rename = "stateMutability")]
-    pub state_mutability: Option<String>,
-    #[serde(rename = "type")]
-    pub abi_type: String,
-    pub name: Option<String>,
-    pub outputs: Option<Vec<Item>>,
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct Item {
-    #[serde(rename = "internalType")]
-    pub internal_type: String,
-    pub name: String,
-    #[serde(rename = "type")]
-    pub put_type: String,
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct Doc {
-    pub kind: String,
-    pub methods: Libraries,
-    pub version: u32,
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct Libraries {
-    #[serde(flatten)]
-    pub libs: HashMap<String, serde_json::Value>,
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct Settings {
-    #[serde(rename = "compilationTarget")]
-    pub compilation_target: CompilationTarget,
-    #[serde(rename = "evmVersion")]
-    pub evm_version: String,
-    pub libraries: Libraries,
-    pub metadata: MetadataClass,
-    pub optimizer: Optimizer,
-    pub remappings: Vec<Option<serde_json::Value>>,
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct CompilationTarget {
-    #[serde(flatten)]
-    pub inner: HashMap<String, serde_json::Value>,
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct MetadataClass {
-    #[serde(rename = "bytecodeHash")]
-    pub bytecode_hash: String,
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct Optimizer {
-    pub enabled: bool,
-    pub runs: u32,
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct Sources {
-    #[serde(flatten)]
-    pub inner: HashMap<String, serde_json::Value>,
-}
-
-pub fn deserialize_bytes<'de, D>(d: D) -> std::result::Result<Bytes, D::Error>
-where
-    D: Deserializer<'de>,
-{
-    let value = String::deserialize(d)?;
-
-    Ok(hex::decode(&value).map_err(|e| serde::de::Error::custom(e.to_string()))?.into())
-}
-
-fn de_from_json_opt<'de, D, T>(deserializer: D) -> std::result::Result<Option<T>, D::Error>
-where
-    D: Deserializer<'de>,
-    T: DeserializeOwned,
-{
-    if let Some(val) = <Option<String>>::deserialize(deserializer)? {
-        serde_json::from_str(&val).map_err(serde::de::Error::custom)
-    } else {
-        Ok(None)
-    }
-}
-
-fn ser_to_inner_json<S, T>(val: &T, s: S) -> std::result::Result<S::Ok, S::Error>
-where
-    S: Serializer,
-    T: Serialize,
-{
-    let val = serde_json::to_string(val).map_err(serde::ser::Error::custom)?;
-    s.serialize_str(&val)
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn test_solc_version() {
-        Solc::version(None);
-    }
-
-    #[test]
-    fn test_evm_version_normalization() {
-        for (solc_version, evm_version, expected) in &[
-            // Ensure 0.4.21 it always returns None
-            ("0.4.20", EvmVersion::Homestead, None),
-            // Constantinople clipping
-            ("0.4.21", EvmVersion::Homestead, Some(EvmVersion::Homestead)),
-            ("0.4.21", EvmVersion::Constantinople, Some(EvmVersion::Constantinople)),
-            ("0.4.21", EvmVersion::London, Some(EvmVersion::Constantinople)),
-            // Petersburg
-            ("0.5.5", EvmVersion::Homestead, Some(EvmVersion::Homestead)),
-            ("0.5.5", EvmVersion::Petersburg, Some(EvmVersion::Petersburg)),
-            ("0.5.5", EvmVersion::London, Some(EvmVersion::Petersburg)),
-            // Istanbul
-            ("0.5.14", EvmVersion::Homestead, Some(EvmVersion::Homestead)),
-            ("0.5.14", EvmVersion::Istanbul, Some(EvmVersion::Istanbul)),
-            ("0.5.14", EvmVersion::London, Some(EvmVersion::Istanbul)),
-            // Berlin
-            ("0.8.5", EvmVersion::Homestead, Some(EvmVersion::Homestead)),
-            ("0.8.5", EvmVersion::Berlin, Some(EvmVersion::Berlin)),
-            ("0.8.5", EvmVersion::London, Some(EvmVersion::Berlin)),
-            // London
-            ("0.8.7", EvmVersion::Homestead, Some(EvmVersion::Homestead)),
-            ("0.8.7", EvmVersion::London, Some(EvmVersion::London)),
-            ("0.8.7", EvmVersion::London, Some(EvmVersion::London)),
-        ] {
-            assert_eq!(
-                &normalize_evm_version(&Version::from_str(solc_version).unwrap(), *evm_version),
-                expected
-            )
-        }
-    }
-}
diff --git a/ethers-middleware/Cargo.toml b/ethers-middleware/Cargo.toml
index 97408aba..669b5903 100644
--- a/ethers-middleware/Cargo.toml
+++ b/ethers-middleware/Cargo.toml
@@ -42,6 +42,7 @@ hex = { version = "0.4.3", default-features = false, features = ["std"] }
 rand = { version = "0.8.4", default-features = false }
 ethers-providers = { version = "^0.5.0", path = "../ethers-providers", default-features = false, features = ["ws", "rustls"] }
 once_cell = "1.8.0"
+ethers-solc = { version = "^0.1.0", path = "../ethers-solc", default-features = false }
 
 [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
 tokio = { version = "1.5", default-features = false, features = ["rt", "macros", "time"] }
diff --git a/ethers-solc/Cargo.toml b/ethers-solc/Cargo.toml
index 655a4cf5..f9ade2b2 100644
--- a/ethers-solc/Cargo.toml
+++ b/ethers-solc/Cargo.toml
@@ -11,9 +11,10 @@ homepage = "https://docs.rs/ethers"
 description = """
 Utilites for working with solc
 """
-keywords = ["ethereum", "web3", "etherscan", "ethers"]
+keywords = ["ethereum", "web3", "solc", "solidity", "ethers"]
 
 [dependencies]
+ethers-core = { version = "^0.5.0", path = "../ethers-core", default-features = false }
 serde_json = "1.0.68"
 serde = { version = "1.0.130", features = ["derive"] }
 semver = "1.0.4"
@@ -26,6 +27,10 @@ md-5 = "0.9.1"
 thiserror = "1.0.30"
 hex = "0.4.3"
 colored = "2.0.0"
+svm = { package = "svm-rs", git = "https://github.com/roynalnaruto/svm-rs", optional = true }
+
+[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
+home = "0.5.3"
 
 [dev-dependencies]
 tokio = { version = "1.12.0", features = ["full"] }
@@ -33,3 +38,4 @@ tempdir = "0.3.7"
 
 [features]
 async = ["tokio", "futures-util"]
+full = ["async", "svm"]
diff --git a/ethers-solc/src/artifacts.rs b/ethers-solc/src/artifacts.rs
index 461220c3..44bf8f95 100644
--- a/ethers-solc/src/artifacts.rs
+++ b/ethers-solc/src/artifacts.rs
@@ -1,4 +1,5 @@
 //! Solc artifact types
+use ethers_core::{abi::Abi, types::Bytes};
 
 use colored::Colorize;
 use md5::Digest;
@@ -11,10 +12,7 @@ use std::{
 };
 
 use crate::{compile::*, utils};
-use serde::{
-    de::{self, Visitor},
-    Deserialize, Deserializer, Serialize, Serializer,
-};
+use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
 
 /// An ordered list of files and their source
 pub type Sources = BTreeMap<PathBuf, Source>;
@@ -366,7 +364,7 @@ impl AsRef<str> for Source {
 }
 
 /// Output type `solc` produces
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Default)]
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
 pub struct CompilerOutput {
     #[serde(default, skip_serializing_if = "Vec::is_empty")]
     pub errors: Vec<Error>,
@@ -385,6 +383,15 @@ impl CompilerOutput {
     pub fn diagnostics<'a>(&'a self, ignored_error_codes: &'a [u64]) -> OutputDiagnostics {
         OutputDiagnostics { errors: &self.errors, ignored_error_codes }
     }
+
+    /// Given the contract file's path and the contract's name, tries to return the contract's
+    /// bytecode, runtime bytecode, and abi
+    pub fn get(&self, path: &str, contract: &str) -> Option<CompactContractRef> {
+        self.contracts
+            .get(path)
+            .and_then(|contracts| contracts.get(contract))
+            .map(CompactContractRef::from)
+    }
 }
 
 /// Helper type to implement display for solc errors
@@ -419,11 +426,11 @@ impl<'a> fmt::Display for OutputDiagnostics<'a> {
     }
 }
 
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
 pub struct Contract {
-    /// The Ethereum Contract ABI. If empty, it is represented as an empty
-    /// array. See https://docs.soliditylang.org/en/develop/abi-spec.html
-    pub abi: Vec<serde_json::Value>,
+    /// The Ethereum Contract ABI.
+    /// See https://docs.soliditylang.org/en/develop/abi-spec.html
+    pub abi: Option<Abi>,
     #[serde(default, skip_serializing_if = "Option::is_none")]
     pub metadata: Option<String>,
     #[serde(default)]
@@ -443,15 +450,19 @@ pub struct Contract {
 }
 
 /// Minimal representation of a contract's abi with bytecode
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
 pub struct CompactContract {
     /// The Ethereum Contract ABI. If empty, it is represented as an empty
     /// array. See https://docs.soliditylang.org/en/develop/abi-spec.html
-    pub abi: Vec<serde_json::Value>,
-    #[serde(default, skip_serializing_if = "Option::is_none")]
-    pub bin: Option<String>,
+    pub abi: Option<Abi>,
+    #[serde(
+        default,
+        deserialize_with = "deserialize_opt_bytes",
+        skip_serializing_if = "Option::is_none"
+    )]
+    pub bin: Option<Bytes>,
     #[serde(default, rename = "bin-runtime", skip_serializing_if = "Option::is_none")]
-    pub bin_runtime: Option<String>,
+    pub bin_runtime: Option<Bytes>,
 }
 
 impl From<Contract> for CompactContract {
@@ -469,25 +480,25 @@ impl From<Contract> for CompactContract {
 /// Helper type to serialize while borrowing from `Contract`
 #[derive(Clone, Debug, Serialize)]
 pub struct CompactContractRef<'a> {
-    pub abi: &'a [serde_json::Value],
+    pub abi: Option<&'a Abi>,
     #[serde(default, skip_serializing_if = "Option::is_none")]
-    pub bin: Option<&'a str>,
+    pub bin: Option<&'a Bytes>,
     #[serde(default, rename = "bin-runtime", skip_serializing_if = "Option::is_none")]
-    pub bin_runtime: Option<&'a str>,
+    pub bin_runtime: Option<&'a Bytes>,
 }
 
 impl<'a> From<&'a Contract> for CompactContractRef<'a> {
     fn from(c: &'a Contract) -> Self {
         let (bin, bin_runtime) = if let Some(ref evm) = c.evm {
             (
-                Some(evm.bytecode.object.as_str()),
-                evm.deployed_bytecode.bytecode.as_ref().map(|evm| evm.object.as_str()),
+                Some(&evm.bytecode.object),
+                evm.deployed_bytecode.bytecode.as_ref().map(|evm| &evm.object),
             )
         } else {
             (None, None)
         };
 
-        Self { abi: &c.abi, bin, bin_runtime }
+        Self { abi: c.abi.as_ref(), bin, bin_runtime }
     }
 }
 
@@ -545,7 +556,8 @@ pub struct Bytecode {
     #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
     pub function_debug_data: BTreeMap<String, FunctionDebugData>,
     /// The bytecode as a hex string.
-    pub object: String,
+    #[serde(deserialize_with = "deserialize_bytes")]
+    pub object: Bytes,
     /// Opcodes list (string)
     pub opcodes: String,
     /// The source mapping as a string. See the source mapping definition.
@@ -660,26 +672,12 @@ pub struct Error {
     pub r#type: String,
     pub component: String,
     pub severity: Severity,
-    #[serde(default, deserialize_with = "from_optional_str")]
+    #[serde(default, with = "display_from_str_opt")]
     pub error_code: Option<u64>,
     pub message: String,
     pub formatted_message: Option<String>,
 }
 
-fn from_optional_str<'de, T, D>(deserializer: D) -> Result<Option<T>, D::Error>
-where
-    T: FromStr,
-    T::Err: fmt::Display,
-    D: Deserializer<'de>,
-{
-    let s = Option::<String>::deserialize(deserializer)?;
-    if let Some(s) = s {
-        T::from_str(&s).map_err(de::Error::custom).map(Some)
-    } else {
-        Ok(None)
-    }
-}
-
 impl fmt::Display for Error {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         if let Some(msg) = &self.formatted_message {
@@ -821,6 +819,26 @@ mod display_from_str_opt {
     }
 }
 
+pub fn deserialize_bytes<'de, D>(d: D) -> std::result::Result<Bytes, D::Error>
+where
+    D: Deserializer<'de>,
+{
+    let value = String::deserialize(d)?;
+    Ok(hex::decode(&value).map_err(|e| serde::de::Error::custom(e.to_string()))?.into())
+}
+
+pub fn deserialize_opt_bytes<'de, D>(d: D) -> std::result::Result<Option<Bytes>, D::Error>
+where
+    D: Deserializer<'de>,
+{
+    let value = Option::<String>::deserialize(d)?;
+    if let Some(value) = value {
+        Ok(Some(hex::decode(&value).map_err(|e| serde::de::Error::custom(e.to_string()))?.into()))
+    } else {
+        Ok(None)
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
diff --git a/ethers-solc/src/compile.rs b/ethers-solc/src/compile.rs
index 21b48ea0..76de8870 100644
--- a/ethers-solc/src/compile.rs
+++ b/ethers-solc/src/compile.rs
@@ -52,8 +52,65 @@ impl Solc {
         Solc(path.into())
     }
 
+    /// Returns the directory in which [svm](https://github.com/roynalnaruto/svm-rs) stores all versions
+    ///
+    /// This will be `~/.svm` on unix
+    #[cfg(not(target_arch = "wasm32"))]
+    pub fn svm_home() -> Option<PathBuf> {
+        home::home_dir().map(|dir| dir.join(".svm"))
+    }
+
+    /// Returns the path for a [svm](https://github.com/roynalnaruto/svm-rs) installed version.
+    ///
+    /// # Example
+    /// ```no_run
+    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
+    ///  use ethers_solc::Solc;
+    /// let solc = Solc::find_svm_installed_version("0.8.9").unwrap();
+    /// assert_eq!(solc, Some(Solc::new("~/.svm/0.8.9/solc-0.8.9")));
+    /// # Ok(())
+    /// # }
+    /// ```
+    #[cfg(not(target_arch = "wasm32"))]
+    pub fn find_svm_installed_version(version: impl AsRef<str>) -> Result<Option<Self>> {
+        let version = version.as_ref();
+        let solc = walkdir::WalkDir::new(
+            Self::svm_home().ok_or_else(|| SolcError::solc("svm home dir not found"))?,
+        )
+        .max_depth(1)
+        .into_iter()
+        .filter_map(std::result::Result::ok)
+        .filter(|e| e.file_type().is_dir())
+        .find(|e| e.path().ends_with(version))
+        .map(|e| e.path().join(format!("solc-{}", version)))
+        .map(Solc::new);
+        Ok(solc)
+    }
+
+    /// Installs the provided version of Solc in the machine under the svm dir
+    /// # Example
+    /// ```no_run
+    /// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
+    ///  use ethers_solc::{Solc, ISTANBUL_SOLC};
+    ///  Solc::install(&ISTANBUL_SOLC).await.unwrap();
+    ///  let solc = Solc::find_svm_installed_version(&ISTANBUL_SOLC.to_string());
+    /// # Ok(())
+    /// # }
+    /// ```
+    #[cfg(feature = "svm")]
+    pub async fn install(version: &Version) -> std::result::Result<(), svm::SolcVmError> {
+        svm::install(version).await
+    }
+
+    /// Blocking version of `Self::install`
+    #[cfg(all(feature = "svm", feature = "async"))]
+    pub fn blocking_install(version: &Version) -> std::result::Result<(), svm::SolcVmError> {
+        tokio::runtime::Runtime::new().unwrap().block_on(svm::install(version))?;
+        Ok(())
+    }
+
     /// Convenience function for compiling all sources under the given path
-    pub fn compile_source<T: Serialize>(&self, path: impl AsRef<Path>) -> Result<CompilerOutput> {
+    pub fn compile_source(&self, path: impl AsRef<Path>) -> Result<CompilerOutput> {
         self.compile(&CompilerInput::new(path)?)
     }
 
@@ -218,6 +275,12 @@ mod tests {
         let _version = Version::from_str("0.6.6+commit.6c089d02.Linux.gcc").unwrap();
     }
 
+    #[test]
+    #[ignore]
+    fn can_find_solc() {
+        let _solc = Solc::find_svm_installed_version("0.8.9").unwrap();
+    }
+
     #[cfg(feature = "async")]
     #[tokio::test]
     async fn async_solc_version_works() {
diff --git a/ethers-solc/src/config.rs b/ethers-solc/src/config.rs
index d476ba34..744c9678 100644
--- a/ethers-solc/src/config.rs
+++ b/ethers-solc/src/config.rs
@@ -206,6 +206,8 @@ impl SolcConfigBuilder {
 
 /// Determines how to handle compiler output
 pub enum ArtifactOutput {
+    /// No-op, does not write the artifacts to disk.
+    Nothing,
     /// Creates a single json artifact with
     /// ```json
     ///  {
@@ -225,6 +227,7 @@ impl ArtifactOutput {
     /// Is expected to handle the output and where to store it
     pub fn on_output(&self, output: &CompilerOutput, layout: &ProjectPathsConfig) -> Result<()> {
         match self {
+            ArtifactOutput::Nothing => Ok(()),
             ArtifactOutput::MinimalCombined => {
                 fs::create_dir_all(&layout.artifacts)?;
 
@@ -254,6 +257,9 @@ impl Default for ArtifactOutput {
 impl fmt::Debug for ArtifactOutput {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         match self {
+            ArtifactOutput::Nothing => {
+                write!(f, "Nothing")
+            }
             ArtifactOutput::MinimalCombined => {
                 write!(f, "MinimalCombined")
             }
diff --git a/ethers-solc/src/lib.rs b/ethers-solc/src/lib.rs
index 5b873449..b9ad04e6 100644
--- a/ethers-solc/src/lib.rs
+++ b/ethers-solc/src/lib.rs
@@ -8,7 +8,7 @@ use std::collections::btree_map::Entry;
 pub mod cache;
 
 mod compile;
-pub use compile::Solc;
+pub use compile::*;
 
 mod config;
 pub use config::{ArtifactOutput, ProjectPathsConfig, SolcConfig};
@@ -231,7 +231,7 @@ impl Default for ProjectBuilder {
     }
 }
 
-#[derive(Debug, Clone, PartialEq, Eq)]
+#[derive(Debug, Clone, PartialEq)]
 pub enum ProjectCompileOutput<'a> {
     /// Nothing to compile because unchanged sources
     Unchanged,
diff --git a/examples/abigen.rs b/examples/abigen.rs
index 3d29e0e5..e3c8bc1a 100644
--- a/examples/abigen.rs
+++ b/examples/abigen.rs
@@ -1,4 +1,4 @@
-use ethers::{contract::Abigen, utils::Solc};
+use ethers::{contract::Abigen, solc::Solc};
 
 fn main() -> anyhow::Result<()> {
     let mut args = std::env::args();
@@ -9,10 +9,11 @@ fn main() -> anyhow::Result<()> {
 
     println!("Generating bindings for {}\n", contract);
 
-    // compile it if needed
+    // compile it
     let abi = if contract.ends_with(".sol") {
-        let contracts = Solc::new(&contract).build_raw()?;
-        contracts.get(&contract_name).unwrap().abi.clone()
+        let contracts = Solc::default().compile_source(&contract)?;
+        let abi = contracts.get(&contract, &contract_name).unwrap().abi.unwrap();
+        serde_json::to_string(abi).unwrap()
     } else {
         contract
     };
diff --git a/examples/contract_human_readable.rs b/examples/contract_human_readable.rs
index 5e1bae22..9829b065 100644
--- a/examples/contract_human_readable.rs
+++ b/examples/contract_human_readable.rs
@@ -1,9 +1,7 @@
 use anyhow::Result;
-use ethers::{
-    prelude::*,
-    utils::{compile_and_launch_ganache, Ganache, Solc},
-};
-use std::{convert::TryFrom, sync::Arc, time::Duration};
+use ethers::{prelude::*, utils::Ganache};
+use ethers_solc::{ArtifactOutput, Project, ProjectCompileOutput, ProjectPathsConfig};
+use std::{convert::TryFrom, path::PathBuf, sync::Arc, time::Duration};
 
 // Generate the type-safe contract bindings by providing the ABI
 // definition in human readable format
@@ -19,13 +17,30 @@ abigen!(
 
 #[tokio::main]
 async fn main() -> Result<()> {
-    // 1. compile the contract (note this requires that you are inside the `examples` directory) and
-    // launch ganache
-    let (compiled, ganache) =
-        compile_and_launch_ganache(Solc::new("**/contract.sol"), Ganache::new()).await?;
-    let contract = compiled.get("SimpleStorage").expect("could not find contract");
+    // the directory we use is root-dir/examples
+    let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("examples");
+    // we use `root` for both the project root and for where to search for contracts since
+    // everything is in the same directory
+    let paths = ProjectPathsConfig::builder().root(&root).sources(&root).build().unwrap();
+    // get the solc project instance using the paths above
+    let solc = Project::builder()
+        .paths(paths)
+        .ephemeral()
+        .artifacts(ArtifactOutput::Nothing)
+        .build()
+        .unwrap();
+    // compile the project and get the artifacts
+    let compiled = solc.compile().unwrap();
+    let compiled = match compiled {
+        ProjectCompileOutput::Compiled((output, _)) => output,
+        _ => panic!("expected compilation artifacts"),
+    };
+    let path = root.join("contract.sol");
+    let path = path.to_str();
+    let contract = compiled.get(path.unwrap(), "SimpleStorage").expect("could not find contract");
 
-    // 2. instantiate our wallet
+    // 2. instantiate our wallet & ganache
+    let ganache = Ganache::new().spawn();
     let wallet: LocalWallet = ganache.keys()[0].clone().into();
 
     // 3. connect to the network
@@ -37,8 +52,11 @@ async fn main() -> Result<()> {
     let client = Arc::new(client);
 
     // 5. create a factory which will be used to deploy instances of the contract
-    let factory =
-        ContractFactory::new(contract.abi.clone(), contract.bytecode.clone(), client.clone());
+    let factory = ContractFactory::new(
+        contract.abi.unwrap().clone(),
+        contract.bin.unwrap().clone(),
+        client.clone(),
+    );
 
     // 6. deploy it with the constructor arguments
     let contract = factory.deploy("initial value".to_string())?.legacy().send().await?;
diff --git a/src/lib.rs b/src/lib.rs
index 707b55e6..ab77ce62 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -88,6 +88,7 @@ pub use ethers_core as core;
 pub use ethers_middleware as middleware;
 pub use ethers_providers as providers;
 pub use ethers_signers as signers;
+pub use ethers_solc as solc;
 
 // Re-export ethers_core::utils/types/abi
 // We hide these docs so that the rustdoc links send the visitor
@@ -107,4 +108,5 @@ pub mod prelude {
     pub use ethers_middleware::*;
     pub use ethers_providers::*;
     pub use ethers_signers::*;
+    pub use ethers_solc::*;
 }


Test patch:
diff --git a/ethers-contract/tests/abigen.rs b/ethers-contract/tests/abigen.rs
index 449b52b7..388188ab 100644
--- a/ethers-contract/tests/abigen.rs
+++ b/ethers-contract/tests/abigen.rs
@@ -4,9 +4,9 @@ use ethers_contract::{abigen, EthEvent};
 use ethers_core::{
     abi::{AbiDecode, AbiEncode, Address, Tokenizable},
     types::{transaction::eip2718::TypedTransaction, Eip1559TransactionRequest, U256},
-    utils::Solc,
 };
 use ethers_providers::Provider;
+use ethers_solc::Solc;
 use std::{convert::TryFrom, sync::Arc};
 
 #[test]
@@ -191,6 +191,8 @@ fn can_handle_overloaded_functions() {
         getValue() (uint256)
         getValue(uint256 otherValue) (uint256)
         getValue(uint256 otherValue, address addr) (uint256)
+        setValue(string, string)
+        setValue(string)
     ]"#
     );
 
@@ -238,6 +240,30 @@ fn can_handle_overloaded_functions() {
     let decoded_enum = SimpleContractCalls::decode(encoded_call.as_ref()).unwrap();
     assert_eq!(contract_call, decoded_enum);
     assert_eq!(encoded_call, contract_call.encode().into());
+
+    let call = SetValue0Call("message".to_string());
+    let _contract_call = SimpleContractCalls::SetValue0(call);
+    let call = SetValue1Call("message".to_string(), "message".to_string());
+    let _contract_call = SimpleContractCalls::SetValue1(call);
+}
+
+#[test]
+fn can_handle_even_more_overloaded_functions() {
+    abigen!(
+        ConsoleLog,
+        r#"[
+            log()
+            log(string, string)
+            log(string)
+    ]"#
+    );
+
+    let _call = Log0Call;
+    let _contract_call = ConsoleLogCalls::Log0;
+    let call = Log1Call("message".to_string());
+    let _contract_call = ConsoleLogCalls::Log1(call);
+    let call = Log2Call("message".to_string(), "message".to_string());
+    let _contract_call = ConsoleLogCalls::Log2(call);
 }
 
 #[tokio::test]
@@ -261,11 +287,13 @@ async fn can_handle_underscore_functions() {
         .interval(std::time::Duration::from_millis(10));
     let client = Arc::new(provider);
 
-    let compiled = Solc::new("./tests/solidity-contracts/SimpleStorage.sol").build().unwrap();
-    let compiled = compiled.get("SimpleStorage").unwrap();
+    let contract = "SimpleStorage";
+    let path = "./tests/solidity-contracts/SimpleStorage.sol";
+    let compiled = Solc::default().compile_source(path).unwrap();
+    let compiled = compiled.get(path, contract).unwrap();
     let factory = ethers_contract::ContractFactory::new(
-        compiled.abi.clone(),
-        compiled.bytecode.clone(),
+        compiled.abi.unwrap().clone(),
+        compiled.bin.unwrap().clone(),
         client.clone(),
     );
     let addr = factory.deploy("hi".to_string()).unwrap().legacy().send().await.unwrap().address();
diff --git a/ethers-contract/tests/common/mod.rs b/ethers-contract/tests/common/mod.rs
index fcb94ebf..1ebf5002 100644
--- a/ethers-contract/tests/common/mod.rs
+++ b/ethers-contract/tests/common/mod.rs
@@ -10,12 +10,9 @@ use ethers_contract::EthEvent;
 mod derive;
 
 use ethers_contract::{Contract, ContractFactory};
-use ethers_core::{
-    abi::Abi,
-    types::Bytes,
-    utils::{GanacheInstance, Solc},
-};
+use ethers_core::{abi::Abi, types::Bytes, utils::GanacheInstance};
 use ethers_providers::{Http, Middleware, Provider};
+use ethers_solc::Solc;
 use std::{convert::TryFrom, sync::Arc, time::Duration};
 
 // Note: The `EthEvent` derive macro implements the necessary conversion between `Tokens` and
@@ -33,9 +30,10 @@ pub struct ValueChanged {
 
 /// compiles the given contract and returns the ABI and Bytecode
 pub fn compile_contract(name: &str, filename: &str) -> (Abi, Bytes) {
-    let compiled = Solc::new(&format!("./tests/solidity-contracts/{}", filename)).build().unwrap();
-    let contract = compiled.get(name).expect("could not find contract");
-    (contract.abi.clone(), contract.bytecode.clone())
+    let path = format!("./tests/solidity-contracts/{}", filename);
+    let compiled = Solc::default().compile_source(&path).unwrap();
+    let contract = compiled.get(&path, name).expect("could not find contract");
+    (contract.abi.unwrap().clone(), contract.bin.unwrap().clone())
 }
 
 /// connects the private key to http://localhost:8545
diff --git a/ethers-middleware/tests/signer.rs b/ethers-middleware/tests/signer.rs
index 6ddad69e..de5d6bdf 100644
--- a/ethers-middleware/tests/signer.rs
+++ b/ethers-middleware/tests/signer.rs
@@ -214,18 +214,19 @@ async fn deploy_and_call_contract() {
     use ethers_core::{
         abi::Abi,
         types::{BlockNumber, Bytes, H256, U256},
-        utils::Solc,
     };
+    use ethers_solc::Solc;
     use std::sync::Arc;
 
-    fn compile_contract(name: &str, filename: &str) -> (Abi, Bytes) {
-        let compiled =
-            Solc::new(&format!("./tests/solidity-contracts/{}", filename)).build().unwrap();
-        let contract = compiled.get(name).expect("could not find contract");
-        (contract.abi.clone(), contract.bytecode.clone())
+    // compiles the given contract and returns the ABI and Bytecode
+    fn compile_contract(path: &str, name: &str) -> (Abi, Bytes) {
+        let path = format!("./tests/solidity-contracts/{}", path);
+        let compiled = Solc::default().compile_source(&path).unwrap();
+        let contract = compiled.get(&path, name).expect("could not find contract");
+        (contract.abi.unwrap().clone(), contract.bin.unwrap().clone())
     }
 
-    let (abi, bytecode) = compile_contract("SimpleStorage", "SimpleStorage.sol");
+    let (abi, bytecode) = compile_contract("SimpleStorage.sol", "SimpleStorage");
 
     // Celo testnet
     let provider = Provider::<Http>::try_from("https://alfajores-forno.celo-testnet.org")
diff --git a/ethers-middleware/tests/transformer.rs b/ethers-middleware/tests/transformer.rs
index 92767b04..377de503 100644
--- a/ethers-middleware/tests/transformer.rs
+++ b/ethers-middleware/tests/transformer.rs
@@ -1,21 +1,27 @@
 #![cfg(not(target_arch = "wasm32"))]
 #![allow(unused)]
 use ethers_contract::{BaseContract, ContractFactory};
-use ethers_core::{
-    types::*,
-    utils::{Ganache, Solc},
-};
+use ethers_core::{abi::Abi, types::*, utils::Ganache};
 use ethers_middleware::{
     transformer::{DsProxy, TransformerMiddleware},
     SignerMiddleware,
 };
 use ethers_providers::{Http, Middleware, Provider};
 use ethers_signers::{LocalWallet, Signer};
+use ethers_solc::Solc;
 use rand::Rng;
 use std::{convert::TryFrom, sync::Arc, time::Duration};
 
 type HttpWallet = SignerMiddleware<Provider<Http>, LocalWallet>;
 
+// compiles the given contract and returns the ABI and Bytecode
+fn compile_contract(path: &str, name: &str) -> (Abi, Bytes) {
+    let path = format!("./tests/solidity-contracts/{}", path);
+    let compiled = Solc::default().compile_source(&path).unwrap();
+    let contract = compiled.get(&path, name).expect("could not find contract");
+    (contract.abi.unwrap().clone(), contract.bin.unwrap().clone())
+}
+
 #[tokio::test]
 #[cfg(not(feature = "celo"))]
 async fn ds_proxy_transformer() {
@@ -35,15 +41,8 @@ async fn ds_proxy_transformer() {
     let provider = Arc::new(signer_middleware.clone());
 
     // deploy DsProxyFactory which we'll use to deploy a new DsProxy contract.
-    let compiled = Solc::new("./tests/solidity-contracts/DSProxy.sol")
-        .build()
-        .expect("could not compile DSProxyFactory");
-    let contract = compiled.get("DSProxyFactory").expect("could not find DSProxyFactory");
-    let factory = ContractFactory::new(
-        contract.abi.clone(),
-        contract.bytecode.clone(),
-        Arc::clone(&provider),
-    );
+    let (abi, bytecode) = compile_contract("DSProxy.sol", "DSProxyFactory");
+    let factory = ContractFactory::new(abi, bytecode, Arc::clone(&provider));
     let ds_proxy_factory = factory.deploy(()).unwrap().legacy();
     let ds_proxy_factory = ds_proxy_factory.send().await.unwrap();
 
@@ -58,15 +57,8 @@ async fn ds_proxy_transformer() {
     let ds_proxy_addr = ds_proxy.address();
 
     // deploy SimpleStorage and try to update its value via transformer middleware.
-    let compiled = Solc::new("./tests/solidity-contracts/SimpleStorage.sol")
-        .build()
-        .expect("could not compile SimpleStorage");
-    let contract = compiled.get("SimpleStorage").expect("could not find SimpleStorage");
-    let factory = ContractFactory::new(
-        contract.abi.clone(),
-        contract.bytecode.clone(),
-        Arc::clone(&provider),
-    );
+    let (abi, bytecode) = compile_contract("SimpleStorage.sol", "SimpleStorage");
+    let factory = ContractFactory::new(abi, bytecode, Arc::clone(&provider));
     let deployer = factory.deploy(()).unwrap().legacy();
     let simple_storage = deployer.send().await.unwrap();
 
@@ -108,15 +100,8 @@ async fn ds_proxy_code() {
     let provider = Arc::new(signer_middleware.clone());
 
     // deploy DsProxyFactory which we'll use to deploy a new DsProxy contract.
-    let compiled = Solc::new("./tests/solidity-contracts/DSProxy.sol")
-        .build()
-        .expect("could not compile DSProxyFactory");
-    let contract = compiled.get("DSProxyFactory").expect("could not find DSProxyFactory");
-    let factory = ContractFactory::new(
-        contract.abi.clone(),
-        contract.bytecode.clone(),
-        Arc::clone(&provider),
-    );
+    let (abi, bytecode) = compile_contract("DSProxy.sol", "DSProxyFactory");
+    let factory = ContractFactory::new(abi, bytecode, Arc::clone(&provider));
     let ds_proxy_factory = factory.deploy(()).unwrap().legacy();
     let ds_proxy_factory = ds_proxy_factory.send().await.unwrap();
 
@@ -131,11 +116,8 @@ async fn ds_proxy_code() {
     let ds_proxy_addr = ds_proxy.address();
 
     // compile the SimpleStorage contract which we will use to interact via DsProxy.
-    let compiled = Solc::new("./tests/solidity-contracts/SimpleStorage.sol")
-        .build()
-        .expect("could not compile SimpleStorage");
-    let ss = compiled.get("SimpleStorage").expect("could not find SimpleStorage");
-    let ss_base_contract: BaseContract = ss.abi.clone().into();
+    let (abi, bytecode) = compile_contract("SimpleStorage.sol", "SimpleStorage");
+    let ss_base_contract: BaseContract = abi.into();
     let expected_value: u64 = rng.gen();
     let calldata = ss_base_contract
         .encode("setValue", U256::from(expected_value))
@@ -145,7 +127,7 @@ async fn ds_proxy_code() {
     ds_proxy
         .execute::<HttpWallet, Arc<HttpWallet>, Bytes>(
             Arc::clone(&provider),
-            ss.bytecode.clone(),
+            bytecode.clone(),
             calldata,
         )
         .expect("could not construct DSProxy contract call")