diff --git a/tools/internal/testnet-manager/src/cli/initialise_new_network.rs b/tools/internal/testnet-manager/src/cli/initialise_new_network.rs index db9e4fd4ec..574559bf82 100644 --- a/tools/internal/testnet-manager/src/cli/initialise_new_network.rs +++ b/tools/internal/testnet-manager/src/cli/initialise_new_network.rs @@ -3,6 +3,7 @@ use crate::cli::CommonArgs; use crate::error::NetworkManagerError; +use crate::manager::env::Env; use nym_bin_common::output_format::OutputFormat; use std::path::PathBuf; use std::time::Duration; @@ -20,6 +21,7 @@ pub(crate) struct Args { network_name: Option, /// Specifies custom duration of mixnet epochs + /// It's recommended to set it to rather low value (like 60s) if you intend to bond the mixnet afterward. #[clap(long)] custom_epoch_duration_secs: Option, @@ -37,12 +39,11 @@ pub(crate) async fn execute(args: Args) -> Result<(), NetworkManagerError> { args.network_name, args.custom_epoch_duration_secs.map(Duration::from_secs), ) - .await?; + .await? + .into_loaded(); - println!( - "add the following to your .env file: \n{}", - network.unchecked_to_env_file_section() - ); + let env = Env::from(&network); + println!("add the following to your .env file: \n{env}",); Ok(()) } diff --git a/tools/internal/testnet-manager/src/cli/initialise_post_dkg_network.rs b/tools/internal/testnet-manager/src/cli/initialise_post_dkg_network.rs index 47e59a8c11..d302177fad 100644 --- a/tools/internal/testnet-manager/src/cli/initialise_post_dkg_network.rs +++ b/tools/internal/testnet-manager/src/cli/initialise_post_dkg_network.rs @@ -4,6 +4,7 @@ use crate::cli::CommonArgs; use crate::error::NetworkManagerError; use crate::helpers::default_storage_dir; +use crate::manager::env::Env; use crate::manager::network::LoadedNetwork; use nym_bin_common::output_format::OutputFormat; use std::path::PathBuf; @@ -34,6 +35,7 @@ pub(crate) struct Args { bypass_dkg_contract: PathBuf, /// Specifies custom duration of mixnet epochs + /// It's recommended to set it to rather low value (like 60s) if you intend to bond the mixnet afterward. #[clap(long)] custom_epoch_duration_secs: Option, @@ -59,7 +61,7 @@ pub(crate) async fn execute(args: Args) -> Result<(), NetworkManagerError> { default_storage_dir().join(&network.name) }; - let env = network.to_env_file_section(); + let env = Env::from(&network); manager .attempt_bypass_dkg( diff --git a/tools/internal/testnet-manager/src/cli/load_network_details.rs b/tools/internal/testnet-manager/src/cli/load_network_details.rs index 32aa708e2d..657b68ce1a 100644 --- a/tools/internal/testnet-manager/src/cli/load_network_details.rs +++ b/tools/internal/testnet-manager/src/cli/load_network_details.rs @@ -3,6 +3,7 @@ use crate::error::NetworkManagerError; use crate::helpers::default_db_file; +use crate::manager::env::Env; use crate::manager::NetworkManager; use nym_bin_common::output_format::OutputFormat; use std::path::PathBuf; @@ -27,10 +28,8 @@ pub(crate) async fn execute(args: Args) -> Result<(), NetworkManagerError> { .load_existing_network(args.network_name) .await?; - println!( - "add the following to your .env file: \n{}", - network.to_env_file_section() - ); + let env = Env::from(&network); + println!("add the following to your .env file: \n{env}",); Ok(()) } diff --git a/tools/internal/testnet-manager/src/manager/env.rs b/tools/internal/testnet-manager/src/manager/env.rs new file mode 100644 index 0000000000..005e6b48f4 --- /dev/null +++ b/tools/internal/testnet-manager/src/manager/env.rs @@ -0,0 +1,173 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::error::NetworkManagerError; +use crate::manager::network::LoadedNetwork; +use nym_config::defaults::var_names; +use std::fmt::{Display, Formatter}; +use std::fs; +use std::fs::File; +use std::io::Write; +use std::path::Path; +use tracing::{trace, warn}; + +#[derive(Default)] +pub struct Env { + pub(crate) mixnet_contract_address: Option, + pub(crate) vesting_contract_address: Option, + pub(crate) ecash_contract_address: Option, + pub(crate) cw4_group_contract_address: Option, + pub(crate) cw3_multisig_contract_address: Option, + pub(crate) dkg_contract_address: Option, + pub(crate) nyxd_endpoint: Option, + pub(crate) nym_api_endpoint: Option, +} + +impl Env { + pub fn with_nym_api>(mut self, nym_api: S) -> Self { + self.nym_api_endpoint = Some(nym_api.into()); + self + } + + // this will be used eventually + #[allow(dead_code)] + pub fn try_load>(path: P) -> Result { + let mut env = Env::default(); + let content = fs::read_to_string(path)?; + + for entry in content.lines().map(|l| l.trim()).filter(|l| !l.is_empty()) { + let Some((k, v)) = entry.split_once('=') else { + warn!("malformed .env entry: '{entry}'"); + continue; + }; + + match k { + var_names::CONFIGURED + | var_names::BECH32_PREFIX + | var_names::MIX_DENOM + | var_names::MIX_DENOM_DISPLAY + | var_names::STAKE_DENOM + | var_names::STAKE_DENOM_DISPLAY + | var_names::DENOMS_EXPONENT => { + trace!("ignoring values for {k} and using default instead") + } + var_names::MIXNET_CONTRACT_ADDRESS => { + env.mixnet_contract_address = Some(v.to_string()) + } + var_names::VESTING_CONTRACT_ADDRESS => { + env.vesting_contract_address = Some(v.to_string()) + } + var_names::ECASH_CONTRACT_ADDRESS => { + env.ecash_contract_address = Some(v.to_string()) + } + var_names::GROUP_CONTRACT_ADDRESS => { + env.cw4_group_contract_address = Some(v.to_string()) + } + var_names::MULTISIG_CONTRACT_ADDRESS => { + env.cw3_multisig_contract_address = Some(v.to_string()) + } + var_names::COCONUT_DKG_CONTRACT_ADDRESS => { + env.dkg_contract_address = Some(v.to_string()) + } + var_names::NYXD => env.nyxd_endpoint = Some(v.to_string()), + var_names::NYM_API => env.nym_api_endpoint = Some(v.to_string()), + other => warn!("unsupported .env entry: '{other}'"), + } + } + + Ok(env) + } + + pub fn save>(&self, path: P) -> Result<(), NetworkManagerError> { + let path = path.as_ref(); + if let Some(parent) = path.parent() { + fs::create_dir_all(parent)?; + } + let mut env_file = File::create(path)?; + let content = self.to_string(); + env_file.write_all(content.as_bytes())?; + Ok(()) + } +} + +impl<'a> From<&'a LoadedNetwork> for Env { + fn from(network: &'a LoadedNetwork) -> Self { + Env { + mixnet_contract_address: Some(network.contracts.mixnet.address.to_string()), + vesting_contract_address: Some(network.contracts.vesting.address.to_string()), + ecash_contract_address: Some(network.contracts.ecash.address.to_string()), + cw4_group_contract_address: Some(network.contracts.cw4_group.address.to_string()), + cw3_multisig_contract_address: Some(network.contracts.cw3_multisig.address.to_string()), + dkg_contract_address: Some(network.contracts.dkg.address.to_string()), + nyxd_endpoint: Some(network.rpc_endpoint.to_string()), + nym_api_endpoint: None, + } + } +} + +impl Display for Env { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "CONFIGURED=true\n\ +\n\ +BECH32_PREFIX=n\n\ +MIX_DENOM=unym\n\ +MIX_DENOM_DISPLAY=nym\n\ +STAKE_DENOM=unyx\n\ +STAKE_DENOM_DISPLAY=nyx\n\ +DENOMS_EXPONENT=6\n\ +\n\ +" + )?; + if let Some(mixnet_contract_address) = &self.mixnet_contract_address { + writeln!( + f, + "{}={mixnet_contract_address}", + var_names::MIXNET_CONTRACT_ADDRESS + )?; + } + if let Some(vesting_contract_address) = &self.vesting_contract_address { + writeln!( + f, + "{}={vesting_contract_address}", + var_names::VESTING_CONTRACT_ADDRESS + )?; + } + if let Some(ecash_contract_address) = &self.ecash_contract_address { + writeln!( + f, + "{}={ecash_contract_address}", + var_names::ECASH_CONTRACT_ADDRESS + )?; + } + if let Some(cw4_group_contract_address) = &self.cw4_group_contract_address { + writeln!( + f, + "{}={cw4_group_contract_address}", + var_names::GROUP_CONTRACT_ADDRESS + )?; + } + if let Some(cw3_multisig_contract_address) = &self.cw3_multisig_contract_address { + writeln!( + f, + "{}={cw3_multisig_contract_address}", + var_names::MULTISIG_CONTRACT_ADDRESS + )?; + } + if let Some(dkg_contract_address) = &self.dkg_contract_address { + writeln!( + f, + "{}={dkg_contract_address}", + var_names::COCONUT_DKG_CONTRACT_ADDRESS + )?; + } + if let Some(nyxd_endpoint) = &self.nyxd_endpoint { + writeln!(f, "{}={nyxd_endpoint}", var_names::NYXD)?; + } + if let Some(nym_api_endpoint) = &self.nym_api_endpoint { + writeln!(f, "{}={nym_api_endpoint}", var_names::NYM_API)?; + } + Ok(()) + } +} diff --git a/tools/internal/testnet-manager/src/manager/local_apis.rs b/tools/internal/testnet-manager/src/manager/local_apis.rs index 89f6c94d25..9fc92d17d0 100644 --- a/tools/internal/testnet-manager/src/manager/local_apis.rs +++ b/tools/internal/testnet-manager/src/manager/local_apis.rs @@ -4,6 +4,7 @@ use crate::error::NetworkManagerError; use crate::helpers::{ProgressCtx, ProgressTracker, RunCommands}; use crate::manager::dkg_skip::EcashSignerWithPaths; +use crate::manager::env::Env; use crate::manager::network::LoadedNetwork; use crate::manager::NetworkManager; use console::style; @@ -11,8 +12,6 @@ use nym_config::{ must_get_home, DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME, DEFAULT_NYM_APIS_DIR, NYM_DIR, }; use std::fs; -use std::fs::File; -use std::io::Write; use std::path::{Path, PathBuf}; use std::process::Stdio; use tokio::process::Command; @@ -183,21 +182,15 @@ impl NetworkManager { ctx: &LocalApisCtx, env_file: P, ) -> Result<(), NetworkManagerError> { - let base_env = ctx.network.to_env_file_section(); - let updated_env = format!("{base_env}NYM_API={}", ctx.signers[0].data.endpoint); - - let env_file_path = env_file.as_ref(); - if let Some(parent) = env_file_path.parent() { - fs::create_dir_all(parent)?; - } + let env = Env::from(ctx.network).with_nym_api(ctx.signers[0].data.endpoint.as_ref()); let latest = self.default_latest_env_file_path(); if fs::read_link(&latest).is_ok() { fs::remove_file(&latest)?; } - let mut env_file = File::create(env_file_path)?; - env_file.write_all(updated_env.as_bytes())?; + let env_file_path = env_file.as_ref(); + env.save(env_file_path)?; // make symlink for usability purposes std::os::unix::fs::symlink(env_file_path, &latest)?; diff --git a/tools/internal/testnet-manager/src/manager/mod.rs b/tools/internal/testnet-manager/src/manager/mod.rs index 5502bc4fdd..1832ef6cd5 100644 --- a/tools/internal/testnet-manager/src/manager/mod.rs +++ b/tools/internal/testnet-manager/src/manager/mod.rs @@ -18,6 +18,7 @@ use zeroize::Zeroizing; mod contract; mod dkg_skip; +pub(crate) mod env; mod local_apis; mod local_client; mod local_nodes; diff --git a/tools/internal/testnet-manager/src/manager/network.rs b/tools/internal/testnet-manager/src/manager/network.rs index aeda4a344d..72bc2b1aad 100644 --- a/tools/internal/testnet-manager/src/manager/network.rs +++ b/tools/internal/testnet-manager/src/manager/network.rs @@ -27,35 +27,8 @@ pub struct Network { } impl Network { - pub fn unchecked_to_env_file_section(&self) -> String { - format!( - "CONFIGURED=true\n\ -\n\ -BECH32_PREFIX=n\n\ -MIX_DENOM=unym\n\ -MIX_DENOM_DISPLAY=nym\n\ -STAKE_DENOM=unyx\n\ -STAKE_DENOM_DISPLAY=nyx\n\ -DENOMS_EXPONENT=6\n\ -\n\ -REWARDING_VALIDATOR_ADDRESS={}\n\ -MIXNET_CONTRACT_ADDRESS={}\n\ -VESTING_CONTRACT_ADDRESS={}\n\ -ECASH_CONTRACT_ADDRESS={}\n\ -GROUP_CONTRACT_ADDRESS={}\n\ -MULTISIG_CONTRACT_ADDRESS={}\n\ -COCONUT_DKG_CONTRACT_ADDRESS={}\n\ -NYXD={}\n\ -", - self.auxiliary_addresses.mixnet_rewarder.address, - self.contracts.mixnet.address().unwrap(), - self.contracts.vesting.address().unwrap(), - self.contracts.ecash.address().unwrap(), - self.contracts.cw4_group.address().unwrap(), - self.contracts.cw3_multisig.address().unwrap(), - self.contracts.dkg.address().unwrap(), - self.rpc_endpoint, - ) + pub fn into_loaded(self) -> LoadedNetwork { + self.into() } } @@ -151,37 +124,6 @@ impl LoadedNetwork { self.contracts.cw4_group.admin_mnemonic.clone(), )?) } - - pub fn to_env_file_section(&self) -> String { - format!( - "CONFIGURED=true\n\ -\n\ -BECH32_PREFIX=n\n\ -MIX_DENOM=unym\n\ -MIX_DENOM_DISPLAY=nym\n\ -STAKE_DENOM=unyx\n\ -STAKE_DENOM_DISPLAY=nyx\n\ -DENOMS_EXPONENT=6\n\ -\n\ -REWARDING_VALIDATOR_ADDRESS={}\n\ -MIXNET_CONTRACT_ADDRESS={}\n\ -VESTING_CONTRACT_ADDRESS={}\n\ -ECASH_CONTRACT_ADDRESS={}\n\ -GROUP_CONTRACT_ADDRESS={}\n\ -MULTISIG_CONTRACT_ADDRESS={}\n\ -COCONUT_DKG_CONTRACT_ADDRESS={}\n\ -NYXD={}\n\ -", - self.auxiliary_addresses.mixnet_rewarder.address, - self.contracts.mixnet.address, - self.contracts.vesting.address, - self.contracts.ecash.address, - self.contracts.cw4_group.address, - self.contracts.cw3_multisig.address, - self.contracts.dkg.address, - self.rpc_endpoint, - ) - } } #[derive(Debug, Clone, Serialize, Deserialize)]