1#![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::{CompactBytecode, 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#[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 pub root: PathBuf,
40 pub contracts: ArtifactContracts<CompactContractBytecodeCow<'a>>,
42}
43
44pub struct LinkOutput {
46 pub libraries: Libraries,
49 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 fn convert_artifact_id_to_lib_path(&self, id: &ArtifactId) -> (PathBuf, String) {
67 let name = id.name.split('.').next().unwrap();
69
70 (self.project_relative_path(&id.source), name.to_owned())
71 }
72
73 fn project_relative_path(&self, path: &Path) -> PathBuf {
74 if path.is_relative() {
75 return path.to_path_buf();
76 }
77
78 if let Ok(stripped) = path.strip_prefix(&self.root) {
79 return stripped.to_path_buf();
80 }
81
82 if let Ok(root) = self.root.canonicalize()
83 && let Ok(path) = path.canonicalize()
84 && let Ok(stripped) = path.strip_prefix(root)
85 {
86 return stripped.to_path_buf();
87 }
88
89 path.to_path_buf()
90 }
91
92 fn link_bytecode(
93 &self,
94 bytecode: &mut CompactBytecode,
95 file: &Path,
96 name: &str,
97 address: Address,
98 ) {
99 let file_relative = self.project_relative_path(file);
100 let references = bytecode
101 .link_references
102 .keys()
103 .filter(|reference| {
104 let reference = Path::new(reference);
105 reference == file || self.project_relative_path(reference) == file_relative
106 })
107 .cloned()
108 .collect::<Vec<_>>();
109
110 if references.is_empty() {
111 bytecode.link(&file.to_string_lossy(), name, address);
112 } else {
113 for reference in references {
114 bytecode.link(&reference, name, address);
115 }
116 }
117 }
118
119 fn find_artifact_id_by_library_path(
124 &'a self,
125 file: &str,
126 name: &str,
127 version: Option<&Version>,
128 ) -> Option<&'a ArtifactId> {
129 for id in self.contracts.keys() {
130 if let Some(version) = version
131 && id.version != *version
132 {
133 continue;
134 }
135 let (artifact_path, artifact_name) = self.convert_artifact_id_to_lib_path(id);
136 let library_path = self.project_relative_path(Path::new(file));
137
138 if artifact_name == *name && artifact_path == library_path {
139 return Some(id);
140 }
141 }
142
143 None
144 }
145
146 fn collect_dependencies(
148 &'a self,
149 target: &'a ArtifactId,
150 deps: &mut BTreeSet<&'a ArtifactId>,
151 ) -> Result<(), LinkerError> {
152 let contract = self.contracts.get(target).ok_or(LinkerError::MissingTargetArtifact)?;
153
154 let mut references: BTreeMap<String, BTreeSet<String>> = BTreeMap::new();
155 let mut extend = |bytecode: &CompactBytecode| {
156 for (file, libs) in &bytecode.link_references {
157 references.entry(file.clone()).or_default().extend(libs.keys().cloned());
158 }
159 };
160 if let Some(bytecode) = &contract.bytecode {
161 extend(bytecode);
162 }
163 if let Some(deployed_bytecode) = &contract.deployed_bytecode
164 && let Some(bytecode) = &deployed_bytecode.bytecode
165 {
166 extend(bytecode);
167 }
168
169 for (file, libs) in references {
170 for name in libs {
171 let id = self
172 .find_artifact_id_by_library_path(&file, &name, Some(&target.version))
173 .ok_or_else(|| LinkerError::MissingLibraryArtifact {
174 file: file.clone(),
175 name,
176 })?;
177 if deps.insert(id) {
178 self.collect_dependencies(id, deps)?;
179 }
180 }
181 }
182
183 Ok(())
184 }
185
186 pub fn link_with_nonce_or_address(
196 &'a self,
197 libraries: Libraries,
198 sender: Address,
199 mut nonce: u64,
200 targets: impl IntoIterator<Item = &'a ArtifactId>,
201 ) -> Result<LinkOutput, LinkerError> {
202 let mut libraries = libraries.with_stripped_file_prefixes(self.root.as_path());
205
206 let mut needed_libraries = BTreeSet::new();
207 for target in targets {
208 self.collect_dependencies(target, &mut needed_libraries)?;
209 }
210
211 let mut libs_to_deploy = Vec::new();
212
213 for id in needed_libraries {
216 let (lib_path, lib_name) = self.convert_artifact_id_to_lib_path(id);
217
218 libraries.libs.entry(lib_path).or_default().entry(lib_name).or_insert_with(|| {
219 let address = sender.create(nonce);
220 libs_to_deploy.push((id, address));
221 nonce += 1;
222
223 address.to_checksum(None)
224 });
225 }
226
227 let libs_to_deploy = libs_to_deploy
229 .into_par_iter()
230 .map(|(id, _)| {
231 Ok(self.link(id, &libraries)?.get_bytecode_bytes().unwrap().into_owned())
232 })
233 .collect::<Result<Vec<_>, LinkerError>>()?;
234
235 Ok(LinkOutput { libraries, libs_to_deploy })
236 }
237
238 pub fn link_with_create2(
239 &'a self,
240 libraries: Libraries,
241 sender: Address,
242 salt: B256,
243 target: &'a ArtifactId,
244 ) -> Result<LinkOutput, LinkerError> {
245 let mut libraries = libraries.with_stripped_file_prefixes(self.root.as_path());
248
249 let mut needed_libraries = BTreeSet::new();
250 self.collect_dependencies(target, &mut needed_libraries)?;
251
252 let mut needed_libraries = needed_libraries
253 .into_par_iter()
254 .filter(|id| {
255 let (file, name) = self.convert_artifact_id_to_lib_path(id);
257 libraries.libs.get(&file).is_none_or(|lib| !lib.contains_key(&name))
258 })
259 .map(|id| {
260 let bytecode = self.link(id, &libraries).unwrap().bytecode.unwrap();
262 (id, bytecode)
263 })
264 .collect::<Vec<_>>();
265
266 let mut libs_to_deploy = Vec::new();
267
268 while !needed_libraries.is_empty() {
271 let deployable = needed_libraries
273 .iter()
274 .enumerate()
275 .find(|(_, (_, bytecode))| !bytecode.object.is_unlinked());
276
277 let Some((index, &(id, _))) = deployable else {
279 return Err(LinkerError::CyclicDependency);
280 };
281 let (_, bytecode) = needed_libraries.swap_remove(index);
282 let code = bytecode.bytes().unwrap();
283 let address = sender.create2_from_code(salt, code);
284 libs_to_deploy.push(code.clone());
285
286 let (file, name) = self.convert_artifact_id_to_lib_path(id);
287
288 needed_libraries.par_iter_mut().for_each(|(_, bytecode)| {
289 bytecode.to_mut().link(&file.to_string_lossy(), &name, address);
290 });
291
292 libraries.libs.entry(file).or_default().insert(name, address.to_checksum(None));
293 }
294
295 Ok(LinkOutput { libraries, libs_to_deploy })
296 }
297
298 pub fn link(
300 &self,
301 target: &ArtifactId,
302 libraries: &Libraries,
303 ) -> Result<CompactContractBytecodeCow<'a>, LinkerError> {
304 let mut contract =
305 self.contracts.get(target).ok_or(LinkerError::MissingTargetArtifact)?.clone();
306 for (file, libs) in &libraries.libs {
307 for (name, address) in libs {
308 let address = Address::from_str(address).map_err(LinkerError::InvalidAddress)?;
309 if let Some(bytecode) = contract.bytecode.as_mut() {
310 self.link_bytecode(bytecode.to_mut(), file, name, address);
311 }
312 if let Some(deployed_bytecode) =
313 contract.deployed_bytecode.as_mut().and_then(|b| b.to_mut().bytecode.as_mut())
314 {
315 self.link_bytecode(deployed_bytecode, file, name, address);
316 }
317 }
318 }
319 Ok(contract)
320 }
321
322 pub fn ensure_linked(
324 &self,
325 contract: &CompactContractBytecodeCow<'a>,
326 target: &ArtifactId,
327 ) -> Result<(), LinkerError> {
328 if let Some(bytecode) = &contract.bytecode
329 && bytecode.object.is_unlinked()
330 {
331 return Err(LinkerError::LinkingFailed {
332 artifact: target.source.to_string_lossy().into(),
333 });
334 }
335 if let Some(deployed_bytecode) = &contract.deployed_bytecode
336 && let Some(deployed_bytecode_obj) = &deployed_bytecode.bytecode
337 && deployed_bytecode_obj.object.is_unlinked()
338 {
339 return Err(LinkerError::LinkingFailed {
340 artifact: target.source.to_string_lossy().into(),
341 });
342 }
343 Ok(())
344 }
345
346 pub fn get_linked_artifacts(
347 &self,
348 libraries: &Libraries,
349 ) -> Result<ArtifactContracts, LinkerError> {
350 self.get_linked_artifacts_cow(libraries).map(ArtifactContracts::from_iter)
351 }
352
353 pub fn get_linked_artifacts_cow(
354 &self,
355 libraries: &Libraries,
356 ) -> Result<ArtifactContracts<CompactContractBytecodeCow<'a>>, LinkerError> {
357 self.contracts
358 .par_iter()
359 .map(|(id, _)| Ok((id.clone(), self.link(id, libraries)?)))
360 .collect::<Result<_, _>>()
361 .map(ArtifactContracts)
362 }
363}
364
365#[cfg(test)]
366mod tests {
367 use super::*;
368 use alloy_primitives::{address, fixed_bytes, map::HashMap};
369 use foundry_compilers::{
370 Project, ProjectCompileOutput, ProjectPathsConfig,
371 multi::MultiCompiler,
372 solc::{Solc, SolcCompiler},
373 };
374 use std::sync::OnceLock;
375
376 fn testdata() -> &'static Path {
377 static CACHE: OnceLock<PathBuf> = OnceLock::new();
378 CACHE.get_or_init(|| {
379 PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../testdata").canonicalize().unwrap()
380 })
381 }
382
383 #[must_use]
384 struct LinkerTest {
385 project: Project,
386 output: ProjectCompileOutput,
387 dependency_assertions: HashMap<&'static str, Vec<(&'static str, Address)>>,
388 }
389
390 impl LinkerTest {
391 fn new(path: &Path, strip_prefixes: bool) -> Self {
392 assert!(path.exists(), "Path {path:?} does not exist");
393 let paths = ProjectPathsConfig::builder()
394 .root(testdata())
395 .lib(testdata().join("lib"))
396 .sources(path)
397 .tests(path)
398 .build()
399 .unwrap();
400
401 let solc = Solc::find_or_install(&Version::new(0, 8, 18)).unwrap();
402 let project = Project::builder()
403 .paths(paths)
404 .ephemeral()
405 .no_artifacts()
406 .build(MultiCompiler { solc: Some(SolcCompiler::Specific(solc)), vyper: None })
407 .unwrap();
408
409 let mut output = project.compile().unwrap();
410
411 if strip_prefixes {
412 output = output.with_stripped_file_prefixes(project.root());
413 }
414
415 Self { project, output, dependency_assertions: HashMap::default() }
416 }
417
418 fn assert_dependencies(
419 mut self,
420 artifact_id: &'static str,
421 deps: &[(&'static str, Address)],
422 ) -> Self {
423 self.dependency_assertions.insert(artifact_id, deps.to_vec());
424 self
425 }
426
427 fn test_with_sender_and_nonce(self, sender: Address, initial_nonce: u64) {
428 let linker = Linker::new(self.project.root(), self.output.artifact_ids().collect());
429 for (id, identifier) in self.iter_linking_targets(&linker) {
430 let output = linker
431 .link_with_nonce_or_address(Default::default(), sender, initial_nonce, [id])
432 .expect("Linking failed");
433 self.validate_assertions(identifier, output);
434 }
435 }
436
437 fn test_with_create2(self, sender: Address, salt: B256) {
438 let linker = Linker::new(self.project.root(), self.output.artifact_ids().collect());
439 for (id, identifier) in self.iter_linking_targets(&linker) {
440 let output = linker
441 .link_with_create2(Default::default(), sender, salt, id)
442 .expect("Linking failed");
443 self.validate_assertions(identifier, output);
444 }
445 }
446
447 fn iter_linking_targets<'a>(
448 &'a self,
449 linker: &'a Linker<'_>,
450 ) -> impl Iterator<Item = (&'a ArtifactId, String)> + 'a {
451 self.sanity_check(linker);
452 linker.contracts.keys().filter_map(move |id| {
453 let source = id
457 .source
458 .strip_prefix(self.project.root())
459 .unwrap_or(&id.source)
460 .to_string_lossy();
461 let identifier = format!("{source}:{}", id.name);
462
463 if identifier.contains("utils/") {
465 return None;
466 }
467
468 Some((id, identifier))
469 })
470 }
471
472 fn sanity_check(&self, linker: &Linker<'_>) {
473 assert!(!self.dependency_assertions.is_empty(), "Dependency assertions are empty");
474 assert!(!linker.contracts.is_empty(), "Linker contracts are empty");
475 }
476
477 fn validate_assertions(&self, identifier: String, output: LinkOutput) {
478 let LinkOutput { libs_to_deploy, libraries } = output;
479
480 let assertions = self
481 .dependency_assertions
482 .get(identifier.as_str())
483 .unwrap_or_else(|| panic!("Unexpected artifact: {identifier}"));
484
485 assert_eq!(
486 libs_to_deploy.len(),
487 assertions.len(),
488 "artifact {identifier} has more/less dependencies than expected ({} vs {}): {:#?}",
489 libs_to_deploy.len(),
490 assertions.len(),
491 libs_to_deploy
492 );
493
494 for &(dep_identifier, address) in assertions {
495 let (file, name) = dep_identifier.split_once(':').unwrap();
496 if let Some(lib_address) =
497 libraries.libs.get(Path::new(file)).and_then(|libs| libs.get(name))
498 {
499 assert_eq!(
500 lib_address.parse::<Address>().unwrap(),
501 address,
502 "incorrect library address for dependency {dep_identifier} of {identifier}"
503 );
504 } else {
505 panic!("Library {dep_identifier} not found");
506 }
507 }
508 }
509 }
510
511 fn link_test(path: impl AsRef<Path>, mut test_fn: impl FnMut(LinkerTest)) {
512 fn link_test(path: &Path, test_fn: &mut dyn FnMut(LinkerTest)) {
513 test_fn(LinkerTest::new(path, true));
514 test_fn(LinkerTest::new(path, false));
515 }
516 link_test(path.as_ref(), &mut test_fn);
517 }
518
519 #[test]
520 #[should_panic = "assertions are empty"]
521 fn no_assertions() {
522 link_test(testdata().join("default/linking/simple"), |linker| {
523 linker.test_with_sender_and_nonce(Address::default(), 1);
524 });
525 }
526
527 #[test]
528 #[should_panic = "does not exist"]
529 fn unknown_path() {
530 link_test("doesnotexist", |linker| {
531 linker
532 .assert_dependencies("a:b", &[])
533 .test_with_sender_and_nonce(Address::default(), 1);
534 });
535 }
536
537 #[test]
538 fn link_simple() {
539 link_test(testdata().join("default/linking/simple"), |linker| {
540 linker
541 .assert_dependencies("default/linking/simple/Simple.t.sol:Lib", &[])
542 .assert_dependencies(
543 "default/linking/simple/Simple.t.sol:LibraryConsumer",
544 &[(
545 "default/linking/simple/Simple.t.sol:Lib",
546 address!("0x5a443704dd4b594b382c22a083e2bd3090a6fef3"),
547 )],
548 )
549 .assert_dependencies(
550 "default/linking/simple/Simple.t.sol:SimpleLibraryLinkingTest",
551 &[(
552 "default/linking/simple/Simple.t.sol:Lib",
553 address!("0x5a443704dd4b594b382c22a083e2bd3090a6fef3"),
554 )],
555 )
556 .test_with_sender_and_nonce(Address::default(), 1);
557 });
558 }
559
560 #[test]
561 fn link_nested() {
562 link_test(testdata().join("default/linking/nested"), |linker| {
563 linker
564 .assert_dependencies("default/linking/nested/Nested.t.sol:Lib", &[])
565 .assert_dependencies(
566 "default/linking/nested/Nested.t.sol:NestedLib",
567 &[(
568 "default/linking/nested/Nested.t.sol:Lib",
569 address!("0x5a443704dd4b594b382c22a083e2bd3090a6fef3"),
570 )],
571 )
572 .assert_dependencies(
573 "default/linking/nested/Nested.t.sol:LibraryConsumer",
574 &[
575 (
578 "default/linking/nested/Nested.t.sol:Lib",
579 Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3")
580 .unwrap(),
581 ),
582 (
583 "default/linking/nested/Nested.t.sol:NestedLib",
584 Address::from_str("0x47e9Fbef8C83A1714F1951F142132E6e90F5fa5D")
585 .unwrap(),
586 ),
587 ],
588 )
589 .assert_dependencies(
590 "default/linking/nested/Nested.t.sol:NestedLibraryLinkingTest",
591 &[
592 (
593 "default/linking/nested/Nested.t.sol:Lib",
594 Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3")
595 .unwrap(),
596 ),
597 (
598 "default/linking/nested/Nested.t.sol:NestedLib",
599 Address::from_str("0x47e9fbef8c83a1714f1951f142132e6e90f5fa5d")
600 .unwrap(),
601 ),
602 ],
603 )
604 .test_with_sender_and_nonce(Address::default(), 1);
605 });
606 }
607
608 #[test]
609 fn link_duplicate() {
610 link_test(testdata().join("default/linking/duplicate"), |linker| {
611 linker
612 .assert_dependencies("default/linking/duplicate/Duplicate.t.sol:A", &[])
613 .assert_dependencies("default/linking/duplicate/Duplicate.t.sol:B", &[])
614 .assert_dependencies(
615 "default/linking/duplicate/Duplicate.t.sol:C",
616 &[(
617 "default/linking/duplicate/Duplicate.t.sol:A",
618 address!("0x5a443704dd4b594b382c22a083e2bd3090a6fef3"),
619 )],
620 )
621 .assert_dependencies(
622 "default/linking/duplicate/Duplicate.t.sol:D",
623 &[(
624 "default/linking/duplicate/Duplicate.t.sol:B",
625 address!("0x5a443704dd4b594b382c22a083e2bd3090a6fef3"),
626 )],
627 )
628 .assert_dependencies(
629 "default/linking/duplicate/Duplicate.t.sol:E",
630 &[
631 (
632 "default/linking/duplicate/Duplicate.t.sol:A",
633 Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3")
634 .unwrap(),
635 ),
636 (
637 "default/linking/duplicate/Duplicate.t.sol:C",
638 Address::from_str("0x47e9fbef8c83a1714f1951f142132e6e90f5fa5d")
639 .unwrap(),
640 ),
641 ],
642 )
643 .assert_dependencies(
644 "default/linking/duplicate/Duplicate.t.sol:LibraryConsumer",
645 &[
646 (
647 "default/linking/duplicate/Duplicate.t.sol:A",
648 Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3")
649 .unwrap(),
650 ),
651 (
652 "default/linking/duplicate/Duplicate.t.sol:B",
653 Address::from_str("0x47e9fbef8c83a1714f1951f142132e6e90f5fa5d")
654 .unwrap(),
655 ),
656 (
657 "default/linking/duplicate/Duplicate.t.sol:C",
658 Address::from_str("0x8be503bcded90ed42eff31f56199399b2b0154ca")
659 .unwrap(),
660 ),
661 (
662 "default/linking/duplicate/Duplicate.t.sol:D",
663 Address::from_str("0x47c5e40890bce4a473a49d7501808b9633f29782")
664 .unwrap(),
665 ),
666 (
667 "default/linking/duplicate/Duplicate.t.sol:E",
668 Address::from_str("0x29b2440db4a256b0c1e6d3b4cdcaa68e2440a08f")
669 .unwrap(),
670 ),
671 ],
672 )
673 .assert_dependencies(
674 "default/linking/duplicate/Duplicate.t.sol:DuplicateLibraryLinkingTest",
675 &[
676 (
677 "default/linking/duplicate/Duplicate.t.sol:A",
678 Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3")
679 .unwrap(),
680 ),
681 (
682 "default/linking/duplicate/Duplicate.t.sol:B",
683 Address::from_str("0x47e9fbef8c83a1714f1951f142132e6e90f5fa5d")
684 .unwrap(),
685 ),
686 (
687 "default/linking/duplicate/Duplicate.t.sol:C",
688 Address::from_str("0x8be503bcded90ed42eff31f56199399b2b0154ca")
689 .unwrap(),
690 ),
691 (
692 "default/linking/duplicate/Duplicate.t.sol:D",
693 Address::from_str("0x47c5e40890bce4a473a49d7501808b9633f29782")
694 .unwrap(),
695 ),
696 (
697 "default/linking/duplicate/Duplicate.t.sol:E",
698 Address::from_str("0x29b2440db4a256b0c1e6d3b4cdcaa68e2440a08f")
699 .unwrap(),
700 ),
701 ],
702 )
703 .test_with_sender_and_nonce(Address::default(), 1);
704 });
705 }
706
707 #[test]
708 fn link_cycle() {
709 link_test(testdata().join("default/linking/cycle"), |linker| {
710 linker
711 .assert_dependencies(
712 "default/linking/cycle/Cycle.t.sol:Foo",
713 &[
714 (
715 "default/linking/cycle/Cycle.t.sol:Foo",
716 Address::from_str("0x47e9Fbef8C83A1714F1951F142132E6e90F5fa5D")
717 .unwrap(),
718 ),
719 (
720 "default/linking/cycle/Cycle.t.sol:Bar",
721 Address::from_str("0x5a443704dd4B594B382c22a083e2BD3090A6feF3")
722 .unwrap(),
723 ),
724 ],
725 )
726 .assert_dependencies(
727 "default/linking/cycle/Cycle.t.sol:Bar",
728 &[
729 (
730 "default/linking/cycle/Cycle.t.sol:Foo",
731 Address::from_str("0x47e9Fbef8C83A1714F1951F142132E6e90F5fa5D")
732 .unwrap(),
733 ),
734 (
735 "default/linking/cycle/Cycle.t.sol:Bar",
736 Address::from_str("0x5a443704dd4B594B382c22a083e2BD3090A6feF3")
737 .unwrap(),
738 ),
739 ],
740 )
741 .test_with_sender_and_nonce(Address::default(), 1);
742 });
743 }
744
745 #[test]
746 #[ignore = "addresses depend on testdata utils internals for some reason"]
747 fn link_create2_nested() {
748 link_test(testdata().join("default/linking/nested"), |linker| {
749 linker
750 .assert_dependencies("default/linking/nested/Nested.t.sol:Lib", &[])
751 .assert_dependencies(
752 "default/linking/nested/Nested.t.sol:NestedLib",
753 &[(
754 "default/linking/nested/Nested.t.sol:Lib",
755 address!("0x773253227cce756e50c3993ec6366b3ec27786f9"),
756 )],
757 )
758 .assert_dependencies(
759 "default/linking/nested/Nested.t.sol:LibraryConsumer",
760 &[
761 (
764 "default/linking/nested/Nested.t.sol:Lib",
765 Address::from_str("0x773253227cce756e50c3993ec6366b3ec27786f9")
766 .unwrap(),
767 ),
768 (
769 "default/linking/nested/Nested.t.sol:NestedLib",
770 Address::from_str("0xac231df03403867b05d092c26fc91b6b83f4bebe")
771 .unwrap(),
772 ),
773 ],
774 )
775 .assert_dependencies(
776 "default/linking/nested/Nested.t.sol:NestedLibraryLinkingTest",
777 &[
778 (
779 "default/linking/nested/Nested.t.sol:Lib",
780 Address::from_str("0x773253227cce756e50c3993ec6366b3ec27786f9")
781 .unwrap(),
782 ),
783 (
784 "default/linking/nested/Nested.t.sol:NestedLib",
785 Address::from_str("0xac231df03403867b05d092c26fc91b6b83f4bebe")
786 .unwrap(),
787 ),
788 ],
789 )
790 .test_with_create2(
791 Address::default(),
792 fixed_bytes!(
793 "19bf59b7b67ae8edcbc6e53616080f61fa99285c061450ad601b0bc40c9adfc9"
794 ),
795 );
796 });
797 }
798
799 #[test]
800 fn link_samefile_union() {
801 link_test(testdata().join("default/linking/samefile_union"), |linker| {
802 linker
803 .assert_dependencies("default/linking/samefile_union/Libs.sol:LInit", &[])
804 .assert_dependencies("default/linking/samefile_union/Libs.sol:LRun", &[])
805 .assert_dependencies(
806 "default/linking/samefile_union/SameFileUnion.t.sol:UsesBoth",
807 &[
808 (
809 "default/linking/samefile_union/Libs.sol:LInit",
810 Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3")
811 .unwrap(),
812 ),
813 (
814 "default/linking/samefile_union/Libs.sol:LRun",
815 Address::from_str("0x47e9fbef8c83a1714f1951f142132e6e90f5fa5d")
816 .unwrap(),
817 ),
818 ],
819 )
820 .test_with_sender_and_nonce(Address::default(), 1);
821 });
822 }
823
824 #[test]
825 fn linking_failure() {
826 let linker = LinkerTest::new(&testdata().join("default/linking/simple"), true);
827 let linker_instance =
828 Linker::new(linker.project.root(), linker.output.artifact_ids().collect());
829
830 let mut libraries = Libraries::default();
832 libraries.libs.entry("default/linking/simple/Simple.t.sol".into()).or_default().insert(
833 "NonExistentLib".to_string(),
834 "0x5a443704dd4b594b382c22a083e2bd3090a6fef3".to_string(),
835 );
836
837 let artifact_id = linker_instance
839 .contracts
840 .keys()
841 .find(|id| id.name == "LibraryConsumer")
842 .expect("LibraryConsumer contract not found");
843
844 let contract = linker_instance.contracts.get(artifact_id).unwrap();
845
846 assert!(
848 linker_instance.ensure_linked(contract, artifact_id).is_err(),
849 "Expected artifact to have unlinked bytecode"
850 );
851 }
852}