Skip to content

Commit

Permalink
feat(solc): improve error diagnostic (#2280)
Browse files Browse the repository at this point in the history
  • Loading branch information
mattsse committed Mar 19, 2023
1 parent d5831b2 commit 279280c
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 8 deletions.
69 changes: 61 additions & 8 deletions ethers-solc/src/resolver/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@
//! [version pragma](https://docs.soliditylang.org/en/develop/layout-of-source-files.html#version-pragma),
//! which is defined on a per source file basis.

use crate::{error::Result, utils, IncludePaths, ProjectPathsConfig, SolcError, Source, Sources};
use crate::{
error::Result, utils, IncludePaths, ProjectPathsConfig, SolcError, SolcVersion, Source, Sources,
};
use parse::{SolData, SolDataUnit, SolImport};
use rayon::prelude::*;
use semver::VersionReq;
Expand Down Expand Up @@ -577,8 +579,7 @@ impl Graph {
// on first error, instead gather all the errors and return a bundled error message instead
let mut errors = Vec::new();
// we also don't want duplicate error diagnostic
let mut erroneous_nodes =
std::collections::HashSet::with_capacity(self.edges.num_input_files);
let mut erroneous_nodes = HashSet::with_capacity(self.edges.num_input_files);

// the sorted list of all versions
let all_versions = if offline { Solc::installed_versions() } else { Solc::all_versions() };
Expand All @@ -596,11 +597,20 @@ impl Graph {
self.retain_compatible_versions(idx, &mut candidates);

if candidates.is_empty() && !erroneous_nodes.contains(&idx) {
let mut msg = String::new();
self.format_imports_list(idx, &mut msg).unwrap();
errors.push(format!(
"Discovered incompatible solidity versions in following\n: {msg}"
));
// check if the version is even valid
if let Some(Err(version_err)) =
self.node(idx).check_available_version(&all_versions, offline)
{
let f = utils::source_name(&self.node(idx).path, &self.root).display();
errors.push(format!("Encountered invalid solc version in {f}: {version_err}"));
} else {
let mut msg = String::new();
self.format_imports_list(idx, &mut msg).unwrap();
errors.push(format!(
"Discovered incompatible solidity versions in following\n: {msg}"
));
}

erroneous_nodes.insert(idx);
} else {
// found viable candidates, pick the most recent version that's already installed
Expand Down Expand Up @@ -888,6 +898,37 @@ impl Node {
pub fn unpack(&self) -> (&PathBuf, &Source) {
(&self.path, &self.source)
}

/// Checks that the file's version is even available.
///
/// This returns an error if the file's version is invalid semver, or is not available such as
/// 0.8.20, if the highest available version is `0.8.19`
fn check_available_version(
&self,
all_versions: &[SolcVersion],
offline: bool,
) -> Option<std::result::Result<(), SourceVersionError>> {
fn ensure_version(
v: &str,
all_versions: &[SolcVersion],
offline: bool,
) -> std::result::Result<(), SourceVersionError> {
let req: VersionReq =
v.parse().map_err(|err| SourceVersionError::InvalidVersion(v.to_string(), err))?;

if !all_versions.iter().any(|v| req.matches(v.as_ref())) {
return if offline {
Err(SourceVersionError::NoMatchingVersionOffline(req))
} else {
Err(SourceVersionError::NoMatchingVersion(req))
}
}

Ok(())
}
let v = self.data.version.as_ref()?.data();
Some(ensure_version(v, all_versions, offline))
}
}

/// Helper type for formatting a node
Expand All @@ -907,6 +948,18 @@ impl<'a> fmt::Display for DisplayNode<'a> {
}
}

/// Errors thrown when checking the solc version of a file
#[derive(Debug, thiserror::Error)]
#[allow(unused)]
enum SourceVersionError {
#[error("Failed to parse solidity version {0}: {1}")]
InvalidVersion(String, semver::Error),
#[error("No solc version exists that matches the version requirement: {0}")]
NoMatchingVersion(VersionReq),
#[error("No solc version installed that matches the version requirement: {0}")]
NoMatchingVersionOffline(VersionReq),
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
22 changes: 22 additions & 0 deletions ethers-solc/tests/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use ethers_solc::{
},
buildinfo::BuildInfo,
cache::{SolFilesCache, SOLIDITY_FILES_CACHE_FILENAME},
error::SolcError,
info::ContractInfo,
project_util::*,
remappings::Remapping,
Expand Down Expand Up @@ -1199,6 +1200,27 @@ library MyLib {
let bytecode = &contract.bytecode.as_ref().unwrap().object;
assert!(!bytecode.is_unlinked());
}

#[test]
fn can_detect_invalid_version() {
let tmp = TempProject::dapptools().unwrap();
let content = r#"
pragma solidity ^0.100.10;
contract A {}
"#;
tmp.add_source("A", content).unwrap();

let out = tmp.compile().unwrap_err();
match out {
SolcError::Message(err) => {
assert_eq!(err, "Encountered invalid solc version in src/A.sol: No solc version exists that matches the version requirement: ^0.100.10");
}
_ => {
unreachable!()
}
}
}

#[test]
fn can_recompile_with_changes() {
let mut tmp = TempProject::dapptools().unwrap();
Expand Down

0 comments on commit 279280c

Please sign in to comment.