Scaffold utility and library (#3)

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
Signed-off-by: xermicus <bigcyrill@hotmail.com>
This commit is contained in:
xermicus
2025-03-31 11:40:05 +02:00
committed by GitHub
parent 4b7af83be6
commit c590fa7bfd
36 changed files with 6720 additions and 0 deletions
+71
View File
@@ -0,0 +1,71 @@
//! Helper for caching the solc binaries.
use std::{
collections::HashSet,
fs::{File, create_dir_all},
io::{BufWriter, Write},
os::unix::fs::PermissionsExt,
path::{Path, PathBuf},
sync::{LazyLock, Mutex},
};
use crate::download::GHDownloader;
pub const SOLC_CACHE_DIRECTORY: &str = "solc";
pub(crate) static SOLC_CACHER: LazyLock<Mutex<HashSet<PathBuf>>> = LazyLock::new(Default::default);
pub(crate) fn get_or_download(
working_directory: &Path,
downloader: &GHDownloader,
) -> anyhow::Result<PathBuf> {
let target_directory = working_directory
.join(SOLC_CACHE_DIRECTORY)
.join(downloader.version.to_string());
let target_file = target_directory.join(downloader.target);
let mut cache = SOLC_CACHER.lock().unwrap();
if cache.contains(&target_file) {
log::debug!("using cached solc: {}", target_file.display());
return Ok(target_file);
}
create_dir_all(target_directory)?;
download_to_file(&target_file, downloader)?;
cache.insert(target_file.clone());
Ok(target_file)
}
fn download_to_file(path: &Path, downloader: &GHDownloader) -> anyhow::Result<()> {
log::info!("caching file: {}", path.display());
let Ok(file) = File::create_new(path) else {
log::debug!("cache file already exists: {}", path.display());
return Ok(());
};
#[cfg(unix)]
{
let mut permissions = file.metadata()?.permissions();
permissions.set_mode(permissions.mode() | 0o111);
file.set_permissions(permissions)?;
}
let mut file = BufWriter::new(file);
file.write_all(&downloader.download()?)?;
file.flush()?;
drop(file);
#[cfg(target_os = "macos")]
std::process::Command::new("xattr")
.arg("-d")
.arg("com.apple.quarantine")
.arg(&path)
.stderr(std::process::Stdio::null())
.stdout(std::process::Stdio::null())
.stdout(std::process::Stdio::null())
.spawn()?
.wait()?;
Ok(())
}
+146
View File
@@ -0,0 +1,146 @@
//! This module downloads solc binaries.
use std::{
collections::HashMap,
sync::{LazyLock, Mutex},
};
use semver::Version;
use sha2::{Digest, Sha256};
use crate::list::List;
pub static LIST_CACHE: LazyLock<Mutex<HashMap<&'static str, List>>> =
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 fn download(url: &'static str) -> anyhow::Result<Self> {
if let Some(list) = LIST_CACHE.lock().unwrap().get(url) {
return Ok(list.clone());
}
let body: List = reqwest::blocking::get(url)?.json()?;
LIST_CACHE.lock().unwrap().insert(url, body.clone());
Ok(body)
}
}
/// Download solc binaries from GitHub releases (IPFS links aren't reliable).
#[derive(Clone, Debug)]
pub struct GHDownloader {
pub version: Version,
pub target: &'static str,
pub list: &'static str,
}
impl GHDownloader {
pub const BASE_URL: &str = "https://github.com/ethereum/solidity/releases/download";
pub const LINUX_NAME: &str = "solc-static-linux";
pub const MACOSX_NAME: &str = "solc-macos";
pub const WINDOWS_NAME: &str = "solc-windows.exe";
pub const WASM_NAME: &str = "soljson.js";
fn new(version: Version, target: &'static str, list: &'static str) -> Self {
Self {
version,
target,
list,
}
}
pub fn linux(version: Version) -> Self {
Self::new(version, Self::LINUX_NAME, List::LINUX_URL)
}
pub fn macosx(version: Version) -> Self {
Self::new(version, Self::MACOSX_NAME, List::MACOSX_URL)
}
pub fn windows(version: Version) -> Self {
Self::new(version, Self::WINDOWS_NAME, List::WINDOWS_URL)
}
pub fn wasm(version: Version) -> Self {
Self::new(version, Self::WASM_NAME, List::WASM_URL)
}
/// Returns the download link.
pub fn url(&self) -> String {
format!("{}/v{}/{}", Self::BASE_URL, &self.version, &self.target)
}
/// 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 fn download(&self) -> anyhow::Result<Vec<u8>> {
log::info!("downloading solc: {self:?}");
let expected_digest = List::download(self.list)?
.builds
.iter()
.find(|build| build.version == self.version)
.ok_or_else(|| anyhow::anyhow!("solc v{} not found builds", self.version))
.map(|b| b.sha256.strip_prefix("0x").unwrap_or(&b.sha256).to_string())?;
let file = reqwest::blocking::get(self.url())?.bytes()?.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::GHDownloader, list::List};
#[test]
fn try_get_windows() {
let version = List::download(List::WINDOWS_URL)
.unwrap()
.latest_release
.into();
GHDownloader::windows(version).download().unwrap();
}
#[test]
fn try_get_macosx() {
let version = List::download(List::MACOSX_URL)
.unwrap()
.latest_release
.into();
GHDownloader::macosx(version).download().unwrap();
}
#[test]
fn try_get_linux() {
let version = List::download(List::LINUX_URL)
.unwrap()
.latest_release
.into();
GHDownloader::linux(version).download().unwrap();
}
#[test]
fn try_get_wasm() {
let version = List::download(List::WASM_URL)
.unwrap()
.latest_release
.into();
GHDownloader::wasm(version).download().unwrap();
}
}
+39
View File
@@ -0,0 +1,39 @@
//! This crates provides serializable Rust type definitions for the [solc binary lists][0]
//! and download helpers.
//!
//! [0]: https://binaries.soliditylang.org
use std::path::{Path, PathBuf};
use cache::get_or_download;
use download::GHDownloader;
use semver::Version;
pub mod cache;
pub mod download;
pub mod list;
/// Downloads the solc binary for Wasm is `wasm` is set, otherwise for
/// the target platform.
///
/// Subsequent calls for the same version will use a cached artifact
/// and not download it again.
pub fn download_solc(
cache_directory: &Path,
version: Version,
wasm: bool,
) -> anyhow::Result<PathBuf> {
let downloader = if wasm {
GHDownloader::wasm(version)
} else if cfg!(target_os = "linux") {
GHDownloader::linux(version)
} else if cfg!(target_os = "macos") {
GHDownloader::macosx(version)
} else if cfg!(target_os = "windows") {
GHDownloader::windows(version)
} else {
unimplemented!()
};
get_or_download(cache_directory, &downloader)
}
+26
View File
@@ -0,0 +1,26 @@
//! Rust type definitions for the solc binary lists.
use std::{collections::HashMap, path::PathBuf};
use semver::Version;
use serde::Deserialize;
#[derive(Debug, Deserialize, Clone, Eq, PartialEq)]
pub struct List {
pub builds: Vec<Build>,
pub releases: HashMap<Version, String>,
#[serde(rename = "latestRelease")]
pub latest_release: Version,
}
#[derive(Debug, Deserialize, Clone, Eq, PartialEq)]
pub struct Build {
pub path: PathBuf,
pub version: Version,
pub build: String,
#[serde(rename = "longVersion")]
pub long_version: String,
pub keccak256: String,
pub sha256: String,
pub urls: Vec<String>,
}