forge_script/
multi_sequence.rs

1use eyre::{ContextCompat, Result, WrapErr};
2use forge_script_sequence::{
3    now, sig_to_file_name, ScriptSequence, SensitiveScriptSequence, DRY_RUN_DIR,
4};
5use foundry_common::{fs, shell};
6use foundry_compilers::ArtifactId;
7use foundry_config::Config;
8use serde::{Deserialize, Serialize};
9use std::{
10    io::{BufWriter, Write},
11    path::PathBuf,
12};
13
14/// Holds the sequences of multiple chain deployments.
15#[derive(Clone, Default, Serialize, Deserialize)]
16pub struct MultiChainSequence {
17    pub deployments: Vec<ScriptSequence>,
18    #[serde(skip)]
19    pub path: PathBuf,
20    #[serde(skip)]
21    pub sensitive_path: PathBuf,
22    pub timestamp: u64,
23}
24
25/// Sensitive values from script sequences.
26#[derive(Clone, Default, Serialize, Deserialize)]
27pub struct SensitiveMultiChainSequence {
28    pub deployments: Vec<SensitiveScriptSequence>,
29}
30
31impl SensitiveMultiChainSequence {
32    fn from_multi_sequence(sequence: MultiChainSequence) -> Self {
33        Self {
34            deployments: sequence.deployments.into_iter().map(|sequence| sequence.into()).collect(),
35        }
36    }
37}
38
39impl MultiChainSequence {
40    pub fn new(
41        deployments: Vec<ScriptSequence>,
42        sig: &str,
43        target: &ArtifactId,
44        config: &Config,
45        dry_run: bool,
46    ) -> Result<Self> {
47        let (path, sensitive_path) = Self::get_paths(config, sig, target, dry_run)?;
48
49        Ok(Self { deployments, path, sensitive_path, timestamp: now().as_secs() })
50    }
51
52    /// Gets paths in the formats
53    /// ./broadcast/multi/contract_filename[-timestamp]/sig.json and
54    /// ./cache/multi/contract_filename[-timestamp]/sig.json
55    pub fn get_paths(
56        config: &Config,
57        sig: &str,
58        target: &ArtifactId,
59        dry_run: bool,
60    ) -> Result<(PathBuf, PathBuf)> {
61        let mut broadcast = config.broadcast.to_path_buf();
62        let mut cache = config.cache_path.to_path_buf();
63        let mut common = PathBuf::new();
64
65        common.push("multi");
66
67        if dry_run {
68            common.push(DRY_RUN_DIR);
69        }
70
71        let target_fname = target
72            .source
73            .file_name()
74            .wrap_err_with(|| format!("No filename for {:?}", target.source))?
75            .to_string_lossy();
76
77        common.push(format!("{target_fname}-latest"));
78
79        broadcast.push(common.clone());
80        cache.push(common);
81
82        fs::create_dir_all(&broadcast)?;
83        fs::create_dir_all(&cache)?;
84
85        let filename = format!("{}.json", sig_to_file_name(sig));
86
87        broadcast.push(filename.clone());
88        cache.push(filename);
89
90        Ok((broadcast, cache))
91    }
92
93    /// Loads the sequences for the multi chain deployment.
94    pub fn load(config: &Config, sig: &str, target: &ArtifactId, dry_run: bool) -> Result<Self> {
95        let (path, sensitive_path) = Self::get_paths(config, sig, target, dry_run)?;
96        let mut sequence: Self = foundry_compilers::utils::read_json_file(&path)
97            .wrap_err("Multi-chain deployment not found.")?;
98        let sensitive_sequence: SensitiveMultiChainSequence =
99            foundry_compilers::utils::read_json_file(&sensitive_path)
100                .wrap_err("Multi-chain deployment sensitive details not found.")?;
101
102        sequence.deployments.iter_mut().enumerate().for_each(|(i, sequence)| {
103            sequence.fill_sensitive(&sensitive_sequence.deployments[i]);
104        });
105
106        sequence.path = path;
107        sequence.sensitive_path = sensitive_path;
108
109        Ok(sequence)
110    }
111
112    /// Saves the transactions as file if it's a standalone deployment.
113    pub fn save(&mut self, silent: bool, save_ts: bool) -> Result<()> {
114        self.deployments.iter_mut().for_each(|sequence| sequence.sort_receipts());
115
116        self.timestamp = now().as_secs();
117
118        let sensitive_sequence = SensitiveMultiChainSequence::from_multi_sequence(self.clone());
119
120        // broadcast writes
121        //../Contract-latest/run.json
122        let mut writer = BufWriter::new(fs::create_file(&self.path)?);
123        serde_json::to_writer_pretty(&mut writer, &self)?;
124        writer.flush()?;
125
126        if save_ts {
127            //../Contract-[timestamp]/run.json
128            let path = self.path.to_string_lossy();
129            let file = PathBuf::from(&path.replace("-latest", &format!("-{}", self.timestamp)));
130            fs::create_dir_all(file.parent().unwrap())?;
131            fs::copy(&self.path, &file)?;
132        }
133
134        // cache writes
135        //../Contract-latest/run.json
136        let mut writer = BufWriter::new(fs::create_file(&self.sensitive_path)?);
137        serde_json::to_writer_pretty(&mut writer, &sensitive_sequence)?;
138        writer.flush()?;
139
140        if save_ts {
141            //../Contract-[timestamp]/run.json
142            let path = self.sensitive_path.to_string_lossy();
143            let file = PathBuf::from(&path.replace("-latest", &format!("-{}", self.timestamp)));
144            fs::create_dir_all(file.parent().unwrap())?;
145            fs::copy(&self.sensitive_path, &file)?;
146        }
147
148        if !silent {
149            if shell::is_json() {
150                sh_println!(
151                    "{}",
152                    serde_json::json!({
153                        "status": "success",
154                        "transactions": self.path.display().to_string(),
155                        "sensitive": self.sensitive_path.display().to_string(),
156                    })
157                )?;
158            } else {
159                sh_println!("\nTransactions saved to: {}\n", self.path.display())?;
160                sh_println!("Sensitive details saved to: {}\n", self.sensitive_path.display())?;
161            }
162        }
163
164        Ok(())
165    }
166}