Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cw-orch-daemon/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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}")]
Expand Down
127 changes: 95 additions & 32 deletions cw-orch-daemon/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,15 @@ use crate::{channel::GrpcChannel, networks::ChainKind};

use cosmwasm_std::Addr;
use cw_orch_core::{
env::STATE_FOLDER_ENV_NAME,
env::default_state_folder,
environment::{DeployDetails, StateInterface},
log::{connectivity_target, local_target},
CwEnvError, CwOrchEnvVars,
};
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.
Expand Down Expand Up @@ -52,23 +48,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_target(), "Using state file : {}", json_file_path);

Expand Down Expand Up @@ -131,6 +112,39 @@ impl DaemonState {
Ok(state)
}

fn state_file_path() -> Result<String, DaemonError> {
// 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 "."
let first_path_component = env_file_path
.components()
.map(|comp| comp.as_os_str().to_owned().into_string().unwrap())
.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()?;

// 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<serde_json::Value, DaemonError> {
crate::json_file::read(&self.json_file_path)
Expand Down Expand Up @@ -161,16 +175,6 @@ impl DaemonState {
serde_json::to_writer_pretty(File::create(&self.json_file_path).unwrap(), &json)?;
Ok(())
}

pub fn state_dir() -> Result<PathBuf, DaemonError> {
// 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
.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
)))
}
}

impl StateInterface for DaemonState {
Expand Down Expand Up @@ -234,3 +238,62 @@ 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);
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()?;
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
);

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(())
}
}
10 changes: 6 additions & 4 deletions docs/src/contracts/env-variable.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@ 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`
# `../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
Expand All @@ -34,9 +39,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.
Expand Down
37 changes: 22 additions & 15 deletions packages/cw-orch-core/src/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@

use std::{env, path::PathBuf, str::FromStr};

use cosmwasm_std::StdError;

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";
Expand All @@ -30,16 +31,13 @@ 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<PathBuf>,

/// Optional - Path
/// /// This is the name of the state file
/// If the path is relative, this is taken from StateFolder
/// Defaults to "state.json"
/// 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,

/// Optional - Path
Expand Down Expand Up @@ -109,11 +107,23 @@ pub struct CwOrchEnvVars {
pub fee_granter: Option<String>,
}

/// 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<PathBuf, StdError> {
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_folder: dirs::home_dir().map(|home| home.join(".cw-orchestrator")),
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,
Expand All @@ -136,9 +146,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();
}
Expand Down