1use crate::{
2 ScriptArgs, ScriptConfig, broadcast::BundledState, execute::LinkedState,
3 multi_sequence::MultiChainSequence, sequence::ScriptSequenceKind,
4};
5use alloy_network::AnyNetwork;
6use alloy_primitives::{B256, Bytes};
7use alloy_provider::Provider;
8use eyre::{OptionExt, Result};
9use forge_script_sequence::ScriptSequence;
10use foundry_cheatcodes::Wallets;
11use foundry_common::{
12 ContractData, ContractsByArtifact, compile::ProjectCompiler, provider::ProviderBuilder,
13};
14use foundry_compilers::{
15 ArtifactId, ProjectCompileOutput,
16 artifacts::{BytecodeObject, Libraries},
17 compilers::{Language, multi::MultiCompilerLanguage},
18 info::ContractInfo,
19 utils::source_files_iter,
20};
21use foundry_evm::{core::evm::FoundryEvmNetwork, traces::debug::ContractSources};
22use foundry_linking::Linker;
23use foundry_wallets::wallet_browser::signer::BrowserSigner;
24use std::{path::PathBuf, str::FromStr, sync::Arc};
25
26#[derive(Debug)]
28pub struct BuildData {
29 pub project_root: PathBuf,
31 pub output: ProjectCompileOutput,
33 pub target: ArtifactId,
35}
36
37impl BuildData {
38 pub fn get_linker(&self) -> Linker<'_> {
39 Linker::new(self.project_root.clone(), self.output.artifact_ids().collect())
40 }
41
42 pub async fn link<FEN: FoundryEvmNetwork>(
45 self,
46 script_config: &ScriptConfig<FEN>,
47 ) -> Result<LinkedBuildData> {
48 let create2_deployer = script_config.evm_opts.create2_deployer;
49 let can_use_create2 = if let Some(fork_url) = &script_config.evm_opts.fork_url {
50 let provider = ProviderBuilder::<AnyNetwork>::new(fork_url).build()?;
51 let deployer_code = provider.get_code_at(create2_deployer).await?;
52
53 !deployer_code.is_empty()
54 } else {
55 true
57 };
58
59 let known_libraries = script_config.config.libraries_with_remappings()?;
60
61 let maybe_create2_link_output = can_use_create2
62 .then(|| {
63 self.get_linker()
64 .link_with_create2(
65 known_libraries.clone(),
66 create2_deployer,
67 script_config.config.create2_library_salt,
68 &self.target,
69 )
70 .ok()
71 })
72 .flatten();
73
74 let (libraries, predeploy_libs) = if let Some(output) = maybe_create2_link_output {
75 (
76 output.libraries,
77 ScriptPredeployLibraries::Create2(
78 output.libs_to_deploy,
79 script_config.config.create2_library_salt,
80 ),
81 )
82 } else {
83 let output = self.get_linker().link_with_nonce_or_address(
84 known_libraries,
85 script_config.evm_opts.sender,
86 script_config.sender_nonce,
87 [&self.target],
88 )?;
89
90 (output.libraries, ScriptPredeployLibraries::Default(output.libs_to_deploy))
91 };
92
93 LinkedBuildData::new(libraries, predeploy_libs, self)
94 }
95
96 pub fn link_with_libraries(self, libraries: Libraries) -> Result<LinkedBuildData> {
99 LinkedBuildData::new(libraries, ScriptPredeployLibraries::Default(Vec::new()), self)
100 }
101}
102
103#[derive(Debug)]
104pub enum ScriptPredeployLibraries {
105 Default(Vec<Bytes>),
106 Create2(Vec<Bytes>, B256),
107}
108
109impl ScriptPredeployLibraries {
110 pub fn libraries_count(&self) -> usize {
111 match self {
112 Self::Default(libs) => libs.len(),
113 Self::Create2(libs, _) => libs.len(),
114 }
115 }
116}
117
118#[derive(Debug)]
120pub struct LinkedBuildData {
121 pub build_data: BuildData,
123 pub known_contracts: ContractsByArtifact,
125 pub libraries: Libraries,
127 pub predeploy_libraries: ScriptPredeployLibraries,
129 pub sources: ContractSources,
131}
132
133impl LinkedBuildData {
134 pub fn new(
135 libraries: Libraries,
136 predeploy_libraries: ScriptPredeployLibraries,
137 build_data: BuildData,
138 ) -> Result<Self> {
139 let sources = ContractSources::from_project_output(
140 &build_data.output,
141 &build_data.project_root,
142 Some(&libraries),
143 )?;
144
145 let known_contracts =
146 ContractsByArtifact::new(build_data.get_linker().get_linked_artifacts(&libraries)?);
147
148 Ok(Self { build_data, known_contracts, libraries, predeploy_libraries, sources })
149 }
150
151 pub fn get_target_contract(&self) -> Result<&ContractData> {
153 self.known_contracts
154 .get(&self.build_data.target)
155 .ok_or_eyre("target not found in linked artifacts")
156 }
157}
158
159pub struct PreprocessedState<FEN: FoundryEvmNetwork> {
161 pub args: ScriptArgs,
162 pub script_config: ScriptConfig<FEN>,
163 pub script_wallets: Wallets,
164 pub browser_wallet: Option<BrowserSigner<FEN::Network>>,
165}
166
167impl<FEN: FoundryEvmNetwork> PreprocessedState<FEN> {
168 pub fn compile(self) -> Result<CompiledState<FEN>> {
171 let Self { args, script_config, script_wallets, browser_wallet } = self;
172 let project = script_config.config.project()?;
173
174 let mut target_name = args.target_contract.clone();
175
176 let target_path = if let Ok(path) = dunce::canonicalize(&args.path) {
180 path
181 } else {
182 let contract = ContractInfo::from_str(&args.path)?;
183 target_name = Some(contract.name.clone());
184 if let Some(path) = contract.path {
185 dunce::canonicalize(path)?
186 } else {
187 project.find_contract_path(contract.name.as_str())?
188 }
189 };
190
191 let sources_to_compile = source_files_iter(
192 project.paths.sources.as_path(),
193 MultiCompilerLanguage::FILE_EXTENSIONS,
194 )
195 .chain([target_path.clone()]);
196
197 let output = ProjectCompiler::new().files(sources_to_compile).compile(&project)?;
198
199 let mut target_id: Option<ArtifactId> = None;
200
201 for (id, contract) in output.artifact_ids().filter(|(id, _)| id.source == target_path) {
203 if let Some(name) = &target_name {
204 if id.name != *name {
205 continue;
206 }
207 } else if contract.abi.as_ref().is_none_or(|abi| abi.is_empty())
208 || contract.bytecode.as_ref().is_none_or(|b| match &b.object {
209 BytecodeObject::Bytecode(b) => b.is_empty(),
210 BytecodeObject::Unlinked(_) => false,
211 })
212 {
213 continue;
216 }
217
218 if let Some(target) = target_id {
219 let target_name = target.name.split('.').next().unwrap();
223 let id_name = id.name.split('.').next().unwrap();
224 if target_name != id_name {
225 eyre::bail!(
226 "Multiple contracts in the target path. Please specify the contract name with `--tc ContractName`"
227 )
228 }
229 }
230 target_id = Some(id);
231 }
232
233 let target = target_id.ok_or_eyre("Could not find target contract")?;
234
235 Ok(CompiledState {
236 args,
237 script_config,
238 script_wallets,
239 browser_wallet,
240 build_data: BuildData { output, target, project_root: project.root().to_path_buf() },
241 })
242 }
243}
244
245pub struct CompiledState<FEN: FoundryEvmNetwork> {
247 pub args: ScriptArgs,
248 pub script_config: ScriptConfig<FEN>,
249 pub script_wallets: Wallets,
250 pub browser_wallet: Option<BrowserSigner<FEN::Network>>,
251 pub build_data: BuildData,
252}
253
254impl<FEN: FoundryEvmNetwork> CompiledState<FEN> {
255 pub async fn link(self) -> Result<LinkedState<FEN>> {
257 let Self { args, script_config, script_wallets, browser_wallet, build_data } = self;
258
259 let build_data = build_data.link(&script_config).await?;
260
261 Ok(LinkedState { args, script_config, script_wallets, browser_wallet, build_data })
262 }
263
264 pub async fn resume(self) -> Result<BundledState<FEN>> {
266 let chain = if self.args.multi {
267 None
268 } else {
269 let fork_url = self.script_config.evm_opts.fork_url.clone().ok_or_eyre("Missing --fork-url field, if you were trying to broadcast a multi-chain sequence, please use --multi flag")?;
270 let provider = Arc::new(ProviderBuilder::<AnyNetwork>::new(&fork_url).build()?);
271 Some(provider.get_chain_id().await?)
272 };
273
274 let sequence = match self.try_load_sequence(chain, false) {
275 Ok(sequence) => sequence,
276 Err(_) => {
277 let mut sequence = self.try_load_sequence(chain, true)?;
280
281 sequence.update_paths_to_broadcasted(
284 &self.script_config.config,
285 &self.args.sig,
286 &self.build_data.target,
287 )?;
288
289 sequence.save(true, true)?;
290 sequence
291 }
292 };
293
294 let (args, build_data, script_wallets, browser_wallet, script_config) =
295 if self.args.unlocked {
296 (
297 self.args,
298 self.build_data,
299 self.script_wallets,
300 self.browser_wallet,
301 self.script_config,
302 )
303 } else {
304 let mut froms = sequence.sequences().iter().flat_map(|s| {
305 s.transactions
306 .iter()
307 .skip(s.receipts.len())
308 .map(|t| t.transaction.from().expect("from is missing in script artifact"))
309 });
310
311 let available_signers = self
312 .script_wallets
313 .signers()
314 .map_err(|e| eyre::eyre!("Failed to get available signers: {}", e))?;
315
316 if froms.all(|from| available_signers.contains(&from)) {
317 (
318 self.args,
319 self.build_data,
320 self.script_wallets,
321 self.browser_wallet,
322 self.script_config,
323 )
324 } else {
325 let executed = self.link().await?.prepare_execution().await?.execute().await?;
328 (
329 executed.args,
330 executed.build_data.build_data,
331 executed.script_wallets,
332 executed.browser_wallet,
333 executed.script_config,
334 )
335 }
336 };
337
338 let libraries = match sequence {
340 ScriptSequenceKind::Single(ref seq) => Libraries::parse(&seq.libraries)?,
341 ScriptSequenceKind::Multi(_) => Libraries::default(),
343 };
344
345 let linked_build_data = build_data.link_with_libraries(libraries)?;
346
347 Ok(BundledState {
348 args,
349 script_config,
350 script_wallets,
351 browser_wallet,
352 build_data: linked_build_data,
353 sequence,
354 })
355 }
356
357 fn try_load_sequence(
358 &self,
359 chain: Option<u64>,
360 dry_run: bool,
361 ) -> Result<ScriptSequenceKind<FEN::Network>> {
362 if let Some(chain) = chain {
363 let sequence = ScriptSequence::load(
364 &self.script_config.config,
365 &self.args.sig,
366 &self.build_data.target,
367 chain,
368 dry_run,
369 )?;
370 Ok(ScriptSequenceKind::Single(sequence))
371 } else {
372 let sequence = MultiChainSequence::load(
373 &self.script_config.config,
374 &self.args.sig,
375 &self.build_data.target,
376 dry_run,
377 )?;
378 Ok(ScriptSequenceKind::Multi(sequence))
379 }
380 }
381}