Skip to main content

foundry_linking/
lib.rs

1//! # foundry-linking
2//!
3//! EVM bytecode linker.
4
5#![cfg_attr(not(test), warn(unused_crate_dependencies))]
6#![cfg_attr(docsrs, feature(doc_cfg))]
7
8use alloy_primitives::{Address, B256, Bytes, map::HashMap as AlloyHashMap};
9use foundry_compilers::{
10    Artifact, ArtifactId,
11    artifacts::{CompactBytecode, CompactContractBytecodeCow, Libraries},
12    contracts::ArtifactContracts,
13};
14use rayon::prelude::*;
15use std::{
16    collections::{BTreeMap, BTreeSet},
17    path::{Path, PathBuf},
18    str::FromStr,
19    sync::OnceLock,
20};
21
22/// Errors that can occur during linking.
23#[derive(Debug, thiserror::Error)]
24pub enum LinkerError {
25    #[error("wasn't able to find artifact for library {name} at {file}")]
26    MissingLibraryArtifact { file: String, name: String },
27    #[error("multiple library artifacts resolve to the same key {file}:{name}")]
28    ConflictingLibraryArtifacts { file: String, name: String },
29    #[error("target artifact is not present in provided artifacts set")]
30    MissingTargetArtifact,
31    #[error(transparent)]
32    InvalidAddress(<Address as std::str::FromStr>::Err),
33    #[error("cyclic dependency found, can't link libraries via CREATE2")]
34    CyclicDependency,
35    #[error("failed linking {artifact}")]
36    LinkingFailed { artifact: String },
37}
38
39type Index = AlloyHashMap<PathBuf, AlloyHashMap<String, Vec<ArtifactId>>>;
40
41pub struct Linker<'a> {
42    /// Root of the project, used to determine whether artifact/library path can be stripped.
43    pub root: PathBuf,
44    /// Compilation artifacts.
45    pub contracts: ArtifactContracts<CompactContractBytecodeCow<'a>>,
46}
47
48/// Reuses artifact lookups across multiple linker operations.
49pub struct Resolver<'a, 'b> {
50    linker: &'b Linker<'a>,
51    index: OnceLock<Index>,
52}
53
54/// Output of the `link_with_nonce_or_address`
55pub struct LinkOutput {
56    /// Flattened resolved library addresses. Contains both user-provided and newly deployed
57    /// libraries, with stripped path prefixes. Auto-linked keys that resolve to different
58    /// addresses for different artifacts are omitted; use
59    /// [`DetailedLinkOutput::artifact_libraries`] for complete per-artifact mappings.
60    pub libraries: Libraries,
61    /// Addresses of libraries required by the linked targets.
62    pub library_addresses: Vec<Address>,
63    /// Vector of libraries that need to be deployed from sender address.
64    /// The order in which they appear in the vector is the order in which they should be deployed.
65    pub libs_to_deploy: Vec<Bytes>,
66}
67
68/// Detailed linker output for callers that need metadata about auto-linked libraries.
69pub struct DetailedLinkOutput {
70    /// The backwards-compatible linker output.
71    pub output: LinkOutput,
72    /// Complete transitive library address mapping selected for each linked artifact.
73    pub artifact_libraries: BTreeMap<ArtifactId, Libraries>,
74    /// Address selected for every resolved library artifact, including byte-identical variants.
75    pub artifact_addresses: BTreeMap<ArtifactId, Address>,
76    /// Unique physical library deployments and their linked creation bytecode.
77    ///
78    /// Multiple byte-identical artifacts can share one CREATE2 deployment. In that case, this
79    /// contains one representative artifact while [`Self::artifact_addresses`] preserves every
80    /// artifact identity.
81    pub linked_libraries: Vec<LinkedLibrary>,
82}
83
84/// An auto-linked library and the data used to deploy and classify it.
85#[derive(Clone, Debug)]
86pub struct LinkedLibrary {
87    /// Compilation artifact for the library.
88    pub id: ArtifactId,
89    /// Address assigned by the linker.
90    pub address: Address,
91    /// Fully linked creation bytecode.
92    pub bytecode: Bytes,
93}
94
95impl<'a> Linker<'a> {
96    pub fn new(
97        root: impl Into<PathBuf>,
98        contracts: ArtifactContracts<CompactContractBytecodeCow<'a>>,
99    ) -> Self {
100        Linker { root: root.into(), contracts }
101    }
102
103    /// Helper method to convert [ArtifactId] to the format in which libraries are stored in
104    /// [Libraries] object.
105    ///
106    /// Strips project root path from source file path.
107    fn convert_artifact_id_to_lib_path(&self, id: &ArtifactId) -> (PathBuf, String) {
108        // name is either {LibName} or {LibName}.{version}
109        let name = id.name.split('.').next().unwrap();
110
111        (self.project_relative_path(&id.source), name.to_owned())
112    }
113
114    fn project_relative_path(&self, path: &Path) -> PathBuf {
115        if path.is_relative() {
116            return path.to_path_buf();
117        }
118
119        if let Ok(stripped) = path.strip_prefix(&self.root) {
120            return stripped.to_path_buf();
121        }
122
123        if let Ok(root) = self.root.canonicalize()
124            && let Ok(path) = path.canonicalize()
125            && let Ok(stripped) = path.strip_prefix(root)
126        {
127            return stripped.to_path_buf();
128        }
129
130        path.to_path_buf()
131    }
132
133    fn index<'b>(&self, index: &'b OnceLock<Index>) -> &'b Index {
134        index.get_or_init(|| {
135            let mut artifacts = AlloyHashMap::<_, AlloyHashMap<_, Vec<_>>>::default();
136            for id in self.contracts.keys() {
137                let path = self.project_relative_path(&id.source);
138                let name = id.name.split('.').next().unwrap().to_owned();
139                artifacts.entry(path).or_default().entry(name).or_default().push(id.clone());
140            }
141            artifacts
142        })
143    }
144
145    /// Resolves `path` against the project root and canonicalizes it for comparison only.
146    fn canonical_path(&self, path: &Path) -> Option<PathBuf> {
147        let path = if path.is_relative() { self.root.join(path) } else { path.to_path_buf() };
148        path.canonicalize().ok()
149    }
150
151    fn path_matches(
152        &self,
153        path: &Path,
154        expected: &Path,
155        canonical_expected: Option<&Path>,
156    ) -> bool {
157        let path = self.project_relative_path(path);
158        path == expected
159            || canonical_expected
160                .is_some_and(|expected| self.canonical_path(&path).as_deref() == Some(expected))
161    }
162
163    fn link_bytecode(
164        &self,
165        bytecode: &mut CompactBytecode,
166        target: &ArtifactId,
167        file: &Path,
168        name: &str,
169        address: Address,
170    ) -> Result<(), LinkerError> {
171        self.link_bytecode_inner(bytecode, target, file, name, address, true)
172    }
173
174    fn link_bytecode_inner(
175        &self,
176        bytecode: &mut CompactBytecode,
177        target: &ArtifactId,
178        file: &Path,
179        name: &str,
180        address: Address,
181        canonical: bool,
182    ) -> Result<(), LinkerError> {
183        let file_relative = self.project_relative_path(file);
184        let canonical_file = self.canonical_path(&file_relative);
185        let mut references = Vec::new();
186        for (reference, libraries) in &bytecode.link_references {
187            if !libraries.contains_key(name) {
188                continue;
189            }
190            let reference_path = self.project_relative_path(Path::new(reference));
191            if reference_path == file_relative {
192                references.push(reference.clone());
193                continue;
194            }
195            if !canonical {
196                continue;
197            }
198            if !self.path_matches(Path::new(reference), &file_relative, canonical_file.as_deref()) {
199                continue;
200            }
201            if let Some(id) =
202                self.find_artifact_id_by_library_path(None, reference, name, target)?
203            {
204                let (artifact_file, artifact_name) = self.convert_artifact_id_to_lib_path(id);
205                if artifact_file == file_relative && artifact_name == name {
206                    references.push(reference.clone());
207                }
208            }
209        }
210
211        if references.is_empty() && canonical {
212            bytecode.link(&file.to_string_lossy(), name, address);
213        } else {
214            for reference in references {
215                bytecode.link(&reference, name, address);
216            }
217        }
218        Ok(())
219    }
220
221    /// Finds an [ArtifactId] object in the given [ArtifactContracts] keys which corresponds to the
222    /// library path in the form of "./path/to/Lib.sol:Lib"
223    ///
224    /// Dependency lookups can use the index. Canonical paths retain the fallback scan required
225    /// for symlinked library aliases.
226    fn find_artifact_id_by_library_path<'b>(
227        &'b self,
228        index: Option<&'b OnceLock<Index>>,
229        file: &str,
230        name: &str,
231        target: &ArtifactId,
232    ) -> Result<Option<&'b ArtifactId>, LinkerError> {
233        let library_path = self.project_relative_path(Path::new(file));
234        let candidates = if let Some(index) = index {
235            self.index(index)
236                .get(&library_path)
237                .and_then(|artifacts| artifacts.get(name))
238                .into_iter()
239                .flatten()
240                .filter(|id| id.version == target.version)
241                .collect::<Vec<_>>()
242        } else {
243            self.contracts
244                .keys()
245                .filter(|id| {
246                    if id.version != target.version {
247                        return false;
248                    }
249                    let (artifact_path, artifact_name) = self.convert_artifact_id_to_lib_path(id);
250                    artifact_name == *name && artifact_path == library_path
251                })
252                .collect()
253        };
254        let candidates = if candidates.is_empty() {
255            let canonical_library_path = self.canonical_path(&library_path);
256            self.contracts
257                .keys()
258                .filter(|id| {
259                    if id.version != target.version {
260                        return false;
261                    }
262                    let (artifact_path, artifact_name) = self.convert_artifact_id_to_lib_path(id);
263                    artifact_name == *name
264                        && self.path_matches(
265                            &artifact_path,
266                            &library_path,
267                            canonical_library_path.as_deref(),
268                        )
269                })
270                .collect()
271        } else {
272            candidates
273        };
274        let same_build_and_profile = candidates
275            .iter()
276            .copied()
277            .filter(|id| id.build_id == target.build_id && id.profile == target.profile)
278            .collect::<Vec<_>>();
279        let candidates =
280            if same_build_and_profile.is_empty() { candidates } else { same_build_and_profile };
281
282        if candidates.len() > 1 {
283            return Err(LinkerError::ConflictingLibraryArtifacts {
284                file: library_path.display().to_string(),
285                name: name.to_owned(),
286            });
287        }
288        Ok(candidates.into_iter().next())
289    }
290
291    fn direct_dependencies<'b>(
292        &'b self,
293        index: &'b OnceLock<Index>,
294        target: &ArtifactId,
295        references: &mut BTreeMap<&'b ArtifactId, BTreeSet<PathBuf>>,
296    ) -> Result<BTreeSet<&'b ArtifactId>, LinkerError> {
297        let contract = self.contracts.get(target).ok_or(LinkerError::MissingTargetArtifact)?;
298
299        let mut link_references: BTreeMap<String, BTreeSet<String>> = BTreeMap::new();
300        let mut extend = |bytecode: &CompactBytecode| {
301            for (file, libs) in &bytecode.link_references {
302                link_references.entry(file.clone()).or_default().extend(libs.keys().cloned());
303            }
304        };
305        if let Some(bytecode) = &contract.bytecode {
306            extend(bytecode);
307        }
308        if let Some(deployed_bytecode) = &contract.deployed_bytecode
309            && let Some(bytecode) = &deployed_bytecode.bytecode
310        {
311            extend(bytecode);
312        }
313
314        let mut dependencies = BTreeSet::new();
315        for (file, libs) in link_references {
316            for name in libs {
317                let id = self
318                    .find_artifact_id_by_library_path(Some(index), &file, &name, target)?
319                    .ok_or_else(|| LinkerError::MissingLibraryArtifact {
320                        file: file.clone(),
321                        name,
322                    })?;
323                references.entry(id).or_default().insert(file.clone().into());
324                dependencies.insert(id);
325            }
326        }
327
328        Ok(dependencies)
329    }
330
331    /// Performs DFS on the graph of link references, and populates `deps` with all found libraries.
332    fn collect_dependencies<'b>(
333        &'b self,
334        index: &'b OnceLock<Index>,
335        target: &ArtifactId,
336        deps: &mut BTreeSet<&'b ArtifactId>,
337        references: &mut BTreeMap<&'b ArtifactId, BTreeSet<PathBuf>>,
338    ) -> Result<(), LinkerError> {
339        for id in self.direct_dependencies(index, target, references)? {
340            if deps.insert(id) {
341                self.collect_dependencies(index, id, deps, references)?;
342            }
343        }
344
345        Ok(())
346    }
347
348    fn apply_configured_references(
349        &self,
350        references: &BTreeMap<&ArtifactId, BTreeSet<PathBuf>>,
351        libraries: &mut Libraries,
352    ) -> Result<(), LinkerError> {
353        for (id, references) in references {
354            let (file, name) = self.convert_artifact_id_to_lib_path(id);
355            let mut configured = references
356                .iter()
357                .filter_map(|reference| libraries.libs.get(reference)?.get(&name))
358                .map(|address| Address::from_str(address).map_err(LinkerError::InvalidAddress))
359                .collect::<Result<BTreeSet<_>, _>>()?;
360            let exact = !configured.is_empty();
361            if configured.is_empty()
362                && let Some(canonical_file) = self.canonical_path(&file)
363            {
364                configured = libraries
365                    .libs
366                    .iter()
367                    .filter_map(|(configured_file, libraries)| {
368                        if self.canonical_path(configured_file).as_deref() == Some(&canonical_file)
369                        {
370                            libraries.get(&name)
371                        } else {
372                            None
373                        }
374                    })
375                    .map(|address| Address::from_str(address).map_err(LinkerError::InvalidAddress))
376                    .collect::<Result<BTreeSet<_>, _>>()?;
377            }
378            if configured.len() > 1 {
379                return Err(LinkerError::ConflictingLibraryArtifacts {
380                    file: file.display().to_string(),
381                    name,
382                });
383            }
384            let Some(address) = configured.first().copied() else { continue };
385            let canonical = libraries.libs.entry(file.clone()).or_default();
386            if exact {
387                canonical.insert(name, address.to_checksum(None));
388            } else {
389                canonical.entry(name).or_insert_with(|| address.to_checksum(None));
390            }
391        }
392        Ok(())
393    }
394
395    fn collect_library_keys(
396        &self,
397        needed_libraries: &BTreeSet<&ArtifactId>,
398        libraries: &Libraries,
399    ) -> Result<BTreeSet<(PathBuf, String)>, LinkerError> {
400        let mut library_keys = BTreeSet::new();
401        for id in needed_libraries {
402            let (file, name) = self.convert_artifact_id_to_lib_path(id);
403            let is_configured =
404                libraries.libs.get(&file).is_some_and(|libraries| libraries.contains_key(&name));
405            if !library_keys.insert((file.clone(), name.clone())) && !is_configured {
406                return Err(LinkerError::ConflictingLibraryArtifacts {
407                    file: file.display().to_string(),
408                    name,
409                });
410            }
411        }
412        Ok(library_keys)
413    }
414
415    fn configured_library_address(
416        &self,
417        id: &ArtifactId,
418        libraries: &Libraries,
419    ) -> Result<Option<Address>, LinkerError> {
420        let (file, name) = self.convert_artifact_id_to_lib_path(id);
421        libraries
422            .libs
423            .get(&file)
424            .and_then(|libraries| libraries.get(&name))
425            .map(|address| Address::from_str(address).map_err(LinkerError::InvalidAddress))
426            .transpose()
427    }
428
429    fn libraries_for_artifact<'b>(
430        &'b self,
431        index: &'b OnceLock<Index>,
432        target: &ArtifactId,
433        configured: &Libraries,
434        addresses: &BTreeMap<&'b ArtifactId, Address>,
435    ) -> Result<Libraries, LinkerError> {
436        let mut libraries = configured.clone();
437        let mut dependencies = BTreeSet::new();
438        self.collect_dependencies(index, target, &mut dependencies, &mut BTreeMap::new())?;
439        for id in dependencies {
440            let Some(&address) = addresses.get(id) else { continue };
441            let (file, name) = self.convert_artifact_id_to_lib_path(id);
442            let entry = libraries.libs.entry(file.clone()).or_default().entry(name.clone());
443            if let std::collections::btree_map::Entry::Occupied(entry) = &entry
444                && Address::from_str(entry.get()).map_err(LinkerError::InvalidAddress)? != address
445            {
446                return Err(LinkerError::ConflictingLibraryArtifacts {
447                    file: file.display().to_string(),
448                    name,
449                });
450            }
451            entry.or_insert_with(|| address.to_checksum(None));
452        }
453        Ok(libraries)
454    }
455
456    fn artifact_libraries<'b>(
457        &'b self,
458        index: &'b OnceLock<Index>,
459        artifacts: impl IntoIterator<Item = &'b ArtifactId>,
460        configured: &Libraries,
461        addresses: &BTreeMap<&'b ArtifactId, Address>,
462    ) -> Result<BTreeMap<ArtifactId, Libraries>, LinkerError> {
463        artifacts
464            .into_iter()
465            .map(|id| {
466                Ok((id.clone(), self.libraries_for_artifact(index, id, configured, addresses)?))
467            })
468            .collect()
469    }
470
471    fn output_libraries(
472        &self,
473        mut libraries: Libraries,
474        addresses: &BTreeMap<&ArtifactId, Address>,
475    ) -> Libraries {
476        let mut by_key = BTreeMap::<(PathBuf, String), BTreeSet<Address>>::new();
477        for (&id, &address) in addresses {
478            by_key.entry(self.convert_artifact_id_to_lib_path(id)).or_default().insert(address);
479        }
480        for ((file, name), addresses) in by_key {
481            if let Some(address) = addresses.first().filter(|_| addresses.len() == 1) {
482                libraries
483                    .libs
484                    .entry(file)
485                    .or_default()
486                    .entry(name)
487                    .or_insert_with(|| address.to_checksum(None));
488            }
489        }
490        libraries
491    }
492
493    fn assigned_library_addresses(
494        &self,
495        addresses: &BTreeMap<&ArtifactId, Address>,
496    ) -> Vec<Address> {
497        addresses.values().copied().collect::<BTreeSet<_>>().into_iter().collect()
498    }
499
500    fn artifact_addresses(
501        &self,
502        addresses: &BTreeMap<&ArtifactId, Address>,
503    ) -> BTreeMap<ArtifactId, Address> {
504        addresses.iter().map(|(&id, &address)| (id.clone(), address)).collect()
505    }
506
507    fn artifact_addresses_from_libraries(
508        &self,
509        artifacts: impl IntoIterator<Item = &'a ArtifactId>,
510        libraries: &Libraries,
511    ) -> Result<BTreeMap<ArtifactId, Address>, LinkerError> {
512        artifacts
513            .into_iter()
514            .map(|id| {
515                let address = self
516                    .configured_library_address(id, libraries)?
517                    .ok_or_else(|| LinkerError::LinkingFailed { artifact: id.identifier() })?;
518                Ok((id.clone(), address))
519            })
520            .collect()
521    }
522
523    fn ensure_flattened_libraries(
524        &self,
525        artifact_libraries: &BTreeMap<ArtifactId, Libraries>,
526    ) -> Result<(), LinkerError> {
527        let mut addresses = BTreeMap::<(PathBuf, String), BTreeSet<Address>>::new();
528        for libraries in artifact_libraries.values() {
529            for (file, libraries) in &libraries.libs {
530                for (name, address) in libraries {
531                    addresses
532                        .entry((file.clone(), name.clone()))
533                        .or_default()
534                        .insert(Address::from_str(address).map_err(LinkerError::InvalidAddress)?);
535                }
536            }
537        }
538        if let Some(((file, name), _)) =
539            addresses.into_iter().find(|(_, addresses)| addresses.len() > 1)
540        {
541            return Err(LinkerError::ConflictingLibraryArtifacts {
542                file: file.display().to_string(),
543                name,
544            });
545        }
546        Ok(())
547    }
548
549    fn library_addresses(
550        &self,
551        library_keys: &BTreeSet<(PathBuf, String)>,
552        libraries: &Libraries,
553    ) -> Result<Vec<Address>, LinkerError> {
554        let addresses = library_keys
555            .iter()
556            .map(|(file, name)| {
557                let address =
558                    libraries.libs.get(file).and_then(|libraries| libraries.get(name)).ok_or_else(
559                        || LinkerError::LinkingFailed {
560                            artifact: format!("{}:{name}", file.display()),
561                        },
562                    )?;
563                Address::from_str(address).map_err(LinkerError::InvalidAddress)
564            })
565            .collect::<Result<BTreeSet<_>, _>>()?;
566        Ok(addresses.into_iter().collect())
567    }
568
569    /// Returns the resolved addresses of all libraries required by `target`.
570    pub fn linked_library_addresses(
571        &'a self,
572        target: &'a ArtifactId,
573        libraries: &Libraries,
574    ) -> Result<BTreeSet<Address>, LinkerError> {
575        Resolver::new(self).linked_library_addresses(target, libraries)
576    }
577
578    fn linked_library_addresses_inner<'b>(
579        &'b self,
580        index: &'b OnceLock<Index>,
581        target: &ArtifactId,
582        libraries: &Libraries,
583    ) -> Result<BTreeSet<Address>, LinkerError> {
584        let mut dependencies = BTreeSet::new();
585        let mut references = BTreeMap::new();
586        self.collect_dependencies(index, target, &mut dependencies, &mut references)?;
587        let mut libraries = libraries.clone();
588        self.apply_configured_references(&references, &mut libraries)?;
589
590        dependencies
591            .into_iter()
592            .map(|id| {
593                let (file, name) = self.convert_artifact_id_to_lib_path(id);
594                libraries
595                    .libs
596                    .get(&file)
597                    .and_then(|libs| libs.get(&name))
598                    .ok_or_else(|| LinkerError::LinkingFailed { artifact: id.identifier() })?
599                    .parse()
600                    .map_err(LinkerError::InvalidAddress)
601            })
602            .collect()
603    }
604
605    /// Returns the transitive set of libraries referenced by `target`.
606    pub fn dependencies(
607        &'a self,
608        target: &'a ArtifactId,
609    ) -> Result<BTreeSet<ArtifactId>, LinkerError> {
610        let index = OnceLock::new();
611        self.dependencies_inner(&index, target)
612    }
613
614    fn dependencies_inner<'b>(
615        &'b self,
616        index: &'b OnceLock<Index>,
617        target: &ArtifactId,
618    ) -> Result<BTreeSet<ArtifactId>, LinkerError> {
619        let mut dependencies = BTreeSet::new();
620        self.collect_dependencies(index, target, &mut dependencies, &mut BTreeMap::new())?;
621        Ok(dependencies.into_iter().cloned().collect())
622    }
623
624    fn linked_creation_bytecode(
625        &self,
626        target: &ArtifactId,
627        libraries: &Libraries,
628    ) -> Result<Bytes, LinkerError> {
629        let contract = self.link(target, libraries)?;
630        self.ensure_linked(&contract, target)?;
631        contract.get_bytecode_bytes().map(|code| code.into_owned()).ok_or_else(|| {
632            LinkerError::LinkingFailed { artifact: target.source.to_string_lossy().into_owned() }
633        })
634    }
635
636    /// Links given artifact with either given library addresses or address computed from sender and
637    /// nonce.
638    ///
639    /// Each key in `libraries` should either be a global path or relative to project root. All
640    /// remappings should be resolved.
641    ///
642    /// When calling for `target` being an external library itself, you should check that `target`
643    /// does not appear in `libs_to_deploy` to avoid deploying it twice. It may happen in cases
644    /// when there is a dependency cycle including `target`.
645    pub fn link_with_nonce_or_address(
646        &'a self,
647        libraries: Libraries,
648        sender: Address,
649        nonce: u64,
650        targets: impl IntoIterator<Item = &'a ArtifactId>,
651    ) -> Result<LinkOutput, LinkerError> {
652        let output = self.link_with_nonce_or_address_detailed(libraries, sender, nonce, targets)?;
653        self.ensure_flattened_libraries(&output.artifact_libraries)?;
654        Ok(output.output)
655    }
656
657    /// Links like [`Self::link_with_nonce_or_address`] and includes auto-linked library metadata.
658    pub fn link_with_nonce_or_address_detailed(
659        &'a self,
660        libraries: Libraries,
661        sender: Address,
662        mut nonce: u64,
663        targets: impl IntoIterator<Item = &'a ArtifactId>,
664    ) -> Result<DetailedLinkOutput, LinkerError> {
665        let index = OnceLock::new();
666        // Library paths in `link_references` keys are always stripped, so we have to strip
667        // user-provided paths to be able to match them correctly.
668        let mut libraries = libraries.with_stripped_file_prefixes(self.root.as_path());
669
670        let targets = targets.into_iter().collect::<Vec<_>>();
671        let mut needed_libraries = BTreeSet::new();
672        let mut references = BTreeMap::new();
673        for &target in &targets {
674            self.collect_dependencies(&index, target, &mut needed_libraries, &mut references)?;
675        }
676        self.apply_configured_references(&references, &mut libraries)?;
677
678        let mut addresses = BTreeMap::new();
679        let mut libs_to_deploy = Vec::new();
680        for &id in &needed_libraries {
681            let address = if let Some(address) = self.configured_library_address(id, &libraries)? {
682                address
683            } else {
684                let address = sender.create(nonce);
685                nonce += 1;
686                libs_to_deploy.push((id, address));
687                address
688            };
689            addresses.insert(id, address);
690        }
691
692        let artifact_libraries = self.artifact_libraries(
693            &index,
694            targets.iter().copied().chain(needed_libraries.iter().copied()),
695            &libraries,
696            &addresses,
697        )?;
698
699        // Link and collect bytecodes for `libs_to_deploy`.
700        let linked_libraries = libs_to_deploy
701            .into_par_iter()
702            .map(|(id, address)| {
703                let artifact_libraries = artifact_libraries.get(id).unwrap();
704                let bytecode =
705                    self.link(id, artifact_libraries)?.get_bytecode_bytes().unwrap().into_owned();
706                Ok(LinkedLibrary { id: id.clone(), address, bytecode })
707            })
708            .collect::<Result<Vec<_>, LinkerError>>()?;
709        let libs_to_deploy = linked_libraries.iter().map(|lib| lib.bytecode.clone()).collect();
710
711        let library_addresses = self.assigned_library_addresses(&addresses);
712        let artifact_addresses = self.artifact_addresses(&addresses);
713        let libraries = self.output_libraries(libraries, &addresses);
714        Ok(DetailedLinkOutput {
715            output: LinkOutput { libraries, library_addresses, libs_to_deploy },
716            artifact_libraries,
717            artifact_addresses,
718            linked_libraries,
719        })
720    }
721
722    pub fn link_with_create2(
723        &'a self,
724        libraries: Libraries,
725        sender: Address,
726        salt: B256,
727        targets: impl IntoIterator<Item = &'a ArtifactId>,
728    ) -> Result<LinkOutput, LinkerError> {
729        let output = self.link_with_create2_detailed(libraries, sender, salt, targets)?;
730        self.ensure_flattened_libraries(&output.artifact_libraries)?;
731        Ok(output.output)
732    }
733
734    /// Links like [`Self::link_with_create2`] and includes auto-linked library metadata.
735    pub fn link_with_create2_detailed(
736        &'a self,
737        libraries: Libraries,
738        sender: Address,
739        salt: B256,
740        targets: impl IntoIterator<Item = &'a ArtifactId>,
741    ) -> Result<DetailedLinkOutput, LinkerError> {
742        let index = OnceLock::new();
743        // Library paths in `link_references` keys are always stripped, so we have to strip
744        // user-provided paths to be able to match them correctly.
745        let mut libraries = libraries.with_stripped_file_prefixes(self.root.as_path());
746
747        let targets = targets.into_iter().collect::<Vec<_>>();
748        let mut needed_libraries = BTreeSet::new();
749        let mut references = BTreeMap::new();
750        for &target in &targets {
751            self.collect_dependencies(&index, target, &mut needed_libraries, &mut references)?;
752        }
753        self.apply_configured_references(&references, &mut libraries)?;
754
755        let mut addresses = BTreeMap::new();
756        let mut pending = Vec::new();
757        for &id in &needed_libraries {
758            if let Some(address) = self.configured_library_address(id, &libraries)? {
759                addresses.insert(id, address);
760            } else {
761                pending.push(id);
762            }
763        }
764        let mut linked_libraries = Vec::<LinkedLibrary>::new();
765
766        // Iteratively compute addresses and link libraries until we have no unlinked libraries
767        // left.
768        while !pending.is_empty() {
769            let mut deployable = None;
770            for (position, &id) in pending.iter().enumerate() {
771                let artifact_libraries =
772                    self.libraries_for_artifact(&index, id, &libraries, &addresses)?;
773                let bytecode = self.link(id, &artifact_libraries)?.bytecode.ok_or_else(|| {
774                    LinkerError::LinkingFailed { artifact: id.source.to_string_lossy().into() }
775                })?;
776                if !bytecode.object.is_unlinked() {
777                    deployable = Some((position, id, bytecode));
778                    break;
779                }
780            }
781            let Some((position, id, bytecode)) = deployable else {
782                return Err(LinkerError::CyclicDependency);
783            };
784            pending.swap_remove(position);
785            let code = bytecode.bytes().ok_or_else(|| LinkerError::LinkingFailed {
786                artifact: id.source.to_string_lossy().into(),
787            })?;
788            let address = sender.create2_from_code(salt, code);
789            if linked_libraries.iter().all(|library| library.address != address) {
790                linked_libraries.push(LinkedLibrary {
791                    id: id.clone(),
792                    address,
793                    bytecode: code.clone(),
794                });
795            }
796            addresses.insert(id, address);
797        }
798
799        let artifact_libraries = self.artifact_libraries(
800            &index,
801            targets.iter().copied().chain(needed_libraries.iter().copied()),
802            &libraries,
803            &addresses,
804        )?;
805        let libs_to_deploy = linked_libraries.iter().map(|lib| lib.bytecode.clone()).collect();
806        let library_addresses = self.assigned_library_addresses(&addresses);
807        let artifact_addresses = self.artifact_addresses(&addresses);
808        let libraries = self.output_libraries(libraries, &addresses);
809        Ok(DetailedLinkOutput {
810            output: LinkOutput { libraries, library_addresses, libs_to_deploy },
811            artifact_libraries,
812            artifact_addresses,
813            linked_libraries,
814        })
815    }
816
817    /// Relinks a target while assigning libraries not needed onchain to an isolated deployer.
818    pub fn link_with_partition(
819        &'a self,
820        libraries: Libraries,
821        sender: Address,
822        mut sender_nonce: u64,
823        local_deployer: Address,
824        required: &BTreeSet<ArtifactId>,
825        target: &'a ArtifactId,
826    ) -> Result<(DetailedLinkOutput, Vec<LinkedLibrary>), LinkerError> {
827        let index = OnceLock::new();
828        let mut libraries = libraries.with_stripped_file_prefixes(self.root.as_path());
829        let mut needed = BTreeSet::new();
830        let mut references = BTreeMap::new();
831        self.collect_dependencies(&index, target, &mut needed, &mut references)?;
832        self.apply_configured_references(&references, &mut libraries)?;
833        let library_keys = self.collect_library_keys(&needed, &libraries)?;
834        let mut required_with_dependencies = required.clone();
835        for id in required {
836            required_with_dependencies.extend(self.dependencies_inner(&index, id)?);
837        }
838        let mut local_nonce = 0;
839        let mut assigned = Vec::new();
840        for &id in &needed {
841            let (file, name) = self.convert_artifact_id_to_lib_path(id);
842            libraries.libs.entry(file).or_default().entry(name).or_insert_with(|| {
843                let onchain = required_with_dependencies.contains(id);
844                let address = if onchain {
845                    let address = sender.create(sender_nonce);
846                    sender_nonce += 1;
847                    address
848                } else {
849                    let address = local_deployer.create(local_nonce);
850                    local_nonce += 1;
851                    address
852                };
853                assigned.push((id, address, onchain));
854                address.to_checksum(None)
855            });
856        }
857        let linked = assigned
858            .into_iter()
859            .map(|(id, address, onchain)| {
860                let bytecode = self.linked_creation_bytecode(id, &libraries)?;
861                Ok((LinkedLibrary { id: id.clone(), address, bytecode }, onchain))
862            })
863            .collect::<Result<Vec<_>, LinkerError>>()?;
864        let libs_to_deploy = linked
865            .iter()
866            .filter(|(_, onchain)| *onchain)
867            .map(|(lib, _)| lib.bytecode.clone())
868            .collect();
869        let local =
870            linked.iter().filter(|(_, onchain)| !*onchain).map(|(lib, _)| lib.clone()).collect();
871        let linked_libraries = linked.into_iter().map(|(lib, _)| lib).collect();
872        let library_addresses = self.library_addresses(&library_keys, &libraries)?;
873        let artifact_addresses =
874            self.artifact_addresses_from_libraries(needed.iter().copied(), &libraries)?;
875        let artifact_libraries = BTreeMap::from([(target.clone(), libraries.clone())]);
876        Ok((
877            DetailedLinkOutput {
878                output: LinkOutput { libraries, library_addresses, libs_to_deploy },
879                artifact_libraries,
880                artifact_addresses,
881                linked_libraries,
882            },
883            local,
884        ))
885    }
886
887    /// Relinks a target with CREATE2 onchain assignments and isolated local assignments.
888    pub fn link_with_create2_partition(
889        &'a self,
890        libraries: Libraries,
891        create2_deployer: Address,
892        salt: B256,
893        local_deployer: Address,
894        required: &BTreeSet<ArtifactId>,
895        target: &'a ArtifactId,
896    ) -> Result<(DetailedLinkOutput, Vec<LinkedLibrary>), LinkerError> {
897        let index = OnceLock::new();
898        let mut libraries = libraries.with_stripped_file_prefixes(self.root.as_path());
899        let mut needed = BTreeSet::new();
900        let mut references = BTreeMap::new();
901        self.collect_dependencies(&index, target, &mut needed, &mut references)?;
902        self.apply_configured_references(&references, &mut libraries)?;
903        let library_keys = self.collect_library_keys(&needed, &libraries)?;
904        let mut required_with_dependencies = required.clone();
905        for id in required {
906            required_with_dependencies.extend(self.dependencies_inner(&index, id)?);
907        }
908        let mut local_ids = Vec::new();
909        let mut required_ids = Vec::new();
910        for &id in &needed {
911            let (file, name) = self.convert_artifact_id_to_lib_path(id);
912            if libraries.libs.get(&file).is_some_and(|libs| libs.contains_key(&name)) {
913                continue;
914            }
915            if required_with_dependencies.contains(id) {
916                required_ids.push(id);
917            } else {
918                let address = local_deployer.create(local_ids.len() as u64);
919                libraries.libs.entry(file).or_default().insert(name, address.to_checksum(None));
920                local_ids.push((id, address));
921            }
922        }
923
924        let mut pending = required_ids
925            .into_iter()
926            .map(|id| {
927                let contract = self.link(id, &libraries)?;
928                let bytecode = contract.bytecode.ok_or_else(|| LinkerError::LinkingFailed {
929                    artifact: id.source.to_string_lossy().into_owned(),
930                })?;
931                Ok((id, bytecode))
932            })
933            .collect::<Result<Vec<_>, LinkerError>>()?;
934        let mut onchain = Vec::new();
935        while !pending.is_empty() {
936            let Some(position) = pending.iter().position(|(_, code)| !code.object.is_unlinked())
937            else {
938                return Err(LinkerError::CyclicDependency);
939            };
940            let (id, code) = pending.swap_remove(position);
941            let bytecode = code.bytes().cloned().ok_or_else(|| LinkerError::LinkingFailed {
942                artifact: id.source.to_string_lossy().into_owned(),
943            })?;
944            let address = create2_deployer.create2_from_code(salt, &bytecode);
945            let (file, name) = self.convert_artifact_id_to_lib_path(id);
946            libraries
947                .libs
948                .entry(file.clone())
949                .or_default()
950                .insert(name.clone(), address.to_checksum(None));
951            for (target, pending_code) in &mut pending {
952                self.link_bytecode(pending_code.to_mut(), target, &file, &name, address)?;
953            }
954            onchain.push(LinkedLibrary { id: id.clone(), address, bytecode });
955        }
956
957        let local_bytecodes = local_ids
958            .iter()
959            .map(|(id, _)| self.linked_creation_bytecode(id, &libraries))
960            .collect::<Result<Vec<_>, LinkerError>>()?;
961        let mut linked_libraries = onchain.clone();
962        let local = local_ids
963            .into_iter()
964            .zip(local_bytecodes)
965            .map(|((id, address), bytecode)| LinkedLibrary { id: id.clone(), address, bytecode })
966            .collect::<Vec<_>>();
967        linked_libraries.extend(local.iter().cloned());
968        let libs_to_deploy = onchain.into_iter().map(|lib| lib.bytecode).collect();
969        let library_addresses = self.library_addresses(&library_keys, &libraries)?;
970        let artifact_addresses =
971            self.artifact_addresses_from_libraries(needed.iter().copied(), &libraries)?;
972        let artifact_libraries = BTreeMap::from([(target.clone(), libraries.clone())]);
973        Ok((
974            DetailedLinkOutput {
975                output: LinkOutput { libraries, library_addresses, libs_to_deploy },
976                artifact_libraries,
977                artifact_addresses,
978                linked_libraries,
979            },
980            local,
981        ))
982    }
983
984    /// Links given artifact with given libraries.
985    pub fn link(
986        &self,
987        target: &ArtifactId,
988        libraries: &Libraries,
989    ) -> Result<CompactContractBytecodeCow<'a>, LinkerError> {
990        let mut contract =
991            self.contracts.get(target).ok_or(LinkerError::MissingTargetArtifact)?.clone();
992        let libraries = libraries
993            .libs
994            .iter()
995            .flat_map(|(file, libraries)| {
996                libraries.iter().map(move |(name, address)| {
997                    Ok((
998                        file,
999                        name,
1000                        Address::from_str(address).map_err(LinkerError::InvalidAddress)?,
1001                    ))
1002                })
1003            })
1004            .collect::<Result<Vec<_>, LinkerError>>()?;
1005        let link = |bytecode: &mut CompactBytecode| -> Result<(), LinkerError> {
1006            for canonical in [false, true] {
1007                for &(file, name, address) in &libraries {
1008                    self.link_bytecode_inner(bytecode, target, file, name, address, canonical)?;
1009                }
1010            }
1011            Ok(())
1012        };
1013        if let Some(bytecode) = contract.bytecode.as_mut() {
1014            link(bytecode.to_mut())?;
1015        }
1016        if let Some(deployed_bytecode) =
1017            contract.deployed_bytecode.as_mut().and_then(|b| b.to_mut().bytecode.as_mut())
1018        {
1019            link(deployed_bytecode)?;
1020        }
1021        Ok(contract)
1022    }
1023
1024    /// Ensures that both initial and deployed bytecode are linked.
1025    pub fn ensure_linked(
1026        &self,
1027        contract: &CompactContractBytecodeCow<'a>,
1028        target: &ArtifactId,
1029    ) -> Result<(), LinkerError> {
1030        if let Some(bytecode) = &contract.bytecode
1031            && bytecode.object.is_unlinked()
1032        {
1033            return Err(LinkerError::LinkingFailed {
1034                artifact: target.source.to_string_lossy().into(),
1035            });
1036        }
1037        if let Some(deployed_bytecode) = &contract.deployed_bytecode
1038            && let Some(deployed_bytecode_obj) = &deployed_bytecode.bytecode
1039            && deployed_bytecode_obj.object.is_unlinked()
1040        {
1041            return Err(LinkerError::LinkingFailed {
1042                artifact: target.source.to_string_lossy().into(),
1043            });
1044        }
1045        Ok(())
1046    }
1047
1048    pub fn get_linked_artifacts(
1049        &self,
1050        libraries: &Libraries,
1051    ) -> Result<ArtifactContracts, LinkerError> {
1052        self.get_linked_artifacts_cow(libraries).map(ArtifactContracts::from_iter)
1053    }
1054
1055    pub fn get_linked_artifacts_cow(
1056        &self,
1057        libraries: &Libraries,
1058    ) -> Result<ArtifactContracts<CompactContractBytecodeCow<'a>>, LinkerError> {
1059        self.get_linked_artifacts_cow_with_artifact_libraries(libraries, &BTreeMap::new())
1060    }
1061
1062    pub fn get_linked_artifacts_cow_with_artifact_libraries(
1063        &self,
1064        libraries: &Libraries,
1065        artifact_libraries: &BTreeMap<ArtifactId, Libraries>,
1066    ) -> Result<ArtifactContracts<CompactContractBytecodeCow<'a>>, LinkerError> {
1067        self.contracts
1068            .par_iter()
1069            .map(|(id, _)| {
1070                let libraries = artifact_libraries.get(id).unwrap_or(libraries);
1071                Ok((id.clone(), self.link(id, libraries)?))
1072            })
1073            .collect::<Result<_, _>>()
1074            .map(ArtifactContracts)
1075    }
1076}
1077
1078impl<'a, 'b> Resolver<'a, 'b> {
1079    /// Creates a resolver that reuses artifact lookups across calls.
1080    pub const fn new(linker: &'b Linker<'a>) -> Self {
1081        Self { linker, index: OnceLock::new() }
1082    }
1083
1084    /// Returns the resolved addresses of all libraries required by `target`.
1085    pub fn linked_library_addresses(
1086        &self,
1087        target: &ArtifactId,
1088        libraries: &Libraries,
1089    ) -> Result<BTreeSet<Address>, LinkerError> {
1090        self.linker.linked_library_addresses_inner(&self.index, target, libraries)
1091    }
1092}
1093
1094#[cfg(test)]
1095mod tests {
1096    use super::*;
1097    use alloy_primitives::{address, fixed_bytes, map::HashMap};
1098    use foundry_compilers::{
1099        Project, ProjectCompileOutput, ProjectPathsConfig,
1100        artifacts::BytecodeObject,
1101        multi::MultiCompiler,
1102        solc::{Solc, SolcCompiler},
1103    };
1104    use semver::Version;
1105
1106    fn testdata() -> &'static Path {
1107        static CACHE: OnceLock<PathBuf> = OnceLock::new();
1108        CACHE.get_or_init(|| {
1109            PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../testdata").canonicalize().unwrap()
1110        })
1111    }
1112
1113    #[must_use]
1114    struct LinkerTest {
1115        project: Project,
1116        output: ProjectCompileOutput,
1117        dependency_assertions: HashMap<&'static str, Vec<(&'static str, Address)>>,
1118    }
1119
1120    impl LinkerTest {
1121        fn new(path: &Path, strip_prefixes: bool) -> Self {
1122            assert!(path.exists(), "Path {path:?} does not exist");
1123            let paths = ProjectPathsConfig::builder()
1124                .root(testdata())
1125                .lib(testdata().join("lib"))
1126                .sources(path)
1127                .tests(path)
1128                .build()
1129                .unwrap();
1130
1131            let solc = Solc::find_or_install(&Version::new(0, 8, 18)).unwrap();
1132            let project = Project::builder()
1133                .paths(paths)
1134                .ephemeral()
1135                .no_artifacts()
1136                .build(MultiCompiler { solc: Some(SolcCompiler::Specific(solc)), vyper: None })
1137                .unwrap();
1138
1139            let mut output = project.compile().unwrap();
1140
1141            if strip_prefixes {
1142                output = output.with_stripped_file_prefixes(project.root());
1143            }
1144
1145            Self { project, output, dependency_assertions: HashMap::default() }
1146        }
1147
1148        fn assert_dependencies(
1149            mut self,
1150            artifact_id: &'static str,
1151            deps: &[(&'static str, Address)],
1152        ) -> Self {
1153            self.dependency_assertions.insert(artifact_id, deps.to_vec());
1154            self
1155        }
1156
1157        fn test_with_sender_and_nonce(self, sender: Address, initial_nonce: u64) {
1158            let linker = Linker::new(self.project.root(), self.output.artifact_ids().collect());
1159            for (id, identifier) in self.iter_linking_targets(&linker) {
1160                let output = linker
1161                    .link_with_nonce_or_address(Default::default(), sender, initial_nonce, [id])
1162                    .expect("Linking failed");
1163                self.validate_assertions(identifier, output);
1164            }
1165        }
1166
1167        fn test_with_create2(self, sender: Address, salt: B256) {
1168            let linker = Linker::new(self.project.root(), self.output.artifact_ids().collect());
1169            for (id, identifier) in self.iter_linking_targets(&linker) {
1170                let output = linker
1171                    .link_with_create2(Default::default(), sender, salt, [id])
1172                    .expect("Linking failed");
1173                self.validate_assertions(identifier, output);
1174            }
1175        }
1176
1177        fn iter_linking_targets<'a>(
1178            &'a self,
1179            linker: &'a Linker<'_>,
1180        ) -> impl Iterator<Item = (&'a ArtifactId, String)> + 'a {
1181            self.sanity_check(linker);
1182            linker.contracts.keys().filter_map(move |id| {
1183                // If we didn't strip paths, artifacts will have absolute paths.
1184                // That's expected and we want to ensure that only `libraries` object has relative
1185                // paths, artifacts should be kept as is.
1186                let source = id
1187                    .source
1188                    .strip_prefix(self.project.root())
1189                    .unwrap_or(&id.source)
1190                    .to_string_lossy();
1191                let identifier = format!("{source}:{}", id.name);
1192
1193                // Skip test utils as they always have no dependencies.
1194                if identifier.contains("utils/") {
1195                    return None;
1196                }
1197
1198                Some((id, identifier))
1199            })
1200        }
1201
1202        fn sanity_check(&self, linker: &Linker<'_>) {
1203            assert!(!self.dependency_assertions.is_empty(), "Dependency assertions are empty");
1204            assert!(!linker.contracts.is_empty(), "Linker contracts are empty");
1205        }
1206
1207        fn validate_assertions(&self, identifier: String, output: LinkOutput) {
1208            let LinkOutput { libs_to_deploy, libraries, .. } = output;
1209
1210            let assertions = self
1211                .dependency_assertions
1212                .get(identifier.as_str())
1213                .unwrap_or_else(|| panic!("Unexpected artifact: {identifier}"));
1214
1215            assert_eq!(
1216                libs_to_deploy.len(),
1217                assertions.len(),
1218                "artifact {identifier} has more/less dependencies than expected ({} vs {}): {:#?}",
1219                libs_to_deploy.len(),
1220                assertions.len(),
1221                libs_to_deploy
1222            );
1223
1224            for &(dep_identifier, address) in assertions {
1225                let (file, name) = dep_identifier.split_once(':').unwrap();
1226                if let Some(lib_address) =
1227                    libraries.libs.get(Path::new(file)).and_then(|libs| libs.get(name))
1228                {
1229                    assert_eq!(
1230                        lib_address.parse::<Address>().unwrap(),
1231                        address,
1232                        "incorrect library address for dependency {dep_identifier} of {identifier}"
1233                    );
1234                } else {
1235                    panic!("Library {dep_identifier} not found");
1236                }
1237            }
1238        }
1239    }
1240
1241    fn link_test(path: impl AsRef<Path>, mut test_fn: impl FnMut(LinkerTest)) {
1242        fn link_test(path: &Path, test_fn: &mut dyn FnMut(LinkerTest)) {
1243            test_fn(LinkerTest::new(path, true));
1244            test_fn(LinkerTest::new(path, false));
1245        }
1246        link_test(path.as_ref(), &mut test_fn);
1247    }
1248
1249    #[test]
1250    #[should_panic = "assertions are empty"]
1251    fn no_assertions() {
1252        link_test(testdata().join("default/linking/simple"), |linker| {
1253            linker.test_with_sender_and_nonce(Address::default(), 1);
1254        });
1255    }
1256
1257    #[test]
1258    #[should_panic = "does not exist"]
1259    fn unknown_path() {
1260        link_test("doesnotexist", |linker| {
1261            linker
1262                .assert_dependencies("a:b", &[])
1263                .test_with_sender_and_nonce(Address::default(), 1);
1264        });
1265    }
1266
1267    #[test]
1268    fn link_simple() {
1269        link_test(testdata().join("default/linking/simple"), |linker| {
1270            linker
1271                .assert_dependencies("default/linking/simple/Simple.t.sol:Lib", &[])
1272                .assert_dependencies(
1273                    "default/linking/simple/Simple.t.sol:LibraryConsumer",
1274                    &[(
1275                        "default/linking/simple/Simple.t.sol:Lib",
1276                        address!("0x5a443704dd4b594b382c22a083e2bd3090a6fef3"),
1277                    )],
1278                )
1279                .assert_dependencies(
1280                    "default/linking/simple/Simple.t.sol:SimpleLibraryLinkingTest",
1281                    &[(
1282                        "default/linking/simple/Simple.t.sol:Lib",
1283                        address!("0x5a443704dd4b594b382c22a083e2bd3090a6fef3"),
1284                    )],
1285                )
1286                .test_with_sender_and_nonce(Address::default(), 1);
1287        });
1288    }
1289
1290    #[test]
1291    fn link_nested() {
1292        link_test(testdata().join("default/linking/nested"), |linker| {
1293            linker
1294                .assert_dependencies("default/linking/nested/Nested.t.sol:Lib", &[])
1295                .assert_dependencies(
1296                    "default/linking/nested/Nested.t.sol:NestedLib",
1297                    &[(
1298                        "default/linking/nested/Nested.t.sol:Lib",
1299                        address!("0x5a443704dd4b594b382c22a083e2bd3090a6fef3"),
1300                    )],
1301                )
1302                .assert_dependencies(
1303                    "default/linking/nested/Nested.t.sol:LibraryConsumer",
1304                    &[
1305                        // Lib shows up here twice, because the linker sees it twice, but it should
1306                        // have the same address and nonce.
1307                        (
1308                            "default/linking/nested/Nested.t.sol:Lib",
1309                            Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3")
1310                                .unwrap(),
1311                        ),
1312                        (
1313                            "default/linking/nested/Nested.t.sol:NestedLib",
1314                            Address::from_str("0x47e9Fbef8C83A1714F1951F142132E6e90F5fa5D")
1315                                .unwrap(),
1316                        ),
1317                    ],
1318                )
1319                .assert_dependencies(
1320                    "default/linking/nested/Nested.t.sol:NestedLibraryLinkingTest",
1321                    &[
1322                        (
1323                            "default/linking/nested/Nested.t.sol:Lib",
1324                            Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3")
1325                                .unwrap(),
1326                        ),
1327                        (
1328                            "default/linking/nested/Nested.t.sol:NestedLib",
1329                            Address::from_str("0x47e9fbef8c83a1714f1951f142132e6e90f5fa5d")
1330                                .unwrap(),
1331                        ),
1332                    ],
1333                )
1334                .test_with_sender_and_nonce(Address::default(), 1);
1335        });
1336    }
1337
1338    #[test]
1339    fn link_duplicate() {
1340        link_test(testdata().join("default/linking/duplicate"), |linker| {
1341            linker
1342                .assert_dependencies("default/linking/duplicate/Duplicate.t.sol:A", &[])
1343                .assert_dependencies("default/linking/duplicate/Duplicate.t.sol:B", &[])
1344                .assert_dependencies(
1345                    "default/linking/duplicate/Duplicate.t.sol:C",
1346                    &[(
1347                        "default/linking/duplicate/Duplicate.t.sol:A",
1348                        address!("0x5a443704dd4b594b382c22a083e2bd3090a6fef3"),
1349                    )],
1350                )
1351                .assert_dependencies(
1352                    "default/linking/duplicate/Duplicate.t.sol:D",
1353                    &[(
1354                        "default/linking/duplicate/Duplicate.t.sol:B",
1355                        address!("0x5a443704dd4b594b382c22a083e2bd3090a6fef3"),
1356                    )],
1357                )
1358                .assert_dependencies(
1359                    "default/linking/duplicate/Duplicate.t.sol:E",
1360                    &[
1361                        (
1362                            "default/linking/duplicate/Duplicate.t.sol:A",
1363                            Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3")
1364                                .unwrap(),
1365                        ),
1366                        (
1367                            "default/linking/duplicate/Duplicate.t.sol:C",
1368                            Address::from_str("0x47e9fbef8c83a1714f1951f142132e6e90f5fa5d")
1369                                .unwrap(),
1370                        ),
1371                    ],
1372                )
1373                .assert_dependencies(
1374                    "default/linking/duplicate/Duplicate.t.sol:LibraryConsumer",
1375                    &[
1376                        (
1377                            "default/linking/duplicate/Duplicate.t.sol:A",
1378                            Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3")
1379                                .unwrap(),
1380                        ),
1381                        (
1382                            "default/linking/duplicate/Duplicate.t.sol:B",
1383                            Address::from_str("0x47e9fbef8c83a1714f1951f142132e6e90f5fa5d")
1384                                .unwrap(),
1385                        ),
1386                        (
1387                            "default/linking/duplicate/Duplicate.t.sol:C",
1388                            Address::from_str("0x8be503bcded90ed42eff31f56199399b2b0154ca")
1389                                .unwrap(),
1390                        ),
1391                        (
1392                            "default/linking/duplicate/Duplicate.t.sol:D",
1393                            Address::from_str("0x47c5e40890bce4a473a49d7501808b9633f29782")
1394                                .unwrap(),
1395                        ),
1396                        (
1397                            "default/linking/duplicate/Duplicate.t.sol:E",
1398                            Address::from_str("0x29b2440db4a256b0c1e6d3b4cdcaa68e2440a08f")
1399                                .unwrap(),
1400                        ),
1401                    ],
1402                )
1403                .assert_dependencies(
1404                    "default/linking/duplicate/Duplicate.t.sol:DuplicateLibraryLinkingTest",
1405                    &[
1406                        (
1407                            "default/linking/duplicate/Duplicate.t.sol:A",
1408                            Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3")
1409                                .unwrap(),
1410                        ),
1411                        (
1412                            "default/linking/duplicate/Duplicate.t.sol:B",
1413                            Address::from_str("0x47e9fbef8c83a1714f1951f142132e6e90f5fa5d")
1414                                .unwrap(),
1415                        ),
1416                        (
1417                            "default/linking/duplicate/Duplicate.t.sol:C",
1418                            Address::from_str("0x8be503bcded90ed42eff31f56199399b2b0154ca")
1419                                .unwrap(),
1420                        ),
1421                        (
1422                            "default/linking/duplicate/Duplicate.t.sol:D",
1423                            Address::from_str("0x47c5e40890bce4a473a49d7501808b9633f29782")
1424                                .unwrap(),
1425                        ),
1426                        (
1427                            "default/linking/duplicate/Duplicate.t.sol:E",
1428                            Address::from_str("0x29b2440db4a256b0c1e6d3b4cdcaa68e2440a08f")
1429                                .unwrap(),
1430                        ),
1431                    ],
1432                )
1433                .test_with_sender_and_nonce(Address::default(), 1);
1434        });
1435    }
1436
1437    #[test]
1438    fn link_cycle() {
1439        link_test(testdata().join("default/linking/cycle"), |linker| {
1440            linker
1441                .assert_dependencies(
1442                    "default/linking/cycle/Cycle.t.sol:Foo",
1443                    &[
1444                        (
1445                            "default/linking/cycle/Cycle.t.sol:Foo",
1446                            Address::from_str("0x47e9Fbef8C83A1714F1951F142132E6e90F5fa5D")
1447                                .unwrap(),
1448                        ),
1449                        (
1450                            "default/linking/cycle/Cycle.t.sol:Bar",
1451                            Address::from_str("0x5a443704dd4B594B382c22a083e2BD3090A6feF3")
1452                                .unwrap(),
1453                        ),
1454                    ],
1455                )
1456                .assert_dependencies(
1457                    "default/linking/cycle/Cycle.t.sol:Bar",
1458                    &[
1459                        (
1460                            "default/linking/cycle/Cycle.t.sol:Foo",
1461                            Address::from_str("0x47e9Fbef8C83A1714F1951F142132E6e90F5fa5D")
1462                                .unwrap(),
1463                        ),
1464                        (
1465                            "default/linking/cycle/Cycle.t.sol:Bar",
1466                            Address::from_str("0x5a443704dd4B594B382c22a083e2BD3090A6feF3")
1467                                .unwrap(),
1468                        ),
1469                    ],
1470                )
1471                .test_with_sender_and_nonce(Address::default(), 1);
1472        });
1473    }
1474
1475    #[test]
1476    #[ignore = "addresses depend on testdata utils internals for some reason"]
1477    fn link_create2_nested() {
1478        link_test(testdata().join("default/linking/nested"), |linker| {
1479            linker
1480                .assert_dependencies("default/linking/nested/Nested.t.sol:Lib", &[])
1481                .assert_dependencies(
1482                    "default/linking/nested/Nested.t.sol:NestedLib",
1483                    &[(
1484                        "default/linking/nested/Nested.t.sol:Lib",
1485                        address!("0x773253227cce756e50c3993ec6366b3ec27786f9"),
1486                    )],
1487                )
1488                .assert_dependencies(
1489                    "default/linking/nested/Nested.t.sol:LibraryConsumer",
1490                    &[
1491                        // Lib shows up here twice, because the linker sees it twice, but it should
1492                        // have the same address and nonce.
1493                        (
1494                            "default/linking/nested/Nested.t.sol:Lib",
1495                            Address::from_str("0x773253227cce756e50c3993ec6366b3ec27786f9")
1496                                .unwrap(),
1497                        ),
1498                        (
1499                            "default/linking/nested/Nested.t.sol:NestedLib",
1500                            Address::from_str("0xac231df03403867b05d092c26fc91b6b83f4bebe")
1501                                .unwrap(),
1502                        ),
1503                    ],
1504                )
1505                .assert_dependencies(
1506                    "default/linking/nested/Nested.t.sol:NestedLibraryLinkingTest",
1507                    &[
1508                        (
1509                            "default/linking/nested/Nested.t.sol:Lib",
1510                            Address::from_str("0x773253227cce756e50c3993ec6366b3ec27786f9")
1511                                .unwrap(),
1512                        ),
1513                        (
1514                            "default/linking/nested/Nested.t.sol:NestedLib",
1515                            Address::from_str("0xac231df03403867b05d092c26fc91b6b83f4bebe")
1516                                .unwrap(),
1517                        ),
1518                    ],
1519                )
1520                .test_with_create2(
1521                    Address::default(),
1522                    fixed_bytes!(
1523                        "19bf59b7b67ae8edcbc6e53616080f61fa99285c061450ad601b0bc40c9adfc9"
1524                    ),
1525                );
1526        });
1527    }
1528
1529    #[test]
1530    fn link_samefile_union() {
1531        link_test(testdata().join("default/linking/samefile_union"), |linker| {
1532            linker
1533                .assert_dependencies("default/linking/samefile_union/Libs.sol:LInit", &[])
1534                .assert_dependencies("default/linking/samefile_union/Libs.sol:LRun", &[])
1535                .assert_dependencies(
1536                    "default/linking/samefile_union/SameFileUnion.t.sol:UsesBoth",
1537                    &[
1538                        (
1539                            "default/linking/samefile_union/Libs.sol:LInit",
1540                            Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3")
1541                                .unwrap(),
1542                        ),
1543                        (
1544                            "default/linking/samefile_union/Libs.sol:LRun",
1545                            Address::from_str("0x47e9fbef8c83a1714f1951f142132e6e90f5fa5d")
1546                                .unwrap(),
1547                        ),
1548                    ],
1549                )
1550                .test_with_sender_and_nonce(Address::default(), 1);
1551        });
1552    }
1553
1554    #[test]
1555    fn link_create2_multiple_targets_deduplicates_shared_dependencies() {
1556        let test = LinkerTest::new(&testdata().join("default/linking/simple"), true);
1557        let linker = Linker::new(test.project.root(), test.output.artifact_ids().collect());
1558        let consumer = linker.contracts.keys().find(|id| id.name == "LibraryConsumer").unwrap();
1559        let test_contract =
1560            linker.contracts.keys().find(|id| id.name == "SimpleLibraryLinkingTest").unwrap();
1561
1562        let output = linker
1563            .link_with_create2(
1564                Libraries::default(),
1565                Address::ZERO,
1566                B256::with_last_byte(1),
1567                [consumer, test_contract],
1568            )
1569            .unwrap();
1570
1571        assert_eq!(output.libs_to_deploy.len(), 1);
1572        assert_eq!(output.libraries.libs.values().map(BTreeMap::len).sum::<usize>(), 1);
1573
1574        let linked_address = output
1575            .libraries
1576            .libs
1577            .values()
1578            .flat_map(|libraries| libraries.values())
1579            .next()
1580            .unwrap()
1581            .parse::<Address>()
1582            .unwrap();
1583        for target in [consumer, test_contract] {
1584            let bytecode = linker.link(target, &output.libraries).unwrap().bytecode.unwrap();
1585            assert!(
1586                bytecode
1587                    .bytes()
1588                    .unwrap()
1589                    .windows(Address::len_bytes())
1590                    .any(|window| { window == linked_address.as_slice() }),
1591                "{} was not linked to {linked_address}",
1592                target.name
1593            );
1594        }
1595    }
1596
1597    #[test]
1598    fn detailed_linking_includes_transitive_library_addresses() {
1599        let test = LinkerTest::new(&testdata().join("default/linking/nested"), true);
1600        let linker = Linker::new(test.project.root(), test.output.artifact_ids().collect());
1601        let consumer = linker.contracts.keys().find(|id| id.name == "LibraryConsumer").unwrap();
1602        let resolver = Resolver::new(&linker);
1603
1604        let create2 = linker
1605            .link_with_create2_detailed(Libraries::default(), Address::ZERO, B256::ZERO, [consumer])
1606            .unwrap();
1607        let libraries = create2.artifact_libraries.get(consumer).unwrap();
1608        assert_eq!(resolver.linked_library_addresses(consumer, libraries).unwrap().len(), 2);
1609
1610        let nonce = linker
1611            .link_with_nonce_or_address_detailed(Libraries::default(), Address::ZERO, 0, [consumer])
1612            .unwrap();
1613        let libraries = nonce.artifact_libraries.get(consumer).unwrap();
1614        assert_eq!(resolver.linked_library_addresses(consumer, libraries).unwrap().len(), 2);
1615    }
1616
1617    #[test]
1618    fn linking_preserves_transitive_profile_identity() {
1619        let test = LinkerTest::new(&testdata().join("default/linking/profile_nested"), true);
1620        let linker = Linker::new(test.project.root(), test.output.artifact_ids().collect());
1621        let find = |name| {
1622            linker
1623                .contracts
1624                .iter()
1625                .find(|(id, _)| id.name == name)
1626                .map(|(id, contract)| (id.clone(), contract.clone()))
1627                .unwrap()
1628        };
1629        let (inner_id, inner) = find("Inner");
1630        let (outer_id, outer) = find("Outer");
1631        let (consumer_id, consumer) = find("Consumer");
1632
1633        let mut contracts = linker.contracts.clone();
1634        let mut other_inner_id = inner_id.clone();
1635        other_inner_id.build_id = "other".to_string();
1636        other_inner_id.profile = "other".to_string();
1637        contracts.insert(other_inner_id.clone(), inner);
1638        let mut other_outer_id = outer_id.clone();
1639        other_outer_id.build_id = "other".to_string();
1640        other_outer_id.profile = "other".to_string();
1641        contracts.insert(other_outer_id.clone(), outer);
1642        let mut other_consumer_id = consumer_id.clone();
1643        other_consumer_id.build_id = "other".to_string();
1644        other_consumer_id.profile = "other".to_string();
1645        contracts.insert(other_consumer_id.clone(), consumer);
1646
1647        for id in [&other_inner_id, &other_outer_id] {
1648            let bytecode = contracts.get_mut(id).unwrap().bytecode.as_mut().unwrap().to_mut();
1649            match &mut bytecode.object {
1650                BytecodeObject::Bytecode(bytes) => {
1651                    let mut distinct = bytes.to_vec();
1652                    distinct.push(0);
1653                    *bytes = distinct.into();
1654                }
1655                BytecodeObject::Unlinked(code) => code.push_str("00"),
1656            }
1657        }
1658
1659        let linker = Linker::new(test.project.root(), contracts);
1660        let consumers = [&consumer_id, &other_consumer_id];
1661        let profiles = [
1662            (&consumer_id, &outer_id, &inner_id),
1663            (&other_consumer_id, &other_outer_id, &other_inner_id),
1664        ];
1665        let assert_identity = |output: &DetailedLinkOutput| {
1666            assert_eq!(output.artifact_addresses.len(), 4);
1667            assert_eq!(
1668                output.output.libs_to_deploy,
1669                output
1670                    .linked_libraries
1671                    .iter()
1672                    .map(|library| library.bytecode.clone())
1673                    .collect::<Vec<_>>()
1674            );
1675
1676            for linked in &output.linked_libraries {
1677                assert_eq!(output.artifact_addresses[&linked.id], linked.address);
1678                assert_eq!(
1679                    linker
1680                        .linked_creation_bytecode(
1681                            &linked.id,
1682                            &output.artifact_libraries[&linked.id],
1683                        )
1684                        .unwrap(),
1685                    linked.bytecode
1686                );
1687            }
1688
1689            for (index, &(consumer, outer, inner)) in profiles.iter().enumerate() {
1690                let sibling_outer = profiles[1 - index].1;
1691                let sibling_inner = profiles[1 - index].2;
1692                let outer_address = output.artifact_addresses[outer];
1693                let inner_address = output.artifact_addresses[inner];
1694
1695                let consumer_bytecode = linker
1696                    .link(consumer, &output.artifact_libraries[consumer])
1697                    .unwrap()
1698                    .get_bytecode_bytes()
1699                    .unwrap()
1700                    .into_owned();
1701                assert!(
1702                    consumer_bytecode
1703                        .windows(Address::len_bytes())
1704                        .any(|window| { window == outer_address.as_slice() })
1705                );
1706                assert!(!consumer_bytecode.windows(Address::len_bytes()).any(|window| {
1707                    window == output.artifact_addresses[sibling_outer].as_slice()
1708                }));
1709
1710                let outer_bytecode = &output
1711                    .linked_libraries
1712                    .iter()
1713                    .find(|library| library.id == *outer)
1714                    .unwrap()
1715                    .bytecode;
1716                assert!(
1717                    outer_bytecode
1718                        .windows(Address::len_bytes())
1719                        .any(|window| window == inner_address.as_slice())
1720                );
1721                assert!(!outer_bytecode.windows(Address::len_bytes()).any(|window| {
1722                    window == output.artifact_addresses[sibling_inner].as_slice()
1723                }));
1724            }
1725        };
1726
1727        let sender = address!("1000000000000000000000000000000000000000");
1728        let nonce = 7;
1729        let output = linker
1730            .link_with_nonce_or_address_detailed(Libraries::default(), sender, nonce, consumers)
1731            .unwrap();
1732        assert_identity(&output);
1733        for (index, library) in output.linked_libraries.iter().enumerate() {
1734            assert_eq!(library.address, sender.create(nonce + index as u64));
1735        }
1736
1737        let salt = B256::with_last_byte(1);
1738        let output = linker
1739            .link_with_create2_detailed(Libraries::default(), sender, salt, consumers)
1740            .unwrap();
1741        assert_identity(&output);
1742        for library in &output.linked_libraries {
1743            assert_eq!(library.address, sender.create2_from_code(salt, &library.bytecode));
1744        }
1745    }
1746
1747    #[test]
1748    fn linking_handles_library_key_collisions_across_profiles() {
1749        let test = LinkerTest::new(&testdata().join("default/linking/simple"), true);
1750        let linker = Linker::new(test.project.root(), test.output.artifact_ids().collect());
1751        let (library_id, library) = linker
1752            .contracts
1753            .iter()
1754            .find(|(id, _)| id.name == "Lib")
1755            .map(|(id, contract)| (id.clone(), contract.clone()))
1756            .unwrap();
1757        let (consumer_id, consumer) = linker
1758            .contracts
1759            .iter()
1760            .find(|(id, _)| id.name == "LibraryConsumer")
1761            .map(|(id, contract)| (id.clone(), contract.clone()))
1762            .unwrap();
1763
1764        let mut contracts = linker.contracts.clone();
1765        let mut other_library_id = library_id.clone();
1766        other_library_id.build_id = "other".to_string();
1767        other_library_id.profile = "other".to_string();
1768        contracts.insert(other_library_id.clone(), library);
1769        let mut other_consumer_id = consumer_id.clone();
1770        other_consumer_id.build_id = "other".to_string();
1771        other_consumer_id.profile = "other".to_string();
1772        contracts.insert(other_consumer_id.clone(), consumer);
1773
1774        let linker = Linker::new(test.project.root(), contracts.clone());
1775        let detailed = linker
1776            .link_with_create2_detailed(
1777                Libraries::default(),
1778                Address::ZERO,
1779                B256::ZERO,
1780                [&consumer_id, &other_consumer_id],
1781            )
1782            .unwrap();
1783        assert_eq!(detailed.linked_libraries.len(), 1);
1784        assert_eq!(detailed.artifact_addresses.len(), 2);
1785        assert_eq!(detailed.artifact_addresses[&library_id], detailed.linked_libraries[0].address);
1786        assert_eq!(
1787            detailed.artifact_addresses[&other_library_id],
1788            detailed.linked_libraries[0].address
1789        );
1790
1791        let output = linker
1792            .link_with_create2(
1793                Libraries::default(),
1794                Address::ZERO,
1795                B256::ZERO,
1796                [&consumer_id, &other_consumer_id],
1797            )
1798            .unwrap();
1799        assert_eq!(output.libs_to_deploy.len(), 1);
1800
1801        let Err(err) = linker.link_with_nonce_or_address(
1802            Libraries::default(),
1803            Address::ZERO,
1804            0,
1805            [&consumer_id, &other_consumer_id],
1806        ) else {
1807            panic!("expected conflicting library artifacts");
1808        };
1809        assert!(matches!(err, LinkerError::ConflictingLibraryArtifacts { .. }));
1810
1811        let other_library = contracts.get_mut(&other_library_id).unwrap();
1812        let bytecode = other_library.bytecode.as_mut().unwrap().to_mut();
1813        let mut bytes = bytecode.object.as_bytes().unwrap().to_vec();
1814        bytes.push(0);
1815        bytecode.object = BytecodeObject::Bytecode(bytes.into());
1816
1817        let linker = Linker::new(test.project.root(), contracts);
1818        let output = linker
1819            .link_with_create2_detailed(
1820                Libraries::default(),
1821                Address::ZERO,
1822                B256::ZERO,
1823                [&consumer_id, &other_consumer_id],
1824            )
1825            .unwrap();
1826        assert_eq!(output.linked_libraries.len(), 2);
1827        assert_ne!(output.linked_libraries[0].address, output.linked_libraries[1].address);
1828        for (target, library) in
1829            [(&consumer_id, &library_id), (&other_consumer_id, &other_library_id)]
1830        {
1831            let expected = output
1832                .linked_libraries
1833                .iter()
1834                .find(|linked| linked.id == *library)
1835                .unwrap()
1836                .address;
1837            let (file, name) = linker.convert_artifact_id_to_lib_path(library);
1838            let actual =
1839                output.artifact_libraries[target].libs[&file][&name].parse::<Address>().unwrap();
1840            assert_eq!(actual, expected);
1841        }
1842
1843        let Err(err) = linker.link_with_create2(
1844            Libraries::default(),
1845            Address::ZERO,
1846            B256::ZERO,
1847            [&consumer_id, &other_consumer_id],
1848        ) else {
1849            panic!("expected conflicting library artifacts");
1850        };
1851        assert!(matches!(err, LinkerError::ConflictingLibraryArtifacts { .. }));
1852
1853        let output = linker
1854            .link_with_nonce_or_address_detailed(
1855                Libraries::default(),
1856                Address::ZERO,
1857                0,
1858                [&consumer_id, &other_consumer_id],
1859            )
1860            .unwrap();
1861        assert_eq!(output.linked_libraries.len(), 2);
1862        assert_ne!(output.linked_libraries[0].address, output.linked_libraries[1].address);
1863        for (target, library) in
1864            [(&consumer_id, &library_id), (&other_consumer_id, &other_library_id)]
1865        {
1866            let expected = output
1867                .linked_libraries
1868                .iter()
1869                .find(|linked| linked.id == *library)
1870                .unwrap()
1871                .address;
1872            let (file, name) = linker.convert_artifact_id_to_lib_path(library);
1873            let actual =
1874                output.artifact_libraries[target].libs[&file][&name].parse::<Address>().unwrap();
1875            assert_eq!(actual, expected);
1876        }
1877
1878        let Err(err) = linker.link_with_nonce_or_address(
1879            Libraries::default(),
1880            Address::ZERO,
1881            0,
1882            [&consumer_id, &other_consumer_id],
1883        ) else {
1884            panic!("expected conflicting library artifacts");
1885        };
1886        assert!(matches!(err, LinkerError::ConflictingLibraryArtifacts { .. }));
1887
1888        let configured_address = address!("0000000000000000000000000000000000000001");
1889        let (file, name) = linker.convert_artifact_id_to_lib_path(&library_id);
1890        let mut libraries = Libraries::default();
1891        libraries.libs.entry(file).or_default().insert(name, configured_address.to_checksum(None));
1892
1893        let output = linker
1894            .link_with_create2(
1895                libraries.clone(),
1896                Address::ZERO,
1897                B256::ZERO,
1898                [&consumer_id, &other_consumer_id],
1899            )
1900            .unwrap();
1901        assert!(output.libs_to_deploy.is_empty());
1902        assert_eq!(output.library_addresses, [configured_address]);
1903
1904        let output = linker
1905            .link_with_nonce_or_address(
1906                libraries,
1907                Address::ZERO,
1908                0,
1909                [&consumer_id, &other_consumer_id],
1910            )
1911            .unwrap();
1912        assert!(output.libs_to_deploy.is_empty());
1913        assert_eq!(output.library_addresses, [configured_address]);
1914    }
1915
1916    #[test]
1917    fn linking_resolves_same_version_library_from_target_build() {
1918        let test = LinkerTest::new(&testdata().join("default/linking/simple"), true);
1919        let linker = Linker::new(test.project.root(), test.output.artifact_ids().collect());
1920        let (library_id, library) = linker
1921            .contracts
1922            .iter()
1923            .find(|(id, _)| id.name == "Lib")
1924            .map(|(id, contract)| (id.clone(), contract.clone()))
1925            .unwrap();
1926        let (consumer_id, consumer) = linker
1927            .contracts
1928            .iter()
1929            .find(|(id, _)| id.name == "LibraryConsumer")
1930            .map(|(id, contract)| (id.clone(), contract.clone()))
1931            .unwrap();
1932
1933        let mut contracts = linker.contracts.clone();
1934        let mut other_library_id = library_id;
1935        other_library_id.build_id = "other".to_string();
1936        other_library_id.profile = "other".to_string();
1937        contracts.insert(other_library_id, library);
1938        let mut ambiguous_consumer_id = consumer_id.clone();
1939        ambiguous_consumer_id.build_id = "ambiguous".to_string();
1940        ambiguous_consumer_id.profile = "ambiguous".to_string();
1941        contracts.insert(ambiguous_consumer_id.clone(), consumer);
1942
1943        let linker = Linker::new(test.project.root(), contracts);
1944        linker
1945            .link_with_create2(Libraries::default(), Address::ZERO, B256::ZERO, [&consumer_id])
1946            .unwrap();
1947
1948        let Err(err) = linker.link_with_create2(
1949            Libraries::default(),
1950            Address::ZERO,
1951            B256::ZERO,
1952            [&ambiguous_consumer_id],
1953        ) else {
1954            panic!("expected conflicting library artifacts");
1955        };
1956        assert!(matches!(err, LinkerError::ConflictingLibraryArtifacts { .. }));
1957    }
1958
1959    #[test]
1960    fn link_output_excludes_unreferenced_configured_libraries() {
1961        let test = LinkerTest::new(&testdata().join("default/linking/simple"), true);
1962        let linker = Linker::new(test.project.root(), test.output.artifact_ids().collect());
1963        let consumer = linker.contracts.keys().find(|id| id.name == "LibraryConsumer").unwrap();
1964        let unrelated = address!("0000000000000000000000000000000000000001");
1965        let mut libraries = Libraries::default();
1966        libraries
1967            .libs
1968            .entry("src/Unrelated.sol".into())
1969            .or_default()
1970            .insert("Unrelated".to_string(), unrelated.to_checksum(None));
1971
1972        let output =
1973            linker.link_with_create2(libraries, Address::ZERO, B256::ZERO, [consumer]).unwrap();
1974
1975        assert_eq!(output.library_addresses.len(), 1);
1976        assert!(!output.library_addresses.contains(&unrelated));
1977    }
1978
1979    #[test]
1980    fn exact_artifact_match_uses_configured_library_alias() {
1981        let test = LinkerTest::new(&testdata().join("default/linking/simple"), true);
1982        let linker = Linker::new(test.project.root(), test.output.artifact_ids().collect());
1983        let mut contracts = linker.contracts.clone();
1984        let (library_id, library) = contracts
1985            .iter()
1986            .find(|(id, _)| id.name == "Lib")
1987            .map(|(id, contract)| (id.clone(), contract.clone()))
1988            .unwrap();
1989        let mut alias_id = library_id.clone();
1990        alias_id.source = "./default/linking/simple/Simple.t.sol".into();
1991        contracts.insert(alias_id, library);
1992        let linker = Linker::new(test.project.root(), contracts);
1993        let consumer = linker.contracts.keys().find(|id| id.name == "LibraryConsumer").unwrap();
1994        let configured = address!("0000000000000000000000000000000000000001");
1995        let alias = PathBuf::from("./default/linking/simple/Simple.t.sol");
1996        let generated_key = PathBuf::from("default/linking/simple/Simple.t.sol");
1997        let mut libraries = Libraries::default();
1998        libraries
1999            .libs
2000            .entry(alias.clone())
2001            .or_default()
2002            .insert("Lib".to_string(), configured.to_checksum(None));
2003
2004        let output =
2005            linker.link_with_nonce_or_address(libraries, Address::ZERO, 1, [consumer]).unwrap();
2006        let bytecode = linker.link(consumer, &output.libraries).unwrap().bytecode.unwrap();
2007        let bytecode = bytecode.bytes().unwrap();
2008
2009        assert!(output.libs_to_deploy.is_empty());
2010        assert_eq!(output.library_addresses, [configured]);
2011        assert!(
2012            bytecode.windows(Address::len_bytes()).any(|window| window == configured.as_slice())
2013        );
2014        assert_eq!(output.libraries.libs.len(), 2);
2015        assert!(output.libraries.libs.contains_key(&alias));
2016        assert!(output.libraries.libs.contains_key(&generated_key));
2017
2018        let exact = address!("0000000000000000000000000000000000000002");
2019        let references = BTreeMap::from([(&library_id, BTreeSet::from([alias.clone()]))]);
2020        let mut libraries = output.libraries;
2021        libraries.libs.get_mut(&alias).unwrap().insert("Lib".into(), exact.to_checksum(None));
2022        linker.apply_configured_references(&references, &mut libraries).unwrap();
2023        assert_eq!(libraries.libs[&generated_key]["Lib"], exact.to_checksum(None));
2024
2025        libraries
2026            .libs
2027            .get_mut(&generated_key)
2028            .unwrap()
2029            .insert("Lib".into(), configured.to_checksum(None));
2030        let fallback = PathBuf::from("default/linking/simple/../simple/Simple.t.sol");
2031        let references = BTreeMap::from([(&library_id, BTreeSet::from([fallback]))]);
2032        let err = linker.apply_configured_references(&references, &mut libraries).unwrap_err();
2033        assert!(matches!(err, LinkerError::ConflictingLibraryArtifacts { .. }));
2034    }
2035
2036    #[test]
2037    fn partition_with_nonce_assigns_required_and_local_libraries() {
2038        let test = LinkerTest::new(&testdata().join("default/linking/samefile_union"), true);
2039        let linker = Linker::new(test.project.root(), test.output.artifact_ids().collect());
2040        let find = |name| linker.contracts.keys().find(|id| id.name == name).unwrap();
2041        let (target, required_id) = (find("UsesBoth"), find("LInit").clone());
2042        let sender = address!("0x1000000000000000000000000000000000000000");
2043        let local_deployer = address!("0x2000000000000000000000000000000000000000");
2044        let required = BTreeSet::from([required_id.clone()]);
2045        let (detailed, local) = linker
2046            .link_with_partition(Libraries::default(), sender, 7, local_deployer, &required, target)
2047            .unwrap();
2048
2049        assert_eq!(detailed.linked_libraries.len(), 2);
2050        assert_eq!(detailed.output.libs_to_deploy.len(), 1);
2051        assert_eq!(local.len(), 1);
2052        assert_eq!(
2053            detailed.linked_libraries.iter().find(|lib| lib.id == required_id).unwrap().address,
2054            sender.create(7)
2055        );
2056        assert_eq!(local[0].address, local_deployer.create(0));
2057        linker
2058            .ensure_linked(&linker.link(target, &detailed.output.libraries).unwrap(), target)
2059            .unwrap();
2060        for library in &detailed.linked_libraries {
2061            assert!(!library.bytecode.is_empty());
2062        }
2063
2064        let mut configured = Libraries::default();
2065        let (file, name) = linker.convert_artifact_id_to_lib_path(find("LRun"));
2066        configured.libs.entry(file).or_default().insert(name, Address::ZERO.to_checksum(None));
2067        let (configured, local) = linker
2068            .link_with_partition(configured, sender, 7, local_deployer, &required, target)
2069            .unwrap();
2070        assert_eq!(configured.linked_libraries.len(), 1);
2071        assert!(local.is_empty());
2072    }
2073
2074    #[test]
2075    fn partition_with_create2_assigns_required_and_local_libraries() {
2076        let test = LinkerTest::new(&testdata().join("default/linking/samefile_union"), true);
2077        let linker = Linker::new(test.project.root(), test.output.artifact_ids().collect());
2078        let find = |name| linker.contracts.keys().find(|id| id.name == name).unwrap();
2079        let (target, required_id) = (find("UsesBoth"), find("LInit").clone());
2080        let required = BTreeSet::from([required_id.clone()]);
2081        let deployer = address!("0x3000000000000000000000000000000000000000");
2082        let local_deployer = address!("0x4000000000000000000000000000000000000000");
2083        let salt = fixed_bytes!("19bf59b7b67ae8edcbc6e53616080f61fa99285c061450ad601b0bc40c9adfc9");
2084        let (detailed, local) = linker
2085            .link_with_create2_partition(
2086                Libraries::default(),
2087                deployer,
2088                salt,
2089                local_deployer,
2090                &required,
2091                target,
2092            )
2093            .unwrap();
2094
2095        assert_eq!(detailed.output.libs_to_deploy.len(), 1);
2096        assert_eq!(local.len(), 1);
2097        let onchain =
2098            detailed.linked_libraries.iter().find(|library| library.id == required_id).unwrap();
2099        assert_eq!(onchain.address, deployer.create2_from_code(salt, &onchain.bytecode));
2100        assert_eq!(detailed.output.libs_to_deploy[0], onchain.bytecode);
2101        assert_eq!(local[0].address, local_deployer.create(0));
2102        linker
2103            .ensure_linked(&linker.link(target, &detailed.output.libraries).unwrap(), target)
2104            .unwrap();
2105    }
2106
2107    #[test]
2108    fn partition_with_create2_keeps_transitive_order_deterministic() {
2109        let test = LinkerTest::new(&testdata().join("default/linking/nested"), true);
2110        let linker = Linker::new(test.project.root(), test.output.artifact_ids().collect());
2111        let find = |name| linker.contracts.keys().find(|id| id.name == name).unwrap();
2112        let target = find("LibraryConsumer");
2113        let required = BTreeSet::from([find("NestedLib").clone()]);
2114        let deployer = address!("0x3000000000000000000000000000000000000000");
2115        let local_deployer = address!("0x4000000000000000000000000000000000000000");
2116        let salt = fixed_bytes!("19bf59b7b67ae8edcbc6e53616080f61fa99285c061450ad601b0bc40c9adfc9");
2117        let link = || {
2118            linker
2119                .link_with_create2_partition(
2120                    Libraries::default(),
2121                    deployer,
2122                    salt,
2123                    local_deployer,
2124                    &required,
2125                    target,
2126                )
2127                .unwrap()
2128        };
2129        let (first, local) = link();
2130        let (second, _) = link();
2131
2132        assert!(local.is_empty());
2133        assert_eq!(first.output.libs_to_deploy.len(), 2);
2134        assert_eq!(
2135            first.linked_libraries.iter().map(|lib| (&lib.id, lib.address)).collect::<Vec<_>>(),
2136            second.linked_libraries.iter().map(|lib| (&lib.id, lib.address)).collect::<Vec<_>>()
2137        );
2138        assert_eq!(first.linked_libraries[0].id.name, "Lib");
2139        assert_eq!(first.linked_libraries[1].id.name, "NestedLib");
2140        for library in &first.linked_libraries {
2141            assert_eq!(library.address, deployer.create2_from_code(salt, &library.bytecode));
2142        }
2143        linker
2144            .ensure_linked(&linker.link(target, &first.output.libraries).unwrap(), target)
2145            .unwrap();
2146    }
2147
2148    #[test]
2149    fn linking_failure() {
2150        let linker = LinkerTest::new(&testdata().join("default/linking/simple"), true);
2151        let linker_instance =
2152            Linker::new(linker.project.root(), linker.output.artifact_ids().collect());
2153
2154        // Create a libraries object with an incorrect library name that won't match any references
2155        let mut libraries = Libraries::default();
2156        libraries.libs.entry("default/linking/simple/Simple.t.sol".into()).or_default().insert(
2157            "NonExistentLib".to_string(),
2158            "0x5a443704dd4b594b382c22a083e2bd3090a6fef3".to_string(),
2159        );
2160
2161        // Try to link the LibraryConsumer contract with incorrect library
2162        let artifact_id = linker_instance
2163            .contracts
2164            .keys()
2165            .find(|id| id.name == "LibraryConsumer")
2166            .expect("LibraryConsumer contract not found");
2167
2168        let contract = linker_instance.contracts.get(artifact_id).unwrap();
2169
2170        // Verify that the artifact has unlinked bytecode
2171        assert!(
2172            linker_instance.ensure_linked(contract, artifact_id).is_err(),
2173            "Expected artifact to have unlinked bytecode"
2174        );
2175    }
2176}