feat(solc): improve error diagnostic (#2280)

This commit is contained in:
Matthias Seitz 2023-03-19 19:45:30 +01:00 committed by GitHub
parent d5831b2679
commit 279280c6fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 83 additions and 8 deletions

View File

@ -46,7 +46,9 @@
//! [version pragma](https://docs.soliditylang.org/en/develop/layout-of-source-files.html#version-pragma), //! [version pragma](https://docs.soliditylang.org/en/develop/layout-of-source-files.html#version-pragma),
//! which is defined on a per source file basis. //! 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 parse::{SolData, SolDataUnit, SolImport};
use rayon::prelude::*; use rayon::prelude::*;
use semver::VersionReq; use semver::VersionReq;
@ -577,8 +579,7 @@ impl Graph {
// on first error, instead gather all the errors and return a bundled error message instead // on first error, instead gather all the errors and return a bundled error message instead
let mut errors = Vec::new(); let mut errors = Vec::new();
// we also don't want duplicate error diagnostic // we also don't want duplicate error diagnostic
let mut erroneous_nodes = let mut erroneous_nodes = HashSet::with_capacity(self.edges.num_input_files);
std::collections::HashSet::with_capacity(self.edges.num_input_files);
// the sorted list of all versions // the sorted list of all versions
let all_versions = if offline { Solc::installed_versions() } else { Solc::all_versions() }; let all_versions = if offline { Solc::installed_versions() } else { Solc::all_versions() };
@ -596,11 +597,20 @@ impl Graph {
self.retain_compatible_versions(idx, &mut candidates); self.retain_compatible_versions(idx, &mut candidates);
if candidates.is_empty() && !erroneous_nodes.contains(&idx) { if candidates.is_empty() && !erroneous_nodes.contains(&idx) {
// 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(); let mut msg = String::new();
self.format_imports_list(idx, &mut msg).unwrap(); self.format_imports_list(idx, &mut msg).unwrap();
errors.push(format!( errors.push(format!(
"Discovered incompatible solidity versions in following\n: {msg}" "Discovered incompatible solidity versions in following\n: {msg}"
)); ));
}
erroneous_nodes.insert(idx); erroneous_nodes.insert(idx);
} else { } else {
// found viable candidates, pick the most recent version that's already installed // found viable candidates, pick the most recent version that's already installed
@ -888,6 +898,37 @@ impl Node {
pub fn unpack(&self) -> (&PathBuf, &Source) { pub fn unpack(&self) -> (&PathBuf, &Source) {
(&self.path, &self.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 /// Helper type for formatting a node
@ -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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@ -8,6 +8,7 @@ use ethers_solc::{
}, },
buildinfo::BuildInfo, buildinfo::BuildInfo,
cache::{SolFilesCache, SOLIDITY_FILES_CACHE_FILENAME}, cache::{SolFilesCache, SOLIDITY_FILES_CACHE_FILENAME},
error::SolcError,
info::ContractInfo, info::ContractInfo,
project_util::*, project_util::*,
remappings::Remapping, remappings::Remapping,
@ -1199,6 +1200,27 @@ library MyLib {
let bytecode = &contract.bytecode.as_ref().unwrap().object; let bytecode = &contract.bytecode.as_ref().unwrap().object;
assert!(!bytecode.is_unlinked()); 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] #[test]
fn can_recompile_with_changes() { fn can_recompile_with_changes() {
let mut tmp = TempProject::dapptools().unwrap(); let mut tmp = TempProject::dapptools().unwrap();