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