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, doc_auto_cfg))]
7
8use alloy_primitives::{Address, B256, Bytes};
9use foundry_compilers::{
10    Artifact, ArtifactId,
11    artifacts::{CompactContractBytecodeCow, Libraries},
12    contracts::ArtifactContracts,
13};
14use rayon::prelude::*;
15use semver::Version;
16use std::{
17    collections::{BTreeMap, BTreeSet},
18    path::{Path, PathBuf},
19    str::FromStr,
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("target artifact is not present in provided artifacts set")]
28    MissingTargetArtifact,
29    #[error(transparent)]
30    InvalidAddress(<Address as std::str::FromStr>::Err),
31    #[error("cyclic dependency found, can't link libraries via CREATE2")]
32    CyclicDependency,
33    #[error("failed linking {artifact}")]
34    LinkingFailed { artifact: String },
35}
36
37pub struct Linker<'a> {
38    /// Root of the project, used to determine whether artifact/library path can be stripped.
39    pub root: PathBuf,
40    /// Compilation artifacts.
41    pub contracts: ArtifactContracts<CompactContractBytecodeCow<'a>>,
42}
43
44/// Output of the `link_with_nonce_or_address`
45pub struct LinkOutput {
46    /// Resolved library addresses. Contains both user-provided and newly deployed libraries.
47    /// It will always contain library paths with stripped path prefixes.
48    pub libraries: Libraries,
49    /// Vector of libraries that need to be deployed from sender address.
50    /// The order in which they appear in the vector is the order in which they should be deployed.
51    pub libs_to_deploy: Vec<Bytes>,
52}
53
54impl<'a> Linker<'a> {
55    pub fn new(
56        root: impl Into<PathBuf>,
57        contracts: ArtifactContracts<CompactContractBytecodeCow<'a>>,
58    ) -> Self {
59        Linker { root: root.into(), contracts }
60    }
61
62    /// Helper method to convert [ArtifactId] to the format in which libraries are stored in
63    /// [Libraries] object.
64    ///
65    /// Strips project root path from source file path.
66    fn convert_artifact_id_to_lib_path(&self, id: &ArtifactId) -> (PathBuf, String) {
67        let path = id.source.strip_prefix(self.root.as_path()).unwrap_or(&id.source);
68        // name is either {LibName} or {LibName}.{version}
69        let name = id.name.split('.').next().unwrap();
70
71        (path.to_path_buf(), name.to_owned())
72    }
73
74    /// Finds an [ArtifactId] object in the given [ArtifactContracts] keys which corresponds to the
75    /// library path in the form of "./path/to/Lib.sol:Lib"
76    ///
77    /// Optionally accepts solc version, and if present, only compares artifacts with given version.
78    fn find_artifact_id_by_library_path(
79        &'a self,
80        file: &str,
81        name: &str,
82        version: Option<&Version>,
83    ) -> Option<&'a ArtifactId> {
84        for id in self.contracts.keys() {
85            if let Some(version) = version
86                && id.version != *version
87            {
88                continue;
89            }
90            let (artifact_path, artifact_name) = self.convert_artifact_id_to_lib_path(id);
91
92            if artifact_name == *name && artifact_path == Path::new(file) {
93                return Some(id);
94            }
95        }
96
97        None
98    }
99
100    /// Performs DFS on the graph of link references, and populates `deps` with all found libraries.
101    fn collect_dependencies(
102        &'a self,
103        target: &'a ArtifactId,
104        deps: &mut BTreeSet<&'a ArtifactId>,
105    ) -> Result<(), LinkerError> {
106        let contract = self.contracts.get(target).ok_or(LinkerError::MissingTargetArtifact)?;
107
108        let mut references = BTreeMap::new();
109        if let Some(bytecode) = &contract.bytecode {
110            references.extend(bytecode.link_references.clone());
111        }
112        if let Some(deployed_bytecode) = &contract.deployed_bytecode
113            && let Some(bytecode) = &deployed_bytecode.bytecode
114        {
115            references.extend(bytecode.link_references.clone());
116        }
117
118        for (file, libs) in &references {
119            for contract in libs.keys() {
120                let id = self
121                    .find_artifact_id_by_library_path(file, contract, Some(&target.version))
122                    .ok_or_else(|| LinkerError::MissingLibraryArtifact {
123                        file: file.to_string(),
124                        name: contract.to_string(),
125                    })?;
126                if deps.insert(id) {
127                    self.collect_dependencies(id, deps)?;
128                }
129            }
130        }
131
132        Ok(())
133    }
134
135    /// Links given artifact with either given library addresses or address computed from sender and
136    /// nonce.
137    ///
138    /// Each key in `libraries` should either be a global path or relative to project root. All
139    /// remappings should be resolved.
140    ///
141    /// When calling for `target` being an external library itself, you should check that `target`
142    /// does not appear in `libs_to_deploy` to avoid deploying it twice. It may happen in cases
143    /// when there is a dependency cycle including `target`.
144    pub fn link_with_nonce_or_address(
145        &'a self,
146        libraries: Libraries,
147        sender: Address,
148        mut nonce: u64,
149        targets: impl IntoIterator<Item = &'a ArtifactId>,
150    ) -> Result<LinkOutput, LinkerError> {
151        // Library paths in `link_references` keys are always stripped, so we have to strip
152        // user-provided paths to be able to match them correctly.
153        let mut libraries = libraries.with_stripped_file_prefixes(self.root.as_path());
154
155        let mut needed_libraries = BTreeSet::new();
156        for target in targets {
157            self.collect_dependencies(target, &mut needed_libraries)?;
158        }
159
160        let mut libs_to_deploy = Vec::new();
161
162        // If `libraries` does not contain needed dependency, compute its address and add to
163        // `libs_to_deploy`.
164        for id in needed_libraries {
165            let (lib_path, lib_name) = self.convert_artifact_id_to_lib_path(id);
166
167            libraries.libs.entry(lib_path).or_default().entry(lib_name).or_insert_with(|| {
168                let address = sender.create(nonce);
169                libs_to_deploy.push((id, address));
170                nonce += 1;
171
172                address.to_checksum(None)
173            });
174        }
175
176        // Link and collect bytecodes for `libs_to_deploy`.
177        let libs_to_deploy = libs_to_deploy
178            .into_par_iter()
179            .map(|(id, _)| {
180                Ok(self.link(id, &libraries)?.get_bytecode_bytes().unwrap().into_owned())
181            })
182            .collect::<Result<Vec<_>, LinkerError>>()?;
183
184        Ok(LinkOutput { libraries, libs_to_deploy })
185    }
186
187    pub fn link_with_create2(
188        &'a self,
189        libraries: Libraries,
190        sender: Address,
191        salt: B256,
192        target: &'a ArtifactId,
193    ) -> Result<LinkOutput, LinkerError> {
194        // Library paths in `link_references` keys are always stripped, so we have to strip
195        // user-provided paths to be able to match them correctly.
196        let mut libraries = libraries.with_stripped_file_prefixes(self.root.as_path());
197
198        let mut needed_libraries = BTreeSet::new();
199        self.collect_dependencies(target, &mut needed_libraries)?;
200
201        let mut needed_libraries = needed_libraries
202            .into_par_iter()
203            .filter(|id| {
204                // Filter out already provided libraries.
205                let (file, name) = self.convert_artifact_id_to_lib_path(id);
206                !libraries.libs.contains_key(&file) || !libraries.libs[&file].contains_key(&name)
207            })
208            .map(|id| {
209                // Link library with provided libs and extract bytecode object (possibly unlinked).
210                let bytecode = self.link(id, &libraries).unwrap().bytecode.unwrap();
211                (id, bytecode)
212            })
213            .collect::<Vec<_>>();
214
215        let mut libs_to_deploy = Vec::new();
216
217        // Iteratively compute addresses and link libraries until we have no unlinked libraries
218        // left.
219        while !needed_libraries.is_empty() {
220            // Find any library which is fully linked.
221            let deployable = needed_libraries
222                .iter()
223                .enumerate()
224                .find(|(_, (_, bytecode))| !bytecode.object.is_unlinked());
225
226            // If we haven't found any deployable library, it means we have a cyclic dependency.
227            let Some((index, &(id, _))) = deployable else {
228                return Err(LinkerError::CyclicDependency);
229            };
230            let (_, bytecode) = needed_libraries.swap_remove(index);
231            let code = bytecode.bytes().unwrap();
232            let address = sender.create2_from_code(salt, code);
233            libs_to_deploy.push(code.clone());
234
235            let (file, name) = self.convert_artifact_id_to_lib_path(id);
236
237            needed_libraries.par_iter_mut().for_each(|(_, bytecode)| {
238                bytecode.to_mut().link(&file.to_string_lossy(), &name, address);
239            });
240
241            libraries.libs.entry(file).or_default().insert(name, address.to_checksum(None));
242        }
243
244        Ok(LinkOutput { libraries, libs_to_deploy })
245    }
246
247    /// Links given artifact with given libraries.
248    pub fn link(
249        &self,
250        target: &ArtifactId,
251        libraries: &Libraries,
252    ) -> Result<CompactContractBytecodeCow<'a>, LinkerError> {
253        let mut contract =
254            self.contracts.get(target).ok_or(LinkerError::MissingTargetArtifact)?.clone();
255        for (file, libs) in &libraries.libs {
256            for (name, address) in libs {
257                let address = Address::from_str(address).map_err(LinkerError::InvalidAddress)?;
258                if let Some(bytecode) = contract.bytecode.as_mut() {
259                    bytecode.to_mut().link(&file.to_string_lossy(), name, address);
260                }
261                if let Some(deployed_bytecode) =
262                    contract.deployed_bytecode.as_mut().and_then(|b| b.to_mut().bytecode.as_mut())
263                {
264                    deployed_bytecode.link(&file.to_string_lossy(), name, address);
265                }
266            }
267        }
268        Ok(contract)
269    }
270
271    /// Ensures that both initial and deployed bytecode are linked.
272    pub fn ensure_linked(
273        &self,
274        contract: &CompactContractBytecodeCow<'a>,
275        target: &ArtifactId,
276    ) -> Result<(), LinkerError> {
277        if let Some(bytecode) = &contract.bytecode
278            && bytecode.object.is_unlinked()
279        {
280            return Err(LinkerError::LinkingFailed {
281                artifact: target.source.to_string_lossy().into(),
282            });
283        }
284        if let Some(deployed_bytecode) = &contract.deployed_bytecode
285            && let Some(deployed_bytecode_obj) = &deployed_bytecode.bytecode
286            && deployed_bytecode_obj.object.is_unlinked()
287        {
288            return Err(LinkerError::LinkingFailed {
289                artifact: target.source.to_string_lossy().into(),
290            });
291        }
292        Ok(())
293    }
294
295    pub fn get_linked_artifacts(
296        &self,
297        libraries: &Libraries,
298    ) -> Result<ArtifactContracts, LinkerError> {
299        self.get_linked_artifacts_cow(libraries).map(ArtifactContracts::from_iter)
300    }
301
302    pub fn get_linked_artifacts_cow(
303        &self,
304        libraries: &Libraries,
305    ) -> Result<ArtifactContracts<CompactContractBytecodeCow<'a>>, LinkerError> {
306        self.contracts
307            .par_iter()
308            .map(|(id, _)| Ok((id.clone(), self.link(id, libraries)?)))
309            .collect::<Result<_, _>>()
310            .map(ArtifactContracts)
311    }
312}
313
314#[cfg(test)]
315mod tests {
316    use super::*;
317    use alloy_primitives::{address, fixed_bytes, map::HashMap};
318    use foundry_compilers::{
319        Project, ProjectCompileOutput, ProjectPathsConfig,
320        multi::MultiCompiler,
321        solc::{Solc, SolcCompiler},
322    };
323
324    struct LinkerTest {
325        project: Project,
326        output: ProjectCompileOutput,
327        dependency_assertions: HashMap<String, Vec<(String, Address)>>,
328    }
329
330    impl LinkerTest {
331        fn new(path: impl Into<PathBuf>, strip_prefixes: bool) -> Self {
332            let path = path.into();
333            let paths = ProjectPathsConfig::builder()
334                .root("../../testdata")
335                .lib("../../testdata/lib")
336                .sources(path.clone())
337                .tests(path)
338                .build()
339                .unwrap();
340
341            let solc = Solc::find_or_install(&Version::new(0, 8, 18)).unwrap();
342            let project = Project::builder()
343                .paths(paths)
344                .ephemeral()
345                .no_artifacts()
346                .build(MultiCompiler { solc: Some(SolcCompiler::Specific(solc)), vyper: None })
347                .unwrap();
348
349            let mut output = project.compile().unwrap();
350
351            if strip_prefixes {
352                output = output.with_stripped_file_prefixes(project.root());
353            }
354
355            Self { project, output, dependency_assertions: HashMap::default() }
356        }
357
358        fn assert_dependencies(
359            mut self,
360            artifact_id: String,
361            deps: Vec<(String, Address)>,
362        ) -> Self {
363            self.dependency_assertions.insert(artifact_id, deps);
364            self
365        }
366
367        fn test_with_sender_and_nonce(self, sender: Address, initial_nonce: u64) {
368            let linker = Linker::new(self.project.root(), self.output.artifact_ids().collect());
369            for (id, identifier) in self.iter_linking_targets(&linker) {
370                let output = linker
371                    .link_with_nonce_or_address(Default::default(), sender, initial_nonce, [id])
372                    .expect("Linking failed");
373                self.validate_assertions(identifier, output);
374            }
375        }
376
377        fn test_with_create2(self, sender: Address, salt: B256) {
378            let linker = Linker::new(self.project.root(), self.output.artifact_ids().collect());
379            for (id, identifier) in self.iter_linking_targets(&linker) {
380                let output = linker
381                    .link_with_create2(Default::default(), sender, salt, id)
382                    .expect("Linking failed");
383                self.validate_assertions(identifier, output);
384            }
385        }
386
387        fn iter_linking_targets<'a>(
388            &'a self,
389            linker: &'a Linker<'_>,
390        ) -> impl IntoIterator<Item = (&'a ArtifactId, String)> + 'a {
391            linker.contracts.keys().filter_map(move |id| {
392                // If we didn't strip paths, artifacts will have absolute paths.
393                // That's expected and we want to ensure that only `libraries` object has relative
394                // paths, artifacts should be kept as is.
395                let source = id
396                    .source
397                    .strip_prefix(self.project.root())
398                    .unwrap_or(&id.source)
399                    .to_string_lossy();
400                let identifier = format!("{source}:{}", id.name);
401
402                // Skip ds-test as it always has no dependencies etc. (and the path is outside root
403                // so is not sanitized)
404                if identifier.contains("DSTest") {
405                    return None;
406                }
407
408                Some((id, identifier))
409            })
410        }
411
412        fn validate_assertions(&self, identifier: String, output: LinkOutput) {
413            let LinkOutput { libs_to_deploy, libraries } = output;
414
415            let assertions = self
416                .dependency_assertions
417                .get(&identifier)
418                .unwrap_or_else(|| panic!("Unexpected artifact: {identifier}"));
419
420            assert_eq!(
421                libs_to_deploy.len(),
422                assertions.len(),
423                "artifact {identifier} has more/less dependencies than expected ({} vs {}): {:#?}",
424                libs_to_deploy.len(),
425                assertions.len(),
426                libs_to_deploy
427            );
428
429            for (dep_identifier, address) in assertions {
430                let (file, name) = dep_identifier.split_once(':').unwrap();
431                if let Some(lib_address) =
432                    libraries.libs.get(Path::new(file)).and_then(|libs| libs.get(name))
433                {
434                    assert_eq!(
435                        *lib_address,
436                        address.to_string(),
437                        "incorrect library address for dependency {dep_identifier} of {identifier}"
438                    );
439                } else {
440                    panic!("Library {dep_identifier} not found");
441                }
442            }
443        }
444    }
445
446    fn link_test(path: impl Into<PathBuf>, test_fn: impl Fn(LinkerTest)) {
447        let path = path.into();
448        test_fn(LinkerTest::new(path.clone(), true));
449        test_fn(LinkerTest::new(path, false));
450    }
451
452    #[test]
453    fn link_simple() {
454        link_test("../../testdata/default/linking/simple", |linker| {
455            linker
456                .assert_dependencies("default/linking/simple/Simple.t.sol:Lib".to_string(), vec![])
457                .assert_dependencies(
458                    "default/linking/simple/Simple.t.sol:LibraryConsumer".to_string(),
459                    vec![(
460                        "default/linking/simple/Simple.t.sol:Lib".to_string(),
461                        address!("0x5a443704dd4b594b382c22a083e2bd3090a6fef3"),
462                    )],
463                )
464                .assert_dependencies(
465                    "default/linking/simple/Simple.t.sol:SimpleLibraryLinkingTest".to_string(),
466                    vec![(
467                        "default/linking/simple/Simple.t.sol:Lib".to_string(),
468                        address!("0x5a443704dd4b594b382c22a083e2bd3090a6fef3"),
469                    )],
470                )
471                .test_with_sender_and_nonce(Address::default(), 1);
472        });
473    }
474
475    #[test]
476    fn link_nested() {
477        link_test("../../testdata/default/linking/nested", |linker| {
478            linker
479                .assert_dependencies("default/linking/nested/Nested.t.sol:Lib".to_string(), vec![])
480                .assert_dependencies(
481                    "default/linking/nested/Nested.t.sol:NestedLib".to_string(),
482                    vec![(
483                        "default/linking/nested/Nested.t.sol:Lib".to_string(),
484                        address!("0x5a443704dd4b594b382c22a083e2bd3090a6fef3"),
485                    )],
486                )
487                .assert_dependencies(
488                    "default/linking/nested/Nested.t.sol:LibraryConsumer".to_string(),
489                    vec![
490                        // Lib shows up here twice, because the linker sees it twice, but it should
491                        // have the same address and nonce.
492                        (
493                            "default/linking/nested/Nested.t.sol:Lib".to_string(),
494                            Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3")
495                                .unwrap(),
496                        ),
497                        (
498                            "default/linking/nested/Nested.t.sol:NestedLib".to_string(),
499                            Address::from_str("0x47e9Fbef8C83A1714F1951F142132E6e90F5fa5D")
500                                .unwrap(),
501                        ),
502                    ],
503                )
504                .assert_dependencies(
505                    "default/linking/nested/Nested.t.sol:NestedLibraryLinkingTest".to_string(),
506                    vec![
507                        (
508                            "default/linking/nested/Nested.t.sol:Lib".to_string(),
509                            Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3")
510                                .unwrap(),
511                        ),
512                        (
513                            "default/linking/nested/Nested.t.sol:NestedLib".to_string(),
514                            Address::from_str("0x47e9fbef8c83a1714f1951f142132e6e90f5fa5d")
515                                .unwrap(),
516                        ),
517                    ],
518                )
519                .test_with_sender_and_nonce(Address::default(), 1);
520        });
521    }
522
523    #[test]
524    fn link_duplicate() {
525        link_test("../../testdata/default/linking/duplicate", |linker| {
526            linker
527                .assert_dependencies(
528                    "default/linking/duplicate/Duplicate.t.sol:A".to_string(),
529                    vec![],
530                )
531                .assert_dependencies(
532                    "default/linking/duplicate/Duplicate.t.sol:B".to_string(),
533                    vec![],
534                )
535                .assert_dependencies(
536                    "default/linking/duplicate/Duplicate.t.sol:C".to_string(),
537                    vec![(
538                        "default/linking/duplicate/Duplicate.t.sol:A".to_string(),
539                        address!("0x5a443704dd4b594b382c22a083e2bd3090a6fef3"),
540                    )],
541                )
542                .assert_dependencies(
543                    "default/linking/duplicate/Duplicate.t.sol:D".to_string(),
544                    vec![(
545                        "default/linking/duplicate/Duplicate.t.sol:B".to_string(),
546                        address!("0x5a443704dd4b594b382c22a083e2bd3090a6fef3"),
547                    )],
548                )
549                .assert_dependencies(
550                    "default/linking/duplicate/Duplicate.t.sol:E".to_string(),
551                    vec![
552                        (
553                            "default/linking/duplicate/Duplicate.t.sol:A".to_string(),
554                            Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3")
555                                .unwrap(),
556                        ),
557                        (
558                            "default/linking/duplicate/Duplicate.t.sol:C".to_string(),
559                            Address::from_str("0x47e9fbef8c83a1714f1951f142132e6e90f5fa5d")
560                                .unwrap(),
561                        ),
562                    ],
563                )
564                .assert_dependencies(
565                    "default/linking/duplicate/Duplicate.t.sol:LibraryConsumer".to_string(),
566                    vec![
567                        (
568                            "default/linking/duplicate/Duplicate.t.sol:A".to_string(),
569                            Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3")
570                                .unwrap(),
571                        ),
572                        (
573                            "default/linking/duplicate/Duplicate.t.sol:B".to_string(),
574                            Address::from_str("0x47e9fbef8c83a1714f1951f142132e6e90f5fa5d")
575                                .unwrap(),
576                        ),
577                        (
578                            "default/linking/duplicate/Duplicate.t.sol:C".to_string(),
579                            Address::from_str("0x8be503bcded90ed42eff31f56199399b2b0154ca")
580                                .unwrap(),
581                        ),
582                        (
583                            "default/linking/duplicate/Duplicate.t.sol:D".to_string(),
584                            Address::from_str("0x47c5e40890bce4a473a49d7501808b9633f29782")
585                                .unwrap(),
586                        ),
587                        (
588                            "default/linking/duplicate/Duplicate.t.sol:E".to_string(),
589                            Address::from_str("0x29b2440db4a256b0c1e6d3b4cdcaa68e2440a08f")
590                                .unwrap(),
591                        ),
592                    ],
593                )
594                .assert_dependencies(
595                    "default/linking/duplicate/Duplicate.t.sol:DuplicateLibraryLinkingTest"
596                        .to_string(),
597                    vec![
598                        (
599                            "default/linking/duplicate/Duplicate.t.sol:A".to_string(),
600                            Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3")
601                                .unwrap(),
602                        ),
603                        (
604                            "default/linking/duplicate/Duplicate.t.sol:B".to_string(),
605                            Address::from_str("0x47e9fbef8c83a1714f1951f142132e6e90f5fa5d")
606                                .unwrap(),
607                        ),
608                        (
609                            "default/linking/duplicate/Duplicate.t.sol:C".to_string(),
610                            Address::from_str("0x8be503bcded90ed42eff31f56199399b2b0154ca")
611                                .unwrap(),
612                        ),
613                        (
614                            "default/linking/duplicate/Duplicate.t.sol:D".to_string(),
615                            Address::from_str("0x47c5e40890bce4a473a49d7501808b9633f29782")
616                                .unwrap(),
617                        ),
618                        (
619                            "default/linking/duplicate/Duplicate.t.sol:E".to_string(),
620                            Address::from_str("0x29b2440db4a256b0c1e6d3b4cdcaa68e2440a08f")
621                                .unwrap(),
622                        ),
623                    ],
624                )
625                .test_with_sender_and_nonce(Address::default(), 1);
626        });
627    }
628
629    #[test]
630    fn link_cycle() {
631        link_test("../../testdata/default/linking/cycle", |linker| {
632            linker
633                .assert_dependencies(
634                    "default/linking/cycle/Cycle.t.sol:Foo".to_string(),
635                    vec![
636                        (
637                            "default/linking/cycle/Cycle.t.sol:Foo".to_string(),
638                            Address::from_str("0x47e9Fbef8C83A1714F1951F142132E6e90F5fa5D")
639                                .unwrap(),
640                        ),
641                        (
642                            "default/linking/cycle/Cycle.t.sol:Bar".to_string(),
643                            Address::from_str("0x5a443704dd4B594B382c22a083e2BD3090A6feF3")
644                                .unwrap(),
645                        ),
646                    ],
647                )
648                .assert_dependencies(
649                    "default/linking/cycle/Cycle.t.sol:Bar".to_string(),
650                    vec![
651                        (
652                            "default/linking/cycle/Cycle.t.sol:Foo".to_string(),
653                            Address::from_str("0x47e9Fbef8C83A1714F1951F142132E6e90F5fa5D")
654                                .unwrap(),
655                        ),
656                        (
657                            "default/linking/cycle/Cycle.t.sol:Bar".to_string(),
658                            Address::from_str("0x5a443704dd4B594B382c22a083e2BD3090A6feF3")
659                                .unwrap(),
660                        ),
661                    ],
662                )
663                .test_with_sender_and_nonce(Address::default(), 1);
664        });
665    }
666
667    #[test]
668    fn link_create2_nested() {
669        link_test("../../testdata/default/linking/nested", |linker| {
670            linker
671                .assert_dependencies("default/linking/nested/Nested.t.sol:Lib".to_string(), vec![])
672                .assert_dependencies(
673                    "default/linking/nested/Nested.t.sol:NestedLib".to_string(),
674                    vec![(
675                        "default/linking/nested/Nested.t.sol:Lib".to_string(),
676                        address!("0xddb1Cd2497000DAeA687CEa3dc34Af44084BEa74"),
677                    )],
678                )
679                .assert_dependencies(
680                    "default/linking/nested/Nested.t.sol:LibraryConsumer".to_string(),
681                    vec![
682                        // Lib shows up here twice, because the linker sees it twice, but it should
683                        // have the same address and nonce.
684                        (
685                            "default/linking/nested/Nested.t.sol:Lib".to_string(),
686                            Address::from_str("0xddb1Cd2497000DAeA687CEa3dc34Af44084BEa74")
687                                .unwrap(),
688                        ),
689                        (
690                            "default/linking/nested/Nested.t.sol:NestedLib".to_string(),
691                            Address::from_str("0xfebE2F30641170642f317Ff6F644Cee60E7Ac369")
692                                .unwrap(),
693                        ),
694                    ],
695                )
696                .assert_dependencies(
697                    "default/linking/nested/Nested.t.sol:NestedLibraryLinkingTest".to_string(),
698                    vec![
699                        (
700                            "default/linking/nested/Nested.t.sol:Lib".to_string(),
701                            Address::from_str("0xddb1Cd2497000DAeA687CEa3dc34Af44084BEa74")
702                                .unwrap(),
703                        ),
704                        (
705                            "default/linking/nested/Nested.t.sol:NestedLib".to_string(),
706                            Address::from_str("0xfebE2F30641170642f317Ff6F644Cee60E7Ac369")
707                                .unwrap(),
708                        ),
709                    ],
710                )
711                .test_with_create2(
712                    Address::default(),
713                    fixed_bytes!(
714                        "19bf59b7b67ae8edcbc6e53616080f61fa99285c061450ad601b0bc40c9adfc9"
715                    ),
716                );
717        });
718    }
719
720    #[test]
721    fn linking_failure() {
722        let linker = LinkerTest::new("../../testdata/default/linking/simple", true);
723        let linker_instance =
724            Linker::new(linker.project.root(), linker.output.artifact_ids().collect());
725
726        // Create a libraries object with an incorrect library name that won't match any references
727        let mut libraries = Libraries::default();
728        libraries.libs.entry("default/linking/simple/Simple.t.sol".into()).or_default().insert(
729            "NonExistentLib".to_string(),
730            "0x5a443704dd4b594b382c22a083e2bd3090a6fef3".to_string(),
731        );
732
733        // Try to link the LibraryConsumer contract with incorrect library
734        let artifact_id = linker_instance
735            .contracts
736            .keys()
737            .find(|id| id.name == "LibraryConsumer")
738            .expect("LibraryConsumer contract not found");
739
740        let contract = linker_instance.contracts.get(artifact_id).unwrap();
741
742        // Verify that the artifact has unlinked bytecode
743        assert!(
744            linker_instance.ensure_linked(contract, artifact_id).is_err(),
745            "Expected artifact to have unlinked bytecode"
746        );
747    }
748}