use super::error::DaemonError;
use crate::{channel::GrpcChannel, networks::ChainKind};
use cosmwasm_std::Addr;
use cw_orch_core::{
env::STATE_FOLDER_ENV_NAME,
environment::{DeployDetails, StateInterface},
log::{CONNECTIVITY_LOGS, LOCAL_LOGS},
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 tonic::transport::Channel;
#[derive(Clone, Debug)]
pub struct DaemonState {
pub json_file_path: String,
pub deployment_id: String,
pub grpc_channel: Channel,
pub chain_data: ChainData,
pub read_only: bool,
}
impl DaemonState {
pub async fn new(
mut chain_data: ChainData,
deployment_id: String,
read_only: bool,
) -> Result<DaemonState, DaemonError> {
if chain_data.apis.grpc.is_empty() {
return Err(DaemonError::GRPCListIsEmpty);
}
log::debug!(target: CONNECTIVITY_LOGS, "Found {} gRPC endpoints", chain_data.apis.grpc.len());
let grpc_channel =
GrpcChannel::connect(&chain_data.apis.grpc, &chain_data.chain_id).await?;
let env_file_path = CwOrchEnvVars::load()?.state_file;
let mut json_file_path = if env_file_path.is_relative() {
let state_folder = Self::state_dir()?;
std::fs::create_dir_all(state_folder.clone())?;
state_folder.join(env_file_path)
} else {
env_file_path
}
.into_os_string()
.into_string()
.unwrap();
log::debug!(target: LOCAL_LOGS, "Using state file : {}", json_file_path);
if chain_data.network_type == ChainKind::Local.to_string() {
let name = Path::new(&json_file_path)
.file_stem()
.unwrap()
.to_str()
.unwrap();
let folder = Path::new(&json_file_path)
.parent()
.unwrap()
.to_str()
.unwrap();
json_file_path = format!("{folder}/{name}_local.json");
}
let shortest_denom_token = chain_data.fees.fee_tokens.iter().fold(
chain_data.fees.fee_tokens[0].clone(),
|acc, item| {
if item.denom.len() < acc.denom.len() {
item.clone()
} else {
acc
}
},
);
chain_data.fees.fee_tokens = vec![shortest_denom_token];
let state = DaemonState {
json_file_path,
deployment_id,
grpc_channel,
chain_data,
read_only,
};
if !read_only {
log::info!(
target: LOCAL_LOGS,
"Writing daemon state JSON file: {:#?}",
state.json_file_path
);
crate::json_file::write(
&state.json_file_path,
&state.chain_data.chain_id.to_string(),
&state.chain_data.chain_name,
&state.deployment_id,
);
}
Ok(state)
}
fn read_state(&self) -> Result<serde_json::Value, DaemonError> {
crate::json_file::read(&self.json_file_path)
}
pub fn get(&self, key: &str) -> Result<Value, DaemonError> {
let json = self.read_state()?;
Ok(json[&self.chain_data.chain_name][&self.chain_data.chain_id.to_string()][key].clone())
}
pub fn set<T: Serialize>(
&self,
key: &str,
contract_id: &str,
value: T,
) -> Result<(), DaemonError> {
if self.read_only {
return Err(DaemonError::StateReadOnly);
}
let mut json = self.read_state()?;
json[&self.chain_data.chain_name][&self.chain_data.chain_id.to_string()][key]
[contract_id] = json!(value);
serde_json::to_writer_pretty(File::create(&self.json_file_path).unwrap(), &json)?;
Ok(())
}
pub fn state_dir() -> Result<PathBuf, DaemonError> {
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 {
fn get_address(&self, contract_id: &str) -> Result<Addr, CwEnvError> {
let value = self
.get(&self.deployment_id)?
.get(contract_id)
.ok_or_else(|| CwEnvError::AddrNotInStore(contract_id.to_owned()))?
.clone();
Ok(Addr::unchecked(value.as_str().unwrap()))
}
fn set_address(&mut self, contract_id: &str, address: &Addr) {
self.set(&self.deployment_id, contract_id, address.as_str())
.unwrap();
}
fn get_code_id(&self, contract_id: &str) -> Result<u64, CwEnvError> {
let value = self
.get("code_ids")?
.get(contract_id)
.ok_or_else(|| CwEnvError::CodeIdNotInStore(contract_id.to_owned()))?
.clone();
Ok(value.as_u64().unwrap())
}
fn set_code_id(&mut self, contract_id: &str, code_id: u64) {
self.set("code_ids", contract_id, code_id).unwrap();
}
fn get_all_addresses(&self) -> Result<HashMap<String, Addr>, CwEnvError> {
let mut store = HashMap::new();
let addresses = self.get(&self.deployment_id)?;
let value = addresses.as_object().cloned().unwrap_or_default();
for (id, addr) in value {
store.insert(id, Addr::unchecked(addr.as_str().unwrap()));
}
Ok(store)
}
fn get_all_code_ids(&self) -> Result<HashMap<String, u64>, CwEnvError> {
let mut store = HashMap::new();
let code_ids = self.get("code_ids")?;
let value = code_ids.as_object().cloned().unwrap_or_default();
for (id, code_id) in value {
store.insert(id, code_id.as_u64().unwrap());
}
Ok(store)
}
fn deploy_details(&self) -> DeployDetails {
DeployDetails {
chain_id: self.chain_data.chain_id.to_string(),
chain_name: self.chain_data.chain_name.clone(),
deployment_id: self.deployment_id.clone(),
}
}
}