Skip to main content

forge_script/
multi_sequence.rs

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