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