forge_script/
build.rs

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/// Container for the compiled contracts.
25#[derive(Debug)]
26pub struct BuildData {
27    /// Root of the project.
28    pub project_root: PathBuf,
29    /// The compiler output.
30    pub output: ProjectCompileOutput,
31    /// ID of target contract artifact.
32    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    /// Links contracts. Uses CREATE2 linking when possible, otherwise falls back to
41    /// default linking with sender nonce and address.
42    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            // If --fork-url is not provided, we are just simulating the script.
51            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    /// Links the build data with the given libraries. Expects supplied libraries set being enough
92    /// to fully link target contract.
93    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/// Container for the linked contracts and their dependencies
114#[derive(Debug)]
115pub struct LinkedBuildData {
116    /// Original build data, might be used to relink this object with different libraries.
117    pub build_data: BuildData,
118    /// Known fully linked contracts.
119    pub known_contracts: ContractsByArtifact,
120    /// Libraries used to link the contracts.
121    pub libraries: Libraries,
122    /// Libraries that need to be deployed by sender before script execution.
123    pub predeploy_libraries: ScriptPredeployLibraries,
124    /// Source files of the contracts. Used by debugger.
125    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    /// Fetches target bytecode from linked contracts.
147    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
154/// First state basically containing only inputs of the user.
155pub struct PreprocessedState {
156    pub args: ScriptArgs,
157    pub script_config: ScriptConfig,
158    pub script_wallets: Wallets,
159}
160
161impl PreprocessedState {
162    /// Parses user input and compiles the contracts depending on script target.
163    /// After compilation, finds exact [ArtifactId] of the target contract.
164    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        // If we've received correct path, use it as target_path
171        // Otherwise, parse input as <path>:<name> and use the path from the contract info, if
172        // present.
173        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        // Find target artfifact id by name and path in compilation artifacts.
197        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                // Ignore contracts with empty abi or linked bytecode of length 0 which are
209                // interfaces/abstract contracts/libraries.
210                continue;
211            }
212
213            if let Some(target) = target_id {
214                // We might have multiple artifacts for the same contract but with different
215                // solc versions. Their names will have form of {name}.0.X.Y, so we are
216                // stripping versions off before comparing them.
217                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
237/// State after we have determined and compiled target contract to be executed.
238pub 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    /// Uses provided sender address to compute library addresses and link contracts with them.
247    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    /// Tries loading the resumed state from the cache files, skipping simulation stage.
256    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                // If the script was simulated, but there was no attempt to broadcast yet,
269                // try to read the script sequence from the `dry-run/` folder
270                let mut sequence = self.try_load_sequence(chain, true)?;
271
272                // If sequence was in /dry-run, Update its paths so it is not saved into /dry-run
273                // this time as we are about to broadcast it.
274                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                // IF we are missing required signers, execute script as we might need to collect
300                // private keys from the execution.
301                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        // Collect libraries from sequence and link contracts with them.
316        let libraries = match sequence {
317            ScriptSequenceKind::Single(ref seq) => Libraries::parse(&seq.libraries)?,
318            // Library linking is not supported for multi-chain sequences
319            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}