use std::{
collections::BTreeMap,
path::{Path, PathBuf},
time::Duration,
};
use semver::{Comparator, Prerelease, Version};
use serde::{Deserialize, Serialize};
use sha2::Digest;
use url::Url;
use crate::{constants::MIN_VERSION, errors::Error};
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Releases {
pub(crate) builds: Vec<Build>,
pub(crate) releases: BTreeMap<Version, String>,
#[serde(rename = "latestRelease")]
pub(crate) latest_release: Version,
}
impl Releases {
pub fn new(url: url::Url) -> Result<Releases, Error> {
reqwest::blocking::get(url)?.json().map_err(Into::into)
}
pub fn merge(&mut self, other: &mut Self) {
self.builds.extend_from_slice(&other.builds);
self.builds.dedup_by_key(|i| i.long_version.clone());
self.releases.append(&mut other.releases);
}
pub fn get_build(&self, version: &Version) -> Result<&Build, Error> {
self.releases
.get(version)
.and_then(|_| self.builds.iter().find(|item| item.version == *version))
.ok_or_else(|| Error::UnknownVersion {
version: version.clone(),
})
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct BinaryInfo {
pub version: Version,
pub first_supported_solc_version: Version,
pub last_supported_solc_version: Version,
}
#[derive(PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum Binary {
Local {
path: PathBuf,
info: BinaryInfo,
},
Remote(BinaryInfo),
}
impl std::fmt::Debug for Binary {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Binary::Local { path, info } => f
.debug_struct("Installed")
.field("path", path)
.field("version", &info.version.to_string())
.field(
"solc_req",
&semver::VersionReq {
comparators: vec![
Comparator {
op: semver::Op::GreaterEq,
major: info.first_supported_solc_version.major,
minor: Some(info.first_supported_solc_version.minor),
patch: Some(info.first_supported_solc_version.patch),
pre: Prerelease::default(),
},
Comparator {
op: semver::Op::LessEq,
major: info.last_supported_solc_version.major,
minor: Some(info.last_supported_solc_version.minor),
patch: Some(info.last_supported_solc_version.patch),
pre: Prerelease::default(),
},
],
}
.to_string(),
)
.finish(),
Binary::Remote(info) => f
.debug_struct("Remote")
.field("version", &info.version.to_string())
.field(
"solc_req",
&semver::VersionReq {
comparators: vec![
Comparator {
op: semver::Op::GreaterEq,
major: info.first_supported_solc_version.major,
minor: Some(info.first_supported_solc_version.minor),
patch: Some(info.first_supported_solc_version.patch),
pre: Prerelease::default(),
},
Comparator {
op: semver::Op::LessEq,
major: info.last_supported_solc_version.major,
minor: Some(info.last_supported_solc_version.minor),
patch: Some(info.last_supported_solc_version.patch),
pre: Prerelease::default(),
},
],
}
.to_string(),
)
.finish(),
}
}
}
impl Binary {
pub fn version(&self) -> &Version {
match self {
Binary::Local { info, .. } => &info.version,
Binary::Remote(info) => &info.version,
}
}
pub fn local(&self) -> Option<&Path> {
match self {
Binary::Local { path, .. } => Some(path.as_ref()),
Binary::Remote(_) => None,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Build {
pub(crate) name: String,
pub(crate) version: Version,
#[serde(rename = "longVersion")]
pub(crate) long_version: String,
pub(crate) url: Url,
#[serde(rename = "firstSolcVersion")]
pub(crate) first_supported_solc_version: Version,
#[serde(rename = "lastSolcVersion")]
pub(crate) last_supported_solc_version: Version,
pub(crate) sha256: String,
}
impl Build {
fn verify_binary(&self, bin: &[u8]) -> Result<(), Error> {
let checksum = hex::decode(&self.sha256)?;
let checksum_from_binary = {
let mut hasher: sha2::Sha256 = Digest::new();
hasher.update(bin);
hasher.finalize()
};
if checksum == checksum_from_binary[..] {
Ok(())
} else {
Err(Error::ChecksumValidationError {
expected: self.sha256.clone(),
actual: hex::encode(checksum_from_binary),
})
}
}
pub fn check_solc_compat(&self, solc_version: &Version) -> Result<(), Error> {
let version_req = semver::VersionReq {
comparators: vec![
Comparator {
op: semver::Op::GreaterEq,
major: self.first_supported_solc_version.major,
minor: Some(self.first_supported_solc_version.minor),
patch: Some(self.first_supported_solc_version.patch),
pre: Prerelease::default(),
},
Comparator {
op: semver::Op::LessEq,
major: self.last_supported_solc_version.major,
minor: Some(self.last_supported_solc_version.minor),
patch: Some(self.last_supported_solc_version.patch),
pre: Prerelease::default(),
},
],
};
if version_req.matches(solc_version) && solc_version >= &MIN_VERSION {
Ok(())
} else {
Err(Error::SolcVersionNotSupported {
solc_version: solc_version.clone(),
resolc_version: self.version.clone(),
supported_range: version_req,
})
}
}
pub fn download_binary(&self) -> Result<Vec<u8>, Error> {
let binary = reqwest::blocking::ClientBuilder::new()
.timeout(Duration::from_secs(300))
.build()?
.get(self.url.as_ref())
.send()?
.error_for_status()?;
let binary = binary.bytes()?;
self.verify_binary(binary.as_ref())?;
Ok(binary.to_vec())
}
pub(crate) fn into_local(self, path: &Path) -> Binary {
Binary::Local {
path: path.join(self.version.to_string()).join(self.name),
info: BinaryInfo {
version: self.version,
first_supported_solc_version: self.first_supported_solc_version,
last_supported_solc_version: self.last_supported_solc_version,
},
}
}
pub(crate) fn into_remote(self) -> Binary {
Binary::Remote(BinaryInfo {
version: self.version,
first_supported_solc_version: self.first_supported_solc_version,
last_supported_solc_version: self.last_supported_solc_version,
})
}
}
#[cfg(test)]
mod test {
use semver::Version;
use super::{Build, Releases};
fn release() -> &'static str {
r#"{
"builds": [
{
"name": "resolc-x86_64-unknown-linux-musl",
"version": "0.1.0-dev.13",
"build": "commit.ad331534",
"longVersion": "0.1.0-dev.13+commit.ad331534",
"url": "https://github.com/paritytech/revive/releases/download/v0.1.0-dev.13/resolc-x86_64-unknown-linux-musl",
"sha256": "14d7c165eae626dbe40d182d7f2a435015efb50b1183bf22b0411749106b8c47",
"firstSolcVersion": "0.8.0",
"lastSolcVersion": "0.8.29"
}
],
"releases": {
"0.1.0-dev.13": "resolc-x86_64-unknown-linux-musl+0.1.0-dev.13+commit.ad331534"
},
"latestRelease": "0.1.0-dev.13"
}"#
}
#[test]
fn find_version() {
let release: Releases = serde_json::from_str(release()).unwrap();
release
.get_build(&Version::parse("0.1.0-dev.13").unwrap())
.unwrap()
.check_solc_compat(&Version::new(0, 8, 0))
.unwrap()
}
#[test]
fn solc_version_support() {
let build = r#"
{
"name": "resolc-x86_64-unknown-linux-musl",
"version": "0.1.0-dev.13",
"build": "commit.ad331534",
"longVersion": "0.1.0-dev.13+commit.ad331534",
"url": "https://github.com/paritytech/revive/releases/download/v0.1.0-dev.13/resolc-x86_64-unknown-linux-musl",
"sha256": "14d7c165eae626dbe40d182d7f2a435015efb50b1183bf22b0411749106b8c47",
"firstSolcVersion": "0.8.0",
"lastSolcVersion": "0.8.29"
}
"#;
let build: Build = serde_json::from_str(build).unwrap();
assert_eq!(
r#"
Unsupported version of `solc` - v0.3.4 for Resolc v0.1.0-dev.13. Only versions ">=0.8.0, <=0.8.29" is supported by this version of Resolc
"#.trim(),
build
.check_solc_compat(&Version::new(0, 3, 4))
.expect_err("Expecting error")
.to_string()
);
}
}