diff --git a/Cargo.toml b/Cargo.toml index 883468e7..bb8c10c7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,6 +56,7 @@ tempfile = { version = "3.8.0", optional = true } fs_extra = { version = "1.3.0", optional = true } rand = { version = "0.8", optional = true } futures-util = { version = "0.3.28", optional = true } +memmap2 = "0.9.0" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] home = "0.5.5" diff --git a/src/artifact_output/mod.rs b/src/artifact_output/mod.rs index 3ec8b2d9..94f44d5c 100644 --- a/src/artifact_output/mod.rs +++ b/src/artifact_output/mod.rs @@ -104,9 +104,7 @@ impl ArtifactFile { pub fn write(&self) -> Result<()> { trace!("writing artifact file {:?} {}", self.file, self.version); utils::create_parent_dir_all(&self.file)?; - fs::write(&self.file, serde_json::to_vec_pretty(&self.artifact)?) - .map_err(|err| SolcError::io(err, &self.file))?; - Ok(()) + utils::write_json_file(&self.artifact, &self.file, 64 * 1024) } } diff --git a/src/cache.rs b/src/cache.rs index 71bb41cf..54c6f765 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -15,8 +15,7 @@ use std::{ btree_map::{BTreeMap, Entry}, hash_map, BTreeSet, HashMap, HashSet, }, - fs::{self}, - io::Write, + fs, path::{Path, PathBuf}, time::{Duration, UNIX_EPOCH}, }; @@ -133,16 +132,13 @@ impl SolFilesCache { /// Write the cache as json file to the given path pub fn write(&self, path: impl AsRef) -> Result<()> { let path = path.as_ref(); - utils::create_parent_dir_all(path)?; - let file = fs::File::create(path).map_err(|err| SolcError::io(err, path))?; tracing::trace!( "writing cache with {} entries to json file: \"{}\"", self.len(), path.display() ); - let mut writer = std::io::BufWriter::with_capacity(1024 * 256, file); - serde_json::to_writer_pretty(&mut writer, self)?; - writer.flush().map_err(|e| SolcError::io(e, path))?; + utils::create_parent_dir_all(path)?; + utils::write_json_file(self, path, 128 * 1024)?; tracing::trace!("cache file located: \"{}\"", path.display()); Ok(()) } @@ -370,17 +366,29 @@ impl SolFilesCache { #[cfg(feature = "async")] impl SolFilesCache { pub async fn async_read(path: impl AsRef) -> Result { - let path = path.as_ref(); - let content = - tokio::fs::read_to_string(path).await.map_err(|err| SolcError::io(err, path))?; - Ok(serde_json::from_str(&content)?) + let path = path.as_ref().to_owned(); + Self::asyncify(move || Self::read(path)).await } pub async fn async_write(&self, path: impl AsRef) -> Result<()> { let path = path.as_ref(); - let content = serde_json::to_vec_pretty(self)?; + let content = serde_json::to_vec(self)?; tokio::fs::write(path, content).await.map_err(|err| SolcError::io(err, path)) } + + async fn asyncify(f: F) -> Result + where + F: FnOnce() -> Result + Send + 'static, + T: Send + 'static, + { + match tokio::task::spawn_blocking(f).await { + Ok(res) => res, + Err(_) => Err(SolcError::io( + std::io::Error::new(std::io::ErrorKind::Other, "background task failed"), + "", + )), + } + } } impl Default for SolFilesCache { diff --git a/src/error.rs b/src/error.rs index fd4cb1b0..5cd823c7 100644 --- a/src/error.rs +++ b/src/error.rs @@ -11,15 +11,15 @@ pub type Result = std::result::Result; #[derive(Debug, Error)] pub enum SolcError { /// Errors related to the Solc executable itself. - #[error("Solc exited with {0}\n{1}")] + #[error("solc exited with {0}\n{1}")] SolcError(std::process::ExitStatus, String), - #[error("Missing pragma from solidity file")] + #[error("missing pragma from Solidity file")] PragmaNotFound, - #[error("Could not find solc version locally or upstream")] + #[error("could not find Solc version locally or upstream")] VersionNotFound, - #[error("Checksum mismatch for {file}: expected {expected} found {detected} for {version}")] + #[error("checksum mismatch for {file}: expected {expected} found {detected} for {version}")] ChecksumMismatch { version: Version, expected: String, detected: String, file: PathBuf }, - #[error("Checksum not found for {version}")] + #[error("checksum not found for {version}")] ChecksumNotFound { version: Version }, #[error(transparent)] SemverError(#[from] semver::Error), @@ -29,12 +29,12 @@ pub enum SolcError { /// Filesystem IO error #[error(transparent)] Io(#[from] SolcIoError), - #[error("File could not be resolved due to broken symlink: {0}.")] + #[error("file could not be resolved due to broken symlink: {0}")] ResolveBadSymlink(SolcIoError), /// Failed to resolve a file - #[error("Failed to resolve file: {0}.\n Check configured remappings.")] + #[error("failed to resolve file: {0}; check configured remappings")] Resolve(SolcIoError), - #[error("File cannot be resolved due to mismatch of file name case: {error}.\n Found existing file: {existing_file:?}\n Please check the case of the import.")] + #[error("file cannot be resolved due to mismatch of file name case: {error}.\nFound existing file: {existing_file:?}\nPlease check the case of the import.")] ResolveCaseSensitiveFileName { error: SolcIoError, existing_file: PathBuf }, #[error( r#"{0}. @@ -45,7 +45,7 @@ pub enum SolcError { #[cfg(all(feature = "svm-solc", not(target_arch = "wasm32")))] #[error(transparent)] SvmError(#[from] svm::SolcVmError), - #[error("No contracts found at \"{0}\"")] + #[error("no contracts found at \"{0}\"")] NoContracts(String), #[error(transparent)] PatternError(#[from] glob::PatternError), @@ -53,7 +53,7 @@ pub enum SolcError { #[error("{0}")] Message(String), - #[error("No artifact found for `{}:{}`", .0.display(), .1)] + #[error("no artifact found for `{}:{}`", .0.display(), .1)] ArtifactNotFound(PathBuf, String), #[cfg(feature = "project-util")] diff --git a/src/utils.rs b/src/utils.rs index 30637f77..e0810683 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,17 +1,18 @@ //! Utility functions +use crate::{error::SolcError, SolcIoError}; use cfg_if::cfg_if; +use once_cell::sync::Lazy; +use regex::{Match, Regex}; +use semver::Version; +use serde::{de::DeserializeOwned, Serialize}; use std::{ collections::HashSet, + fs, + io::Write, ops::Range, path::{Component, Path, PathBuf}, }; - -use crate::{error::SolcError, SolcIoError}; -use once_cell::sync::Lazy; -use regex::{Match, Regex}; -use semver::Version; -use serde::de::DeserializeOwned; use tiny_keccak::{Hasher, Keccak}; use walkdir::WalkDir; @@ -441,25 +442,41 @@ impl RuntimeOrHandle { } } -/// Creates a new named tempdir +/// Creates a new named tempdir. #[cfg(any(test, feature = "project-util"))] pub(crate) fn tempdir(name: &str) -> Result { tempfile::Builder::new().prefix(name).tempdir().map_err(|err| SolcIoError::new(err, name)) } -/// Reads the json file and deserialize it into the provided type +/// Reads the json file and deserialize it into the provided type. pub fn read_json_file(path: impl AsRef) -> Result { let path = path.as_ref(); - let contents = std::fs::read_to_string(path).map_err(|err| SolcError::io(err, path))?; - serde_json::from_str(&contents).map_err(Into::into) + // See: https://github.com/serde-rs/json/issues/160 + let file = fs::File::open(path).map_err(|err| SolcError::io(err, path))?; + let bytes = unsafe { memmap2::Mmap::map(&file).map_err(|err| SolcError::io(err, path))? }; + serde_json::from_slice(&bytes).map_err(Into::into) +} + +/// Writes serializes the provided value to JSON and writes it to a file. +pub fn write_json_file( + value: &T, + path: impl AsRef, + capacity: usize, +) -> Result<(), SolcError> { + let path = path.as_ref(); + let file = fs::File::create(path).map_err(|err| SolcError::io(err, path))?; + let mut writer = std::io::BufWriter::with_capacity(capacity, file); + serde_json::to_writer(&mut writer, value)?; + writer.flush().map_err(|e| SolcError::io(e, path)) } -/// Creates the parent directory of the `file` and all its ancestors if it does not exist -/// See [`std::fs::create_dir_all()`] +/// Creates the parent directory of the `file` and all its ancestors if it does not exist. +/// +/// See [`fs::create_dir_all()`]. pub fn create_parent_dir_all(file: impl AsRef) -> Result<(), SolcError> { let file = file.as_ref(); if let Some(parent) = file.parent() { - std::fs::create_dir_all(parent).map_err(|err| { + fs::create_dir_all(parent).map_err(|err| { SolcError::msg(format!( "Failed to create artifact parent folder \"{}\": {}", parent.display(), @@ -488,7 +505,7 @@ mod tests { create_dir_all(&path).unwrap(); let existing = path.join("Test.sol"); let non_existing = path.join("test.sol"); - std::fs::write(&existing, b"").unwrap(); + fs::write(&existing, b"").unwrap(); #[cfg(target_os = "linux")] assert!(!non_existing.exists()); @@ -505,7 +522,7 @@ mod tests { create_dir_all(&path).unwrap(); let existing = path.join("Test.sol"); let non_existing = path.join("test.sol"); - std::fs::write( + fs::write( existing, " pragma solidity ^0.8.10;