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