diff --git a/crates/common/src/fs/clear_dir.rs b/crates/common/src/fs/clear_dir.rs index 1e6c83d..387c134 100644 --- a/crates/common/src/fs/clear_dir.rs +++ b/crates/common/src/fs/clear_dir.rs @@ -3,19 +3,28 @@ use std::{ path::Path, }; -use anyhow::Result; +use anyhow::{Context, Result}; /// This method clears the passed directory of all of the files and directories contained within /// without deleting the directory. pub fn clear_directory(path: impl AsRef) -> Result<()> { - for entry in read_dir(path.as_ref())? { - let entry = entry?; + for entry in read_dir(path.as_ref()) + .with_context(|| format!("Failed to read directory: {}", path.as_ref().display()))? + { + let entry = entry.with_context(|| { + format!( + "Failed to read an entry in directory: {}", + path.as_ref().display() + ) + })?; let entry_path = entry.path(); if entry_path.is_file() { - remove_file(entry_path)? + remove_file(&entry_path) + .with_context(|| format!("Failed to remove file: {}", entry_path.display()))? } else { - remove_dir_all(entry_path)? + remove_dir_all(&entry_path) + .with_context(|| format!("Failed to remove directory: {}", entry_path.display()))? } } Ok(()) diff --git a/crates/common/src/futures/poll.rs b/crates/common/src/futures/poll.rs index 2697541..8551cb7 100644 --- a/crates/common/src/futures/poll.rs +++ b/crates/common/src/futures/poll.rs @@ -1,7 +1,7 @@ use std::ops::ControlFlow; use std::time::Duration; -use anyhow::{Result, anyhow}; +use anyhow::{Context as _, Result, anyhow}; const EXPONENTIAL_BACKOFF_MAX_WAIT_DURATION: Duration = Duration::from_secs(60); @@ -38,7 +38,10 @@ where )); } - match future().await? { + match future() + .await + .context("Polled future returned an error during polling loop")? + { ControlFlow::Continue(()) => { let next_wait_duration = match polling_wait_behavior { PollingWaitBehavior::Constant(duration) => duration, diff --git a/crates/compiler/src/lib.rs b/crates/compiler/src/lib.rs index 05d9868..3c3e7c9 100644 --- a/crates/compiler/src/lib.rs +++ b/crates/compiler/src/lib.rs @@ -13,6 +13,7 @@ use std::{ use alloy::json_abi::JsonAbi; use alloy_primitives::Address; +use anyhow::Context; use semver::Version; use serde::{Deserialize, Serialize}; @@ -136,9 +137,10 @@ where } pub fn with_source(mut self, path: impl AsRef) -> anyhow::Result { - self.input - .sources - .insert(path.as_ref().to_path_buf(), read_to_string(path.as_ref())?); + self.input.sources.insert( + path.as_ref().to_path_buf(), + read_to_string(path.as_ref()).context("Failed to read the contract source")?, + ); Ok(self) } diff --git a/crates/compiler/src/revive_resolc.rs b/crates/compiler/src/revive_resolc.rs index 8579456..fbc66ed 100644 --- a/crates/compiler/src/revive_resolc.rs +++ b/crates/compiler/src/revive_resolc.rs @@ -119,18 +119,28 @@ impl SolidityCompiler for Resolc { .join(","), ); } - let mut child = command.spawn()?; + let mut child = command + .spawn() + .with_context(|| format!("Failed to spawn resolc at {}", self.resolc_path.display()))?; let stdin_pipe = child.stdin.as_mut().expect("stdin must be piped"); - let serialized_input = serde_json::to_vec(&input)?; - stdin_pipe.write_all(&serialized_input).await?; + let serialized_input = serde_json::to_vec(&input) + .context("Failed to serialize Standard JSON input for resolc")?; + stdin_pipe + .write_all(&serialized_input) + .await + .context("Failed to write Standard JSON to resolc stdin")?; - let output = child.wait_with_output().await?; + let output = child + .wait_with_output() + .await + .context("Failed while waiting for resolc process to finish")?; let stdout = output.stdout; let stderr = output.stderr; if !output.status.success() { - let json_in = serde_json::to_string_pretty(&input)?; + let json_in = serde_json::to_string_pretty(&input) + .context("Failed to pretty-print Standard JSON input for logging")?; let message = String::from_utf8_lossy(&stderr); tracing::error!( status = %output.status, @@ -141,12 +151,14 @@ impl SolidityCompiler for Resolc { anyhow::bail!("Compilation failed with an error: {message}"); } - let parsed = serde_json::from_slice::(&stdout).map_err(|e| { - anyhow::anyhow!( - "failed to parse resolc JSON output: {e}\nstderr: {}", - String::from_utf8_lossy(&stderr) - ) - })?; + let parsed = serde_json::from_slice::(&stdout) + .map_err(|e| { + anyhow::anyhow!( + "failed to parse resolc JSON output: {e}\nstderr: {}", + String::from_utf8_lossy(&stderr) + ) + }) + .context("Failed to parse resolc standard JSON output")?; tracing::debug!( output = %serde_json::to_string(&parsed).unwrap(), @@ -173,7 +185,10 @@ impl SolidityCompiler for Resolc { let mut compiler_output = CompilerOutput::default(); for (source_path, contracts) in contracts.into_iter() { - let source_path = PathBuf::from(source_path).canonicalize()?; + let src_for_msg = source_path.clone(); + let source_path = PathBuf::from(source_path) + .canonicalize() + .with_context(|| format!("Failed to canonicalize path {src_for_msg}"))?; let map = compiler_output.contracts.entry(source_path).or_default(); for (contract_name, contract_information) in contracts.into_iter() { @@ -233,8 +248,20 @@ impl SolidityCompiler for Resolc { let output = Command::new(self.resolc_path.as_path()) .arg("--version") .stdout(Stdio::piped()) - .spawn()? - .wait_with_output()? + .spawn() + .with_context(|| { + format!( + "Failed to spawn resolc at {} to get version", + self.resolc_path.display() + ) + })? + .wait_with_output() + .with_context(|| { + format!( + "Failed waiting for resolc at {} to finish --version", + self.resolc_path.display() + ) + })? .stdout; let output = String::from_utf8_lossy(&output); @@ -246,7 +273,9 @@ impl SolidityCompiler for Resolc { .next() .context("Version parsing failed")?; - let version = Version::parse(version_string)?; + let version = Version::parse(version_string).with_context(|| { + format!("Failed to parse resolc semver from '{version_string}'") + })?; vacant_entry.insert(version.clone()); diff --git a/crates/compiler/src/solc.rs b/crates/compiler/src/solc.rs index a7d8501..99b5a9a 100644 --- a/crates/compiler/src/solc.rs +++ b/crates/compiler/src/solc.rs @@ -49,7 +49,11 @@ impl SolidityCompiler for Solc { }: CompilerInput, _: Self::Options, ) -> anyhow::Result { - let compiler_supports_via_ir = self.version().await? >= SOLC_VERSION_SUPPORTING_VIA_YUL_IR; + let compiler_supports_via_ir = self + .version() + .await + .context("Failed to query solc version to determine via-ir support")? + >= SOLC_VERSION_SUPPORTING_VIA_YUL_IR; // Be careful to entirely omit the viaIR field if the compiler does not support it, // as it will error if you provide fields it does not know about. Because @@ -134,15 +138,25 @@ impl SolidityCompiler for Solc { .join(","), ); } - let mut child = command.spawn()?; + let mut child = command + .spawn() + .with_context(|| format!("Failed to spawn solc at {}", self.solc_path.display()))?; let stdin = child.stdin.as_mut().expect("should be piped"); - let serialized_input = serde_json::to_vec(&input)?; - stdin.write_all(&serialized_input).await?; - let output = child.wait_with_output().await?; + let serialized_input = serde_json::to_vec(&input) + .context("Failed to serialize Standard JSON input for solc")?; + stdin + .write_all(&serialized_input) + .await + .context("Failed to write Standard JSON to solc stdin")?; + let output = child + .wait_with_output() + .await + .context("Failed while waiting for solc process to finish")?; if !output.status.success() { - let json_in = serde_json::to_string_pretty(&input)?; + let json_in = serde_json::to_string_pretty(&input) + .context("Failed to pretty-print Standard JSON input for logging")?; let message = String::from_utf8_lossy(&output.stderr); tracing::error!( status = %output.status, @@ -153,12 +167,14 @@ impl SolidityCompiler for Solc { anyhow::bail!("Compilation failed with an error: {message}"); } - let parsed = serde_json::from_slice::(&output.stdout).map_err(|e| { - anyhow::anyhow!( - "failed to parse resolc JSON output: {e}\nstderr: {}", - String::from_utf8_lossy(&output.stdout) - ) - })?; + let parsed = serde_json::from_slice::(&output.stdout) + .map_err(|e| { + anyhow::anyhow!( + "failed to parse resolc JSON output: {e}\nstderr: {}", + String::from_utf8_lossy(&output.stdout) + ) + }) + .context("Failed to parse solc standard JSON output")?; // Detecting if the compiler output contained errors and reporting them through logs and // errors instead of returning the compiler output that might contain errors. @@ -178,7 +194,12 @@ impl SolidityCompiler for Solc { for (contract_path, contracts) in parsed.contracts { let map = compiler_output .contracts - .entry(contract_path.canonicalize()?) + .entry(contract_path.canonicalize().with_context(|| { + format!( + "Failed to canonicalize contract path {}", + contract_path.display() + ) + })?) .or_default(); for (contract_name, contract_info) in contracts.into_iter() { let source_code = contract_info @@ -207,7 +228,9 @@ impl SolidityCompiler for Solc { config: &Arguments, version: impl Into, ) -> anyhow::Result { - let path = download_solc(config.directory(), version, config.wasm).await?; + let path = download_solc(config.directory(), version, config.wasm) + .await + .context("Failed to download/get path to solc binary")?; Ok(path) } @@ -230,8 +253,19 @@ impl SolidityCompiler for Solc { let child = Command::new(self.solc_path.as_path()) .arg("--version") .stdout(Stdio::piped()) - .spawn()?; - let output = child.wait_with_output()?; + .spawn() + .with_context(|| { + format!( + "Failed to spawn solc at {} to get version", + self.solc_path.display() + ) + })?; + let output = child.wait_with_output().with_context(|| { + format!( + "Failed waiting for solc at {} to finish --version", + self.solc_path.display() + ) + })?; let output = String::from_utf8_lossy(&output.stdout); let version_line = output .split("Version: ") @@ -242,7 +276,9 @@ impl SolidityCompiler for Solc { .next() .context("Version parsing failed")?; - let version = Version::parse(version_string)?; + let version = Version::parse(version_string).with_context(|| { + format!("Failed to parse solc semver from '{version_string}'") + })?; vacant_entry.insert(version.clone()); diff --git a/crates/core/src/cached_compiler.rs b/crates/core/src/cached_compiler.rs index 188befb..14912d9 100644 --- a/crates/core/src/cached_compiler.rs +++ b/crates/core/src/cached_compiler.rs @@ -14,7 +14,7 @@ use revive_dt_config::Arguments; use revive_dt_format::metadata::{ContractIdent, ContractInstance, Metadata}; use alloy::{hex::ToHexExt, json_abi::JsonAbi, primitives::Address}; -use anyhow::{Error, Result}; +use anyhow::{Context as _, Error, Result}; use once_cell::sync::Lazy; use semver::Version; use serde::{Deserialize, Serialize}; @@ -29,7 +29,10 @@ impl CachedCompiler { pub async fn new(path: impl AsRef, invalidate_cache: bool) -> Result { let mut cache = ArtifactsCache::new(path); if invalidate_cache { - cache = cache.with_invalidated_cache().await?; + cache = cache + .with_invalidated_cache() + .await + .context("Failed to invalidate compilation cache directory")?; } Ok(Self(cache)) } @@ -76,9 +79,8 @@ impl CachedCompiler { compiler_version_or_requirement, ) .await - .inspect_err(|err| { - compilation_failure_report_callback(None, None, None, err.to_string()) - })?; + .inspect_err(|err| compilation_failure_report_callback(None, None, None, err.to_string())) + .context("Failed to obtain compiler executable path")?; let compiler_version = ::new(compiler_path.clone()) .version() .await @@ -89,7 +91,8 @@ impl CachedCompiler { None, err.to_string(), ) - })?; + }) + .context("Failed to query compiler version")?; let cache_key = CacheKey { platform_key: P::config_id().to_string(), @@ -104,10 +107,14 @@ impl CachedCompiler { let compilation_success_report_callback = compilation_success_report_callback.clone(); async move { compile_contracts::

( - metadata.directory()?, + metadata + .directory() + .context("Failed to get metadata directory while preparing compilation")?, compiler_path, compiler_version, - metadata.files_to_compile()?, + metadata + .files_to_compile() + .context("Failed to enumerate files to compile from metadata")?, mode, deployed_libraries, compilation_success_report_callback, @@ -131,7 +138,10 @@ impl CachedCompiler { Some(_) => { debug!("Deployed libraries defined, recompilation must take place"); debug!("Cache miss"); - compilation_callback().await?.compiler_output + compilation_callback() + .await + .context("Compilation callback for deployed libraries failed")? + .compiler_output } // If no deployed libraries are specified then we can follow the cached flow and attempt // to lookup the compilation artifacts in the cache. @@ -167,7 +177,12 @@ impl CachedCompiler { ); cache_value.compiler_output } - None => compilation_callback().await?.compiler_output, + None => { + compilation_callback() + .await + .context("Compilation callback failed (cache miss path)")? + .compiler_output + } } } }; @@ -247,7 +262,8 @@ async fn compile_contracts( Some(compiler_input.clone()), err.to_string(), ) - })?; + }) + .context("Failed to configure compiler with sources and options")?; compilation_success_report_callback( compiler_version, compiler_path.as_ref().to_path_buf(), @@ -273,15 +289,20 @@ impl ArtifactsCache { pub async fn with_invalidated_cache(self) -> Result { cacache::clear(self.path.as_path()) .await - .map_err(Into::::into)?; + .map_err(Into::::into) + .with_context(|| format!("Failed to clear cache at {}", self.path.display()))?; Ok(self) } #[instrument(level = "debug", skip_all, err)] pub async fn insert(&self, key: &CacheKey, value: &CacheValue) -> Result<()> { - let key = bson::to_vec(key)?; - let value = bson::to_vec(value)?; - cacache::write(self.path.as_path(), key.encode_hex(), value).await?; + let key = bson::to_vec(key).context("Failed to serialize cache key (bson)")?; + let value = bson::to_vec(value).context("Failed to serialize cache value (bson)")?; + cacache::write(self.path.as_path(), key.encode_hex(), value) + .await + .with_context(|| { + format!("Failed to write cache entry under {}", self.path.display()) + })?; Ok(()) } diff --git a/crates/core/src/driver/mod.rs b/crates/core/src/driver/mod.rs index d06ea52..e7c1766 100644 --- a/crates/core/src/driver/mod.rs +++ b/crates/core/src/driver/mod.rs @@ -86,18 +86,22 @@ where ) -> anyhow::Result { match step { Step::FunctionCall(input) => { - let (receipt, geth_trace, diff_mode) = - self.handle_input(metadata, input, node).await?; + let (receipt, geth_trace, diff_mode) = self + .handle_input(metadata, input, node) + .await + .context("Failed to handle function call step")?; Ok(StepOutput::FunctionCall(receipt, geth_trace, diff_mode)) } Step::BalanceAssertion(balance_assertion) => { self.handle_balance_assertion(metadata, balance_assertion, node) - .await?; + .await + .context("Failed to handle balance assertion step")?; Ok(StepOutput::BalanceAssertion) } Step::StorageEmptyAssertion(storage_empty) => { self.handle_storage_empty(metadata, storage_empty, node) - .await?; + .await + .context("Failed to handle storage empty assertion step")?; Ok(StepOutput::StorageEmptyAssertion) } } @@ -113,18 +117,23 @@ where ) -> anyhow::Result<(TransactionReceipt, GethTrace, DiffMode)> { let deployment_receipts = self .handle_input_contract_deployment(metadata, input, node) - .await?; + .await + .context("Failed during contract deployment phase of input handling")?; let execution_receipt = self .handle_input_execution(input, deployment_receipts, node) - .await?; + .await + .context("Failed during transaction execution phase of input handling")?; let tracing_result = self .handle_input_call_frame_tracing(&execution_receipt, node) - .await?; - self.handle_input_variable_assignment(input, &tracing_result)?; + .await + .context("Failed during callframe tracing phase of input handling")?; + self.handle_input_variable_assignment(input, &tracing_result) + .context("Failed to assign variables from callframe output")?; let (_, (geth_trace, diff_mode)) = try_join!( self.handle_input_expectations(input, &execution_receipt, node, &tracing_result), self.handle_input_diff(&execution_receipt, node) - )?; + ) + .context("Failed while evaluating expectations and diffs in parallel")?; Ok((execution_receipt, geth_trace, diff_mode)) } @@ -136,9 +145,11 @@ where node: &T::Blockchain, ) -> anyhow::Result<()> { self.handle_balance_assertion_contract_deployment(metadata, balance_assertion, node) - .await?; + .await + .context("Failed to deploy contract for balance assertion")?; self.handle_balance_assertion_execution(balance_assertion, node) - .await?; + .await + .context("Failed to execute balance assertion")?; Ok(()) } @@ -150,9 +161,11 @@ where node: &T::Blockchain, ) -> anyhow::Result<()> { self.handle_storage_empty_assertion_contract_deployment(metadata, storage_empty, node) - .await?; + .await + .context("Failed to deploy contract for storage empty assertion")?; self.handle_storage_empty_assertion_execution(storage_empty, node) - .await?; + .await + .context("Failed to execute storage empty assertion")?; Ok(()) } @@ -191,7 +204,8 @@ where value, node, ) - .await? + .await + .context("Failed to get or deploy contract instance during input execution")? { receipts.insert(instance.clone(), receipt); } @@ -213,7 +227,7 @@ where // lookup the transaction receipt in this case and continue on. Method::Deployer => deployment_receipts .remove(&input.instance) - .context("Failed to find deployment receipt"), + .context("Failed to find deployment receipt for constructor call"), Method::Fallback | Method::FunctionName(_) => { let tx = match input .legacy_transaction(node, self.default_resolution_context()) @@ -385,7 +399,8 @@ where let actual = &tracing_result.output.as_ref().unwrap_or_default(); if !expected .is_equivalent(actual, resolver, resolution_context) - .await? + .await + .context("Failed to resolve calldata equivalence for return data assertion")? { tracing::error!( ?execution_receipt, @@ -448,7 +463,8 @@ where let expected = Calldata::new_compound([expected]); if !expected .is_equivalent(&actual.0, resolver, resolution_context) - .await? + .await + .context("Failed to resolve event topic equivalence")? { tracing::error!( event_idx, @@ -468,7 +484,8 @@ where let actual = &actual_event.data().data; if !expected .is_equivalent(&actual.0, resolver, resolution_context) - .await? + .await + .context("Failed to resolve event value equivalence")? { tracing::error!( event_idx, @@ -501,8 +518,12 @@ where let trace = node .trace_transaction(execution_receipt, trace_options) - .await?; - let diff = node.state_diff(execution_receipt).await?; + .await + .context("Failed to obtain geth prestate tracer output")?; + let diff = node + .state_diff(execution_receipt) + .await + .context("Failed to obtain state diff for transaction")?; Ok((trace, diff)) } diff --git a/crates/core/src/main.rs b/crates/core/src/main.rs index 43ae3be..f58d558 100644 --- a/crates/core/src/main.rs +++ b/crates/core/src/main.rs @@ -60,7 +60,7 @@ struct Test<'a> { } fn main() -> anyhow::Result<()> { - let (args, _guard) = init_cli()?; + let (args, _guard) = init_cli().context("Failed to initialize CLI and tracing subscriber")?; info!( leader = args.leader.to_string(), follower = args.follower.to_string(), @@ -74,7 +74,8 @@ fn main() -> anyhow::Result<()> { let number_of_threads = args.number_of_threads; let body = async move { - let tests = collect_corpora(&args)? + let tests = collect_corpora(&args) + .context("Failed to collect corpus files from provided arguments")? .into_iter() .inspect(|(corpus, _)| { reporter @@ -96,7 +97,9 @@ fn main() -> anyhow::Result<()> { Some(platform) => { compile_corpus(&args, &tests, platform, reporter, report_aggregator_task).await } - None => execute_corpus(&args, &tests, reporter, report_aggregator_task).await?, + None => execute_corpus(&args, &tests, reporter, report_aggregator_task) + .await + .context("Failed to execute corpus")?, } Ok(()) }; @@ -183,7 +186,9 @@ where F::Blockchain: revive_dt_node::Node + Send + Sync + 'static, { let tests = prepare_tests::(args, metadata_files, reporter.clone()); - let driver_task = start_driver_task::(args, tests).await?; + let driver_task = start_driver_task::(args, tests) + .await + .context("Failed to start driver task")?; let cli_reporting_task = start_cli_reporting_task(reporter); let (_, _, rtn) = tokio::join!(cli_reporting_task, driver_task, report_aggregator_task); @@ -419,9 +424,13 @@ async fn does_compiler_support_mode( mode: &Mode, ) -> anyhow::Result { let compiler_version_or_requirement = mode.compiler_version_to_use(args.solc.clone()); - let compiler_path = - P::Compiler::get_compiler_executable(args, compiler_version_or_requirement).await?; - let compiler_version = P::Compiler::new(compiler_path.clone()).version().await?; + let compiler_path = P::Compiler::get_compiler_executable(args, compiler_version_or_requirement) + .await + .context("Failed to obtain compiler executable path")?; + let compiler_version = P::Compiler::new(compiler_path.clone()) + .version() + .await + .context("Failed to query compiler version")?; Ok(P::Compiler::supports_mode( &compiler_version, @@ -442,15 +451,20 @@ where { info!("Starting driver task"); - let leader_nodes = Arc::new(NodePool::::new(args)?); - let follower_nodes = Arc::new(NodePool::::new(args)?); + let leader_nodes = Arc::new( + NodePool::::new(args).context("Failed to initialize leader node pool")?, + ); + let follower_nodes = Arc::new( + NodePool::::new(args).context("Failed to initialize follower node pool")?, + ); let number_concurrent_tasks = args.number_of_concurrent_tasks(); let cached_compiler = Arc::new( CachedCompiler::new( args.directory().join("compilation_cache"), args.invalidate_compilation_cache, ) - .await?, + .await + .context("Failed to initialize cached compiler")?, ); Ok(tests.for_each_concurrent( @@ -687,11 +701,15 @@ where .expect("Can't fail") } ) - )?; + ) + .context("Failed to compile pre-link contracts for leader/follower in parallel")?; let mut leader_deployed_libraries = None::>; let mut follower_deployed_libraries = None::>; - let mut contract_sources = test.metadata.contract_sources()?; + let mut contract_sources = test + .metadata + .contract_sources() + .context("Failed to retrieve contract sources from metadata")?; for library_instance in test .metadata .libraries @@ -882,7 +900,8 @@ where .expect("Can't fail") } ) - )?; + ) + .context("Failed to compile post-link contracts for leader/follower in parallel")?; let leader_state = CaseState::::new( leader_compiler_version, diff --git a/crates/format/src/corpus.rs b/crates/format/src/corpus.rs index 69921f0..13d9280 100644 --- a/crates/format/src/corpus.rs +++ b/crates/format/src/corpus.rs @@ -8,6 +8,7 @@ use serde::{Deserialize, Serialize}; use tracing::{debug, info}; use crate::metadata::{Metadata, MetadataFile}; +use anyhow::Context as _; #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] #[serde(untagged)] @@ -20,7 +21,13 @@ impl Corpus { pub fn try_from_path(file_path: impl AsRef) -> anyhow::Result { let mut corpus = File::open(file_path.as_ref()) .map_err(anyhow::Error::from) - .and_then(|file| serde_json::from_reader::<_, Corpus>(file).map_err(Into::into))?; + .and_then(|file| serde_json::from_reader::<_, Corpus>(file).map_err(Into::into)) + .with_context(|| { + format!( + "Failed to open and deserialize corpus file at {}", + file_path.as_ref().display() + ) + })?; for path in corpus.paths_iter_mut() { *path = file_path @@ -28,12 +35,19 @@ impl Corpus { .parent() .ok_or_else(|| { anyhow::anyhow!("Corpus path '{}' does not point to a file", path.display()) + }) + .with_context(|| { + format!( + "Failed resolving parent directory for corpus path '{}'", + file_path.as_ref().display() + ) })? .canonicalize() - .map_err(|error| { - anyhow::anyhow!( - "Failed to canonicalize path to corpus '{}': {error}", - path.display() + .with_context(|| { + format!( + "Failed to canonicalize path to corpus '{}': {}", + path.display(), + file_path.as_ref().display() ) })? .join(path.as_path()) diff --git a/crates/format/src/input.rs b/crates/format/src/input.rs index 918c2d4..5852a63 100644 --- a/crates/format/src/input.rs +++ b/crates/format/src/input.rs @@ -268,7 +268,11 @@ impl Input { ) -> anyhow::Result { match self.method { Method::Deployer | Method::Fallback => { - let calldata = self.calldata.calldata(resolver, context).await?; + let calldata = self + .calldata + .calldata(resolver, context) + .await + .context("Failed to produce calldata for deployer/fallback method")?; Ok(calldata.into()) } @@ -283,14 +287,15 @@ impl Input { // Overloads are handled by providing the full function signature in the "function // name". // https://github.com/matter-labs/era-compiler-tester/blob/1dfa7d07cba0734ca97e24704f12dd57f6990c2c/compiler_tester/src/test/case/input/mod.rs#L158-L190 - let selector = if function_name.contains('(') && function_name.contains(')') { - Function::parse(function_name) + let selector = + if function_name.contains('(') && function_name.contains(')') { + Function::parse(function_name) .context( "Failed to parse the provided function name into a function signature", )? .selector() - } else { - abi.functions() + } else { + abi.functions() .find(|function| function.signature().starts_with(function_name)) .ok_or_else(|| { anyhow::anyhow!( @@ -298,9 +303,13 @@ impl Input { function_name, &self.instance ) - })? + }) + .with_context(|| format!( + "Failed to resolve function selector for {:?} on instance {:?}", + function_name, &self.instance + ))? .selector() - }; + }; // Allocating a vector that we will be using for the calldata. The vector size will be: // 4 bytes for the function selector. @@ -312,7 +321,8 @@ impl Input { calldata.extend(selector.0); self.calldata .calldata_into_slice(&mut calldata, resolver, context) - .await?; + .await + .context("Failed to append encoded argument to calldata buffer")?; Ok(calldata.into()) } @@ -325,7 +335,10 @@ impl Input { resolver: &impl ResolverApi, context: ResolutionContext<'_>, ) -> anyhow::Result { - let input_data = self.encoded_input(resolver, context).await?; + let input_data = self + .encoded_input(resolver, context) + .await + .context("Failed to encode input bytes for transaction request")?; let transaction_request = TransactionRequest::default().from(self.caller).value( self.value .map(|value| value.into_inner()) @@ -437,7 +450,8 @@ impl Calldata { }) .buffered(0xFF) .try_collect::>() - .await?; + .await + .context("Failed to resolve one or more calldata arguments")?; buffer.extend(resolved.into_iter().flatten()); } @@ -478,7 +492,10 @@ impl Calldata { std::borrow::Cow::Borrowed(other) }; - let this = this.resolve(resolver, context).await?; + let this = this + .resolve(resolver, context) + .await + .context("Failed to resolve calldata item during equivalence check")?; let other = U256::from_be_slice(&other); Ok(this == other) }) @@ -664,17 +681,24 @@ impl> CalldataToken { let current_block_number = match context.tip_block_number() { Some(block_number) => *block_number, - None => resolver.last_block_number().await?, + None => resolver.last_block_number().await.context( + "Failed to query last block number while resolving $BLOCK_HASH", + )?, }; let desired_block_number = current_block_number.saturating_sub(offset); - let block_hash = resolver.block_hash(desired_block_number.into()).await?; + let block_hash = resolver + .block_hash(desired_block_number.into()) + .await + .context("Failed to resolve block hash for desired block number")?; Ok(U256::from_be_bytes(block_hash.0)) } else if item == Self::BLOCK_NUMBER_VARIABLE { let current_block_number = match context.tip_block_number() { Some(block_number) => *block_number, - None => resolver.last_block_number().await?, + None => resolver.last_block_number().await.context( + "Failed to query last block number while resolving $BLOCK_NUMBER", + )?, }; Ok(U256::from(current_block_number)) } else if item == Self::BLOCK_TIMESTAMP_VARIABLE { diff --git a/crates/format/src/metadata.rs b/crates/format/src/metadata.rs index 04411b9..66985a5 100644 --- a/crates/format/src/metadata.rs +++ b/crates/format/src/metadata.rs @@ -132,7 +132,15 @@ impl Metadata { ) in contracts { let alias = alias.clone(); - let absolute_path = directory.join(contract_source_path).canonicalize()?; + let absolute_path = directory + .join(contract_source_path) + .canonicalize() + .map_err(|error| { + anyhow::anyhow!( + "Failed to canonicalize contract source path '{}': {error}", + directory.join(contract_source_path).display() + ) + })?; let contract_ident = contract_ident.clone(); sources.insert( diff --git a/crates/format/src/mode.rs b/crates/format/src/mode.rs index 0476e4e..a89b2cb 100644 --- a/crates/format/src/mode.rs +++ b/crates/format/src/mode.rs @@ -1,3 +1,4 @@ +use anyhow::Context; use regex::Regex; use revive_dt_common::types::{Mode, ModeOptimizerSetting, ModePipeline}; use serde::{Deserialize, Serialize}; @@ -44,21 +45,34 @@ impl FromStr for ParsedMode { }; let pipeline = match caps.name("pipeline") { - Some(m) => Some(ModePipeline::from_str(m.as_str())?), + Some(m) => Some( + ModePipeline::from_str(m.as_str()) + .context("Failed to parse mode pipeline from string")?, + ), None => None, }; let optimize_flag = caps.name("optimize_flag").map(|m| m.as_str() == "+"); let optimize_setting = match caps.name("optimize_setting") { - Some(m) => Some(ModeOptimizerSetting::from_str(m.as_str())?), + Some(m) => Some( + ModeOptimizerSetting::from_str(m.as_str()) + .context("Failed to parse optimizer setting from string")?, + ), None => None, }; let version = match caps.name("version") { - Some(m) => Some(semver::VersionReq::parse(m.as_str()).map_err(|e| { - anyhow::anyhow!("Cannot parse the version requirement '{}': {e}", m.as_str()) - })?), + Some(m) => Some( + semver::VersionReq::parse(m.as_str()) + .map_err(|e| { + anyhow::anyhow!( + "Cannot parse the version requirement '{}': {e}", + m.as_str() + ) + }) + .context("Failed to parse semver requirement from mode string")?, + ), None => None, }; diff --git a/crates/node/src/geth.rs b/crates/node/src/geth.rs index e72a7ed..1d87a80 100644 --- a/crates/node/src/geth.rs +++ b/crates/node/src/geth.rs @@ -101,10 +101,13 @@ impl GethNode { let _ = clear_directory(&self.base_directory); let _ = clear_directory(&self.logs_directory); - create_dir_all(&self.base_directory)?; - create_dir_all(&self.logs_directory)?; + create_dir_all(&self.base_directory) + .context("Failed to create base directory for geth node")?; + create_dir_all(&self.logs_directory) + .context("Failed to create logs directory for geth node")?; - let mut genesis = serde_json::from_str::(&genesis)?; + let mut genesis = serde_json::from_str::(&genesis) + .context("Failed to deserialize geth genesis JSON")?; for signer_address in >::signer_addresses(&self.wallet) { @@ -116,7 +119,11 @@ impl GethNode { .or_insert(GenesisAccount::default().with_balance(U256::from(INITIAL_BALANCE))); } let genesis_path = self.base_directory.join(Self::GENESIS_JSON_FILE); - serde_json::to_writer(File::create(&genesis_path)?, &genesis)?; + serde_json::to_writer( + File::create(&genesis_path).context("Failed to create geth genesis file")?, + &genesis, + ) + .context("Failed to serialize geth genesis JSON to file")?; let mut child = Command::new(&self.geth) .arg("--state.scheme") @@ -127,16 +134,22 @@ impl GethNode { .arg(genesis_path) .stderr(Stdio::piped()) .stdout(Stdio::null()) - .spawn()?; + .spawn() + .context("Failed to spawn geth --init process")?; let mut stderr = String::new(); child .stderr .take() .expect("should be piped") - .read_to_string(&mut stderr)?; + .read_to_string(&mut stderr) + .context("Failed to read geth --init stderr")?; - if !child.wait()?.success() { + if !child + .wait() + .context("Failed waiting for geth --init process to finish")? + .success() + { anyhow::bail!("failed to initialize geth node #{:?}: {stderr}", &self.id); } @@ -161,8 +174,11 @@ impl GethNode { let stdout_logs_file = open_options .clone() - .open(self.geth_stdout_log_file_path())?; - let stderr_logs_file = open_options.open(self.geth_stderr_log_file_path())?; + .open(self.geth_stdout_log_file_path()) + .context("Failed to open geth stdout logs file")?; + let stderr_logs_file = open_options + .open(self.geth_stderr_log_file_path()) + .context("Failed to open geth stderr logs file")?; self.handle = Command::new(&self.geth) .arg("--dev") .arg("--datadir") @@ -182,14 +198,24 @@ impl GethNode { .arg("full") .arg("--gcmode") .arg("archive") - .stderr(stderr_logs_file.try_clone()?) - .stdout(stdout_logs_file.try_clone()?) - .spawn()? + .stderr( + stderr_logs_file + .try_clone() + .context("Failed to clone geth stderr log file handle")?, + ) + .stdout( + stdout_logs_file + .try_clone() + .context("Failed to clone geth stdout log file handle")?, + ) + .spawn() + .context("Failed to spawn geth node process")? .into(); if let Err(error) = self.wait_ready() { tracing::error!(?error, "Failed to start geth, shutting down gracefully"); - self.shutdown()?; + self.shutdown() + .context("Failed to gracefully shutdown after geth start error")?; return Err(error); } @@ -211,7 +237,8 @@ impl GethNode { .write(false) .append(false) .truncate(false) - .open(self.geth_stderr_log_file_path())?; + .open(self.geth_stderr_log_file_path()) + .context("Failed to open geth stderr logs file for readiness check")?; let maximum_wait_time = Duration::from_millis(self.start_timeout); let mut stderr = BufReader::new(logs_file).lines(); @@ -277,11 +304,18 @@ impl EthereumNode for GethNode { &self, transaction: TransactionRequest, ) -> anyhow::Result { - let provider = self.provider().await?; + let provider = self + .provider() + .await + .context("Failed to create provider for transaction submission")?; - let pending_transaction = provider.send_transaction(transaction).await.inspect_err( - |err| tracing::error!(%err, "Encountered an error when submitting the transaction"), - )?; + let pending_transaction = provider + .send_transaction(transaction) + .await + .inspect_err( + |err| tracing::error!(%err, "Encountered an error when submitting the transaction"), + ) + .context("Failed to submit transaction to geth node")?; let transaction_hash = *pending_transaction.tx_hash(); // The following is a fix for the "transaction indexing is in progress" error that we used @@ -335,7 +369,11 @@ impl EthereumNode for GethNode { transaction: &TransactionReceipt, trace_options: GethDebugTracingOptions, ) -> anyhow::Result { - let provider = Arc::new(self.provider().await?); + let provider = Arc::new( + self.provider() + .await + .context("Failed to create provider for tracing")?, + ); poll( Self::TRACE_POLLING_DURATION, PollingWaitBehavior::Constant(Duration::from_millis(200)), @@ -371,8 +409,10 @@ impl EthereumNode for GethNode { }); match self .trace_transaction(transaction, trace_options) - .await? - .try_into_pre_state_frame()? + .await + .context("Failed to trace transaction for prestate diff")? + .try_into_pre_state_frame() + .context("Failed to convert trace into pre-state frame")? { PreStateFrame::Diff(diff) => Ok(diff), _ => anyhow::bail!("expected a diff mode trace"), @@ -382,7 +422,8 @@ impl EthereumNode for GethNode { #[instrument(level = "info", skip_all, fields(geth_node_id = self.id))] async fn balance_of(&self, address: Address) -> anyhow::Result { self.provider() - .await? + .await + .context("Failed to get the Geth provider")? .get_balance(address) .await .map_err(Into::into) @@ -395,7 +436,8 @@ impl EthereumNode for GethNode { keys: Vec, ) -> anyhow::Result { self.provider() - .await? + .await + .context("Failed to get the Geth provider")? .get_proof(address, keys) .latest() .await @@ -407,7 +449,8 @@ impl ResolverApi for GethNode { #[instrument(level = "info", skip_all, fields(geth_node_id = self.id))] async fn chain_id(&self) -> anyhow::Result { self.provider() - .await? + .await + .context("Failed to get the Geth provider")? .get_chain_id() .await .map_err(Into::into) @@ -416,7 +459,8 @@ impl ResolverApi for GethNode { #[instrument(level = "info", skip_all, fields(geth_node_id = self.id))] async fn transaction_gas_price(&self, tx_hash: &TxHash) -> anyhow::Result { self.provider() - .await? + .await + .context("Failed to get the Geth provider")? .get_transaction_receipt(*tx_hash) .await? .context("Failed to get the transaction receipt") @@ -426,40 +470,48 @@ impl ResolverApi for GethNode { #[instrument(level = "info", skip_all, fields(geth_node_id = self.id))] async fn block_gas_limit(&self, number: BlockNumberOrTag) -> anyhow::Result { self.provider() - .await? + .await + .context("Failed to get the Geth provider")? .get_block_by_number(number) - .await? - .ok_or(anyhow::Error::msg("Blockchain has no blocks")) + .await + .context("Failed to get the geth block")? + .context("Failed to get the Geth block, perhaps there are no blocks?") .map(|block| block.header.gas_limit as _) } #[instrument(level = "info", skip_all, fields(geth_node_id = self.id))] async fn block_coinbase(&self, number: BlockNumberOrTag) -> anyhow::Result

{ self.provider() - .await? + .await + .context("Failed to get the Geth provider")? .get_block_by_number(number) - .await? - .ok_or(anyhow::Error::msg("Blockchain has no blocks")) + .await + .context("Failed to get the geth block")? + .context("Failed to get the Geth block, perhaps there are no blocks?") .map(|block| block.header.beneficiary) } #[instrument(level = "info", skip_all, fields(geth_node_id = self.id))] async fn block_difficulty(&self, number: BlockNumberOrTag) -> anyhow::Result { self.provider() - .await? + .await + .context("Failed to get the Geth provider")? .get_block_by_number(number) - .await? - .ok_or(anyhow::Error::msg("Blockchain has no blocks")) + .await + .context("Failed to get the geth block")? + .context("Failed to get the Geth block, perhaps there are no blocks?") .map(|block| U256::from_be_bytes(block.header.mix_hash.0)) } #[instrument(level = "info", skip_all, fields(geth_node_id = self.id))] async fn block_base_fee(&self, number: BlockNumberOrTag) -> anyhow::Result { self.provider() - .await? + .await + .context("Failed to get the Geth provider")? .get_block_by_number(number) - .await? - .ok_or(anyhow::Error::msg("Blockchain has no blocks")) + .await + .context("Failed to get the geth block")? + .context("Failed to get the Geth block, perhaps there are no blocks?") .and_then(|block| { block .header @@ -471,27 +523,32 @@ impl ResolverApi for GethNode { #[instrument(level = "info", skip_all, fields(geth_node_id = self.id))] async fn block_hash(&self, number: BlockNumberOrTag) -> anyhow::Result { self.provider() - .await? + .await + .context("Failed to get the Geth provider")? .get_block_by_number(number) - .await? - .ok_or(anyhow::Error::msg("Blockchain has no blocks")) + .await + .context("Failed to get the geth block")? + .context("Failed to get the Geth block, perhaps there are no blocks?") .map(|block| block.header.hash) } #[instrument(level = "info", skip_all, fields(geth_node_id = self.id))] async fn block_timestamp(&self, number: BlockNumberOrTag) -> anyhow::Result { self.provider() - .await? + .await + .context("Failed to get the Geth provider")? .get_block_by_number(number) - .await? - .ok_or(anyhow::Error::msg("Blockchain has no blocks")) + .await + .context("Failed to get the geth block")? + .context("Failed to get the Geth block, perhaps there are no blocks?") .map(|block| block.header.timestamp) } #[instrument(level = "info", skip_all, fields(geth_node_id = self.id))] async fn last_block_number(&self) -> anyhow::Result { self.provider() - .await? + .await + .context("Failed to get the Geth provider")? .get_block_number() .await .map_err(Into::into) @@ -576,8 +633,10 @@ impl Node for GethNode { .stdin(Stdio::null()) .stdout(Stdio::piped()) .stderr(Stdio::null()) - .spawn()? - .wait_with_output()? + .spawn() + .context("Failed to spawn geth --version process")? + .wait_with_output() + .context("Failed to wait for geth --version output")? .stdout; Ok(String::from_utf8_lossy(&output).into()) } diff --git a/crates/node/src/kitchensink.rs b/crates/node/src/kitchensink.rs index ce4d30b..be2927e 100644 --- a/crates/node/src/kitchensink.rs +++ b/crates/node/src/kitchensink.rs @@ -94,8 +94,10 @@ impl KitchensinkNode { let _ = clear_directory(&self.base_directory); let _ = clear_directory(&self.logs_directory); - create_dir_all(&self.base_directory)?; - create_dir_all(&self.logs_directory)?; + create_dir_all(&self.base_directory) + .context("Failed to create base directory for kitchensink node")?; + create_dir_all(&self.logs_directory) + .context("Failed to create logs directory for kitchensink node")?; let template_chainspec_path = self.base_directory.join(Self::CHAIN_SPEC_JSON_FILE); @@ -105,7 +107,8 @@ impl KitchensinkNode { .arg("export-chain-spec") .arg("--chain") .arg("dev") - .output()?; + .output() + .context("Failed to spawn substrate export-chain-spec")?; if !output.status.success() { anyhow::bail!( @@ -114,8 +117,10 @@ impl KitchensinkNode { ); } - let content = String::from_utf8(output.stdout)?; - let mut chainspec_json: JsonValue = serde_json::from_str(&content)?; + let content = String::from_utf8(output.stdout) + .context("Failed to decode substrate export-chain-spec output as UTF-8")?; + let mut chainspec_json: JsonValue = + serde_json::from_str(&content).context("Failed to parse substrate chain spec JSON")?; let existing_chainspec_balances = chainspec_json["genesis"]["runtimeGenesis"]["patch"]["balances"]["balances"] @@ -137,7 +142,8 @@ impl KitchensinkNode { }) .collect(); let mut eth_balances = { - let mut genesis = serde_json::from_str::(genesis)?; + let mut genesis = serde_json::from_str::(genesis) + .context("Failed to deserialize EVM genesis JSON for kitchensink")?; for signer_address in >::signer_addresses(&self.wallet) { @@ -148,7 +154,8 @@ impl KitchensinkNode { .entry(signer_address) .or_insert(GenesisAccount::default().with_balance(U256::from(INITIAL_BALANCE))); } - self.extract_balance_from_genesis_file(&genesis)? + self.extract_balance_from_genesis_file(&genesis) + .context("Failed to extract balances from EVM genesis JSON")? }; merged_balances.append(&mut eth_balances); @@ -156,9 +163,11 @@ impl KitchensinkNode { json!(merged_balances); serde_json::to_writer_pretty( - std::fs::File::create(&template_chainspec_path)?, + std::fs::File::create(&template_chainspec_path) + .context("Failed to create kitchensink template chainspec file")?, &chainspec_json, - )?; + ) + .context("Failed to write kitchensink template chainspec JSON")?; Ok(self) } @@ -184,10 +193,12 @@ impl KitchensinkNode { // Start Substrate node let kitchensink_stdout_logs_file = open_options .clone() - .open(self.kitchensink_stdout_log_file_path())?; + .open(self.kitchensink_stdout_log_file_path()) + .context("Failed to open kitchensink stdout logs file")?; let kitchensink_stderr_logs_file = open_options .clone() - .open(self.kitchensink_stderr_log_file_path())?; + .open(self.kitchensink_stderr_log_file_path()) + .context("Failed to open kitchensink stderr logs file")?; self.process_substrate = Command::new(&self.substrate_binary) .arg("--dev") .arg("--chain") @@ -206,9 +217,18 @@ impl KitchensinkNode { .arg("--rpc-max-connections") .arg(u32::MAX.to_string()) .env("RUST_LOG", Self::SUBSTRATE_LOG_ENV) - .stdout(kitchensink_stdout_logs_file.try_clone()?) - .stderr(kitchensink_stderr_logs_file.try_clone()?) - .spawn()? + .stdout( + kitchensink_stdout_logs_file + .try_clone() + .context("Failed to clone kitchensink stdout log file handle")?, + ) + .stderr( + kitchensink_stderr_logs_file + .try_clone() + .context("Failed to clone kitchensink stderr log file handle")?, + ) + .spawn() + .context("Failed to spawn substrate node process")? .into(); // Give the node a moment to boot @@ -217,14 +237,18 @@ impl KitchensinkNode { Self::SUBSTRATE_READY_MARKER, Duration::from_secs(60), ) { - self.shutdown()?; + self.shutdown() + .context("Failed to gracefully shutdown after substrate start error")?; return Err(error); }; let eth_proxy_stdout_logs_file = open_options .clone() - .open(self.proxy_stdout_log_file_path())?; - let eth_proxy_stderr_logs_file = open_options.open(self.proxy_stderr_log_file_path())?; + .open(self.proxy_stdout_log_file_path()) + .context("Failed to open eth-proxy stdout logs file")?; + let eth_proxy_stderr_logs_file = open_options + .open(self.proxy_stderr_log_file_path()) + .context("Failed to open eth-proxy stderr logs file")?; self.process_proxy = Command::new(&self.eth_proxy_binary) .arg("--dev") .arg("--rpc-port") @@ -234,9 +258,18 @@ impl KitchensinkNode { .arg("--rpc-max-connections") .arg(u32::MAX.to_string()) .env("RUST_LOG", Self::PROXY_LOG_ENV) - .stdout(eth_proxy_stdout_logs_file.try_clone()?) - .stderr(eth_proxy_stderr_logs_file.try_clone()?) - .spawn()? + .stdout( + eth_proxy_stdout_logs_file + .try_clone() + .context("Failed to clone eth-proxy stdout log file handle")?, + ) + .stderr( + eth_proxy_stderr_logs_file + .try_clone() + .context("Failed to clone eth-proxy stderr log file handle")?, + ) + .spawn() + .context("Failed to spawn eth-proxy process")? .into(); if let Err(error) = Self::wait_ready( @@ -244,7 +277,8 @@ impl KitchensinkNode { Self::ETH_PROXY_READY_MARKER, Duration::from_secs(60), ) { - self.shutdown()?; + self.shutdown() + .context("Failed to gracefully shutdown after eth-proxy start error")?; return Err(error); }; @@ -369,11 +403,14 @@ impl EthereumNode for KitchensinkNode { ) -> anyhow::Result { let receipt = self .provider() - .await? + .await + .context("Failed to create provider for transaction submission")? .send_transaction(transaction) - .await? + .await + .context("Failed to submit transaction to kitchensink proxy")? .get_receipt() - .await?; + .await + .context("Failed to fetch transaction receipt from kitchensink proxy")?; Ok(receipt) } @@ -383,11 +420,12 @@ impl EthereumNode for KitchensinkNode { trace_options: GethDebugTracingOptions, ) -> anyhow::Result { let tx_hash = transaction.transaction_hash; - Ok(self - .provider() - .await? + self.provider() + .await + .context("Failed to create provider for debug tracing")? .debug_trace_transaction(tx_hash, trace_options) - .await?) + .await + .context("Failed to obtain debug trace from kitchensink proxy") } async fn state_diff(&self, transaction: &TransactionReceipt) -> anyhow::Result { @@ -408,7 +446,8 @@ impl EthereumNode for KitchensinkNode { async fn balance_of(&self, address: Address) -> anyhow::Result { self.provider() - .await? + .await + .context("Failed to get the Kitchensink provider")? .get_balance(address) .await .map_err(Into::into) @@ -420,7 +459,8 @@ impl EthereumNode for KitchensinkNode { keys: Vec, ) -> anyhow::Result { self.provider() - .await? + .await + .context("Failed to get the Kitchensink provider")? .get_proof(address, keys) .latest() .await @@ -431,7 +471,8 @@ impl EthereumNode for KitchensinkNode { impl ResolverApi for KitchensinkNode { async fn chain_id(&self) -> anyhow::Result { self.provider() - .await? + .await + .context("Failed to get the Kitchensink provider")? .get_chain_id() .await .map_err(Into::into) @@ -439,7 +480,8 @@ impl ResolverApi for KitchensinkNode { async fn transaction_gas_price(&self, tx_hash: &TxHash) -> anyhow::Result { self.provider() - .await? + .await + .context("Failed to get the Kitchensink provider")? .get_transaction_receipt(*tx_hash) .await? .context("Failed to get the transaction receipt") @@ -448,37 +490,45 @@ impl ResolverApi for KitchensinkNode { async fn block_gas_limit(&self, number: BlockNumberOrTag) -> anyhow::Result { self.provider() - .await? + .await + .context("Failed to get the Kitchensink provider")? .get_block_by_number(number) - .await? - .ok_or(anyhow::Error::msg("Blockchain has no blocks")) + .await + .context("Failed to get the kitchensink block")? + .context("Failed to get the Kitchensink block, perhaps the chain has no blocks?") .map(|block| block.header.gas_limit as _) } async fn block_coinbase(&self, number: BlockNumberOrTag) -> anyhow::Result
{ self.provider() - .await? + .await + .context("Failed to get the Kitchensink provider")? .get_block_by_number(number) - .await? - .ok_or(anyhow::Error::msg("Blockchain has no blocks")) + .await + .context("Failed to get the kitchensink block")? + .context("Failed to get the Kitchensink block, perhaps the chain has no blocks?") .map(|block| block.header.beneficiary) } async fn block_difficulty(&self, number: BlockNumberOrTag) -> anyhow::Result { self.provider() - .await? + .await + .context("Failed to get the Kitchensink provider")? .get_block_by_number(number) - .await? - .ok_or(anyhow::Error::msg("Blockchain has no blocks")) + .await + .context("Failed to get the kitchensink block")? + .context("Failed to get the Kitchensink block, perhaps the chain has no blocks?") .map(|block| U256::from_be_bytes(block.header.mix_hash.0)) } async fn block_base_fee(&self, number: BlockNumberOrTag) -> anyhow::Result { self.provider() - .await? + .await + .context("Failed to get the Kitchensink provider")? .get_block_by_number(number) - .await? - .ok_or(anyhow::Error::msg("Blockchain has no blocks")) + .await + .context("Failed to get the kitchensink block")? + .context("Failed to get the Kitchensink block, perhaps the chain has no blocks?") .and_then(|block| { block .header @@ -489,25 +539,30 @@ impl ResolverApi for KitchensinkNode { async fn block_hash(&self, number: BlockNumberOrTag) -> anyhow::Result { self.provider() - .await? + .await + .context("Failed to get the Kitchensink provider")? .get_block_by_number(number) - .await? - .ok_or(anyhow::Error::msg("Blockchain has no blocks")) + .await + .context("Failed to get the kitchensink block")? + .context("Failed to get the Kitchensink block, perhaps the chain has no blocks?") .map(|block| block.header.hash) } async fn block_timestamp(&self, number: BlockNumberOrTag) -> anyhow::Result { self.provider() - .await? + .await + .context("Failed to get the Kitchensink provider")? .get_block_by_number(number) - .await? - .ok_or(anyhow::Error::msg("Blockchain has no blocks")) + .await + .context("Failed to get the kitchensink block")? + .context("Failed to get the Kitchensink block, perhaps the chain has no blocks?") .map(|block| block.header.timestamp) } async fn last_block_number(&self) -> anyhow::Result { self.provider() - .await? + .await + .context("Failed to get the Kitchensink provider")? .get_block_number() .await .map_err(Into::into) @@ -592,8 +647,10 @@ impl Node for KitchensinkNode { .stdin(Stdio::null()) .stdout(Stdio::piped()) .stderr(Stdio::null()) - .spawn()? - .wait_with_output()? + .spawn() + .context("Failed to spawn kitchensink --version")? + .wait_with_output() + .context("Failed to wait for kitchensink --version")? .stdout; Ok(String::from_utf8_lossy(&output).into()) } diff --git a/crates/node/src/pool.rs b/crates/node/src/pool.rs index d195988..6fa4784 100644 --- a/crates/node/src/pool.rs +++ b/crates/node/src/pool.rs @@ -44,8 +44,10 @@ where nodes.push( handle .join() - .map_err(|error| anyhow::anyhow!("failed to spawn node: {:?}", error))? - .map_err(|error| anyhow::anyhow!("node failed to spawn: {error}"))?, + .map_err(|error| anyhow::anyhow!("failed to spawn node: {:?}", error)) + .context("Failed to join node spawn thread")? + .map_err(|error| anyhow::anyhow!("node failed to spawn: {error}")) + .context("Node failed to spawn")?, ); } @@ -69,7 +71,8 @@ fn spawn_node(args: &Arguments, genesis: String) -> anyhow::Resu connection_string = node.connection_string(), "Spawning node" ); - node.spawn(genesis)?; + node.spawn(genesis) + .context("Failed to spawn node process")?; info!( id = node.id(), connection_string = node.connection_string(), diff --git a/crates/report/src/aggregator.rs b/crates/report/src/aggregator.rs index 1f7af7e..913b0d5 100644 --- a/crates/report/src/aggregator.rs +++ b/crates/report/src/aggregator.rs @@ -9,7 +9,7 @@ use std::{ }; use alloy_primitives::Address; -use anyhow::Result; +use anyhow::{Context as _, Result}; use indexmap::IndexMap; use revive_dt_compiler::{CompilerInput, CompilerOutput, Mode}; use revive_dt_config::{Arguments, TestingPlatform}; @@ -113,7 +113,10 @@ impl ReportAggregator { debug!("Report aggregation completed"); let file_name = { - let current_timestamp = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs(); + let current_timestamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .context("System clock is before UNIX_EPOCH; cannot compute report timestamp")? + .as_secs(); let mut file_name = current_timestamp.to_string(); file_name.push_str(".json"); file_name @@ -124,8 +127,16 @@ impl ReportAggregator { .write(true) .truncate(true) .read(false) - .open(file_path)?; - serde_json::to_writer_pretty(file, &self.report)?; + .open(&file_path) + .with_context(|| { + format!( + "Failed to open report file for writing: {}", + file_path.display() + ) + })?; + serde_json::to_writer_pretty(&file, &self.report).with_context(|| { + format!("Failed to serialize report JSON to {}", file_path.display()) + })?; Ok(()) } diff --git a/crates/report/src/runner_event.rs b/crates/report/src/runner_event.rs index ddb67f9..bdd6c0e 100644 --- a/crates/report/src/runner_event.rs +++ b/crates/report/src/runner_event.rs @@ -4,6 +4,7 @@ use std::{collections::BTreeMap, path::PathBuf, sync::Arc}; use alloy_primitives::Address; +use anyhow::Context as _; use indexmap::IndexMap; use revive_dt_compiler::{CompilerInput, CompilerOutput}; use revive_dt_config::TestingPlatform; @@ -630,7 +631,8 @@ define_event! { impl RunnerEventReporter { pub async fn subscribe(&self) -> anyhow::Result> { let (tx, rx) = oneshot::channel::>(); - self.report_subscribe_to_events_event(tx)?; + self.report_subscribe_to_events_event(tx) + .context("Failed to send subscribe request to reporter task")?; rx.await.map_err(Into::into) } } diff --git a/crates/solc-binaries/src/cache.rs b/crates/solc-binaries/src/cache.rs index 57b9696..67d8f9e 100644 --- a/crates/solc-binaries/src/cache.rs +++ b/crates/solc-binaries/src/cache.rs @@ -12,6 +12,7 @@ use std::{ use tokio::sync::Mutex; use crate::download::SolcDownloader; +use anyhow::Context; pub const SOLC_CACHE_DIRECTORY: &str = "solc"; pub(crate) static SOLC_CACHER: LazyLock>> = LazyLock::new(Default::default); @@ -31,8 +32,20 @@ pub(crate) async fn get_or_download( return Ok(target_file); } - create_dir_all(target_directory)?; - download_to_file(&target_file, downloader).await?; + create_dir_all(&target_directory).with_context(|| { + format!( + "Failed to create solc cache directory: {}", + target_directory.display() + ) + })?; + download_to_file(&target_file, downloader) + .await + .with_context(|| { + format!( + "Failed to write downloaded solc to {}", + target_file.display() + ) + })?; cache.insert(target_file.clone()); Ok(target_file) @@ -45,14 +58,26 @@ async fn download_to_file(path: &Path, downloader: &SolcDownloader) -> anyhow::R #[cfg(unix)] { - let mut permissions = file.metadata()?.permissions(); + let mut permissions = file + .metadata() + .with_context(|| format!("Failed to read metadata for {}", path.display()))? + .permissions(); permissions.set_mode(permissions.mode() | 0o111); - file.set_permissions(permissions)?; + file.set_permissions(permissions).with_context(|| { + format!("Failed to set executable permissions on {}", path.display()) + })?; } let mut file = BufWriter::new(file); - file.write_all(&downloader.download().await?)?; - file.flush()?; + file.write_all( + &downloader + .download() + .await + .context("Failed to download solc binary bytes")?, + ) + .with_context(|| format!("Failed to write solc binary to {}", path.display()))?; + file.flush() + .with_context(|| format!("Failed to flush file {}", path.display()))?; drop(file); #[cfg(target_os = "macos")] @@ -63,8 +88,20 @@ async fn download_to_file(path: &Path, downloader: &SolcDownloader) -> anyhow::R .stderr(std::process::Stdio::null()) .stdout(std::process::Stdio::null()) .stdout(std::process::Stdio::null()) - .spawn()? - .wait()?; + .spawn() + .with_context(|| { + format!( + "Failed to spawn xattr to remove quarantine attribute on {}", + path.display() + ) + })? + .wait() + .with_context(|| { + format!( + "Failed waiting for xattr operation to complete on {}", + path.display() + ) + })?; Ok(()) } diff --git a/crates/solc-binaries/src/download.rs b/crates/solc-binaries/src/download.rs index 691f639..0e94ffa 100644 --- a/crates/solc-binaries/src/download.rs +++ b/crates/solc-binaries/src/download.rs @@ -11,6 +11,7 @@ use semver::Version; use sha2::{Digest, Sha256}; use crate::list::List; +use anyhow::Context; pub static LIST_CACHE: LazyLock>> = LazyLock::new(Default::default); @@ -30,7 +31,12 @@ impl List { return Ok(list.clone()); } - let body: List = reqwest::get(url).await?.json().await?; + let body: List = reqwest::get(url) + .await + .with_context(|| format!("Failed to GET solc list from {url}"))? + .json() + .await + .with_context(|| format!("Failed to deserialize solc list JSON from {url}"))?; LIST_CACHE.lock().unwrap().insert(url, body.clone()); @@ -68,7 +74,8 @@ impl SolcDownloader { }), VersionOrRequirement::Requirement(requirement) => { let Some(version) = List::download(list) - .await? + .await + .with_context(|| format!("Failed to download solc builds list from {list}"))? .builds .into_iter() .map(|build| build.version) @@ -107,11 +114,20 @@ impl SolcDownloader { /// 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> { - let builds = List::download(self.list).await?.builds; + let builds = List::download(self.list) + .await + .with_context(|| format!("Failed to download solc builds list from {}", self.list))? + .builds; let build = builds .iter() .find(|build| build.version == self.version) - .ok_or_else(|| anyhow::anyhow!("solc v{} not found builds", self.version))?; + .ok_or_else(|| anyhow::anyhow!("solc v{} not found builds", self.version)) + .with_context(|| { + format!( + "Requested solc version {} was not found in builds list fetched from {}", + self.version, self.list + ) + })?; let path = build.path.clone(); let expected_digest = build @@ -121,7 +137,13 @@ impl SolcDownloader { .to_string(); let url = format!("{}/{}/{}", Self::BASE_URL, self.target, path.display()); - let file = reqwest::get(url).await?.bytes().await?.to_vec(); + let file = reqwest::get(&url) + .await + .with_context(|| format!("Failed to GET solc binary from {url}"))? + .bytes() + .await + .with_context(|| format!("Failed to read solc binary bytes from {url}"))? + .to_vec(); if hex::encode(Sha256::digest(&file)) != expected_digest { anyhow::bail!("sha256 mismatch for solc version {}", self.version); diff --git a/crates/solc-binaries/src/lib.rs b/crates/solc-binaries/src/lib.rs index 05d0de5..8bf4134 100644 --- a/crates/solc-binaries/src/lib.rs +++ b/crates/solc-binaries/src/lib.rs @@ -5,6 +5,7 @@ use std::path::{Path, PathBuf}; +use anyhow::Context; use cache::get_or_download; use download::SolcDownloader; @@ -34,7 +35,8 @@ pub async fn download_solc( SolcDownloader::windows(version).await } else { unimplemented!() - }?; + } + .context("Failed to initialize the Solc Downloader")?; get_or_download(cache_directory, &downloader).await } diff --git a/run_tests.sh b/run_tests.sh old mode 100644 new mode 100755