1use crate::{
2 broadcast::BundledState, execute::LinkedState, multi_sequence::MultiChainSequence,
3 sequence::ScriptSequenceKind, ScriptArgs, ScriptConfig,
4};
5use alloy_primitives::{Bytes, B256};
6use alloy_provider::Provider;
7use eyre::{OptionExt, Result};
8use forge_script_sequence::ScriptSequence;
9use foundry_cheatcodes::Wallets;
10use foundry_common::{
11 compile::ProjectCompiler, provider::try_get_http_provider, ContractData, ContractsByArtifact,
12};
13use foundry_compilers::{
14 artifacts::{BytecodeObject, Libraries},
15 compilers::{multi::MultiCompilerLanguage, Language},
16 info::ContractInfo,
17 utils::source_files_iter,
18 ArtifactId, ProjectCompileOutput,
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!("Multiple contracts in the target path. Please specify the contract name with `--tc ContractName`")
221 }
222 }
223 target_id = Some(id);
224 }
225
226 let target = target_id.ok_or_eyre("Could not find target contract")?;
227
228 Ok(CompiledState {
229 args,
230 script_config,
231 script_wallets,
232 build_data: BuildData { output, target, project_root: project.root().to_path_buf() },
233 })
234 }
235}
236
237pub struct CompiledState {
239 pub args: ScriptArgs,
240 pub script_config: ScriptConfig,
241 pub script_wallets: Wallets,
242 pub build_data: BuildData,
243}
244
245impl CompiledState {
246 pub async fn link(self) -> Result<LinkedState> {
248 let Self { args, script_config, script_wallets, build_data } = self;
249
250 let build_data = build_data.link(&script_config).await?;
251
252 Ok(LinkedState { args, script_config, script_wallets, build_data })
253 }
254
255 pub async fn resume(self) -> Result<BundledState> {
257 let chain = if self.args.multi {
258 None
259 } else {
260 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")?;
261 let provider = Arc::new(try_get_http_provider(fork_url)?);
262 Some(provider.get_chain_id().await?)
263 };
264
265 let sequence = match self.try_load_sequence(chain, false) {
266 Ok(sequence) => sequence,
267 Err(_) => {
268 let mut sequence = self.try_load_sequence(chain, true)?;
271
272 sequence.update_paths_to_broadcasted(
275 &self.script_config.config,
276 &self.args.sig,
277 &self.build_data.target,
278 )?;
279
280 sequence.save(true, true)?;
281 sequence
282 }
283 };
284
285 let (args, build_data, script_wallets, script_config) = if !self.args.unlocked {
286 let mut froms = sequence.sequences().iter().flat_map(|s| {
287 s.transactions
288 .iter()
289 .skip(s.receipts.len())
290 .map(|t| t.transaction.from().expect("from is missing in script artifact"))
291 });
292
293 let available_signers = self
294 .script_wallets
295 .signers()
296 .map_err(|e| eyre::eyre!("Failed to get available signers: {}", e))?;
297
298 if !froms.all(|from| available_signers.contains(&from)) {
299 let executed = self.link().await?.prepare_execution().await?.execute().await?;
302 (
303 executed.args,
304 executed.build_data.build_data,
305 executed.script_wallets,
306 executed.script_config,
307 )
308 } else {
309 (self.args, self.build_data, self.script_wallets, self.script_config)
310 }
311 } else {
312 (self.args, self.build_data, self.script_wallets, self.script_config)
313 };
314
315 let libraries = match sequence {
317 ScriptSequenceKind::Single(ref seq) => Libraries::parse(&seq.libraries)?,
318 ScriptSequenceKind::Multi(_) => Libraries::default(),
320 };
321
322 let linked_build_data = build_data.link_with_libraries(libraries)?;
323
324 Ok(BundledState {
325 args,
326 script_config,
327 script_wallets,
328 build_data: linked_build_data,
329 sequence,
330 })
331 }
332
333 fn try_load_sequence(&self, chain: Option<u64>, dry_run: bool) -> Result<ScriptSequenceKind> {
334 if let Some(chain) = chain {
335 let sequence = ScriptSequence::load(
336 &self.script_config.config,
337 &self.args.sig,
338 &self.build_data.target,
339 chain,
340 dry_run,
341 )?;
342 Ok(ScriptSequenceKind::Single(sequence))
343 } else {
344 let sequence = MultiChainSequence::load(
345 &self.script_config.config,
346 &self.args.sig,
347 &self.build_data.target,
348 dry_run,
349 )?;
350 Ok(ScriptSequenceKind::Multi(sequence))
351 }
352 }
353}