Skip to content

Commit

Permalink
refactor: move compilation to own struct
Browse files Browse the repository at this point in the history
  • Loading branch information
gakonst committed Sep 13, 2021
1 parent bf65739 commit 4843a16
Show file tree
Hide file tree
Showing 2 changed files with 170 additions and 112 deletions.
3 changes: 1 addition & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,9 @@ serde = "1.0.130"
hex = "0.4.3"
regex = { version = "1.5.4", default-features = false }

svm = { package = "solc-vm-rs", git = "https://github.com/roynalnaruto/solc-vm-rs" }
svm = { package = "svm-rs", git = "https://github.com/roynalnaruto/svm-rs" }
glob = "0.3.0"
semver = "1.0.4"

[patch.'crates-io']
ethabi = { git = "https://github.com/gakonst/ethabi/", branch = "patch-1" }
evm = { git = "https://github.com/gakonst/evm" }
279 changes: 169 additions & 110 deletions src/dapp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -289,9 +289,9 @@ impl DapptoolsArtifact {
}

pub fn installed_version_paths() -> Result<Vec<PathBuf>> {
let home_dir = svm::home();
let home_dir = svm::SVM_HOME.clone();
let mut versions = vec![];
for version in std::fs::read_dir(&home_dir)? {
for version in std::fs::read_dir(home_dir)? {
let version = version?;
versions.push(version.path());
}
Expand All @@ -300,6 +300,172 @@ pub fn installed_version_paths() -> Result<Vec<PathBuf>> {
Ok(versions)
}

/// Supports building contracts
struct SolcBuilder<'a> {
contracts: &'a str,
remappings: &'a [String],
lib_paths: &'a [String],
versions: Vec<Version>,
releases: Vec<Version>,
}

use semver::{Version, VersionReq};
use std::fs::File;
use std::io::{BufRead, BufReader};

impl<'a> SolcBuilder<'a> {
pub fn new(
contracts: &'a str,
remappings: &'a [String],
lib_paths: &'a [String],
) -> Result<Self> {
let versions = svm::installed_versions().unwrap_or_default();
let releases = tokio::runtime::Runtime::new()?.block_on(svm::all_versions())?;
Ok(Self {
contracts,
remappings,
lib_paths,
versions,
releases,
})
}

/// Gets a map of compiler version -> vec[contract paths]
fn contract_versions(&mut self) -> Result<HashMap<String, Vec<String>>> {
// Group contracts in the nones with the same version pragma
let files = glob::glob(self.contracts)?;

// get all the corresponding contract versions
Ok(files
.filter_map(|fname| fname.ok())
.filter_map(|fname| self.detect_version(fname).ok().flatten())
.fold(HashMap::new(), |mut map, (version, path)| {
let entry = map.entry(version.to_string()).or_insert(vec![]);
entry.push(path);
map
}))
}

/// Parses the given Solidity file looking for the `pragma` definition and
/// returns the corresponding SemVer version requirement.
fn version_req(path: &PathBuf) -> Result<VersionReq> {
let file = BufReader::new(File::open(path)?);
let version = file
.lines()
.map(|line| line.unwrap())
.find(|line| line.starts_with("pragma"))
.ok_or_else(|| eyre::eyre!("{:?} has no version", path))?;
let version = version
.replace("pragma solidity ", "")
.replace(";", "")
// needed to make it valid semver for things like
// >=0.4.0 <0.5.0
.replace(" ", ",");

Ok(VersionReq::parse(&version)?)
}

/// Find a matching local installation for the specified required version
fn find_matching_installation(
&self,
versions: &[Version],
required_version: &VersionReq,
) -> Option<Version> {
versions
.iter()
// filter these out, unneeded artifact from solc-vm-rs
// .filter(|&version| version != ".global-version")
.find(|version| required_version.matches(version))
.cloned()
}

/// Given a Solidity file, it detects the latest compiler version which can be used
/// to build it, and returns it along with its canonicalized path. If the required
/// compiler version is not installed, it also proceeds to install it.
fn detect_version(&mut self, fname: PathBuf) -> Result<Option<(Version, String)>> {
let path = std::fs::canonicalize(&fname)?;

// detects the required solc version
let sol_version = Self::version_req(&path)?;

let path_str = path
.into_os_string()
.into_string()
.map_err(|_| eyre::eyre!("invalid path, maybe not utf-8?"))?;

// use the installed one, install it if it does not exist
let res = self
.find_matching_installation(&self.versions, &sol_version)
.or_else(|| {
// Check upstream for a matching install
self.find_matching_installation(&self.releases, &sol_version)
.map(|version| {
println!("Installing {}", version);
// Blocking call to install it over RPC.
tokio::runtime::Runtime::new()
.unwrap()
.block_on(svm::install(&version))
.unwrap();
self.versions.push(version.clone());
println!("Done!");
version
})
})
.map(|version| (version, path_str));

Ok(res)
}

/// Builds all provided contract files with the specified compiler version.
/// Assumes that the lib-paths and remappings have already been specified.
pub fn build(
&self,
version: String,
files: Vec<String>,
) -> Result<HashMap<String, CompiledContract>> {
let mut compiler_path = installed_version_paths()?
.iter()
.find(|name| name.to_string_lossy().contains(&version))
.unwrap()
.clone();
compiler_path.push(format!("solc-{}", &version));

let mut solc = Solc::new_with_paths(files).solc_path(compiler_path);
let lib_paths = self
.lib_paths
.iter()
.filter(|path| PathBuf::from(path).exists())
.map(|path| {
std::fs::canonicalize(path)
.unwrap()
.into_os_string()
.into_string()
.unwrap()
})
.collect::<Vec<_>>()
.join(",");
solc = solc.args(["--allow-paths", &lib_paths]);

if !self.remappings.is_empty() {
solc = solc.args(self.remappings)
}

Ok(solc.build()?)
}

/// Builds all contracts with their corresponding compiler versions
pub fn build_all(&mut self) -> Result<HashMap<String, CompiledContract>> {
let contracts_by_version = self.contract_versions()?;
contracts_by_version
.into_iter()
.try_fold(HashMap::new(), |mut map, (version, files)| {
let res = self.build(version, files)?;
map.extend(res);
Ok::<_, eyre::Error>(map)
})
}
}

impl<'a> MultiContractRunner<'a> {
pub fn build(
contracts: &str,
Expand All @@ -316,114 +482,7 @@ impl<'a> MultiContractRunner<'a> {
let out_file = std::fs::read_to_string(out_path)?;
serde_json::from_str::<DapptoolsArtifact>(&out_file)?.contracts()?
} else {
// Group contracts in the nones with the same version pragma
let files = glob::glob(contracts)?;

let mut contracts_by_version = HashMap::new();
let versions = svm::installed_versions()?;
let releases = tokio::runtime::Runtime::new()?.block_on(svm::all_versions())?;
for fname in files {
let path = std::fs::canonicalize(fname?)
.unwrap()
.into_os_string()
.into_string()
.unwrap();
use std::fs::File;
use std::io::{BufRead, BufReader};

// parse the version from the contract
let sol_version = {
use semver::VersionReq;
let file = BufReader::new(File::open(&path)?);
let version = file
.lines()
.map(|line| line.unwrap())
.find(|line| line.starts_with("pragma"))
.ok_or_else(|| eyre::eyre!("{} has no version", path))?;
let version = version
.replace("pragma solidity ", "")
.replace(";", "")
// needed to make it valid semver
.replace(" ", ",");

VersionReq::parse(&version)?
};

let mut found_version = None;
for version in &versions {
if version == ".global-version" {
continue;
}
if sol_version.matches(&version.parse().unwrap()) {
found_version = Some(version.clone());
}
}

// if we don't have the version, we'll try to install it from the
// available releases
if found_version.is_none() {
let mut upstream_version = None;
for version in &releases {
if version == ".global-version" {
continue;
}
if sol_version.matches(&version.parse().unwrap()) {
upstream_version = Some(version.clone());
}
}

// TODO: This blocks the thread, we obviously do not want that
if let Some(upstream_version) = upstream_version {
println!("Installing {}", upstream_version);
tokio::runtime::Runtime::new()?
.block_on(svm::install(&upstream_version))?;
println!("Done!");
found_version = Some(upstream_version);
}
}

// we found a version
if let Some(found_version) = found_version {
let entry = contracts_by_version.entry(found_version).or_insert(vec![]);
entry.push(path);
}
}

let mut res = HashMap::new();
let paths = installed_version_paths()?;
for (version, files) in contracts_by_version {
// override the version for the loop
svm::use_version(&version)?;

let mut compiler_path = paths
.iter()
.find(|name| name.to_string_lossy().contains(&version))
.unwrap()
.clone();
compiler_path.push(format!("solc-{}", &version));

let mut solc = Solc::new_with_paths(files).solc_path(compiler_path);
let lib_paths = lib_paths
.iter()
.filter(|path| !path.is_empty())
.map(|path| {
std::fs::canonicalize(path)
.unwrap()
.into_os_string()
.into_string()
.unwrap()
})
.collect::<Vec<_>>()
.join(",");
solc = solc.args(["--allow-paths", &lib_paths]);

if !remappings.is_empty() {
solc = solc.args(&remappings)
}
res.extend(solc.build()?);
}

res
SolcBuilder::new(contracts, &remappings, &lib_paths)?.build_all()?
})
}

Expand Down

0 comments on commit 4843a16

Please sign in to comment.