Skip to content

Commit

Permalink
cache compiled files
Browse files Browse the repository at this point in the history
  • Loading branch information
nbaztec committed Dec 13, 2023
1 parent e2bc191 commit a23a921
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 57 deletions.
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ tokio = "1"
tracing.workspace = true
url = "2"
walkdir = "2"
xxhash-rust = { version = "0.8.7", features = ["const_xxh3"] }
yansi = "0.5"

[dev-dependencies]
Expand Down
171 changes: 124 additions & 47 deletions crates/common/src/zk_compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,26 @@ use std::{
collections::{BTreeMap, HashMap, HashSet},
fmt, fs,
fs::File,
io::Write,
io::{Read, Write},
path::{Path, PathBuf},
process::{exit, Command, Stdio},
};

#[derive(Debug, Default, Clone)]
pub struct ZkSolcArtifactPaths {
artifact: PathBuf,
contract_hash: PathBuf,
}

impl ZkSolcArtifactPaths {
pub fn new(filename: PathBuf) -> Self {
Self {
artifact: filename.clone().join("artifacts.json"),
contract_hash: filename.join("contract_hash"),
}
}
}

#[derive(Debug, Clone)]
pub struct ZkSolcOpts {
pub compiler_path: PathBuf,
Expand Down Expand Up @@ -289,61 +304,78 @@ impl ZkSolc {
// Step 4: Build Compiler Arguments
let comp_args = self.build_compiler_args(&contract_path, &self.project.solc);

// Step 5: Run Compiler and Handle Output
let mut cmd = Command::new(&self.compiler_path);
let mut child = cmd
.args(&comp_args)
.stdin(Stdio::piped())
.stderr(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.wrap_err("Failed to start the compiler")?;

let stdin = child.stdin.take().expect("Stdin exists.");

serde_json::to_writer(stdin, &self.standard_json.clone().unwrap())
.wrap_err("Could not assign standard_json to writer")?;

let output = child.wait_with_output().wrap_err("Could not run compiler cmd")?;

if !output.status.success() {
// Skip this file if the compiler output is empty
// currently zksolc returns false for success if output is empty
// when output is empty, it has a length of 3, `[]\n`
// solc returns true for success if output is empty
if output.stderr.len() <= 3 {
continue
}
eyre::bail!(
"Compilation failed with {:?}. Using compiler: {:?}, with args {:?} {:?}",
String::from_utf8(output.stderr).unwrap_or_default(),
self.compiler_path,
contract_path,
&comp_args
);
}
// Hash the input contract to allow caching
let mut contract_file = File::open(&contract_path)?;
let mut buffer = Vec::new();
contract_file.read_to_end(&mut buffer)?;
let contract_hash =
hex::encode(xxhash_rust::const_xxh3::xxh3_64(&buffer).to_be_bytes());

let filename = contract_path
.file_name()
.wrap_err(format!("Could not get filename from {:?}", contract_path))?
.to_str()
.unwrap()
.to_string();
let artifact_paths =
ZkSolcArtifactPaths::new(self.project.paths.artifacts.join(&filename));

// Step 5: Run Compiler (or use cached) and Handle Output
let (output, maybe_artifact_paths) = match self
.check_cache(&artifact_paths, &contract_hash)
{
Some(output) => {
info!("Using hashed artifact ({}) for {:?}", contract_hash, filename);
(output, None)
}
None => {
let mut cmd = Command::new(&self.compiler_path);
let mut child = cmd
.args(&comp_args)
.stdin(Stdio::piped())
.stderr(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.wrap_err("Failed to start the compiler")?;

let stdin = child.stdin.take().expect("Stdin exists.");

serde_json::to_writer(stdin, &self.standard_json.clone().unwrap())
.wrap_err("Could not assign standard_json to writer")?;

let output =
child.wait_with_output().wrap_err("Could not run compiler cmd")?;

if !output.status.success() {
// Skip this file if the compiler output is empty
// currently zksolc returns false for success if output is empty
// when output is empty, it has a length of 3, `[]\n`
// solc returns true for success if output is empty
if output.stderr.len() <= 3 {
continue
}
eyre::bail!(
"Compilation failed with {:?}. Using compiler: {:?}, with args {:?} {:?}",
String::from_utf8(output.stderr).unwrap_or_default(),
self.compiler_path,
contract_path,
&comp_args
);
}

(output.stdout, Some(artifact_paths))
}
};

// Step 6: Handle Output (Errors and Warnings)
data.insert(
filename.clone(),
ZkSolc::handle_output(
output.stdout,
output,
&filename,
&mut displayed_warnings,
Some(
self.project
.paths
.artifacts
.join(filename.clone())
.join("artifacts.json"),
),
&contract_hash,
maybe_artifact_paths,
),
);
}
Expand All @@ -353,6 +385,39 @@ impl ZkSolc {
Ok(result)
}

/// Checks if the contract has already been compiled, and if yes then returns the compiled data.
/// The contents will not be persisted again.
fn check_cache(
&self,
artifact_paths: &ZkSolcArtifactPaths,
contract_hash: &str,
) -> Option<Vec<u8>> {
if artifact_paths.contract_hash.exists() && artifact_paths.artifact.exists() {
File::open(&artifact_paths.contract_hash)
.and_then(|mut file| {
let mut cached_contract_hash = String::new();
file.read_to_string(&mut cached_contract_hash).map(|_| cached_contract_hash)
})
.and_then(|cached_contract_hash| {
if cached_contract_hash == contract_hash {
Ok(Some(contract_hash))
} else {
Err(std::io::Error::new(std::io::ErrorKind::Other, "hashes do not match"))
}
})
.and_then(|_| {
File::open(&artifact_paths.artifact).and_then(|mut file| {
let mut buffer = Vec::new();
file.read_to_end(&mut buffer).map(|_| Some(buffer))
})
})
.ok()
.flatten()
} else {
None
}
}

/// Builds the compiler arguments for the Solidity compiler based on the provided versioned
/// source and solc instance. The compiler arguments specify options and settings for the
/// compiler's execution.
Expand Down Expand Up @@ -430,9 +495,10 @@ impl ZkSolc {
/// errors and warnings, and saves the artifacts.
pub fn handle_output(
output: Vec<u8>,
source: &String,
source: &str,
displayed_warnings: &mut HashSet<String>,
write_artifacts: Option<PathBuf>,
contract_hash: &str,
write_artifacts: Option<ZkSolcArtifactPaths>,
) -> BTreeMap<String, Vec<ArtifactFile<ConfigurableContractArtifact>>> {
// Deserialize the compiler output into a serde_json::Value object
let compiler_output: ZkSolcCompilerOutput =
Expand Down Expand Up @@ -473,7 +539,7 @@ impl ZkSolc {
continue
}

println!(
info!(
"{} -> Bytecode Hash: {} ",
contract_name,
contract.hash.as_ref().unwrap()
Expand Down Expand Up @@ -537,13 +603,24 @@ impl ZkSolc {
.unwrap_or_else(|e| panic!("Could not beautify zksolc compiler output: {}", e));

// Create the artifacts file for saving the compiler output
let mut artifacts_file =
File::create(write_artifacts).wrap_err("Could not create artifacts file").unwrap();
let mut artifacts_file = File::create(write_artifacts.artifact)
.wrap_err("Could not create artifacts file")
.unwrap();

// Write the beautified output JSON to the artifacts file
artifacts_file
.write_all(output_json_pretty.as_bytes())
.unwrap_or_else(|e| panic!("Could not write artifacts file: {}", e));

// Create the contract_hash file for saving the input contract hash
let mut contract_hash_file = File::create(write_artifacts.contract_hash)
.wrap_err("Could not create contract_hash file")
.unwrap();

// Write the contract's file hash to the contract_hash file
contract_hash_file
.write_all(contract_hash.as_bytes())
.unwrap_or_else(|e| panic!("Could not write contract_hash file: {}", e));
}

result
Expand Down
9 changes: 0 additions & 9 deletions crates/era-cheatcodes/tests/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,6 @@ SOLC_VERSION=${SOLC_VERSION:-"v0.8.20"}
SOLC="solc-${SOLC_VERSION}"
BINARY_PATH="${REPO_ROOT}/target/release/zkforge"

function cleanup() {
echo "Cleaning up..."
rm "./${SOLC}"
}

function download_solc() {
case "$(uname -s)" in
Darwin*) arch=macos ;;
Expand Down Expand Up @@ -44,8 +39,6 @@ function build_zkforge() {
wait_for_build 30
}

# trap cleanup ERR

echo "Solc: ${SOLC_VERSION}"
echo "Zkforge binary: ${BINARY_PATH}"

Expand All @@ -66,5 +59,3 @@ build_zkforge "${REPO_ROOT}"

echo "Running tests..."
RUST_LOG=debug "${BINARY_PATH}" test --use "./${SOLC}"

# cleanup
2 changes: 1 addition & 1 deletion crates/evm/core/src/era_revm/transactions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ where

let nonces = era_db.get_nonce_for_address(address_to_h160(env.tx.caller));

println!(
debug!(
"*** Starting ERA transaction: block: {:?} timestamp: {:?} - but using {:?} and {:?} instead with nonce {:?}",
env.block.number.to::<u32>(),
env.block.timestamp.to::<u64>(),
Expand Down

0 comments on commit a23a921

Please sign in to comment.