//! This module downloads solc binaries. use std::{ collections::HashMap, sync::{LazyLock, Mutex}, }; use revive_dt_common::types::VersionOrRequirement; use semver::Version; use sha2::{Digest, Sha256}; use crate::list::List; pub static LIST_CACHE: LazyLock>> = LazyLock::new(Default::default); impl List { pub const LINUX_URL: &str = "https://binaries.soliditylang.org/linux-amd64/list.json"; pub const WINDOWS_URL: &str = "https://binaries.soliditylang.org/windows-amd64/list.json"; pub const MACOSX_URL: &str = "https://binaries.soliditylang.org/macosx-amd64/list.json"; pub const WASM_URL: &str = "https://binaries.soliditylang.org/wasm/list.json"; /// Try to downloads the list from the given URL. /// /// Caches the list retrieved from the `url` into [LIST_CACHE], /// subsequent calls with the same `url` will return the cached list. pub async fn download(url: &'static str) -> anyhow::Result { if let Some(list) = LIST_CACHE.lock().unwrap().get(url) { return Ok(list.clone()); } let body: List = reqwest::get(url).await?.json().await?; LIST_CACHE.lock().unwrap().insert(url, body.clone()); Ok(body) } } /// Download solc binaries from the official SolidityLang site #[derive(Clone, Debug)] pub struct SolcDownloader { pub version: Version, pub target: &'static str, pub list: &'static str, } impl SolcDownloader { pub const BASE_URL: &str = "https://binaries.soliditylang.org"; pub const LINUX_NAME: &str = "linux-amd64"; pub const MACOSX_NAME: &str = "macosx-amd64"; pub const WINDOWS_NAME: &str = "windows-amd64"; pub const WASM_NAME: &str = "wasm"; async fn new( version: impl Into, target: &'static str, list: &'static str, ) -> anyhow::Result { let version_or_requirement = version.into(); match version_or_requirement { VersionOrRequirement::Version(version) => Ok(Self { version, target, list, }), VersionOrRequirement::Requirement(requirement) => { let Some(version) = List::download(list) .await? .builds .into_iter() .map(|build| build.version) .filter(|version| requirement.matches(version)) .max() else { anyhow::bail!("Failed to find a version that satisfies {requirement:?}"); }; Ok(Self { version, target, list, }) } } } pub async fn linux(version: impl Into) -> anyhow::Result { Self::new(version, Self::LINUX_NAME, List::LINUX_URL).await } pub async fn macosx(version: impl Into) -> anyhow::Result { Self::new(version, Self::MACOSX_NAME, List::MACOSX_URL).await } pub async fn windows(version: impl Into) -> anyhow::Result { Self::new(version, Self::WINDOWS_NAME, List::WINDOWS_URL).await } pub async fn wasm(version: impl Into) -> anyhow::Result { Self::new(version, Self::WASM_NAME, List::WASM_URL).await } /// Download the solc binary. /// /// Errors out if the download fails or the digest of the downloaded file /// mismatches the expected digest from the release [List]. pub async fn download(&self) -> anyhow::Result> { tracing::info!("downloading solc: {self:?}"); let builds = List::download(self.list).await?.builds; let build = builds .iter() .find(|build| build.version == self.version) .ok_or_else(|| anyhow::anyhow!("solc v{} not found builds", self.version))?; let path = build.path.clone(); let expected_digest = build .sha256 .strip_prefix("0x") .unwrap_or(&build.sha256) .to_string(); let url = format!("{}/{}/{}", Self::BASE_URL, self.target, path.display()); let file = reqwest::get(url).await?.bytes().await?.to_vec(); if hex::encode(Sha256::digest(&file)) != expected_digest { anyhow::bail!("sha256 mismatch for solc version {}", self.version); } Ok(file) } } #[cfg(test)] mod tests { use crate::{download::SolcDownloader, list::List}; #[tokio::test] async fn try_get_windows() { let version = List::download(List::WINDOWS_URL) .await .unwrap() .latest_release; SolcDownloader::windows(version) .await .unwrap() .download() .await .unwrap(); } #[tokio::test] async fn try_get_macosx() { let version = List::download(List::MACOSX_URL) .await .unwrap() .latest_release; SolcDownloader::macosx(version) .await .unwrap() .download() .await .unwrap(); } #[tokio::test] async fn try_get_linux() { let version = List::download(List::LINUX_URL) .await .unwrap() .latest_release; SolcDownloader::linux(version) .await .unwrap() .download() .await .unwrap(); } #[tokio::test] async fn try_get_wasm() { let version = List::download(List::WASM_URL).await.unwrap().latest_release; SolcDownloader::wasm(version) .await .unwrap() .download() .await .unwrap(); } }