1use super::error::DaemonError;
2use crate::{
3 env::{default_state_folder, DaemonEnvVars},
4 json_lock::{patch_state_if_old, JsonLockedState},
5 networks::ChainKind,
6};
7
8use cosmwasm_std::Addr;
9use cw_orch_core::{
10 environment::{ChainInfoOwned, CwEnv, Environment, StateInterface},
11 log::local_target,
12 CwEnvError,
13};
14use once_cell::sync::Lazy;
15use serde::Serialize;
16use serde_json::{json, Value};
17use std::{
18 collections::{HashMap, HashSet},
19 path::Path,
20 sync::{Arc, Mutex},
21};
22
23pub(crate) static LOCKED_FILES: Lazy<Mutex<HashSet<String>>> =
26 Lazy::new(|| Mutex::new(HashSet::new()));
27
28#[derive(Debug, Clone)]
31pub struct DaemonState {
32 pub json_state: DaemonStateFile,
33 pub deployment_id: String,
35 pub chain_data: Arc<ChainInfoOwned>,
37 pub write_on_change: bool,
39}
40
41impl Drop for DaemonState {
42 fn drop(&mut self) {
43 if let DaemonStateFile::FullAccess { json_file_state } = &self.json_state {
44 let json_lock = json_file_state.lock().unwrap();
45 let mut locked_files = LOCKED_FILES.lock().unwrap();
46 locked_files.remove(json_lock.path());
47 }
48 }
49}
50
51#[derive(Debug, Clone)]
52pub enum DaemonStateFile {
53 ReadOnly {
54 path: String,
55 },
56 FullAccess {
57 json_file_state: Arc<Mutex<JsonLockedState>>,
58 },
59}
60
61impl DaemonState {
62 pub fn new(
64 mut json_file_path: String,
65 chain_data: &Arc<ChainInfoOwned>,
66 deployment_id: String,
67 read_only: bool,
68 write_on_change: bool,
69 ) -> Result<DaemonState, DaemonError> {
70 let chain_id = &chain_data.chain_id;
71
72 log::debug!(target: &local_target(), "Using state file : {}", json_file_path);
73
74 if chain_data.kind == ChainKind::Local {
76 let name = Path::new(&json_file_path)
77 .file_stem()
78 .unwrap()
79 .to_str()
80 .unwrap();
81 let folder = Path::new(&json_file_path)
82 .parent()
83 .unwrap()
84 .to_str()
85 .unwrap();
86
87 json_file_path = format!("{folder}/{name}_local.json");
88 }
89
90 let json_state = if read_only {
91 DaemonStateFile::ReadOnly {
92 path: json_file_path,
93 }
94 } else {
95 log::info!(
96 target: &local_target(),
97 "Writing daemon state JSON file: {json_file_path:#?}",
98 );
99
100 let mut lock = LOCKED_FILES.lock().unwrap();
101 if lock.contains(&json_file_path) {
102 return Err(DaemonError::StateAlreadyLocked(json_file_path));
103 }
104 let mut json_file_state = JsonLockedState::new(&json_file_path);
105 lock.insert(json_file_path);
107 drop(lock);
108
109 json_file_state.prepare(chain_id, &deployment_id);
110 if write_on_change {
111 json_file_state.force_write();
112 }
113 DaemonStateFile::FullAccess {
114 json_file_state: Arc::new(Mutex::new(json_file_state)),
115 }
116 };
117
118 Ok(DaemonState {
119 json_state,
120 deployment_id,
121 chain_data: chain_data.clone(),
122 write_on_change,
123 })
124 }
125
126 pub fn state_file_path() -> Result<String, DaemonError> {
128 let env_file_path = DaemonEnvVars::state_file();
130 let state_file_path = if env_file_path.is_relative() {
131 let first_path_component = env_file_path
133 .components()
134 .map(|comp| comp.as_os_str().to_owned().into_string().unwrap())
135 .next();
136 if first_path_component == Some(".".to_string()) {
137 let current_dir = std::env::current_dir()?;
138 let actual_relative_path = env_file_path.strip_prefix("./")?;
139 current_dir.join(actual_relative_path)
140 } else if first_path_component == Some("..".to_string()) {
141 let current_dir = std::env::current_dir()?;
142 current_dir.join(env_file_path)
143 } else {
144 let state_folder = default_state_folder()?;
145
146 std::fs::create_dir_all(state_folder.clone())?;
148
149 state_folder.join(env_file_path)
150 }
151 } else {
152 env_file_path
153 }
154 .into_os_string()
155 .into_string()
156 .unwrap();
157
158 Ok(state_file_path)
159 }
160
161 pub fn get(&self, key: &str) -> Result<Value, DaemonError> {
163 let json = match &self.json_state {
164 DaemonStateFile::ReadOnly { path } => {
165 let j = crate::json_lock::read(path)?;
166 let j = patch_state_if_old(j);
167
168 j[&self.chain_data.chain_id].clone()
169 }
170 DaemonStateFile::FullAccess { json_file_state } => json_file_state
171 .lock()
172 .unwrap()
173 .get(&self.chain_data.chain_id)
174 .clone(),
175 };
176 Ok(json[key].clone())
177 }
178
179 pub fn set<T: Serialize>(
181 &mut self,
182 key: &str,
183 contract_id: &str,
184 value: T,
185 ) -> Result<(), DaemonError> {
186 let json_file_state = match &mut self.json_state {
187 DaemonStateFile::ReadOnly { path } => {
188 return Err(DaemonError::StateReadOnly(path.clone()))
189 }
190 DaemonStateFile::FullAccess { json_file_state } => json_file_state,
191 };
192
193 let mut json_file_lock = json_file_state.lock().unwrap();
194 let val = json_file_lock.get_mut(&self.chain_data.chain_id);
195 val[key][contract_id] = json!(value);
196
197 if self.write_on_change {
198 json_file_lock.force_write();
199 }
200
201 Ok(())
202 }
203
204 pub fn remove(&mut self, key: &str, contract_id: &str) -> Result<(), DaemonError> {
206 let json_file_state = match &mut self.json_state {
207 DaemonStateFile::ReadOnly { path } => {
208 return Err(DaemonError::StateReadOnly(path.clone()))
209 }
210 DaemonStateFile::FullAccess { json_file_state } => json_file_state,
211 };
212
213 let mut json_file_lock = json_file_state.lock().unwrap();
214 let val = json_file_lock.get_mut(&self.chain_data.chain_id);
215 val[key][contract_id] = Value::Null;
216
217 if self.write_on_change {
218 json_file_lock.force_write();
219 }
220
221 Ok(())
222 }
223
224 pub fn force_write(&mut self) -> Result<(), DaemonError> {
226 let json_file_state = match &mut self.json_state {
227 DaemonStateFile::ReadOnly { path } => {
228 return Err(DaemonError::StateReadOnly(path.clone()))
229 }
230 DaemonStateFile::FullAccess { json_file_state } => json_file_state,
231 };
232 json_file_state.lock().unwrap().force_write();
233 Ok(())
234 }
235
236 pub fn flush(&mut self) -> Result<(), DaemonError> {
239 if self.chain_data.kind != ChainKind::Local {
240 panic!("Can only flush local chain state");
241 }
242 let json_file_state = match &mut self.json_state {
243 DaemonStateFile::ReadOnly { path } => {
244 return Err(DaemonError::StateReadOnly(path.clone()))
245 }
246 DaemonStateFile::FullAccess { json_file_state } => json_file_state,
247 };
248
249 let mut json_file_lock = json_file_state.lock().unwrap();
250 let json = json_file_lock.get_mut(&self.chain_data.chain_id);
251
252 *json = json!({});
253
254 if self.write_on_change {
255 json_file_lock.force_write();
256 }
257 Ok(())
258 }
259}
260
261impl StateInterface for DaemonState {
262 fn get_address(&self, contract_id: &str) -> Result<Addr, CwEnvError> {
264 let value = self
265 .get(&self.deployment_id)
266 .ok()
267 .and_then(|v| v.get(contract_id).cloned())
268 .ok_or_else(|| CwEnvError::AddrNotInStore(contract_id.to_owned()))?
269 .clone();
270 Ok(Addr::unchecked(value.as_str().unwrap()))
271 }
272
273 fn set_address(&mut self, contract_id: &str, address: &Addr) {
275 let deployment_id = self.deployment_id.clone();
276 self.set(&deployment_id, contract_id, address.as_str())
277 .unwrap();
278 }
279
280 fn remove_address(&mut self, contract_id: &str) {
281 let deployment_id = self.deployment_id.clone();
282 self.remove(&deployment_id, contract_id).unwrap();
283 }
284
285 fn get_code_id(&self, contract_id: &str) -> Result<u64, CwEnvError> {
287 let value = self
288 .get("code_ids")
289 .ok()
290 .and_then(|v| v.get(contract_id).cloned())
291 .ok_or_else(|| CwEnvError::CodeIdNotInStore(contract_id.to_owned()))?
292 .clone();
293 Ok(value.as_u64().unwrap())
294 }
295
296 fn set_code_id(&mut self, contract_id: &str, code_id: u64) {
298 self.set("code_ids", contract_id, code_id).unwrap();
299 }
300 fn remove_code_id(&mut self, contract_id: &str) {
301 self.remove("code_ids", contract_id).unwrap();
302 }
303
304 fn get_all_addresses(&self) -> Result<HashMap<String, Addr>, CwEnvError> {
306 let mut store = HashMap::new();
307 let addresses = self.get(&self.deployment_id)?;
308 let value = addresses.as_object().cloned().unwrap_or_default();
309 for (id, addr) in value {
310 store.insert(id, Addr::unchecked(addr.as_str().unwrap()));
311 }
312 Ok(store)
313 }
314
315 fn get_all_code_ids(&self) -> Result<HashMap<String, u64>, CwEnvError> {
316 let mut store = HashMap::new();
317 let code_ids = self.get("code_ids")?;
318 let value = code_ids.as_object().cloned().unwrap_or_default();
319 for (id, code_id) in value {
320 store.insert(id, code_id.as_u64().unwrap());
321 }
322 Ok(store)
323 }
324}
325
326pub trait DeployedChains<Chain: CwEnv>: cw_orch_core::contract::Deploy<Chain> {
327 fn get_all_deployed_chains() -> Vec<String> {
342 let deployed_state_file = Self::deployed_state_file_path();
343 if let Some(state_file) = deployed_state_file {
344 if let Ok(module_state_json) = crate::json_lock::read(&state_file) {
345 let module_state_json = patch_state_if_old(module_state_json);
346 return module_state_json
347 .as_object()
348 .unwrap()
349 .keys()
350 .cloned()
351 .collect();
352 }
353 }
354 vec![]
355 }
356 fn set_contracts_state(&mut self, custom_state: Option<Value>) {
359 let mut is_loading_from_file = false;
360 let Some(maybe_old_state) = custom_state.or_else(|| {
361 is_loading_from_file = true;
362 Self::deployed_state_file_path()
363 .and_then(|state_file| crate::json_lock::read(&state_file).ok())
364 }) else {
365 return;
366 };
367 let state = patch_state_if_old(maybe_old_state);
368
369 let all_contracts = self.get_contracts_mut();
370
371 for contract in all_contracts {
372 if is_loading_from_file && !contract.environment().can_load_state_from_state_file() {
374 return;
375 }
376
377 let env_info = contract.environment().env_info();
379 if contract.code_id().is_err() {
382 let code_id = state
383 .get(env_info.chain_id.to_string())
384 .unwrap_or(&Value::Null)
385 .get("code_ids")
386 .unwrap_or(&Value::Null)
387 .get(contract.id());
388
389 if let Some(code_id) = code_id {
390 if code_id.is_u64() {
391 contract.set_default_code_id(code_id.as_u64().unwrap())
392 }
393 }
394 }
395 if contract.address().is_err() {
397 let address = state
399 .get(env_info.chain_id.to_string())
400 .unwrap_or(&Value::Null)
401 .get(env_info.deployment_id)
402 .unwrap_or(&Value::Null)
403 .get(contract.id());
404
405 if let Some(address) = address {
406 if address.is_string() {
407 contract.set_default_address(&Addr::unchecked(address.as_str().unwrap()))
408 }
409 }
410 }
411 }
412 }
413
414 fn deployed_state_file_path() -> Option<String>;
417}
418
419pub(crate) use tempstate::gen_temp_file_path;
420
421mod tempstate {
422
423 use uid::IdU16 as IdT;
424
425 #[derive(Copy, Clone)]
426 struct T(());
427
428 type Id = IdT<T>;
429
430 pub fn gen_temp_file_path() -> std::path::PathBuf {
431 let id = Id::new();
432 let id = id.get();
433 let env_dir = std::env::temp_dir();
434 env_dir.join(format!("tempstate_{id}"))
435 }
436}
437
438#[cfg(test)]
439pub mod test {
440 use std::env;
441
442 use crate::{env::STATE_FILE_ENV_NAME, DaemonState};
443
444 #[test]
445 #[serial_test::serial]
446 fn test_env_variable_state_path() -> anyhow::Result<()> {
447 let absolute_path = "/usr/var/file.json";
448 let relative_path = "folder/file.json";
449 let dotted_relative_path = format!("./{}", relative_path);
450 let parent_and_relative_path = format!("../{}", relative_path);
451
452 std::env::set_var(STATE_FILE_ENV_NAME, absolute_path);
453 let absolute_state_path = DaemonState::state_file_path()?;
454 assert_eq!(absolute_path.to_string(), absolute_state_path);
455
456 std::env::set_var(STATE_FILE_ENV_NAME, dotted_relative_path);
457 let relative_state_path = DaemonState::state_file_path()?;
458 assert_eq!(
459 env::current_dir()?
460 .join(relative_path)
461 .into_os_string()
462 .into_string()
463 .unwrap(),
464 relative_state_path
465 );
466
467 std::env::set_var(STATE_FILE_ENV_NAME, relative_path);
468 let relative_state_path = DaemonState::state_file_path()?;
469 assert_eq!(
470 dirs::home_dir()
471 .unwrap()
472 .join(".cw-orchestrator")
473 .join(relative_path)
474 .into_os_string()
475 .into_string()
476 .unwrap(),
477 relative_state_path
478 );
479
480 std::env::set_var(STATE_FILE_ENV_NAME, parent_and_relative_path);
481 let parent_and_relative_state_path = DaemonState::state_file_path()?;
482 assert_eq!(
483 env::current_dir()?
484 .join("../")
485 .join(relative_path)
486 .into_os_string()
487 .into_string()
488 .unwrap(),
489 parent_and_relative_state_path
490 );
491
492 std::env::remove_var(STATE_FILE_ENV_NAME);
493 Ok(())
494 }
495}