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