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