cw-orch-daemon 0.23.4

Scripting library for deploying and interacting with CosmWasm smart-contracts
Documentation
//! This regroups all env variables used by cw-orch-daemon. It allows for easier documentation and env variable management
//! This is used to import environment variables with safe names (and at a centralized location)
//! To get the env variable parsed value, you can use
//! ```rust,no_run
//! use cw_orch_daemon::env::DaemonEnvVars;
//! let env_variable = DaemonEnvVars::state_file();
//! ```

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

use cosmwasm_std::StdError;
use regex::Regex;
use std::time::Duration;

const DEFAULT_TX_QUERY_RETRIES: usize = 50;

pub const STATE_FILE_ENV_NAME: &str = "STATE_FILE";
pub const GAS_BUFFER_ENV_NAME: &str = "CW_ORCH_GAS_BUFFER";
pub const MIN_GAS_ENV_NAME: &str = "CW_ORCH_MIN_GAS";
pub const MAX_TX_QUERIES_RETRY_ENV_NAME: &str = "CW_ORCH_MAX_TX_QUERY_RETRIES";
pub const MIN_BLOCK_SPEED_ENV_NAME: &str = "CW_ORCH_MIN_BLOCK_SPEED";
pub const WALLET_BALANCE_ASSERTION_ENV_NAME: &str = "CW_ORCH_WALLET_BALANCE_ASSERTION";
pub const LOGS_ACTIVATION_MESSAGE_ENV_NAME: &str = "CW_ORCH_LOGS_ACTIVATION_MESSAGE";

pub const MAIN_MNEMONIC_ENV_NAME: &str = "MAIN_MNEMONIC";
pub const TEST_MNEMONIC_ENV_NAME: &str = "TEST_MNEMONIC";
pub const LOCAL_MNEMONIC_ENV_NAME: &str = "LOCAL_MNEMONIC";
pub struct DaemonEnvVars {}
impl DaemonEnvVars {
    /// Optional - Path
    /// 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 fn state_file() -> PathBuf {
        let state_file_string = env::var(STATE_FILE_ENV_NAME).unwrap_or("state.json".to_string());
        parse_with_log(state_file_string, STATE_FILE_ENV_NAME)
    }

    /// Optional - Float
    /// This allows changing the gas buffer applied after tx simulation
    /// If not specified, a more complex algorithm is applied for dealing with small gas fee cases
    pub fn gas_buffer() -> Option<f64> {
        if let Ok(str_value) = env::var(GAS_BUFFER_ENV_NAME) {
            Some(parse_with_log(str_value, GAS_BUFFER_ENV_NAME))
        } else {
            None
        }
    }

    /// Optional - Integer
    /// Defaults to None
    /// Minimum gas amount. Useful when transaction still won't pass even when setting a high gas_buffer or for mixed transaction scripts
    pub fn min_gas() -> Option<u64> {
        if let Ok(str_value) = env::var(MIN_GAS_ENV_NAME) {
            Some(parse_with_log(str_value, MIN_GAS_ENV_NAME))
        } else {
            None
        }
    }

    /// Optional - Integer
    /// Defaults to [`DEFAULT_TX_QUERY_RETRIES`]
    /// This changes the number of tx queries before it fails if it doesn't find any result
    pub fn max_tx_query_retries() -> usize {
        if let Ok(str_value) = env::var(MAX_TX_QUERIES_RETRY_ENV_NAME) {
            parse_with_log(str_value, MAX_TX_QUERIES_RETRY_ENV_NAME)
        } else {
            DEFAULT_TX_QUERY_RETRIES
        }
    }

    /// Optional - Integer
    /// Defaults to 1
    /// Minimum block speed in milliseconds. Useful when the block speeds are varying a lot
    pub fn min_block_speed() -> Duration {
        if let Ok(str_value) = env::var(MIN_BLOCK_SPEED_ENV_NAME) {
            let ms_re = Regex::new(r"(\d+)ms").unwrap();
            let s_re = Regex::new(r"(\d+)s").unwrap();

            if let Some(caps) = ms_re.captures(&str_value) {
                if let Some(ms) = caps.get(1) {
                    let milliseconds: u64 =
                        parse_with_log(ms.as_str().to_string(), MIN_BLOCK_SPEED_ENV_NAME);
                    Duration::from_millis(milliseconds)
                } else {
                    panic!("Couldn't parse content of env var {MIN_BLOCK_SPEED_ENV_NAME}, error : Didn't find a match for format `{{int}}ms");
                }
            } else if let Some(caps) = s_re.captures(&str_value) {
                if let Some(s) = caps.get(1) {
                    let seconds: u64 =
                        parse_with_log(s.as_str().to_string(), MIN_BLOCK_SPEED_ENV_NAME);
                    Duration::from_secs(seconds)
                } else {
                    panic!("Couldn't parse content of env var {MIN_BLOCK_SPEED_ENV_NAME}, error : Didn't find a match for format `{{int}}s");
                }
            } else {
                // Assuming the number is in seconds if no unit is specified
                let seconds: u64 = parse_with_log(str_value, MIN_BLOCK_SPEED_ENV_NAME);
                Duration::from_secs(seconds)
            }
        } else {
            Duration::from_secs(1)
        }
    }

    /// Optional - boolean
    /// Defaults to "true"
    /// Disable wallet balance assertion.
    /// When balance assertion is enabled, it asserts that the balance of the sender is sufficient before submitting any transactions (during the simulation step)
    pub fn wallet_balance_assertion() -> bool {
        if let Ok(str_value) = env::var(WALLET_BALANCE_ASSERTION_ENV_NAME) {
            parse_with_log(str_value, WALLET_BALANCE_ASSERTION_ENV_NAME)
        } else {
            true
        }
    }

    /// Optional - boolean
    /// Defaults to "true"
    /// Disable the "Enable Logs" message
    /// It allows forcing cw-orch to not output anything
    pub fn logs_message() -> bool {
        if let Ok(str_value) = env::var(LOGS_ACTIVATION_MESSAGE_ENV_NAME) {
            parse_with_log(str_value, LOGS_ACTIVATION_MESSAGE_ENV_NAME)
        } else {
            true
        }
    }

    /// Optional - String
    /// Mandatory when interacting with a daemon on mainnet
    /// Mnemonic of the address interacting with a mainnet
    pub fn main_mnemonic() -> Option<String> {
        env::var(MAIN_MNEMONIC_ENV_NAME).ok()
    }

    /// Optional - String
    /// Mandatory when interacting with a daemon on mainnet
    /// Mnemonic of the address interacting with a testnet
    pub fn test_mnemonic() -> Option<String> {
        env::var(TEST_MNEMONIC_ENV_NAME).ok()
    }

    /// Optional - String
    /// Mandatory when interacting with a daemon on mainnet
    /// Mnemonic of the address interacting with a localnet
    pub fn local_mnemonic() -> Option<String> {
        env::var(LOCAL_MNEMONIC_ENV_NAME).ok()
    }
}

/// 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
            )))
}

fn parse_with_log<F: FromStr<Err = E>, E: std::fmt::Display>(
    value: String,
    env_var_name: &str,
) -> F {
    match value.parse() {
        Ok(parsed) => parsed,
        Err(e) => panic!("Couldn't parse content of env var {env_var_name}, error : {e}"),
    }
}