forge_script/
multi_sequence.rs1use 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#[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#[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 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 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 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 fs::write_pretty_json_file(&self.path, self)?;
131
132 if save_ts {
133 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 fs::write_pretty_json_file(&self.sensitive_path, &sensitive_sequence)?;
143
144 if save_ts {
145 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}