From 6c695310c2a07fbb0f4941441ae3a944758a0984 Mon Sep 17 00:00:00 2001 From: Kayanski Date: Mon, 8 Jan 2024 14:25:47 +0100 Subject: [PATCH 1/6] Fixed borken state file path resolution --- cw-orch-daemon/src/error.rs | 2 + cw-orch-daemon/src/state.rs | 94 ++++++++++++++++++++++++++++++------- 2 files changed, 80 insertions(+), 16 deletions(-) diff --git a/cw-orch-daemon/src/error.rs b/cw-orch-daemon/src/error.rs index 9bb7352aa..e284d97a2 100644 --- a/cw-orch-daemon/src/error.rs +++ b/cw-orch-daemon/src/error.rs @@ -32,6 +32,8 @@ pub enum DaemonError { TendermintError(#[from] ::cosmrs::tendermint::Error), #[error(transparent)] CwEnvError(#[from] ::cw_orch_core::CwEnvError), + #[error(transparent)] + StripPrefixPath(#[from] std::path::StripPrefixError), #[error("Bech32 Decode Error")] Bech32DecodeErr, #[error("Bech32 Decode Error: Key Failed prefix {0} or length {1} Wanted:{2}/{3}")] diff --git a/cw-orch-daemon/src/state.rs b/cw-orch-daemon/src/state.rs index bc816895e..ba0628a5b 100644 --- a/cw-orch-daemon/src/state.rs +++ b/cw-orch-daemon/src/state.rs @@ -52,23 +52,8 @@ impl DaemonState { let grpc_channel = GrpcChannel::connect(&chain_data.apis.grpc, &chain_data.chain_id).await?; - // check if STATE_FILE en var is configured, default to state.json - let env_file_path = CwOrchEnvVars::load()?.state_file; - // If the path is relative, we dis-ambiguate it and take the root at $HOME/$CW_ORCH_STATE_FOLDER - let mut json_file_path = if env_file_path.is_relative() { - let state_folder = Self::state_dir()?; - - // We need to create the default state folder if it doesn't exist - std::fs::create_dir_all(state_folder.clone())?; - - state_folder.join(env_file_path) - } else { - env_file_path - } - .into_os_string() - .into_string() - .unwrap(); + let mut json_file_path = Self::state_file_path()?; log::debug!(target: LOCAL_LOGS, "Using state file : {}", json_file_path); @@ -131,6 +116,37 @@ impl DaemonState { Ok(state) } + fn state_file_path() -> Result { + // check if STATE_FILE en var is configured, default to state.json + let env_file_path = CwOrchEnvVars::load()?.state_file; + let state_file_path = if env_file_path.is_relative() { + // If it's relative, we check if it start with "." + if env_file_path + .components() + .map(|comp| comp.as_os_str().to_owned().into_string().unwrap()) + .next() + == Some(".".to_string()) + { + let current_dir = std::env::current_dir()?; + let actual_relative_path = env_file_path.strip_prefix("./")?; + current_dir.join(actual_relative_path) + } else { + let state_folder = Self::state_dir()?; + + // We need to create the default state folder if it doesn't exist + std::fs::create_dir_all(state_folder.clone())?; + + state_folder.join(env_file_path) + } + } else { + env_file_path + } + .into_os_string() + .into_string() + .unwrap(); + + Ok(state_file_path) + } /// Get the state filepath and read it as json fn read_state(&self) -> Result { crate::json_file::read(&self.json_file_path) @@ -234,3 +250,49 @@ impl StateInterface for DaemonState { } } } + +#[cfg(test)] +pub mod test { + use std::env; + + use cw_orch_core::env::STATE_FILE_ENV_NAME; + + use crate::DaemonState; + + #[test] + fn test_env_variable_state_path() -> anyhow::Result<()> { + let absolute_path = "/usr/var/file.json"; + let relative_path = "folder/file.json"; + let dotted_relative_path = format!("./{}", relative_path); + + std::env::set_var(STATE_FILE_ENV_NAME, absolute_path); + let absolute_state_path = DaemonState::state_file_path()?; + assert_eq!(absolute_path.to_string(), absolute_state_path); + + std::env::set_var(STATE_FILE_ENV_NAME, dotted_relative_path); + let relative_state_path = DaemonState::state_file_path()?; + assert_eq!( + env::current_dir()? + .join(relative_path) + .into_os_string() + .into_string() + .unwrap(), + relative_state_path + ); + + std::env::set_var(STATE_FILE_ENV_NAME, relative_path); + let relative_state_path = DaemonState::state_file_path()?; + assert_eq!( + dirs::home_dir() + .unwrap() + .join(".cw-orchestrator") + .join(relative_path) + .into_os_string() + .into_string() + .unwrap(), + relative_state_path + ); + + Ok(()) + } +} From 4ffd63a989bfe77c53cd1c87b4755bd8cf24adb3 Mon Sep 17 00:00:00 2001 From: Kayanski Date: Mon, 8 Jan 2024 14:38:29 +0100 Subject: [PATCH 2/6] Removed state folder env variable --- cw-orch-daemon/src/lib.rs | 9 +-------- cw-orch-daemon/src/state.rs | 13 +++++++------ packages/cw-orch-core/src/env.rs | 7 +------ 3 files changed, 9 insertions(+), 20 deletions(-) diff --git a/cw-orch-daemon/src/lib.rs b/cw-orch-daemon/src/lib.rs index d996ab737..8c0209998 100644 --- a/cw-orch-daemon/src/lib.rs +++ b/cw-orch-daemon/src/lib.rs @@ -31,17 +31,10 @@ pub(crate) mod cosmos_modules { pub use cosmrs::proto::{ cosmos::{ auth::v1beta1 as auth, - authz::v1beta1 as authz, bank::v1beta1 as bank, - base::{abci::v1beta1 as abci, tendermint::v1beta1 as tendermint, v1beta1 as base}, - crisis::v1beta1 as crisis, - distribution::v1beta1 as distribution, - evidence::v1beta1 as evidence, + base::{abci::v1beta1 as abci, tendermint::v1beta1 as tendermint}, feegrant::v1beta1 as feegrant, gov::v1beta1 as gov, - mint::v1beta1 as mint, - params::v1beta1 as params, - slashing::v1beta1 as slashing, staking::v1beta1 as staking, tx::v1beta1 as tx, vesting::v1beta1 as vesting, diff --git a/cw-orch-daemon/src/state.rs b/cw-orch-daemon/src/state.rs index ba0628a5b..f34530963 100644 --- a/cw-orch-daemon/src/state.rs +++ b/cw-orch-daemon/src/state.rs @@ -3,7 +3,7 @@ use crate::{channel::GrpcChannel, networks::ChainKind}; use cosmwasm_std::Addr; use cw_orch_core::{ - env::STATE_FOLDER_ENV_NAME, + env::STATE_FILE_ENV_NAME, environment::{DeployDetails, StateInterface}, log::{CONNECTIVITY_LOGS, LOCAL_LOGS}, CwEnvError, CwOrchEnvVars, @@ -131,7 +131,7 @@ impl DaemonState { let actual_relative_path = env_file_path.strip_prefix("./")?; current_dir.join(actual_relative_path) } else { - let state_folder = Self::state_dir()?; + let state_folder = Self::home_dir()?; // We need to create the default state folder if it doesn't exist std::fs::create_dir_all(state_folder.clone())?; @@ -178,13 +178,14 @@ impl DaemonState { Ok(()) } - pub fn state_dir() -> Result { + fn home_dir() -> Result { // This function should only error if the home_dir is not set and the `dirs` library is unable to fetch it - CwOrchEnvVars::load()?.state_folder + dirs::home_dir().map(|home| home.join(".cw-orchestrator")) .ok_or( DaemonError::StdErr( format!( - "Your machine doesn't have a home folder. Please specify the {} env variable to use cw-orchestrator", - STATE_FOLDER_ENV_NAME + "Your machine doesn't have a home folder. You can't use relative path for the state file such as 'state.json'. + Please use an absolute path ('/home/root/state.json') or a dot-prefixed-relative path ('./state.json') in the {} env variable.", + STATE_FILE_ENV_NAME ))) } } diff --git a/packages/cw-orch-core/src/env.rs b/packages/cw-orch-core/src/env.rs index d01836f75..082391bc0 100644 --- a/packages/cw-orch-core/src/env.rs +++ b/packages/cw-orch-core/src/env.rs @@ -12,7 +12,6 @@ use crate::CwEnvError; const DEFAULT_TX_QUERY_RETRIES: usize = 50; -pub const STATE_FOLDER_ENV_NAME: &str = "CW_ORCH_STATE_FOLDER"; pub const STATE_FILE_ENV_NAME: &str = "STATE_FILE"; pub const ARTIFACTS_DIR_ENV_NAME: &str = "ARTIFACTS_DIR"; pub const GAS_BUFFER_ENV_NAME: &str = "CW_ORCH_GAS_BUFFER"; @@ -34,7 +33,7 @@ pub struct CwOrchEnvVars { /// Defaults to "~./cw-orchestrator" /// This is the folder in which states of contracts are saved /// This is not enforced to be an absolute path but this is highly recommended - pub state_folder: Option, + // pub state_folder: Option, /// Optional - Path /// /// This is the name of the state file @@ -112,7 +111,6 @@ pub struct CwOrchEnvVars { impl Default for CwOrchEnvVars { fn default() -> Self { CwOrchEnvVars { - state_folder: dirs::home_dir().map(|home| home.join(".cw-orchestrator")), state_file: PathBuf::from_str("state.json").unwrap(), artifacts_dir: None, gas_buffer: None, @@ -136,9 +134,6 @@ impl CwOrchEnvVars { let mut env_values = CwOrchEnvVars::default(); // Then we load the values from env - if let Ok(str_value) = env::var(STATE_FOLDER_ENV_NAME) { - env_values.state_folder = Some(PathBuf::from_str(&str_value).unwrap()); - } if let Ok(str_value) = env::var(STATE_FILE_ENV_NAME) { env_values.state_file = PathBuf::from_str(&str_value).unwrap(); } From 12ddd4adbec35139428ebe5b02d6e229a6687c8b Mon Sep 17 00:00:00 2001 From: Kayanski Date: Mon, 8 Jan 2024 14:45:12 +0100 Subject: [PATCH 3/6] Updated docs --- cw-orch-daemon/src/state.rs | 21 +++------------------ packages/cw-orch-core/src/env.rs | 29 ++++++++++++++++++++--------- 2 files changed, 23 insertions(+), 27 deletions(-) diff --git a/cw-orch-daemon/src/state.rs b/cw-orch-daemon/src/state.rs index f34530963..d3e48f1e6 100644 --- a/cw-orch-daemon/src/state.rs +++ b/cw-orch-daemon/src/state.rs @@ -3,7 +3,7 @@ use crate::{channel::GrpcChannel, networks::ChainKind}; use cosmwasm_std::Addr; use cw_orch_core::{ - env::STATE_FILE_ENV_NAME, + env::default_state_folder, environment::{DeployDetails, StateInterface}, log::{CONNECTIVITY_LOGS, LOCAL_LOGS}, CwEnvError, CwOrchEnvVars, @@ -11,11 +11,7 @@ use cw_orch_core::{ use ibc_chain_registry::chain::ChainData; use serde::Serialize; use serde_json::{json, Value}; -use std::{ - collections::HashMap, - fs::File, - path::{Path, PathBuf}, -}; +use std::{collections::HashMap, fs::File, path::Path}; use tonic::transport::Channel; /// Stores the chain information and deployment state. @@ -131,7 +127,7 @@ impl DaemonState { let actual_relative_path = env_file_path.strip_prefix("./")?; current_dir.join(actual_relative_path) } else { - let state_folder = Self::home_dir()?; + let state_folder = default_state_folder()?; // We need to create the default state folder if it doesn't exist std::fs::create_dir_all(state_folder.clone())?; @@ -177,17 +173,6 @@ impl DaemonState { serde_json::to_writer_pretty(File::create(&self.json_file_path).unwrap(), &json)?; Ok(()) } - - fn home_dir() -> Result { - // This function should only error if the home_dir is not set and the `dirs` library is unable to fetch it - dirs::home_dir().map(|home| home.join(".cw-orchestrator")) - .ok_or( DaemonError::StdErr( - format!( - "Your machine doesn't have a home folder. You can't use relative path for the state file such as 'state.json'. - Please use an absolute path ('/home/root/state.json') or a dot-prefixed-relative path ('./state.json') in the {} env variable.", - STATE_FILE_ENV_NAME - ))) - } } impl StateInterface for DaemonState { diff --git a/packages/cw-orch-core/src/env.rs b/packages/cw-orch-core/src/env.rs index 082391bc0..eb6ccba24 100644 --- a/packages/cw-orch-core/src/env.rs +++ b/packages/cw-orch-core/src/env.rs @@ -8,6 +8,8 @@ use std::{env, path::PathBuf, str::FromStr}; +use cosmwasm_std::StdError; + use crate::CwEnvError; const DEFAULT_TX_QUERY_RETRIES: usize = 50; @@ -29,16 +31,12 @@ pub const TEST_MNEMONIC_ENV_NAME: &str = "TEST_MNEMONIC"; pub const LOCAL_MNEMONIC_ENV_NAME: &str = "LOCAL_MNEMONIC"; pub struct CwOrchEnvVars { - /// Optional - Absolute Path - /// Defaults to "~./cw-orchestrator" - /// This is the folder in which states of contracts are saved - /// This is not enforced to be an absolute path but this is highly recommended - // pub state_folder: Option, - /// Optional - Path /// /// This is the name of the state file - /// If the path is relative, this is taken from StateFolder - /// Defaults to "state.json" + /// `folder/file.json` will resolve to `~/.cw-orchestrator/folder/file.json` + /// `./folder/file.json` will resolve `$pwd/folder/file.json` + /// `/usr/var/file.json` will resolve to `/usr/var/file.json` + /// Defaults to "~./cw-orchestrator/state.json" pub state_file: PathBuf, /// Optional - Path @@ -108,10 +106,23 @@ pub struct CwOrchEnvVars { pub fee_granter: Option, } +/// Fetches the default state folder. +/// This function should only error if the home_dir is not set and the `dirs` library is unable to fetch it +/// This happens only in rare cases +pub fn default_state_folder() -> Result { + dirs::home_dir().map(|home| home.join(".cw-orchestrator")) + .ok_or( StdError::generic_err( + format!( + "Your machine doesn't have a home folder. You can't use relative path for the state file such as 'state.json'. + Please use an absolute path ('/home/root/state.json') or a dot-prefixed-relative path ('./state.json') in the {} env variable.", + STATE_FILE_ENV_NAME + ))) +} + impl Default for CwOrchEnvVars { fn default() -> Self { CwOrchEnvVars { - state_file: PathBuf::from_str("state.json").unwrap(), + state_file: default_state_folder().unwrap().join("state.json"), artifacts_dir: None, gas_buffer: None, min_gas: None, From b4c898d0b4e682824662a75efc942c1488721b15 Mon Sep 17 00:00:00 2001 From: Kayanski Date: Mon, 8 Jan 2024 14:47:21 +0100 Subject: [PATCH 4/6] more docs --- docs/src/contracts/env-variable.md | 9 +++++---- packages/cw-orch-core/src/env.rs | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/src/contracts/env-variable.md b/docs/src/contracts/env-variable.md index 26a363854..5fb02ad55 100644 --- a/docs/src/contracts/env-variable.md +++ b/docs/src/contracts/env-variable.md @@ -15,7 +15,11 @@ RUST_LOG=info # Where the contract wasms are located (used by ArtifactsDir::env()) ARTIFACTS_DIR="../artifacts" -# where to store the state of your deployments (default: ./state.json) +# Optional - Path. +# Sets the location of the state file for your deployments (default: ~./cw-orchestrator/state.json) +# `folder/file.json` will resolve to `~/.cw-orchestrator/folder/file.json` +# `./folder/file.json` will resolve `$pwd/folder/file.json` +# `/usr/var/file.json` will resolve to `/usr/var/file.json` STATE_FILE="./my_state.json" # Mnemonics of the account that will be used to sign transactions @@ -34,9 +38,6 @@ CW_ORCH_MAX_TX_QUERY_RETRIES = 50 CW_ORCH_MIN_BLOCK_SPEED = 1 # Optional - String. If equals to "true", will serialize the blockchain messages as json (for easy copying) instead of Rust Debug formatting CW_ORCH_SERIALIZE_JSON = "false" -# Optional - Absolute Path. Sets the directory where the state file will be saved. -# This is not enforced to be an absolute path but this is highly recommended -CW_ORCH_STATE_FOLDER = "~/.cw-orchestrator" # Optional - Integer. This allows setting a minimum of gas used when broadcasting transactions CW_ORCH_MIN_GAS = 100000 # Optional - boolean. Disable the "Enable Logs" message. diff --git a/packages/cw-orch-core/src/env.rs b/packages/cw-orch-core/src/env.rs index eb6ccba24..c92f59b78 100644 --- a/packages/cw-orch-core/src/env.rs +++ b/packages/cw-orch-core/src/env.rs @@ -32,7 +32,7 @@ pub const LOCAL_MNEMONIC_ENV_NAME: &str = "LOCAL_MNEMONIC"; pub struct CwOrchEnvVars { /// Optional - Path - /// /// This is the name of the state file + /// This is the path to the state file /// `folder/file.json` will resolve to `~/.cw-orchestrator/folder/file.json` /// `./folder/file.json` will resolve `$pwd/folder/file.json` /// `/usr/var/file.json` will resolve to `/usr/var/file.json` From 3b9b811737c8b8c7c80c324ac5b21563aa8427ca Mon Sep 17 00:00:00 2001 From: Kayanski Date: Tue, 9 Jan 2024 14:49:22 +0100 Subject: [PATCH 5/6] Handle ../ prefix on state file --- cw-orch-daemon/src/state.rs | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/cw-orch-daemon/src/state.rs b/cw-orch-daemon/src/state.rs index d3e48f1e6..8feeb7e0f 100644 --- a/cw-orch-daemon/src/state.rs +++ b/cw-orch-daemon/src/state.rs @@ -117,15 +117,17 @@ impl DaemonState { let env_file_path = CwOrchEnvVars::load()?.state_file; let state_file_path = if env_file_path.is_relative() { // If it's relative, we check if it start with "." - if env_file_path + let first_path_component = env_file_path .components() .map(|comp| comp.as_os_str().to_owned().into_string().unwrap()) - .next() - == Some(".".to_string()) - { + .next(); + if first_path_component == Some(".".to_string()) { let current_dir = std::env::current_dir()?; let actual_relative_path = env_file_path.strip_prefix("./")?; current_dir.join(actual_relative_path) + } else if first_path_component == Some("..".to_string()) { + let current_dir = std::env::current_dir()?; + current_dir.join(env_file_path) } else { let state_folder = default_state_folder()?; @@ -250,6 +252,7 @@ pub mod test { let absolute_path = "/usr/var/file.json"; let relative_path = "folder/file.json"; let dotted_relative_path = format!("./{}", relative_path); + let parent_and_relative_path = format!("../{}", relative_path); std::env::set_var(STATE_FILE_ENV_NAME, absolute_path); let absolute_state_path = DaemonState::state_file_path()?; @@ -279,6 +282,18 @@ pub mod test { relative_state_path ); + std::env::set_var(STATE_FILE_ENV_NAME, parent_and_relative_path); + let parent_and_relative_state_path = DaemonState::state_file_path()?; + assert_eq!( + env::current_dir()? + .join("../") + .join(relative_path) + .into_os_string() + .into_string() + .unwrap(), + parent_and_relative_state_path + ); + Ok(()) } } From c77fcfd7a508efa5ec969d608b53e5b3f6963c27 Mon Sep 17 00:00:00 2001 From: Kayanski Date: Wed, 10 Jan 2024 17:26:12 +0100 Subject: [PATCH 6/6] Updated docs on state file behavior --- docs/src/contracts/env-variable.md | 1 + packages/cw-orch-core/src/env.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/docs/src/contracts/env-variable.md b/docs/src/contracts/env-variable.md index 1ea7416e8..9badf3481 100644 --- a/docs/src/contracts/env-variable.md +++ b/docs/src/contracts/env-variable.md @@ -19,6 +19,7 @@ ARTIFACTS_DIR="../artifacts" # Sets the location of the state file for your deployments (default: ~./cw-orchestrator/state.json) # `folder/file.json` will resolve to `~/.cw-orchestrator/folder/file.json` # `./folder/file.json` will resolve `$pwd/folder/file.json` +# `../folder/file.json` will resolve `$pwd/../folder/file.json` # `/usr/var/file.json` will resolve to `/usr/var/file.json` STATE_FILE="./my_state.json" diff --git a/packages/cw-orch-core/src/env.rs b/packages/cw-orch-core/src/env.rs index c92f59b78..b14ca13bf 100644 --- a/packages/cw-orch-core/src/env.rs +++ b/packages/cw-orch-core/src/env.rs @@ -35,6 +35,7 @@ pub struct CwOrchEnvVars { /// This is the path to the state file /// `folder/file.json` will resolve to `~/.cw-orchestrator/folder/file.json` /// `./folder/file.json` will resolve `$pwd/folder/file.json` + /// `../folder/file.json` will resolve `$pwd/../folder/file.json` /// `/usr/var/file.json` will resolve to `/usr/var/file.json` /// Defaults to "~./cw-orchestrator/state.json" pub state_file: PathBuf,