diff --git a/Cargo.lock b/Cargo.lock index 9f20c7a5..1696e807 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1285,6 +1285,7 @@ version = "0.1.0" dependencies = [ "anyhow", "async-trait", + "color-eyre", "cosmos-sdk-proto", "cosmrs", "hex", diff --git a/crates/cli/src/cache.rs b/crates/cli/src/cache.rs index 71485024..1fde2843 100644 --- a/crates/cli/src/cache.rs +++ b/crates/cli/src/cache.rs @@ -1,5 +1,6 @@ use std::path::{Path, PathBuf}; +use color_eyre::{eyre::eyre, Result}; use serde::{Deserialize, Serialize}; use tokio::{ fs::File, @@ -8,7 +9,7 @@ use tokio::{ use tracing::debug; use xxhash_rust::xxh3::Xxh3; -use crate::{config::Config, error::Error}; +use crate::config::Config; const BUFFER_SIZE: usize = 16384; // 16 KB buffer type Hash = u64; @@ -22,7 +23,7 @@ struct DeployedContract { // Porcelain impl Config { - pub async fn contract_has_changed(&self, file: &Path) -> Result { + pub async fn contract_has_changed(&self, file: &Path) -> Result { let cur_hash: Hash = Self::gen_hash(file).await?; debug!("current file hash: {}", cur_hash); @@ -39,20 +40,17 @@ impl Config { } /// Return a hash of the given file's contents - pub async fn gen_hash(file: &Path) -> Result { - let file = File::open(file) - .await - .map_err(|e| Error::GenericErr(e.to_string()))?; + pub async fn gen_hash(file: &Path) -> Result { + let file = File::open(file).await?; + let mut reader = BufReader::new(file); let mut hasher = Xxh3::new(); let mut buffer = [0; BUFFER_SIZE]; loop { - let bytes_read = reader - .read(&mut buffer) - .await - .map_err(|e| Error::GenericErr(e.to_string()))?; + let bytes_read = reader.read(&mut buffer).await?; + if bytes_read == 0 { break; } @@ -65,7 +63,7 @@ impl Config { Ok(hash) } - pub async fn save_codeid_to_cache(&self, file: &Path, code_id: u64) -> Result<(), Error> { + pub async fn save_codeid_to_cache(&self, file: &Path, code_id: u64) -> Result<()> { let contract_hash = Self::gen_hash(file).await?; let dest = Self::to_cache_path(self, file)?; let deployed_contract = DeployedContract { @@ -76,7 +74,7 @@ impl Config { Self::write_to_cache(dest.as_path(), &deployed_contract).await } - pub async fn get_cached_codeid(&self, file: &Path) -> Result { + pub async fn get_cached_codeid(&self, file: &Path) -> Result { let cache_path = Self::to_cache_path(self, file)?; let code_id = Self::read_from_cache(cache_path.as_path()).await?.code_id; @@ -85,12 +83,16 @@ impl Config { // Plumbing - fn to_cache_path(&self, file: &Path) -> Result { + fn to_cache_path(&self, file: &Path) -> Result { // Get cache filepath (".quartz/cache/example.wasm.json") from "example.wasm" filepath let mut filename = file .file_name() - .ok_or(Error::PathNotFile(file.display().to_string()))? + .ok_or(eyre!( + "file at cache filepath does not exist {}", + file.display() + ))? .to_os_string(); + filename.push(".json"); let cached_file_path = Self::cache_dir(self)?.join::(filename.into()); @@ -99,42 +101,43 @@ impl Config { } /// Retreive hash from cache file - async fn read_from_cache(cache_file: &Path) -> Result { - let content = tokio::fs::read_to_string(cache_file) - .await - .map_err(|e| Error::GenericErr(e.to_string()))?; - serde_json::from_str(&content).map_err(|e| Error::GenericErr(e.to_string())) + async fn read_from_cache(cache_file: &Path) -> Result { + let content = tokio::fs::read_to_string(cache_file).await?; + + serde_json::from_str(&content).map_err(|e| eyre!(e)) } /// Write a given file's contents hash to a file in cache directory - async fn write_to_cache(cache_file: &Path, data: &DeployedContract) -> Result<(), Error> { - let content = serde_json::to_string(data).map_err(|e| Error::GenericErr(e.to_string()))?; + async fn write_to_cache(cache_file: &Path, data: &DeployedContract) -> Result<()> { + let content = serde_json::to_string(data)?; + tokio::fs::write(cache_file, content) .await - .map_err(|e| Error::GenericErr(e.to_string())) + .map_err(|e| eyre!(e)) } - - pub fn cache_dir(&self) -> Result { - Ok(self.app_dir.join(".cache/")) + pub fn cache_dir(&self) -> Result { + let cache_dir = self.app_dir.join(".cache/"); + std::fs::create_dir_all(&cache_dir)?; + Ok(cache_dir) } - pub fn build_log_dir(&self) -> Result { - Ok(self.app_dir.join(".cache/log/")) + pub fn build_log_dir(&self) -> Result { + let build_log_dir = self.app_dir.join(".cache/log/"); + std::fs::create_dir_all(&build_log_dir)?; + Ok(build_log_dir) } /// Creates the build log if it isn't created already, returns relative path from app_dir to log directory - pub async fn create_build_log(&self) -> Result { + pub async fn create_build_log(&self) -> Result { let log_dir = Self::build_log_dir(self)?; if !log_dir.exists() { - tokio::fs::create_dir_all(&log_dir) - .await - .map_err(|e| Error::GenericErr(e.to_string()))?; + tokio::fs::create_dir_all(&log_dir).await?; } Ok(log_dir) } - pub async fn log_build(&self, is_enclave: bool) -> Result<(), Error> { + pub async fn log_build(&self, is_enclave: bool) -> Result<()> { let log_dir = Self::create_build_log(self).await?; let filename = match is_enclave { @@ -142,9 +145,7 @@ impl Config { false => "contract", }; - tokio::fs::write(log_dir.join(filename), "test") - .await - .map_err(|e| Error::GenericErr(e.to_string()))?; + tokio::fs::write(log_dir.join(filename), "log").await?; Ok(()) } diff --git a/crates/cli/src/config.rs b/crates/cli/src/config.rs index e43c108a..ecae1ed4 100644 --- a/crates/cli/src/config.rs +++ b/crates/cli/src/config.rs @@ -79,7 +79,7 @@ fn default_ws_url() -> Url { } fn default_grpc_url() -> Url { - "http://127.0.0.1:9090," + "http://127.0.0.1:9090" .parse() .expect("valid hardcoded URL") } diff --git a/crates/cli/src/error.rs b/crates/cli/src/error.rs deleted file mode 100644 index 96a57c2a..00000000 --- a/crates/cli/src/error.rs +++ /dev/null @@ -1,58 +0,0 @@ -use displaydoc::Display; -use thiserror::Error; - -#[derive(Debug, Display, Error)] -pub enum Error { - /// Specified path `{0}` is not a directory - PathNotDir(String), - /// Specified file `{0}` does not exist - PathNotFile(String), - /// unspecified error: {0} - GenericErr(String), - /// IoError: {0} - IoError(String), - /// TOML Error : {0} - TomlError(String), - /// Tendermint error: {0} - TendermintError(String), - /// Clearscreen error: {0} - ClearscreenError(String), - /// JSON Error: {0} - JsonError(String), -} - -impl From for Error { - fn from(err: std::io::Error) -> Self { - Error::IoError(err.to_string()) - } -} - -impl From for Error { - fn from(err: toml::de::Error) -> Self { - Error::TomlError(err.to_string()) - } -} - -impl From for Error { - fn from(err: toml::ser::Error) -> Self { - Error::TomlError(err.to_string()) - } -} - -impl From for Error { - fn from(err: tendermint::Error) -> Self { - Error::TendermintError(err.to_string()) - } -} - -impl From for Error { - fn from(err: clearscreen::Error) -> Self { - Error::ClearscreenError(err.to_string()) - } -} - -impl From for Error { - fn from(err: serde_json::Error) -> Self { - Error::JsonError(err.to_string()) - } -} diff --git a/crates/cli/src/handler.rs b/crates/cli/src/handler.rs index 7d5d882f..be800c96 100644 --- a/crates/cli/src/handler.rs +++ b/crates/cli/src/handler.rs @@ -1,6 +1,7 @@ use async_trait::async_trait; +use color_eyre::{Report, Result}; -use crate::{config::Config, error::Error, request::Request, response::Response}; +use crate::{config::Config, request::Request, response::Response}; pub mod utils; // commands @@ -14,24 +15,16 @@ pub mod init; #[async_trait] pub trait Handler { - type Error; type Response; - async fn handle + Send>( - self, - config: C, - ) -> Result; + async fn handle + Send>(self, config: C) -> Result; } #[async_trait] impl Handler for Request { - type Error = Error; type Response = Response; - async fn handle + Send>( - self, - config: C, - ) -> Result { + async fn handle + Send>(self, config: C) -> Result { match self { Request::Init(request) => request.handle(config).await, Request::Handshake(request) => request.handle(config).await, diff --git a/crates/cli/src/handler/contract_build.rs b/crates/cli/src/handler/contract_build.rs index c059c9d1..123d733a 100644 --- a/crates/cli/src/handler/contract_build.rs +++ b/crates/cli/src/handler/contract_build.rs @@ -1,12 +1,11 @@ use std::process::Command; use async_trait::async_trait; -use color_eyre::owo_colors::OwoColorize; +use color_eyre::{eyre::eyre, owo_colors::OwoColorize, Report, Result}; use tracing::{debug, info}; use crate::{ config::Config, - error::Error, handler::Handler, request::contract_build::ContractBuildRequest, response::{contract_build::ContractBuildResponse, Response}, @@ -14,13 +13,9 @@ use crate::{ #[async_trait] impl Handler for ContractBuildRequest { - type Error = Error; type Response = Response; - async fn handle + Send>( - self, - config: C, - ) -> Result { + async fn handle + Send>(self, config: C) -> Result { let config = config.as_ref(); info!("{}", "\nPeforming Contract Build".blue().bold()); @@ -46,15 +41,10 @@ impl Handler for ContractBuildRequest { } info!("{}", "🚧 Building contract binary ...".green().bold()); - let status = command - .status() - .map_err(|e| Error::GenericErr(e.to_string()))?; + let status = command.status()?; if !status.success() { - return Err(Error::GenericErr(format!( - "Couldn't build contract. \n{:?}", - status - ))); + return Err(eyre!("Couldn't build contract. \n{:?}", status)); } config.log_build(false).await?; diff --git a/crates/cli/src/handler/contract_deploy.rs b/crates/cli/src/handler/contract_deploy.rs index 35bcdf26..cca19ed1 100644 --- a/crates/cli/src/handler/contract_deploy.rs +++ b/crates/cli/src/handler/contract_deploy.rs @@ -2,7 +2,11 @@ use std::path::Path; use async_trait::async_trait; use cargo_metadata::MetadataCommand; -use color_eyre::owo_colors::OwoColorize; +use color_eyre::{ + eyre::{eyre, Context}, + owo_colors::OwoColorize, + Report, Result, +}; use cw_client::{CliClient, CwClient}; use serde_json::json; use tendermint_rpc::HttpClient; @@ -11,7 +15,6 @@ use tracing::{debug, info}; use super::utils::{helpers::block_tx_commit, types::WasmdTxResponse}; use crate::{ config::Config, - error::Error, handler::{utils::relay::RelayMessage, Handler}, request::contract_deploy::ContractDeployRequest, response::{contract_deploy::ContractDeployResponse, Response}, @@ -19,24 +22,18 @@ use crate::{ #[async_trait] impl Handler for ContractDeployRequest { - type Error = Error; type Response = Response; - async fn handle + Send>( - self, - config: C, - ) -> Result { + async fn handle + Send>(self, config: C) -> Result { let config = config.as_ref(); info!("{}", "\nPeforming Contract Deploy".blue().bold()); // Get contract package name in snake_case let package_name = MetadataCommand::new() .manifest_path(&self.contract_manifest) - .exec() - .map_err(|e| Error::GenericErr(e.to_string()))? + .exec()? .root_package() - .ok_or("No root package found in the metadata") - .map_err(|e| Error::GenericErr(e.to_string()))? + .ok_or(eyre!("No root package found in the metadata"))? .name .clone() .replace('-', "_"); @@ -47,9 +44,7 @@ impl Handler for ContractDeployRequest { .join(package_name) .with_extension("wasm"); - let (code_id, contract_addr) = deploy(wasm_bin_path.as_path(), self, config) - .await - .map_err(|e| Error::GenericErr(e.to_string()))?; + let (code_id, contract_addr) = deploy(wasm_bin_path.as_path(), self, config).await?; Ok(ContractDeployResponse { code_id, @@ -63,17 +58,23 @@ async fn deploy( wasm_bin_path: &Path, args: ContractDeployRequest, config: &Config, -) -> Result<(u64, String), anyhow::Error> { +) -> Result<(u64, String), Report> { let tmrpc_client = HttpClient::new(config.node_url.as_str())?; let cw_client = CliClient::neutrond(config.node_url.clone()); info!("🚀 Deploying {} Contract", args.label); let code_id = if config.contract_has_changed(wasm_bin_path).await? { - let deploy_output: WasmdTxResponse = serde_json::from_str(&cw_client.deploy( - &config.chain_id, - &config.tx_sender, - wasm_bin_path.display().to_string(), - )?)?; + let deploy_output: WasmdTxResponse = serde_json::from_str( + &cw_client + .deploy( + &config.chain_id, + &config.tx_sender, + wasm_bin_path.display().to_string(), + ) + .map_err(|err| eyre!(Box::new(err)))?, + ) + .wrap_err("Error calling deploy on cw client")?; + let res = block_tx_commit(&tmrpc_client, deploy_output.txhash).await?; // Find the 'code_id' attribute @@ -89,15 +90,21 @@ async fn deploy( .find(|attr| attr.key_str().unwrap_or("") == "code_id") }) .and_then(|attr| attr.value_str().ok().and_then(|v| v.parse().ok())) - .ok_or_else(|| anyhow::anyhow!("Failed to find code_id in the transaction result"))?; + .ok_or_else(|| eyre!("Failed to find code_id in the transaction result"))?; info!("Code ID: {}", code_id); - config.save_codeid_to_cache(wasm_bin_path, code_id).await?; + config + .save_codeid_to_cache(wasm_bin_path, code_id) + .await + .wrap_err("Error saving contract code id to cache")?; code_id } else { - config.get_cached_codeid(wasm_bin_path).await? + config + .get_cached_codeid(wasm_bin_path) + .await + .wrap_err("Error getting contract code id from cache")? }; info!("🚀 Communicating with Relay to Instantiate..."); @@ -116,6 +123,7 @@ async fn deploy( json!(init_msg), &format!("{} Contract #{}", args.label, code_id), )?)?; + let res = block_tx_commit(&tmrpc_client, init_output.txhash).await?; // Find the '_contract_address' attribute @@ -131,9 +139,7 @@ async fn deploy( .find(|attr| attr.key_str().unwrap_or("") == "_contract_address") }) .and_then(|attr| attr.value_str().ok().and_then(|v| v.parse().ok())) - .ok_or_else(|| { - anyhow::anyhow!("Failed to find contract_address in the transaction result") - })?; + .ok_or_else(|| eyre!("Failed to find contract_address in the transaction result"))?; info!("🚀 Successfully deployed and instantiated contract!"); info!("🆔 Code ID: {}", code_id); diff --git a/crates/cli/src/handler/dev.rs b/crates/cli/src/handler/dev.rs index 8be483a6..7caa2b09 100644 --- a/crates/cli/src/handler/dev.rs +++ b/crates/cli/src/handler/dev.rs @@ -1,9 +1,11 @@ use std::{path::PathBuf, time::Duration}; use async_trait::async_trait; -use color_eyre::owo_colors::OwoColorize; -// todo get rid of this? -use miette::{IntoDiagnostic, Result}; +use color_eyre::{ + eyre::{eyre, Context}, + owo_colors::OwoColorize, + Report, Result, +}; use quartz_common::proto::core_client::CoreClient; use tokio::{sync::mpsc, time::sleep}; use tracing::{debug, info}; @@ -11,7 +13,6 @@ use watchexec::Watchexec; use watchexec_signals::Signal; use crate::{ - error::Error, handler::{utils::helpers::wasmaddr_to_id, Handler}, request::{ contract_build::ContractBuildRequest, contract_deploy::ContractDeployRequest, @@ -24,13 +25,9 @@ use crate::{ #[async_trait] impl Handler for DevRequest { - type Error = Error; type Response = Response; - async fn handle + Send>( - self, - config: C, - ) -> Result { + async fn handle + Send>(self, config: C) -> Result { let config = config.as_ref(); info!("\nPeforming Dev"); @@ -58,7 +55,7 @@ async fn dev_driver( mut rx: mpsc::Receiver, args: &DevRequest, config: Config, -) -> Result<(), Error> { +) -> Result<(), Report> { // State let mut first_enclave_message = true; let mut first_contract_message = true; @@ -82,7 +79,10 @@ async fn dev_driver( let contract_build = ContractBuildRequest { contract_manifest: args.contract_manifest.clone(), }; - contract_build.handle(&config).await?; + contract_build + .handle(&config) + .await + .wrap_err("Could not run `contract build`")?; // Start enclave in background spawn_enclave_start(args, &config)?; @@ -99,9 +99,7 @@ async fn dev_driver( info!("{}", "Enclave is listening for requests...".green().bold()); } Err(e) => { - eprintln!("Error launching quartz app"); - - return Err(e); + return Err(e).wrap_err("Error initializing `quartz dev`"); } } } @@ -132,9 +130,7 @@ async fn dev_driver( info!("{}", "Enclave is listening for requests...".green().bold()); } Err(e) => { - eprintln!("Error restarting enclave and handshake"); - - return Err(e); + return Err(e).wrap_err("Error restarting enclave after rebuild"); } } } @@ -154,7 +150,7 @@ async fn dev_driver( Err(e) => { eprintln!("Error deploying contract and handshake:"); - return Err(e); + return Err(e).wrap_err("Error redeploying contract after rebuild"); } } @@ -167,7 +163,7 @@ async fn dev_driver( } // Spawns enclve start in a separate task which runs in the background -fn spawn_enclave_start(args: &DevRequest, config: &Config) -> Result<(), Error> { +fn spawn_enclave_start(args: &DevRequest, config: &Config) -> Result<()> { // In separate process, launch the enclave let enclave_start = EnclaveStartRequest { unsafe_trust_latest: args.unsafe_trust_latest, @@ -180,10 +176,8 @@ fn spawn_enclave_start(args: &DevRequest, config: &Config) -> Result<(), Error> tokio::spawn(async move { if let Err(e) = enclave_start.handle(config_cpy).await { - eprintln!("Error running enclave start.\n {}", e); + eprintln!("Error running enclave start.\n {:?}", e); } - - Ok::<(), Error>(()) }); Ok(()) @@ -194,7 +188,7 @@ async fn deploy_and_handshake( contract: Option<&str>, args: &DevRequest, config: &Config, -) -> Result { +) -> Result { info!("Waiting for enclave start to deploy contract and handshake"); // Wait at most 60 seconds to connect to enclave @@ -210,9 +204,7 @@ async fn deploy_and_handshake( i -= 1; if i == 0 { - return Err(Error::GenericErr( - "Could not connect to enclave".to_string(), - )); + return Err(eyre!("Could not connect to enclave")); } } // Calls which interact with enclave @@ -231,34 +223,33 @@ async fn deploy_and_handshake( contract_manifest: args.contract_manifest.clone(), }; // Call handler - let cd_res = contract_deploy.handle(config).await; - - // Return contract address or shutdown enclave & error - match cd_res { - Ok(Response::ContractDeploy(res)) => res.contract_addr, - Err(e) => return Err(e), - _ => unreachable!("Unexpected response variant"), + let cd_res = contract_deploy + .handle(config) + .await + .wrap_err("Could not run `quartz contract deploy`")?; + + if let Response::ContractDeploy(res) = cd_res { + res.contract_addr + } else { + unreachable!("Unexpected response variant") } }; // Run handshake info!("Running handshake on contract `{}`", contract); let handshake = HandshakeRequest { - contract: wasmaddr_to_id(&contract).map_err(|_| Error::GenericErr(String::default()))?, + contract: wasmaddr_to_id(&contract)?, unsafe_trust_latest: args.unsafe_trust_latest, }; - let h_res = handshake.handle(config).await; - - match h_res { - Ok(Response::Handshake(res)) => { - info!("Handshake complete: {}", res.pub_key); - } - Err(e) => { - return Err(e); - } - _ => unreachable!("Unexpected response variant"), - }; + let h_res = handshake + .handle(config) + .await + .wrap_err("Could not run `quartz handshake`")?; + println!("got here"); + if let Response::Handshake(res) = h_res { + info!("Handshake complete: {}", res.pub_key); + } Ok(contract) } @@ -304,7 +295,7 @@ async fn watcher(tx: mpsc::Sender, log_dir: PathBuf) -> Result<()> { wx.config.pathset([log_dir]); // Keep running until Watchexec quits - let _ = main.await.into_diagnostic()?; + let _ = main.await?; Ok(()) } diff --git a/crates/cli/src/handler/enclave_build.rs b/crates/cli/src/handler/enclave_build.rs index 3ba4ef54..36654722 100644 --- a/crates/cli/src/handler/enclave_build.rs +++ b/crates/cli/src/handler/enclave_build.rs @@ -1,11 +1,10 @@ use async_trait::async_trait; -use color_eyre::owo_colors::OwoColorize; +use color_eyre::{eyre::eyre, owo_colors::OwoColorize, Report, Result}; use tokio::process::Command; use tracing::{debug, info}; use crate::{ config::Config, - error::Error, handler::Handler, request::enclave_build::EnclaveBuildRequest, response::{enclave_build::EnclaveBuildResponse, Response}, @@ -13,13 +12,9 @@ use crate::{ #[async_trait] impl Handler for EnclaveBuildRequest { - type Error = Error; type Response = Response; - async fn handle + Send>( - self, - config: C, - ) -> Result { + async fn handle + Send>(self, config: C) -> Result { let config = config.as_ref(); info!("{}", "\nPeforming Enclave Build".blue().bold()); @@ -43,16 +38,10 @@ impl Handler for EnclaveBuildRequest { } info!("{}", "🚧 Running build command ...".green().bold()); - let status = command - .status() - .await - .map_err(|e| Error::GenericErr(e.to_string()))?; + let status = command.status().await?; if !status.success() { - return Err(Error::GenericErr(format!( - "Couldn't build enclave. {:?}", - status - ))); + return Err(eyre!("Couldn't build enclave. {:?}", status)); } config.log_build(true).await?; diff --git a/crates/cli/src/handler/enclave_start.rs b/crates/cli/src/handler/enclave_start.rs index 053eff28..617220a5 100644 --- a/crates/cli/src/handler/enclave_start.rs +++ b/crates/cli/src/handler/enclave_start.rs @@ -2,7 +2,11 @@ use std::{fs, path::Path}; use async_trait::async_trait; use cargo_metadata::MetadataCommand; -use color_eyre::owo_colors::OwoColorize; +use color_eyre::{ + eyre::{eyre, Context}, + owo_colors::OwoColorize, + Report, Result, +}; use cosmrs::AccountId; use quartz_common::enclave::types::Fmspc; use reqwest::Url; @@ -12,7 +16,6 @@ use tracing::{debug, info}; use crate::{ config::Config, - error::Error, handler::{utils::helpers::write_cache_hash_height, Handler}, request::enclave_start::EnclaveStartRequest, response::{enclave_start::EnclaveStartResponse, Response}, @@ -20,18 +23,16 @@ use crate::{ #[async_trait] impl Handler for EnclaveStartRequest { - type Error = Error; type Response = Response; - async fn handle + Send>( - self, - config: C, - ) -> Result { + async fn handle + Send>(self, config: C) -> Result { let config = config.as_ref().clone(); info!("{}", "\nPeforming Enclave Start".blue().bold()); // Get trusted height and hash - let (trusted_height, trusted_hash) = self.get_hash_height(&config)?; + let (trusted_height, trusted_hash) = self + .get_hash_height(&config) + .wrap_err("Error getting trusted hash and height")?; write_cache_hash_height(trusted_height, trusted_hash, &config).await?; if config.mock_sgx { @@ -59,27 +60,21 @@ impl Handler for EnclaveStartRequest { handle_process(enclave_child).await?; } else { let Some(fmspc) = self.fmspc else { - return Err(Error::GenericErr( - "FMSPC is required if MOCK_SGX isn't set".to_string(), - )); + return Err(eyre!("FMSPC is required if MOCK_SGX isn't set")); }; let Some(tcbinfo_contract) = self.tcbinfo_contract else { - return Err(Error::GenericErr( - "tcbinfo_contract is required if MOCK_SGX isn't set".to_string(), - )); + return Err(eyre!("tcbinfo_contract is required if MOCK_SGX isn't set")); }; let Some(dcap_verifier_contract) = self.dcap_verifier_contract else { - return Err(Error::GenericErr( - "dcap_verifier_contract is required if MOCK_SGX isn't set".to_string(), + return Err(eyre!( + "dcap_verifier_contract is required if MOCK_SGX isn't set" )); }; - if let Err(_) = std::env::var("ADMIN_SK") { - return Err(Error::GenericErr( - "ADMIN_SK environment variable is not set".to_string(), - )); + if std::env::var("ADMIN_SK").is_err() { + return Err(eyre!("ADMIN_SK environment variable is not set")); }; let enclave_dir = fs::canonicalize(config.app_dir.join("enclave"))?; @@ -119,17 +114,11 @@ impl Handler for EnclaveStartRequest { } } -async fn handle_process(mut child: Child) -> Result<(), Error> { - let status = child - .wait() - .await - .map_err(|e| Error::GenericErr(e.to_string()))?; +async fn handle_process(mut child: Child) -> Result<()> { + let status = child.wait().await?; if !status.success() { - return Err(Error::GenericErr(format!( - "Couldn't build enclave. {:?}", - status - ))); + return Err(eyre!("Couldn't build enclave. {:?}", status)); } Ok(()) } @@ -138,18 +127,17 @@ async fn create_mock_enclave_child( app_dir: &Path, release: bool, enclave_args: Vec, -) -> Result { +) -> Result { let enclave_dir = app_dir.join("enclave"); let target_dir = app_dir.join("target"); // Use the enclave package metadata to get the path to the program binary let package_name = MetadataCommand::new() .manifest_path(enclave_dir.join("Cargo.toml")) - .exec() - .map_err(|e| Error::GenericErr(e.to_string()))? + .exec()? .root_package() .ok_or("No root package found in the metadata") - .map_err(|e| Error::GenericErr(e.to_string()))? + .map_err(|e| eyre!(e))? .name .clone(); @@ -166,26 +154,18 @@ async fn create_mock_enclave_child( debug!("Enclave Start Command: {:?}", command); info!("{}", "🚧 Spawning enclave process ...".green().bold()); - let child = command - .kill_on_drop(true) - .spawn() - .map_err(|e| Error::GenericErr(e.to_string()))?; + let child = command.kill_on_drop(true).spawn()?; Ok(child) } -async fn gramine_sgx_gen_private_key(enclave_dir: &Path) -> Result<(), Error> { +async fn gramine_sgx_gen_private_key(enclave_dir: &Path) -> Result<()> { // Launch the gramine-sgx-gen-private-key command Command::new("gramine-sgx-gen-private-key") .current_dir(enclave_dir) .output() .await - .map_err(|e| { - Error::GenericErr(format!( - "Failed to execute gramine-sgx-gen-private-key: {}", - e - )) - })?; + .map_err(|e| eyre!("Failed to execute gramine-sgx-gen-private-key: {}", e))?; // Continue regardless of error // > /dev/null 2>&1 || : # may fail @@ -205,7 +185,7 @@ async fn gramine_manifest( node_url: &Url, ws_url: &Url, grpc_url: &Url, -) -> Result<(), Error> { +) -> Result<()> { let host = target_lexicon::HOST; let arch_libdir = format!( "/lib/{}-{}-{}", @@ -214,7 +194,7 @@ async fn gramine_manifest( let ra_client_spid = "51CAF5A48B450D624AEFE3286D314894"; let home_dir = dirs::home_dir() - .ok_or(Error::GenericErr("home dir not set".to_string()))? + .ok_or_else(|| eyre!("Home directory not set"))? .display() .to_string(); @@ -243,19 +223,19 @@ async fn gramine_manifest( .current_dir(enclave_dir) .status() .await - .map_err(|e| Error::GenericErr(e.to_string()))?; + .map_err(|e| eyre!("Failed to execute gramine-manifest: {}", e))?; if !status.success() { - return Err(Error::GenericErr(format!( - "Couldn't run gramine manifest. {:?}", + return Err(eyre!( + "gramine-manifest command failed with status: {:?}", status - ))); + )); } Ok(()) } -async fn gramine_sgx_sign(enclave_dir: &Path) -> Result<(), Error> { +async fn gramine_sgx_sign(enclave_dir: &Path) -> Result<()> { let status = Command::new("gramine-sgx-sign") .arg("--manifest") .arg("quartz.manifest") @@ -264,26 +244,27 @@ async fn gramine_sgx_sign(enclave_dir: &Path) -> Result<(), Error> { .current_dir(enclave_dir) .status() .await - .map_err(|e| Error::GenericErr(e.to_string()))?; + .map_err(|e| eyre!("Failed to execute gramine-sgx-sign: {}", e))?; if !status.success() { - return Err(Error::GenericErr(format!( - "gramine-sgx-sign command failed. {:?}", + return Err(eyre!( + "gramine-sgx-sign command failed with status: {:?}", status - ))); + )); } Ok(()) } -async fn create_gramine_sgx_child(enclave_dir: &Path) -> Result { +async fn create_gramine_sgx_child(enclave_dir: &Path) -> Result { info!("🚧 Spawning enclave process ..."); let child = Command::new("gramine-sgx") .arg("./quartz") .kill_on_drop(true) .current_dir(enclave_dir) - .spawn()?; + .spawn() + .map_err(|e| eyre!("Failed to spawn gramine-sgx child process: {}", e))?; Ok(child) } diff --git a/crates/cli/src/handler/handshake.rs b/crates/cli/src/handler/handshake.rs index 5ea4bfc1..c23b93c5 100644 --- a/crates/cli/src/handler/handshake.rs +++ b/crates/cli/src/handler/handshake.rs @@ -1,6 +1,5 @@ -use anyhow::anyhow; use async_trait::async_trait; -use color_eyre::owo_colors::OwoColorize; +use color_eyre::{eyre::eyre, owo_colors::OwoColorize, Report, Result}; use cw_client::{CliClient, CwClient}; use futures_util::stream::StreamExt; use quartz_tm_prover::{config::Config as TmProverConfig, prover::prove}; @@ -11,7 +10,6 @@ use tracing::{debug, info}; use super::utils::{helpers::block_tx_commit, types::WasmdTxResponse}; use crate::{ config::Config, - error::Error, handler::{ utils::{helpers::read_cached_hash_height, relay::RelayMessage}, Handler, @@ -22,27 +20,21 @@ use crate::{ #[async_trait] impl Handler for HandshakeRequest { - type Error = Error; type Response = Response; - async fn handle + Send>( - self, - config: C, - ) -> Result { + async fn handle + Send>(self, config: C) -> Result { let config = config.as_ref().clone(); info!("{}", "\nPeforming Handshake".blue().bold()); // TODO: may need to import verbosity here - let pub_key = handshake(self, config) - .await - .map_err(|e| Error::GenericErr(e.to_string()))?; + let pub_key = handshake(self, config).await?; Ok(HandshakeResponse { pub_key }.into()) } } -async fn handshake(args: HandshakeRequest, config: Config) -> Result { +async fn handshake(args: HandshakeRequest, config: Config) -> Result { let tmrpc_client = HttpClient::new(config.node_url.as_str())?; let cw_client = CliClient::neutrond(config.node_url.clone()); @@ -64,7 +56,8 @@ async fn handshake(args: HandshakeRequest, config: Config) -> Result Result Result Result Result Result<(), anyhow::Error> { +async fn two_block_waitoor(wsurl: &str) -> Result<()> { let (client, driver) = WebSocketClient::new(wsurl).await?; let driver_handle = tokio::spawn(async move { driver.run().await }); diff --git a/crates/cli/src/handler/init.rs b/crates/cli/src/handler/init.rs index 8e523560..2aecb4a1 100644 --- a/crates/cli/src/handler/init.rs +++ b/crates/cli/src/handler/init.rs @@ -2,13 +2,12 @@ use std::path::PathBuf; use async_trait::async_trait; use cargo_generate::{generate, GenerateArgs, TemplatePath, Vcs}; -use color_eyre::owo_colors::OwoColorize; +use color_eyre::{eyre::Context, owo_colors::OwoColorize, Report, Result}; use tokio::fs; use tracing::info; use crate::{ config::Config, - error::Error, handler::Handler, request::init::InitRequest, response::{init::InitResponse, Response}, @@ -16,14 +15,10 @@ use crate::{ #[async_trait] impl Handler for InitRequest { - type Error = Error; type Response = Response; // TODO: Add non-template init method - async fn handle + Send>( - self, - config: C, - ) -> Result { + async fn handle + Send>(self, config: C) -> Result { let config = config.as_ref(); info!("{}", "\nPeforming Init".blue().bold()); @@ -36,7 +31,7 @@ impl Handler for InitRequest { .expect("path already validated"); fs::create_dir_all(&parent) .await - .map_err(|e| Error::GenericErr(e.to_string()))?; + .wrap_err("Error creating directories to target app folder")?; let file_name = self .name diff --git a/crates/cli/src/handler/utils/helpers.rs b/crates/cli/src/handler/utils/helpers.rs index f98c37bc..6e9a1272 100644 --- a/crates/cli/src/handler/utils/helpers.rs +++ b/crates/cli/src/handler/utils/helpers.rs @@ -1,6 +1,9 @@ use std::time::Duration; -use anyhow::anyhow; +use color_eyre::{ + eyre::{eyre, WrapErr}, + Result, +}; use cosmrs::{AccountId, ErrorReport}; use cw_client::{CliClient, CwClient}; use regex::Regex; @@ -13,15 +16,15 @@ use tendermint_rpc::{ use tokio::fs::{self}; use tracing::debug; -use crate::{config::Config, error::Error}; +use crate::config::Config; -pub fn wasmaddr_to_id(address_str: &str) -> Result { - let _ = bech32_decode(address_str).map_err(|e| anyhow!(e))?; - address_str.parse().map_err(|e: ErrorReport| anyhow!(e)) +pub fn wasmaddr_to_id(address_str: &str) -> Result { + let _ = bech32_decode(address_str).map_err(|e| eyre!(e))?; + address_str.parse().map_err(|e: ErrorReport| eyre!(e)) } // Note: time until tx commit is empiraclly 800ms on DO wasmd chain. -pub async fn block_tx_commit(client: &HttpClient, tx: Hash) -> Result { +pub async fn block_tx_commit(client: &HttpClient, tx: Hash) -> Result { let re = Regex::new(r"tx \([A-F0-9]{64}\) not found")?; tokio::time::sleep(Duration::from_millis(400)).await; @@ -35,7 +38,7 @@ pub async fn block_tx_commit(client: &HttpClient, tx: Hash) -> Result { if !re.is_match(subdetail.source.data().unwrap_or_default()) { - return Err(anyhow!( + return Err(eyre!( "Error querying for tx: {}", ErrorDetail::Response(subdetail) )); @@ -46,7 +49,7 @@ pub async fn block_tx_commit(client: &HttpClient, tx: Hash) -> Result { - return Err(anyhow!("Error querying for tx: {}", e.0)); + return Err(eyre!("Error querying for tx: {}", e.0)); } } } @@ -55,12 +58,13 @@ pub async fn block_tx_commit(client: &HttpClient, tx: Hash) -> Result Result<(Height, Hash), Error> { +pub fn query_latest_height_hash(node_url: Url) -> Result<(Height, Hash)> { let cw_client = CliClient::neutrond(node_url); let (trusted_height, trusted_hash) = cw_client .trusted_height_hash() - .map_err(|e| Error::GenericErr(e.to_string()))?; + .map_err(|e| eyre!(e)) + .wrap_err("Could not query chain with cw client")?; Ok(( trusted_height.try_into()?, @@ -72,7 +76,7 @@ pub async fn write_cache_hash_height( trusted_height: Height, trusted_hash: Hash, config: &Config, -) -> Result<(), Error> { +) -> Result<()> { let height_path = config.cache_dir()?.join("trusted.height"); fs::write(height_path.as_path(), trusted_height.to_string()).await?; let hash_path = config.cache_dir()?.join("trusted.hash"); @@ -81,15 +85,21 @@ pub async fn write_cache_hash_height( Ok(()) } -pub async fn read_cached_hash_height(config: &Config) -> Result<(Height, Hash), Error> { +pub async fn read_cached_hash_height(config: &Config) -> Result<(Height, Hash)> { let height_path = config.cache_dir()?.join("trusted.height"); let hash_path = config.cache_dir()?.join("trusted.hash"); if !height_path.exists() { - return Err(Error::PathNotFile(height_path.display().to_string())); + return Err(eyre!( + "Could not read trusted height from cache: {}", + height_path.display().to_string() + )); } if !hash_path.exists() { - return Err(Error::PathNotFile(hash_path.display().to_string())); + return Err(eyre!( + "Could not read trusted hash from cache: {}", + hash_path.display().to_string() + )); } let trusted_height: Height = fs::read_to_string(height_path.as_path()).await?.parse()?; diff --git a/crates/cli/src/handler/utils/relay.rs b/crates/cli/src/handler/utils/relay.rs index 01011e3c..d536ae0c 100644 --- a/crates/cli/src/handler/utils/relay.rs +++ b/crates/cli/src/handler/utils/relay.rs @@ -1,11 +1,10 @@ +use color_eyre::{eyre::eyre, Result}; use quartz_common::proto::{ core_client::CoreClient, InstantiateRequest, SessionCreateRequest, SessionSetPubKeyRequest, }; use quartz_tm_prover::config::ProofOutput; use serde_json::{json, Value as JsonValue}; -use crate::error::Error; - #[derive(Debug)] pub enum RelayMessage { Instantiate { init_msg: JsonValue }, @@ -14,17 +13,25 @@ pub enum RelayMessage { } impl RelayMessage { - pub async fn run_relay(self, enclave_rpc: String) -> Result { + pub async fn run_relay(self, enclave_rpc: String) -> Result { // Query the gRPC quartz enclave service - let mut qc_client = CoreClient::connect(enclave_rpc) - .await - .map_err(|e| Error::GenericErr(e.to_string()))?; + let mut qc_client = CoreClient::connect(enclave_rpc).await.map_err(|e| { + eyre!( + "Failed to connect to the gRPC quartz enclave service: {}", + e + ) + })?; let attested_msg = match self { RelayMessage::Instantiate { mut init_msg } => qc_client .instantiate(tonic::Request::new(InstantiateRequest {})) .await - .map_err(|e| Error::GenericErr(e.to_string())) + .map_err(|e| { + eyre!( + "Failed to instantiate via gRPC quartz enclave service: {}", + e + ) + }) .map(|res| serde_json::from_str::(&res.into_inner().message))? .map(|msg| { init_msg["quartz"] = msg; @@ -33,7 +40,12 @@ impl RelayMessage { RelayMessage::SessionCreate => qc_client .session_create(tonic::Request::new(SessionCreateRequest {})) .await - .map_err(|e| Error::GenericErr(e.to_string())) + .map_err(|e| { + eyre!( + "Failed to create session via gRPC quartz enclave service: {}", + e + ) + }) .map(|res| serde_json::from_str::(&res.into_inner().message))? .map(|msg| json!({ "quartz": {"session_create": msg}}).to_string())?, RelayMessage::SessionSetPubKey { proof } => qc_client @@ -41,7 +53,12 @@ impl RelayMessage { message: serde_json::to_string(&proof)?, }) .await - .map_err(|e| Error::GenericErr(e.to_string())) + .map_err(|e| { + eyre!( + "Failed to set public key via gRPC quartz enclave service: {}", + e + ) + }) .map(|res| serde_json::from_str::(&res.into_inner().message))? .map(|msg| json!({ "quartz": {"session_set_pub_key": msg}}).to_string())?, }; diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index e98f3074..3df25e7a 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -16,7 +16,6 @@ pub mod cache; pub mod cli; pub mod config; -pub mod error; pub mod handler; pub mod request; pub mod response; @@ -25,7 +24,10 @@ use std::path::PathBuf; use clap::Parser; use cli::ToFigment; -use color_eyre::{eyre::Result, owo_colors::OwoColorize}; +use color_eyre::{ + eyre::{eyre, Result}, + owo_colors::OwoColorize, +}; use config::Config; use figment::{ providers::{Env, Format, Serialized, Toml}, @@ -97,10 +99,10 @@ async fn main() -> Result<()> { Ok(()) } -fn check_path(path: &Option) -> Result<(), error::Error> { +fn check_path(path: &Option) -> Result<()> { if let Some(path) = path { if !path.is_dir() { - return Err(error::Error::PathNotDir(format!("{}", path.display()))); + return Err(eyre!("Path is not a directory: {}", path.display())); } } diff --git a/crates/cli/src/request.rs b/crates/cli/src/request.rs index 60ad6c1a..17c98209 100644 --- a/crates/cli/src/request.rs +++ b/crates/cli/src/request.rs @@ -1,6 +1,7 @@ +use color_eyre::{eyre::eyre, Report, Result}; + use crate::{ cli::{Command, ContractCommand, EnclaveCommand}, - error::Error, request::{ contract_build::ContractBuildRequest, contract_deploy::ContractDeployRequest, dev::DevRequest, enclave_build::EnclaveBuildRequest, enclave_start::EnclaveStartRequest, @@ -28,7 +29,7 @@ pub enum Request { } impl TryFrom for Request { - type Error = Error; + type Error = Report; fn try_from(cmd: Command) -> Result { match cmd { @@ -40,38 +41,46 @@ impl TryFrom for Request { .into()), Command::Contract { contract_command } => contract_command.try_into(), Command::Enclave { enclave_command } => enclave_command.try_into(), - Command::Dev(args) => Ok(DevRequest { - watch: args.watch, - unsafe_trust_latest: args.unsafe_trust_latest, - init_msg: serde_json::from_str(&args.contract_deploy.init_msg) - .map_err(|e| Error::GenericErr(e.to_string()))?, - label: args.contract_deploy.label, - contract_manifest: args.contract_deploy.contract_manifest, - release: args.enclave_build.release, - fmspc: args.fmspc, - tcbinfo_contract: args.tcbinfo_contract, - dcap_verifier_contract: args.dcap_verifier_contract, + Command::Dev(args) => { + if !args.contract_deploy.contract_manifest.exists() { + return Err(eyre!( + "The contract manifest file does not exist: {}", + args.contract_deploy.contract_manifest.display() + )); + } + + Ok(DevRequest { + watch: args.watch, + unsafe_trust_latest: args.unsafe_trust_latest, + contract_manifest: args.contract_deploy.contract_manifest, + init_msg: serde_json::from_str(&args.contract_deploy.init_msg)?, + label: args.contract_deploy.label, + release: args.enclave_build.release, + fmspc: args.fmspc, + tcbinfo_contract: args.tcbinfo_contract, + dcap_verifier_contract: args.dcap_verifier_contract, + } + .into()) } - .into()), } } } impl TryFrom for Request { - type Error = Error; + type Error = Report; - fn try_from(cmd: ContractCommand) -> Result { + fn try_from(cmd: ContractCommand) -> Result { match cmd { ContractCommand::Deploy(args) => { if !args.contract_manifest.exists() { - return Err(Error::PathNotFile( - args.contract_manifest.display().to_string(), + return Err(eyre!( + "The contract manifest file does not exist: {}", + args.contract_manifest.display() )); } Ok(ContractDeployRequest { - init_msg: serde_json::from_str(&args.init_msg) - .map_err(|e| Error::GenericErr(e.to_string()))?, + init_msg: serde_json::from_str(&args.init_msg)?, label: args.label, contract_manifest: args.contract_manifest, } @@ -79,8 +88,9 @@ impl TryFrom for Request { } ContractCommand::Build(args) => { if !args.contract_manifest.exists() { - return Err(Error::PathNotFile( - args.contract_manifest.display().to_string(), + return Err(eyre!( + "The contract manifest file does not exist: {}", + args.contract_manifest.display() )); } @@ -94,9 +104,9 @@ impl TryFrom for Request { } impl TryFrom for Request { - type Error = Error; + type Error = Report; - fn try_from(cmd: EnclaveCommand) -> Result { + fn try_from(cmd: EnclaveCommand) -> Result { match cmd { EnclaveCommand::Build(_) => Ok(EnclaveBuildRequest {}.into()), EnclaveCommand::Start(args) => Ok(EnclaveStartRequest { diff --git a/crates/cli/src/request/contract_deploy.rs b/crates/cli/src/request/contract_deploy.rs index c727e67a..120de28e 100644 --- a/crates/cli/src/request/contract_deploy.rs +++ b/crates/cli/src/request/contract_deploy.rs @@ -1,8 +1,9 @@ use std::{collections::HashMap, path::PathBuf}; +use color_eyre::{eyre::Context, Result}; use serde::{Deserialize, Serialize}; -use crate::{error::Error, request::Request}; +use crate::request::Request; #[derive(Clone, Debug)] pub struct ContractDeployRequest { @@ -18,10 +19,9 @@ impl From for Request { } impl ContractDeployRequest { - pub fn checked_init(init_msg: String) -> Result { - let parsed: GenericQuartzInit = serde_json::from_str(&init_msg).map_err(|_| { - Error::GenericErr("Init message doesn't contain mandatory quartz field.".to_string()) - })?; + pub fn checked_init(init_msg: String) -> Result { + let parsed: GenericQuartzInit = serde_json::from_str(&init_msg) + .wrap_err("Init message doesn't contain mandatory quartz field")?; Ok(parsed) } diff --git a/crates/cli/src/request/enclave_start.rs b/crates/cli/src/request/enclave_start.rs index 2946b8a6..8f1584e1 100644 --- a/crates/cli/src/request/enclave_start.rs +++ b/crates/cli/src/request/enclave_start.rs @@ -1,12 +1,10 @@ +use color_eyre::Result; use cosmrs::AccountId; use quartz_common::enclave::types::Fmspc; use tendermint::{block::Height, Hash}; use tracing::debug; -use crate::{ - config::Config, error::Error, handler::utils::helpers::query_latest_height_hash, - request::Request, -}; +use crate::{config::Config, handler::utils::helpers::query_latest_height_hash, request::Request}; #[derive(Clone, Debug)] pub struct EnclaveStartRequest { @@ -24,7 +22,7 @@ impl From for Request { impl EnclaveStartRequest { /// Returns the trusted hash and height - pub fn get_hash_height(&self, config: &Config) -> Result<(Height, Hash), Error> { + pub fn get_hash_height(&self, config: &Config) -> Result<(Height, Hash)> { if self.unsafe_trust_latest || config.trusted_height == 0 || config.trusted_hash.is_empty() { debug!("querying latest trusted hash & height from node"); @@ -35,10 +33,7 @@ impl EnclaveStartRequest { debug!("reusing config trusted hash & height"); Ok(( config.trusted_height.try_into()?, - config - .trusted_hash - .parse() - .map_err(|_| Error::GenericErr("invalid hash".to_string()))?, + config.trusted_hash.parse()?, )) } } diff --git a/crates/cli/src/request/init.rs b/crates/cli/src/request/init.rs index 51f5ee95..e2603735 100644 --- a/crates/cli/src/request/init.rs +++ b/crates/cli/src/request/init.rs @@ -1,6 +1,8 @@ use std::path::PathBuf; -use crate::{error::Error, request::Request}; +use color_eyre::{eyre::eyre, Report, Result}; + +use crate::request::Request; #[derive(Clone, Debug)] pub struct InitRequest { @@ -8,16 +10,16 @@ pub struct InitRequest { } impl TryFrom for Request { - type Error = Error; + type Error = Report; - fn try_from(request: InitRequest) -> Result { + fn try_from(request: InitRequest) -> Result { if request.name.extension().is_some() { - return Err(Error::PathNotDir(format!("{}", request.name.display()))); + return Err(eyre!("Path is not a directory: {}", request.name.display())); } else if request.name.exists() { - return Err(Error::GenericErr(format!( + return Err(eyre!( "Directory already exists: {}", request.name.display() - ))); + )); } Ok(Request::Init(request)) diff --git a/crates/utils/cw-client/Cargo.toml b/crates/utils/cw-client/Cargo.toml index 7e9226a1..2c4c5242 100644 --- a/crates/utils/cw-client/Cargo.toml +++ b/crates/utils/cw-client/Cargo.toml @@ -12,12 +12,13 @@ authors.workspace = true path = "src/lib.rs" [dependencies] +anyhow.workspace = true async-trait.workspace = true +color-eyre.workspace = true hex.workspace = true reqwest.workspace = true serde.workspace = true serde_json.workspace = true -anyhow.workspace = true tonic.workspace = true cosmrs = { workspace = true, default-features = false, features = ["cosmwasm"] } diff --git a/crates/utils/cw-client/src/cli.rs b/crates/utils/cw-client/src/cli.rs index 8aea0721..a352be46 100644 --- a/crates/utils/cw-client/src/cli.rs +++ b/crates/utils/cw-client/src/cli.rs @@ -1,6 +1,6 @@ use std::process::Command; -use anyhow::anyhow; +use color_eyre::{eyre::eyre, Help, Report, Result}; use cosmrs::{tendermint::chain::Id, AccountId}; use reqwest::Url; use serde::de::DeserializeOwned; @@ -55,8 +55,23 @@ impl CliClient { } } - fn new_command(&self) -> Command { - Command::new(self.kind.bin()) + fn new_command(&self) -> Result { + let bin = self.kind.bin(); + if !self.is_bin_available(&bin) { + return Err(eyre!("Binary '{}' not found in PATH", bin)).suggestion(format!( + "Have you installed {}? If so, check that it's in your PATH.", + bin + )); + } + + Ok(Command::new(self.kind.bin())) + } + fn is_bin_available(&self, bin: &str) -> bool { + Command::new("which") + .arg(bin) + .output() + .map(|output| output.status.success()) + .unwrap_or(false) } } @@ -66,14 +81,14 @@ impl CwClient for CliClient { type Query = serde_json::Value; type RawQuery = String; type ChainId = Id; - type Error = anyhow::Error; + type Error = Report; async fn query_smart( &self, contract: &Self::Address, query: Self::Query, ) -> Result { - let mut command = self.new_command(); + let mut command = self.new_command()?; let command = command .args(["--node", self.url.as_str()]) .args(["query", "wasm"]) @@ -83,11 +98,11 @@ impl CwClient for CliClient { let output = command.output()?; if !output.status.success() { - return Err(anyhow!("{:?}", output)); + return Err(eyre!("{:?}", output)); } let query_result: R = serde_json::from_slice(&output.stdout) - .map_err(|e| anyhow!("Error deserializing: {}", e))?; + .map_err(|e| eyre!("Error deserializing: {}", e))?; Ok(query_result) } @@ -96,7 +111,7 @@ impl CwClient for CliClient { contract: &Self::Address, query: Self::RawQuery, ) -> Result { - let mut command = self.new_command(); + let mut command = self.new_command()?; let command = command .args(["--node", self.url.as_str()]) .args(["query", "wasm"]) @@ -106,7 +121,7 @@ impl CwClient for CliClient { let output = command.output()?; if !output.status.success() { - return Err(anyhow!("{:?}", output)); + return Err(eyre!("{:?}", output)); } let query_result: R = serde_json::from_slice(&output.stdout).unwrap_or_default(); @@ -114,7 +129,7 @@ impl CwClient for CliClient { } fn query_tx(&self, txhash: &str) -> Result { - let mut command = self.new_command(); + let mut command = self.new_command()?; let command = command .args(["--node", self.url.as_str()]) .args(["query", "tx"]) @@ -123,7 +138,7 @@ impl CwClient for CliClient { let output = command.output()?; if !output.status.success() { - return Err(anyhow!("{:?}", output)); + return Err(eyre!("{:?}", output)); } let query_result: R = serde_json::from_slice(&output.stdout).unwrap_or_default(); @@ -139,7 +154,7 @@ impl CwClient for CliClient { msg: M, fees: &str, ) -> Result { - let mut command = self.new_command(); + let mut command = self.new_command()?; let command = command .args(["--node", self.url.as_str()]) .args(["--chain-id", chain_id.as_ref()]) @@ -154,7 +169,7 @@ impl CwClient for CliClient { let output = command.output()?; if !output.status.success() { - return Err(anyhow!("{:?}", output)); + return Err(eyre!("{:?}", output)); } // TODO: find the rust type for the tx output and return that @@ -167,7 +182,7 @@ impl CwClient for CliClient { sender: &str, wasm_path: M, ) -> Result { - let mut command = self.new_command(); + let mut command = self.new_command()?; let command = command .args(["--node", self.url.as_str()]) .args(["tx", "wasm", "store", &wasm_path.to_string()]) @@ -182,7 +197,7 @@ impl CwClient for CliClient { let output = command.output()?; if !output.status.success() { - return Err(anyhow!("{:?}", output)); + return Err(eyre!("{:?}", output)); } // TODO: find the rust type for the tx output and return that @@ -197,7 +212,7 @@ impl CwClient for CliClient { init_msg: M, label: &str, ) -> Result { - let mut command = self.new_command(); + let mut command = self.new_command()?; let command = command .args(["--node", self.url.as_str()]) .args(["tx", "wasm", "instantiate"]) @@ -215,7 +230,7 @@ impl CwClient for CliClient { let output = command.output()?; if !output.status.success() { - return Err(anyhow!("{:?}", output)); + return Err(eyre!("{:?}", output)); } // TODO: find the rust type for the tx output and return that @@ -223,13 +238,13 @@ impl CwClient for CliClient { } fn trusted_height_hash(&self) -> Result<(u64, String), Self::Error> { - let mut command = self.new_command(); + let mut command = self.new_command()?; let command = command.args(["--node", self.url.as_str()]).arg("status"); let output = command.output()?; if !output.status.success() { - return Err(anyhow!("{:?}", output)); + return Err(eyre!("{:?}", output)); } let query_result: serde_json::Value = @@ -241,13 +256,13 @@ impl CwClient for CliClient { }; let trusted_height = query_result[sync_info]["latest_block_height"] .as_str() - .ok_or(anyhow!("Could not query height"))?; + .ok_or(eyre!("Could not query height"))?; let trusted_height = trusted_height.parse::()?; let trusted_hash = query_result[sync_info]["latest_block_hash"] .as_str() - .ok_or(anyhow!("Could not query height"))? + .ok_or(eyre!("Could not query height"))? .to_string(); Ok((trusted_height, trusted_hash)) diff --git a/examples/transfers/enclave/Cargo.lock b/examples/transfers/enclave/Cargo.lock index fd26df5a..8db89875 100644 --- a/examples/transfers/enclave/Cargo.lock +++ b/examples/transfers/enclave/Cargo.lock @@ -950,6 +950,7 @@ version = "0.1.0" dependencies = [ "anyhow", "async-trait", + "color-eyre", "cosmos-sdk-proto", "cosmrs", "hex", diff --git a/examples/transfers/enclave/src/main.rs b/examples/transfers/enclave/src/main.rs index 20892f09..d2504de9 100644 --- a/examples/transfers/enclave/src/main.rs +++ b/examples/transfers/enclave/src/main.rs @@ -41,7 +41,8 @@ use crate::wslistener::WsListener; async fn main() -> Result<(), Box> { let args = Cli::parse(); - let admin_sk = std::env::var("ADMIN_SK")?; + let admin_sk = std::env::var("ADMIN_SK") + .map_err(|_| anyhow::anyhow!("Admin secret key not found in env vars"))?; let light_client_opts = LightClientOpts::new( args.chain_id.clone(),