1#![cfg_attr(not(test), warn(unused_crate_dependencies))]
6#![cfg_attr(docsrs, feature(doc_cfg))]
7
8use alloy_primitives::{Address, B256, Bytes, map::HashMap as AlloyHashMap};
9use foundry_compilers::{
10 Artifact, ArtifactId,
11 artifacts::{CompactBytecode, CompactContractBytecodeCow, Libraries},
12 contracts::ArtifactContracts,
13};
14use rayon::prelude::*;
15use std::{
16 collections::{BTreeMap, BTreeSet},
17 path::{Path, PathBuf},
18 str::FromStr,
19 sync::OnceLock,
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("multiple library artifacts resolve to the same key {file}:{name}")]
28 ConflictingLibraryArtifacts { file: String, name: String },
29 #[error("target artifact is not present in provided artifacts set")]
30 MissingTargetArtifact,
31 #[error(transparent)]
32 InvalidAddress(<Address as std::str::FromStr>::Err),
33 #[error("cyclic dependency found, can't link libraries via CREATE2")]
34 CyclicDependency,
35 #[error("failed linking {artifact}")]
36 LinkingFailed { artifact: String },
37}
38
39type Index = AlloyHashMap<PathBuf, AlloyHashMap<String, Vec<ArtifactId>>>;
40
41pub struct Linker<'a> {
42 pub root: PathBuf,
44 pub contracts: ArtifactContracts<CompactContractBytecodeCow<'a>>,
46}
47
48pub struct Resolver<'a, 'b> {
50 linker: &'b Linker<'a>,
51 index: OnceLock<Index>,
52}
53
54pub struct LinkOutput {
56 pub libraries: Libraries,
61 pub library_addresses: Vec<Address>,
63 pub libs_to_deploy: Vec<Bytes>,
66}
67
68pub struct DetailedLinkOutput {
70 pub output: LinkOutput,
72 pub artifact_libraries: BTreeMap<ArtifactId, Libraries>,
74 pub artifact_addresses: BTreeMap<ArtifactId, Address>,
76 pub linked_libraries: Vec<LinkedLibrary>,
82}
83
84#[derive(Clone, Debug)]
86pub struct LinkedLibrary {
87 pub id: ArtifactId,
89 pub address: Address,
91 pub bytecode: Bytes,
93}
94
95impl<'a> Linker<'a> {
96 pub fn new(
97 root: impl Into<PathBuf>,
98 contracts: ArtifactContracts<CompactContractBytecodeCow<'a>>,
99 ) -> Self {
100 Linker { root: root.into(), contracts }
101 }
102
103 fn convert_artifact_id_to_lib_path(&self, id: &ArtifactId) -> (PathBuf, String) {
108 let name = id.name.split('.').next().unwrap();
110
111 (self.project_relative_path(&id.source), name.to_owned())
112 }
113
114 fn project_relative_path(&self, path: &Path) -> PathBuf {
115 if path.is_relative() {
116 return path.to_path_buf();
117 }
118
119 if let Ok(stripped) = path.strip_prefix(&self.root) {
120 return stripped.to_path_buf();
121 }
122
123 if let Ok(root) = self.root.canonicalize()
124 && let Ok(path) = path.canonicalize()
125 && let Ok(stripped) = path.strip_prefix(root)
126 {
127 return stripped.to_path_buf();
128 }
129
130 path.to_path_buf()
131 }
132
133 fn index<'b>(&self, index: &'b OnceLock<Index>) -> &'b Index {
134 index.get_or_init(|| {
135 let mut artifacts = AlloyHashMap::<_, AlloyHashMap<_, Vec<_>>>::default();
136 for id in self.contracts.keys() {
137 let path = self.project_relative_path(&id.source);
138 let name = id.name.split('.').next().unwrap().to_owned();
139 artifacts.entry(path).or_default().entry(name).or_default().push(id.clone());
140 }
141 artifacts
142 })
143 }
144
145 fn canonical_path(&self, path: &Path) -> Option<PathBuf> {
147 let path = if path.is_relative() { self.root.join(path) } else { path.to_path_buf() };
148 path.canonicalize().ok()
149 }
150
151 fn path_matches(
152 &self,
153 path: &Path,
154 expected: &Path,
155 canonical_expected: Option<&Path>,
156 ) -> bool {
157 let path = self.project_relative_path(path);
158 path == expected
159 || canonical_expected
160 .is_some_and(|expected| self.canonical_path(&path).as_deref() == Some(expected))
161 }
162
163 fn link_bytecode(
164 &self,
165 bytecode: &mut CompactBytecode,
166 target: &ArtifactId,
167 file: &Path,
168 name: &str,
169 address: Address,
170 ) -> Result<(), LinkerError> {
171 self.link_bytecode_inner(bytecode, target, file, name, address, true)
172 }
173
174 fn link_bytecode_inner(
175 &self,
176 bytecode: &mut CompactBytecode,
177 target: &ArtifactId,
178 file: &Path,
179 name: &str,
180 address: Address,
181 canonical: bool,
182 ) -> Result<(), LinkerError> {
183 let file_relative = self.project_relative_path(file);
184 let canonical_file = self.canonical_path(&file_relative);
185 let mut references = Vec::new();
186 for (reference, libraries) in &bytecode.link_references {
187 if !libraries.contains_key(name) {
188 continue;
189 }
190 let reference_path = self.project_relative_path(Path::new(reference));
191 if reference_path == file_relative {
192 references.push(reference.clone());
193 continue;
194 }
195 if !canonical {
196 continue;
197 }
198 if !self.path_matches(Path::new(reference), &file_relative, canonical_file.as_deref()) {
199 continue;
200 }
201 if let Some(id) =
202 self.find_artifact_id_by_library_path(None, reference, name, target)?
203 {
204 let (artifact_file, artifact_name) = self.convert_artifact_id_to_lib_path(id);
205 if artifact_file == file_relative && artifact_name == name {
206 references.push(reference.clone());
207 }
208 }
209 }
210
211 if references.is_empty() && canonical {
212 bytecode.link(&file.to_string_lossy(), name, address);
213 } else {
214 for reference in references {
215 bytecode.link(&reference, name, address);
216 }
217 }
218 Ok(())
219 }
220
221 fn find_artifact_id_by_library_path<'b>(
227 &'b self,
228 index: Option<&'b OnceLock<Index>>,
229 file: &str,
230 name: &str,
231 target: &ArtifactId,
232 ) -> Result<Option<&'b ArtifactId>, LinkerError> {
233 let library_path = self.project_relative_path(Path::new(file));
234 let candidates = if let Some(index) = index {
235 self.index(index)
236 .get(&library_path)
237 .and_then(|artifacts| artifacts.get(name))
238 .into_iter()
239 .flatten()
240 .filter(|id| id.version == target.version)
241 .collect::<Vec<_>>()
242 } else {
243 self.contracts
244 .keys()
245 .filter(|id| {
246 if id.version != target.version {
247 return false;
248 }
249 let (artifact_path, artifact_name) = self.convert_artifact_id_to_lib_path(id);
250 artifact_name == *name && artifact_path == library_path
251 })
252 .collect()
253 };
254 let candidates = if candidates.is_empty() {
255 let canonical_library_path = self.canonical_path(&library_path);
256 self.contracts
257 .keys()
258 .filter(|id| {
259 if id.version != target.version {
260 return false;
261 }
262 let (artifact_path, artifact_name) = self.convert_artifact_id_to_lib_path(id);
263 artifact_name == *name
264 && self.path_matches(
265 &artifact_path,
266 &library_path,
267 canonical_library_path.as_deref(),
268 )
269 })
270 .collect()
271 } else {
272 candidates
273 };
274 let same_build_and_profile = candidates
275 .iter()
276 .copied()
277 .filter(|id| id.build_id == target.build_id && id.profile == target.profile)
278 .collect::<Vec<_>>();
279 let candidates =
280 if same_build_and_profile.is_empty() { candidates } else { same_build_and_profile };
281
282 if candidates.len() > 1 {
283 return Err(LinkerError::ConflictingLibraryArtifacts {
284 file: library_path.display().to_string(),
285 name: name.to_owned(),
286 });
287 }
288 Ok(candidates.into_iter().next())
289 }
290
291 fn direct_dependencies<'b>(
292 &'b self,
293 index: &'b OnceLock<Index>,
294 target: &ArtifactId,
295 references: &mut BTreeMap<&'b ArtifactId, BTreeSet<PathBuf>>,
296 ) -> Result<BTreeSet<&'b ArtifactId>, LinkerError> {
297 let contract = self.contracts.get(target).ok_or(LinkerError::MissingTargetArtifact)?;
298
299 let mut link_references: BTreeMap<String, BTreeSet<String>> = BTreeMap::new();
300 let mut extend = |bytecode: &CompactBytecode| {
301 for (file, libs) in &bytecode.link_references {
302 link_references.entry(file.clone()).or_default().extend(libs.keys().cloned());
303 }
304 };
305 if let Some(bytecode) = &contract.bytecode {
306 extend(bytecode);
307 }
308 if let Some(deployed_bytecode) = &contract.deployed_bytecode
309 && let Some(bytecode) = &deployed_bytecode.bytecode
310 {
311 extend(bytecode);
312 }
313
314 let mut dependencies = BTreeSet::new();
315 for (file, libs) in link_references {
316 for name in libs {
317 let id = self
318 .find_artifact_id_by_library_path(Some(index), &file, &name, target)?
319 .ok_or_else(|| LinkerError::MissingLibraryArtifact {
320 file: file.clone(),
321 name,
322 })?;
323 references.entry(id).or_default().insert(file.clone().into());
324 dependencies.insert(id);
325 }
326 }
327
328 Ok(dependencies)
329 }
330
331 fn collect_dependencies<'b>(
333 &'b self,
334 index: &'b OnceLock<Index>,
335 target: &ArtifactId,
336 deps: &mut BTreeSet<&'b ArtifactId>,
337 references: &mut BTreeMap<&'b ArtifactId, BTreeSet<PathBuf>>,
338 ) -> Result<(), LinkerError> {
339 for id in self.direct_dependencies(index, target, references)? {
340 if deps.insert(id) {
341 self.collect_dependencies(index, id, deps, references)?;
342 }
343 }
344
345 Ok(())
346 }
347
348 fn apply_configured_references(
349 &self,
350 references: &BTreeMap<&ArtifactId, BTreeSet<PathBuf>>,
351 libraries: &mut Libraries,
352 ) -> Result<(), LinkerError> {
353 for (id, references) in references {
354 let (file, name) = self.convert_artifact_id_to_lib_path(id);
355 let mut configured = references
356 .iter()
357 .filter_map(|reference| libraries.libs.get(reference)?.get(&name))
358 .map(|address| Address::from_str(address).map_err(LinkerError::InvalidAddress))
359 .collect::<Result<BTreeSet<_>, _>>()?;
360 let exact = !configured.is_empty();
361 if configured.is_empty()
362 && let Some(canonical_file) = self.canonical_path(&file)
363 {
364 configured = libraries
365 .libs
366 .iter()
367 .filter_map(|(configured_file, libraries)| {
368 if self.canonical_path(configured_file).as_deref() == Some(&canonical_file)
369 {
370 libraries.get(&name)
371 } else {
372 None
373 }
374 })
375 .map(|address| Address::from_str(address).map_err(LinkerError::InvalidAddress))
376 .collect::<Result<BTreeSet<_>, _>>()?;
377 }
378 if configured.len() > 1 {
379 return Err(LinkerError::ConflictingLibraryArtifacts {
380 file: file.display().to_string(),
381 name,
382 });
383 }
384 let Some(address) = configured.first().copied() else { continue };
385 let canonical = libraries.libs.entry(file.clone()).or_default();
386 if exact {
387 canonical.insert(name, address.to_checksum(None));
388 } else {
389 canonical.entry(name).or_insert_with(|| address.to_checksum(None));
390 }
391 }
392 Ok(())
393 }
394
395 fn collect_library_keys(
396 &self,
397 needed_libraries: &BTreeSet<&ArtifactId>,
398 libraries: &Libraries,
399 ) -> Result<BTreeSet<(PathBuf, String)>, LinkerError> {
400 let mut library_keys = BTreeSet::new();
401 for id in needed_libraries {
402 let (file, name) = self.convert_artifact_id_to_lib_path(id);
403 let is_configured =
404 libraries.libs.get(&file).is_some_and(|libraries| libraries.contains_key(&name));
405 if !library_keys.insert((file.clone(), name.clone())) && !is_configured {
406 return Err(LinkerError::ConflictingLibraryArtifacts {
407 file: file.display().to_string(),
408 name,
409 });
410 }
411 }
412 Ok(library_keys)
413 }
414
415 fn configured_library_address(
416 &self,
417 id: &ArtifactId,
418 libraries: &Libraries,
419 ) -> Result<Option<Address>, LinkerError> {
420 let (file, name) = self.convert_artifact_id_to_lib_path(id);
421 libraries
422 .libs
423 .get(&file)
424 .and_then(|libraries| libraries.get(&name))
425 .map(|address| Address::from_str(address).map_err(LinkerError::InvalidAddress))
426 .transpose()
427 }
428
429 fn libraries_for_artifact<'b>(
430 &'b self,
431 index: &'b OnceLock<Index>,
432 target: &ArtifactId,
433 configured: &Libraries,
434 addresses: &BTreeMap<&'b ArtifactId, Address>,
435 ) -> Result<Libraries, LinkerError> {
436 let mut libraries = configured.clone();
437 let mut dependencies = BTreeSet::new();
438 self.collect_dependencies(index, target, &mut dependencies, &mut BTreeMap::new())?;
439 for id in dependencies {
440 let Some(&address) = addresses.get(id) else { continue };
441 let (file, name) = self.convert_artifact_id_to_lib_path(id);
442 let entry = libraries.libs.entry(file.clone()).or_default().entry(name.clone());
443 if let std::collections::btree_map::Entry::Occupied(entry) = &entry
444 && Address::from_str(entry.get()).map_err(LinkerError::InvalidAddress)? != address
445 {
446 return Err(LinkerError::ConflictingLibraryArtifacts {
447 file: file.display().to_string(),
448 name,
449 });
450 }
451 entry.or_insert_with(|| address.to_checksum(None));
452 }
453 Ok(libraries)
454 }
455
456 fn artifact_libraries<'b>(
457 &'b self,
458 index: &'b OnceLock<Index>,
459 artifacts: impl IntoIterator<Item = &'b ArtifactId>,
460 configured: &Libraries,
461 addresses: &BTreeMap<&'b ArtifactId, Address>,
462 ) -> Result<BTreeMap<ArtifactId, Libraries>, LinkerError> {
463 artifacts
464 .into_iter()
465 .map(|id| {
466 Ok((id.clone(), self.libraries_for_artifact(index, id, configured, addresses)?))
467 })
468 .collect()
469 }
470
471 fn output_libraries(
472 &self,
473 mut libraries: Libraries,
474 addresses: &BTreeMap<&ArtifactId, Address>,
475 ) -> Libraries {
476 let mut by_key = BTreeMap::<(PathBuf, String), BTreeSet<Address>>::new();
477 for (&id, &address) in addresses {
478 by_key.entry(self.convert_artifact_id_to_lib_path(id)).or_default().insert(address);
479 }
480 for ((file, name), addresses) in by_key {
481 if let Some(address) = addresses.first().filter(|_| addresses.len() == 1) {
482 libraries
483 .libs
484 .entry(file)
485 .or_default()
486 .entry(name)
487 .or_insert_with(|| address.to_checksum(None));
488 }
489 }
490 libraries
491 }
492
493 fn assigned_library_addresses(
494 &self,
495 addresses: &BTreeMap<&ArtifactId, Address>,
496 ) -> Vec<Address> {
497 addresses.values().copied().collect::<BTreeSet<_>>().into_iter().collect()
498 }
499
500 fn artifact_addresses(
501 &self,
502 addresses: &BTreeMap<&ArtifactId, Address>,
503 ) -> BTreeMap<ArtifactId, Address> {
504 addresses.iter().map(|(&id, &address)| (id.clone(), address)).collect()
505 }
506
507 fn artifact_addresses_from_libraries(
508 &self,
509 artifacts: impl IntoIterator<Item = &'a ArtifactId>,
510 libraries: &Libraries,
511 ) -> Result<BTreeMap<ArtifactId, Address>, LinkerError> {
512 artifacts
513 .into_iter()
514 .map(|id| {
515 let address = self
516 .configured_library_address(id, libraries)?
517 .ok_or_else(|| LinkerError::LinkingFailed { artifact: id.identifier() })?;
518 Ok((id.clone(), address))
519 })
520 .collect()
521 }
522
523 fn ensure_flattened_libraries(
524 &self,
525 artifact_libraries: &BTreeMap<ArtifactId, Libraries>,
526 ) -> Result<(), LinkerError> {
527 let mut addresses = BTreeMap::<(PathBuf, String), BTreeSet<Address>>::new();
528 for libraries in artifact_libraries.values() {
529 for (file, libraries) in &libraries.libs {
530 for (name, address) in libraries {
531 addresses
532 .entry((file.clone(), name.clone()))
533 .or_default()
534 .insert(Address::from_str(address).map_err(LinkerError::InvalidAddress)?);
535 }
536 }
537 }
538 if let Some(((file, name), _)) =
539 addresses.into_iter().find(|(_, addresses)| addresses.len() > 1)
540 {
541 return Err(LinkerError::ConflictingLibraryArtifacts {
542 file: file.display().to_string(),
543 name,
544 });
545 }
546 Ok(())
547 }
548
549 fn library_addresses(
550 &self,
551 library_keys: &BTreeSet<(PathBuf, String)>,
552 libraries: &Libraries,
553 ) -> Result<Vec<Address>, LinkerError> {
554 let addresses = library_keys
555 .iter()
556 .map(|(file, name)| {
557 let address =
558 libraries.libs.get(file).and_then(|libraries| libraries.get(name)).ok_or_else(
559 || LinkerError::LinkingFailed {
560 artifact: format!("{}:{name}", file.display()),
561 },
562 )?;
563 Address::from_str(address).map_err(LinkerError::InvalidAddress)
564 })
565 .collect::<Result<BTreeSet<_>, _>>()?;
566 Ok(addresses.into_iter().collect())
567 }
568
569 pub fn linked_library_addresses(
571 &'a self,
572 target: &'a ArtifactId,
573 libraries: &Libraries,
574 ) -> Result<BTreeSet<Address>, LinkerError> {
575 Resolver::new(self).linked_library_addresses(target, libraries)
576 }
577
578 fn linked_library_addresses_inner<'b>(
579 &'b self,
580 index: &'b OnceLock<Index>,
581 target: &ArtifactId,
582 libraries: &Libraries,
583 ) -> Result<BTreeSet<Address>, LinkerError> {
584 let mut dependencies = BTreeSet::new();
585 let mut references = BTreeMap::new();
586 self.collect_dependencies(index, target, &mut dependencies, &mut references)?;
587 let mut libraries = libraries.clone();
588 self.apply_configured_references(&references, &mut libraries)?;
589
590 dependencies
591 .into_iter()
592 .map(|id| {
593 let (file, name) = self.convert_artifact_id_to_lib_path(id);
594 libraries
595 .libs
596 .get(&file)
597 .and_then(|libs| libs.get(&name))
598 .ok_or_else(|| LinkerError::LinkingFailed { artifact: id.identifier() })?
599 .parse()
600 .map_err(LinkerError::InvalidAddress)
601 })
602 .collect()
603 }
604
605 pub fn dependencies(
607 &'a self,
608 target: &'a ArtifactId,
609 ) -> Result<BTreeSet<ArtifactId>, LinkerError> {
610 let index = OnceLock::new();
611 self.dependencies_inner(&index, target)
612 }
613
614 fn dependencies_inner<'b>(
615 &'b self,
616 index: &'b OnceLock<Index>,
617 target: &ArtifactId,
618 ) -> Result<BTreeSet<ArtifactId>, LinkerError> {
619 let mut dependencies = BTreeSet::new();
620 self.collect_dependencies(index, target, &mut dependencies, &mut BTreeMap::new())?;
621 Ok(dependencies.into_iter().cloned().collect())
622 }
623
624 fn linked_creation_bytecode(
625 &self,
626 target: &ArtifactId,
627 libraries: &Libraries,
628 ) -> Result<Bytes, LinkerError> {
629 let contract = self.link(target, libraries)?;
630 self.ensure_linked(&contract, target)?;
631 contract.get_bytecode_bytes().map(|code| code.into_owned()).ok_or_else(|| {
632 LinkerError::LinkingFailed { artifact: target.source.to_string_lossy().into_owned() }
633 })
634 }
635
636 pub fn link_with_nonce_or_address(
646 &'a self,
647 libraries: Libraries,
648 sender: Address,
649 nonce: u64,
650 targets: impl IntoIterator<Item = &'a ArtifactId>,
651 ) -> Result<LinkOutput, LinkerError> {
652 let output = self.link_with_nonce_or_address_detailed(libraries, sender, nonce, targets)?;
653 self.ensure_flattened_libraries(&output.artifact_libraries)?;
654 Ok(output.output)
655 }
656
657 pub fn link_with_nonce_or_address_detailed(
659 &'a self,
660 libraries: Libraries,
661 sender: Address,
662 mut nonce: u64,
663 targets: impl IntoIterator<Item = &'a ArtifactId>,
664 ) -> Result<DetailedLinkOutput, LinkerError> {
665 let index = OnceLock::new();
666 let mut libraries = libraries.with_stripped_file_prefixes(self.root.as_path());
669
670 let targets = targets.into_iter().collect::<Vec<_>>();
671 let mut needed_libraries = BTreeSet::new();
672 let mut references = BTreeMap::new();
673 for &target in &targets {
674 self.collect_dependencies(&index, target, &mut needed_libraries, &mut references)?;
675 }
676 self.apply_configured_references(&references, &mut libraries)?;
677
678 let mut addresses = BTreeMap::new();
679 let mut libs_to_deploy = Vec::new();
680 for &id in &needed_libraries {
681 let address = if let Some(address) = self.configured_library_address(id, &libraries)? {
682 address
683 } else {
684 let address = sender.create(nonce);
685 nonce += 1;
686 libs_to_deploy.push((id, address));
687 address
688 };
689 addresses.insert(id, address);
690 }
691
692 let artifact_libraries = self.artifact_libraries(
693 &index,
694 targets.iter().copied().chain(needed_libraries.iter().copied()),
695 &libraries,
696 &addresses,
697 )?;
698
699 let linked_libraries = libs_to_deploy
701 .into_par_iter()
702 .map(|(id, address)| {
703 let artifact_libraries = artifact_libraries.get(id).unwrap();
704 let bytecode =
705 self.link(id, artifact_libraries)?.get_bytecode_bytes().unwrap().into_owned();
706 Ok(LinkedLibrary { id: id.clone(), address, bytecode })
707 })
708 .collect::<Result<Vec<_>, LinkerError>>()?;
709 let libs_to_deploy = linked_libraries.iter().map(|lib| lib.bytecode.clone()).collect();
710
711 let library_addresses = self.assigned_library_addresses(&addresses);
712 let artifact_addresses = self.artifact_addresses(&addresses);
713 let libraries = self.output_libraries(libraries, &addresses);
714 Ok(DetailedLinkOutput {
715 output: LinkOutput { libraries, library_addresses, libs_to_deploy },
716 artifact_libraries,
717 artifact_addresses,
718 linked_libraries,
719 })
720 }
721
722 pub fn link_with_create2(
723 &'a self,
724 libraries: Libraries,
725 sender: Address,
726 salt: B256,
727 targets: impl IntoIterator<Item = &'a ArtifactId>,
728 ) -> Result<LinkOutput, LinkerError> {
729 let output = self.link_with_create2_detailed(libraries, sender, salt, targets)?;
730 self.ensure_flattened_libraries(&output.artifact_libraries)?;
731 Ok(output.output)
732 }
733
734 pub fn link_with_create2_detailed(
736 &'a self,
737 libraries: Libraries,
738 sender: Address,
739 salt: B256,
740 targets: impl IntoIterator<Item = &'a ArtifactId>,
741 ) -> Result<DetailedLinkOutput, LinkerError> {
742 let index = OnceLock::new();
743 let mut libraries = libraries.with_stripped_file_prefixes(self.root.as_path());
746
747 let targets = targets.into_iter().collect::<Vec<_>>();
748 let mut needed_libraries = BTreeSet::new();
749 let mut references = BTreeMap::new();
750 for &target in &targets {
751 self.collect_dependencies(&index, target, &mut needed_libraries, &mut references)?;
752 }
753 self.apply_configured_references(&references, &mut libraries)?;
754
755 let mut addresses = BTreeMap::new();
756 let mut pending = Vec::new();
757 for &id in &needed_libraries {
758 if let Some(address) = self.configured_library_address(id, &libraries)? {
759 addresses.insert(id, address);
760 } else {
761 pending.push(id);
762 }
763 }
764 let mut linked_libraries = Vec::<LinkedLibrary>::new();
765
766 while !pending.is_empty() {
769 let mut deployable = None;
770 for (position, &id) in pending.iter().enumerate() {
771 let artifact_libraries =
772 self.libraries_for_artifact(&index, id, &libraries, &addresses)?;
773 let bytecode = self.link(id, &artifact_libraries)?.bytecode.ok_or_else(|| {
774 LinkerError::LinkingFailed { artifact: id.source.to_string_lossy().into() }
775 })?;
776 if !bytecode.object.is_unlinked() {
777 deployable = Some((position, id, bytecode));
778 break;
779 }
780 }
781 let Some((position, id, bytecode)) = deployable else {
782 return Err(LinkerError::CyclicDependency);
783 };
784 pending.swap_remove(position);
785 let code = bytecode.bytes().ok_or_else(|| LinkerError::LinkingFailed {
786 artifact: id.source.to_string_lossy().into(),
787 })?;
788 let address = sender.create2_from_code(salt, code);
789 if linked_libraries.iter().all(|library| library.address != address) {
790 linked_libraries.push(LinkedLibrary {
791 id: id.clone(),
792 address,
793 bytecode: code.clone(),
794 });
795 }
796 addresses.insert(id, address);
797 }
798
799 let artifact_libraries = self.artifact_libraries(
800 &index,
801 targets.iter().copied().chain(needed_libraries.iter().copied()),
802 &libraries,
803 &addresses,
804 )?;
805 let libs_to_deploy = linked_libraries.iter().map(|lib| lib.bytecode.clone()).collect();
806 let library_addresses = self.assigned_library_addresses(&addresses);
807 let artifact_addresses = self.artifact_addresses(&addresses);
808 let libraries = self.output_libraries(libraries, &addresses);
809 Ok(DetailedLinkOutput {
810 output: LinkOutput { libraries, library_addresses, libs_to_deploy },
811 artifact_libraries,
812 artifact_addresses,
813 linked_libraries,
814 })
815 }
816
817 pub fn link_with_partition(
819 &'a self,
820 libraries: Libraries,
821 sender: Address,
822 mut sender_nonce: u64,
823 local_deployer: Address,
824 required: &BTreeSet<ArtifactId>,
825 target: &'a ArtifactId,
826 ) -> Result<(DetailedLinkOutput, Vec<LinkedLibrary>), LinkerError> {
827 let index = OnceLock::new();
828 let mut libraries = libraries.with_stripped_file_prefixes(self.root.as_path());
829 let mut needed = BTreeSet::new();
830 let mut references = BTreeMap::new();
831 self.collect_dependencies(&index, target, &mut needed, &mut references)?;
832 self.apply_configured_references(&references, &mut libraries)?;
833 let library_keys = self.collect_library_keys(&needed, &libraries)?;
834 let mut required_with_dependencies = required.clone();
835 for id in required {
836 required_with_dependencies.extend(self.dependencies_inner(&index, id)?);
837 }
838 let mut local_nonce = 0;
839 let mut assigned = Vec::new();
840 for &id in &needed {
841 let (file, name) = self.convert_artifact_id_to_lib_path(id);
842 libraries.libs.entry(file).or_default().entry(name).or_insert_with(|| {
843 let onchain = required_with_dependencies.contains(id);
844 let address = if onchain {
845 let address = sender.create(sender_nonce);
846 sender_nonce += 1;
847 address
848 } else {
849 let address = local_deployer.create(local_nonce);
850 local_nonce += 1;
851 address
852 };
853 assigned.push((id, address, onchain));
854 address.to_checksum(None)
855 });
856 }
857 let linked = assigned
858 .into_iter()
859 .map(|(id, address, onchain)| {
860 let bytecode = self.linked_creation_bytecode(id, &libraries)?;
861 Ok((LinkedLibrary { id: id.clone(), address, bytecode }, onchain))
862 })
863 .collect::<Result<Vec<_>, LinkerError>>()?;
864 let libs_to_deploy = linked
865 .iter()
866 .filter(|(_, onchain)| *onchain)
867 .map(|(lib, _)| lib.bytecode.clone())
868 .collect();
869 let local =
870 linked.iter().filter(|(_, onchain)| !*onchain).map(|(lib, _)| lib.clone()).collect();
871 let linked_libraries = linked.into_iter().map(|(lib, _)| lib).collect();
872 let library_addresses = self.library_addresses(&library_keys, &libraries)?;
873 let artifact_addresses =
874 self.artifact_addresses_from_libraries(needed.iter().copied(), &libraries)?;
875 let artifact_libraries = BTreeMap::from([(target.clone(), libraries.clone())]);
876 Ok((
877 DetailedLinkOutput {
878 output: LinkOutput { libraries, library_addresses, libs_to_deploy },
879 artifact_libraries,
880 artifact_addresses,
881 linked_libraries,
882 },
883 local,
884 ))
885 }
886
887 pub fn link_with_create2_partition(
889 &'a self,
890 libraries: Libraries,
891 create2_deployer: Address,
892 salt: B256,
893 local_deployer: Address,
894 required: &BTreeSet<ArtifactId>,
895 target: &'a ArtifactId,
896 ) -> Result<(DetailedLinkOutput, Vec<LinkedLibrary>), LinkerError> {
897 let index = OnceLock::new();
898 let mut libraries = libraries.with_stripped_file_prefixes(self.root.as_path());
899 let mut needed = BTreeSet::new();
900 let mut references = BTreeMap::new();
901 self.collect_dependencies(&index, target, &mut needed, &mut references)?;
902 self.apply_configured_references(&references, &mut libraries)?;
903 let library_keys = self.collect_library_keys(&needed, &libraries)?;
904 let mut required_with_dependencies = required.clone();
905 for id in required {
906 required_with_dependencies.extend(self.dependencies_inner(&index, id)?);
907 }
908 let mut local_ids = Vec::new();
909 let mut required_ids = Vec::new();
910 for &id in &needed {
911 let (file, name) = self.convert_artifact_id_to_lib_path(id);
912 if libraries.libs.get(&file).is_some_and(|libs| libs.contains_key(&name)) {
913 continue;
914 }
915 if required_with_dependencies.contains(id) {
916 required_ids.push(id);
917 } else {
918 let address = local_deployer.create(local_ids.len() as u64);
919 libraries.libs.entry(file).or_default().insert(name, address.to_checksum(None));
920 local_ids.push((id, address));
921 }
922 }
923
924 let mut pending = required_ids
925 .into_iter()
926 .map(|id| {
927 let contract = self.link(id, &libraries)?;
928 let bytecode = contract.bytecode.ok_or_else(|| LinkerError::LinkingFailed {
929 artifact: id.source.to_string_lossy().into_owned(),
930 })?;
931 Ok((id, bytecode))
932 })
933 .collect::<Result<Vec<_>, LinkerError>>()?;
934 let mut onchain = Vec::new();
935 while !pending.is_empty() {
936 let Some(position) = pending.iter().position(|(_, code)| !code.object.is_unlinked())
937 else {
938 return Err(LinkerError::CyclicDependency);
939 };
940 let (id, code) = pending.swap_remove(position);
941 let bytecode = code.bytes().cloned().ok_or_else(|| LinkerError::LinkingFailed {
942 artifact: id.source.to_string_lossy().into_owned(),
943 })?;
944 let address = create2_deployer.create2_from_code(salt, &bytecode);
945 let (file, name) = self.convert_artifact_id_to_lib_path(id);
946 libraries
947 .libs
948 .entry(file.clone())
949 .or_default()
950 .insert(name.clone(), address.to_checksum(None));
951 for (target, pending_code) in &mut pending {
952 self.link_bytecode(pending_code.to_mut(), target, &file, &name, address)?;
953 }
954 onchain.push(LinkedLibrary { id: id.clone(), address, bytecode });
955 }
956
957 let local_bytecodes = local_ids
958 .iter()
959 .map(|(id, _)| self.linked_creation_bytecode(id, &libraries))
960 .collect::<Result<Vec<_>, LinkerError>>()?;
961 let mut linked_libraries = onchain.clone();
962 let local = local_ids
963 .into_iter()
964 .zip(local_bytecodes)
965 .map(|((id, address), bytecode)| LinkedLibrary { id: id.clone(), address, bytecode })
966 .collect::<Vec<_>>();
967 linked_libraries.extend(local.iter().cloned());
968 let libs_to_deploy = onchain.into_iter().map(|lib| lib.bytecode).collect();
969 let library_addresses = self.library_addresses(&library_keys, &libraries)?;
970 let artifact_addresses =
971 self.artifact_addresses_from_libraries(needed.iter().copied(), &libraries)?;
972 let artifact_libraries = BTreeMap::from([(target.clone(), libraries.clone())]);
973 Ok((
974 DetailedLinkOutput {
975 output: LinkOutput { libraries, library_addresses, libs_to_deploy },
976 artifact_libraries,
977 artifact_addresses,
978 linked_libraries,
979 },
980 local,
981 ))
982 }
983
984 pub fn link(
986 &self,
987 target: &ArtifactId,
988 libraries: &Libraries,
989 ) -> Result<CompactContractBytecodeCow<'a>, LinkerError> {
990 let mut contract =
991 self.contracts.get(target).ok_or(LinkerError::MissingTargetArtifact)?.clone();
992 let libraries = libraries
993 .libs
994 .iter()
995 .flat_map(|(file, libraries)| {
996 libraries.iter().map(move |(name, address)| {
997 Ok((
998 file,
999 name,
1000 Address::from_str(address).map_err(LinkerError::InvalidAddress)?,
1001 ))
1002 })
1003 })
1004 .collect::<Result<Vec<_>, LinkerError>>()?;
1005 let link = |bytecode: &mut CompactBytecode| -> Result<(), LinkerError> {
1006 for canonical in [false, true] {
1007 for &(file, name, address) in &libraries {
1008 self.link_bytecode_inner(bytecode, target, file, name, address, canonical)?;
1009 }
1010 }
1011 Ok(())
1012 };
1013 if let Some(bytecode) = contract.bytecode.as_mut() {
1014 link(bytecode.to_mut())?;
1015 }
1016 if let Some(deployed_bytecode) =
1017 contract.deployed_bytecode.as_mut().and_then(|b| b.to_mut().bytecode.as_mut())
1018 {
1019 link(deployed_bytecode)?;
1020 }
1021 Ok(contract)
1022 }
1023
1024 pub fn ensure_linked(
1026 &self,
1027 contract: &CompactContractBytecodeCow<'a>,
1028 target: &ArtifactId,
1029 ) -> Result<(), LinkerError> {
1030 if let Some(bytecode) = &contract.bytecode
1031 && bytecode.object.is_unlinked()
1032 {
1033 return Err(LinkerError::LinkingFailed {
1034 artifact: target.source.to_string_lossy().into(),
1035 });
1036 }
1037 if let Some(deployed_bytecode) = &contract.deployed_bytecode
1038 && let Some(deployed_bytecode_obj) = &deployed_bytecode.bytecode
1039 && deployed_bytecode_obj.object.is_unlinked()
1040 {
1041 return Err(LinkerError::LinkingFailed {
1042 artifact: target.source.to_string_lossy().into(),
1043 });
1044 }
1045 Ok(())
1046 }
1047
1048 pub fn get_linked_artifacts(
1049 &self,
1050 libraries: &Libraries,
1051 ) -> Result<ArtifactContracts, LinkerError> {
1052 self.get_linked_artifacts_cow(libraries).map(ArtifactContracts::from_iter)
1053 }
1054
1055 pub fn get_linked_artifacts_cow(
1056 &self,
1057 libraries: &Libraries,
1058 ) -> Result<ArtifactContracts<CompactContractBytecodeCow<'a>>, LinkerError> {
1059 self.get_linked_artifacts_cow_with_artifact_libraries(libraries, &BTreeMap::new())
1060 }
1061
1062 pub fn get_linked_artifacts_cow_with_artifact_libraries(
1063 &self,
1064 libraries: &Libraries,
1065 artifact_libraries: &BTreeMap<ArtifactId, Libraries>,
1066 ) -> Result<ArtifactContracts<CompactContractBytecodeCow<'a>>, LinkerError> {
1067 self.contracts
1068 .par_iter()
1069 .map(|(id, _)| {
1070 let libraries = artifact_libraries.get(id).unwrap_or(libraries);
1071 Ok((id.clone(), self.link(id, libraries)?))
1072 })
1073 .collect::<Result<_, _>>()
1074 .map(ArtifactContracts)
1075 }
1076}
1077
1078impl<'a, 'b> Resolver<'a, 'b> {
1079 pub const fn new(linker: &'b Linker<'a>) -> Self {
1081 Self { linker, index: OnceLock::new() }
1082 }
1083
1084 pub fn linked_library_addresses(
1086 &self,
1087 target: &ArtifactId,
1088 libraries: &Libraries,
1089 ) -> Result<BTreeSet<Address>, LinkerError> {
1090 self.linker.linked_library_addresses_inner(&self.index, target, libraries)
1091 }
1092}
1093
1094#[cfg(test)]
1095mod tests {
1096 use super::*;
1097 use alloy_primitives::{address, fixed_bytes, map::HashMap};
1098 use foundry_compilers::{
1099 Project, ProjectCompileOutput, ProjectPathsConfig,
1100 artifacts::BytecodeObject,
1101 multi::MultiCompiler,
1102 solc::{Solc, SolcCompiler},
1103 };
1104 use semver::Version;
1105
1106 fn testdata() -> &'static Path {
1107 static CACHE: OnceLock<PathBuf> = OnceLock::new();
1108 CACHE.get_or_init(|| {
1109 PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../testdata").canonicalize().unwrap()
1110 })
1111 }
1112
1113 #[must_use]
1114 struct LinkerTest {
1115 project: Project,
1116 output: ProjectCompileOutput,
1117 dependency_assertions: HashMap<&'static str, Vec<(&'static str, Address)>>,
1118 }
1119
1120 impl LinkerTest {
1121 fn new(path: &Path, strip_prefixes: bool) -> Self {
1122 assert!(path.exists(), "Path {path:?} does not exist");
1123 let paths = ProjectPathsConfig::builder()
1124 .root(testdata())
1125 .lib(testdata().join("lib"))
1126 .sources(path)
1127 .tests(path)
1128 .build()
1129 .unwrap();
1130
1131 let solc = Solc::find_or_install(&Version::new(0, 8, 18)).unwrap();
1132 let project = Project::builder()
1133 .paths(paths)
1134 .ephemeral()
1135 .no_artifacts()
1136 .build(MultiCompiler { solc: Some(SolcCompiler::Specific(solc)), vyper: None })
1137 .unwrap();
1138
1139 let mut output = project.compile().unwrap();
1140
1141 if strip_prefixes {
1142 output = output.with_stripped_file_prefixes(project.root());
1143 }
1144
1145 Self { project, output, dependency_assertions: HashMap::default() }
1146 }
1147
1148 fn assert_dependencies(
1149 mut self,
1150 artifact_id: &'static str,
1151 deps: &[(&'static str, Address)],
1152 ) -> Self {
1153 self.dependency_assertions.insert(artifact_id, deps.to_vec());
1154 self
1155 }
1156
1157 fn test_with_sender_and_nonce(self, sender: Address, initial_nonce: u64) {
1158 let linker = Linker::new(self.project.root(), self.output.artifact_ids().collect());
1159 for (id, identifier) in self.iter_linking_targets(&linker) {
1160 let output = linker
1161 .link_with_nonce_or_address(Default::default(), sender, initial_nonce, [id])
1162 .expect("Linking failed");
1163 self.validate_assertions(identifier, output);
1164 }
1165 }
1166
1167 fn test_with_create2(self, sender: Address, salt: B256) {
1168 let linker = Linker::new(self.project.root(), self.output.artifact_ids().collect());
1169 for (id, identifier) in self.iter_linking_targets(&linker) {
1170 let output = linker
1171 .link_with_create2(Default::default(), sender, salt, [id])
1172 .expect("Linking failed");
1173 self.validate_assertions(identifier, output);
1174 }
1175 }
1176
1177 fn iter_linking_targets<'a>(
1178 &'a self,
1179 linker: &'a Linker<'_>,
1180 ) -> impl Iterator<Item = (&'a ArtifactId, String)> + 'a {
1181 self.sanity_check(linker);
1182 linker.contracts.keys().filter_map(move |id| {
1183 let source = id
1187 .source
1188 .strip_prefix(self.project.root())
1189 .unwrap_or(&id.source)
1190 .to_string_lossy();
1191 let identifier = format!("{source}:{}", id.name);
1192
1193 if identifier.contains("utils/") {
1195 return None;
1196 }
1197
1198 Some((id, identifier))
1199 })
1200 }
1201
1202 fn sanity_check(&self, linker: &Linker<'_>) {
1203 assert!(!self.dependency_assertions.is_empty(), "Dependency assertions are empty");
1204 assert!(!linker.contracts.is_empty(), "Linker contracts are empty");
1205 }
1206
1207 fn validate_assertions(&self, identifier: String, output: LinkOutput) {
1208 let LinkOutput { libs_to_deploy, libraries, .. } = output;
1209
1210 let assertions = self
1211 .dependency_assertions
1212 .get(identifier.as_str())
1213 .unwrap_or_else(|| panic!("Unexpected artifact: {identifier}"));
1214
1215 assert_eq!(
1216 libs_to_deploy.len(),
1217 assertions.len(),
1218 "artifact {identifier} has more/less dependencies than expected ({} vs {}): {:#?}",
1219 libs_to_deploy.len(),
1220 assertions.len(),
1221 libs_to_deploy
1222 );
1223
1224 for &(dep_identifier, address) in assertions {
1225 let (file, name) = dep_identifier.split_once(':').unwrap();
1226 if let Some(lib_address) =
1227 libraries.libs.get(Path::new(file)).and_then(|libs| libs.get(name))
1228 {
1229 assert_eq!(
1230 lib_address.parse::<Address>().unwrap(),
1231 address,
1232 "incorrect library address for dependency {dep_identifier} of {identifier}"
1233 );
1234 } else {
1235 panic!("Library {dep_identifier} not found");
1236 }
1237 }
1238 }
1239 }
1240
1241 fn link_test(path: impl AsRef<Path>, mut test_fn: impl FnMut(LinkerTest)) {
1242 fn link_test(path: &Path, test_fn: &mut dyn FnMut(LinkerTest)) {
1243 test_fn(LinkerTest::new(path, true));
1244 test_fn(LinkerTest::new(path, false));
1245 }
1246 link_test(path.as_ref(), &mut test_fn);
1247 }
1248
1249 #[test]
1250 #[should_panic = "assertions are empty"]
1251 fn no_assertions() {
1252 link_test(testdata().join("default/linking/simple"), |linker| {
1253 linker.test_with_sender_and_nonce(Address::default(), 1);
1254 });
1255 }
1256
1257 #[test]
1258 #[should_panic = "does not exist"]
1259 fn unknown_path() {
1260 link_test("doesnotexist", |linker| {
1261 linker
1262 .assert_dependencies("a:b", &[])
1263 .test_with_sender_and_nonce(Address::default(), 1);
1264 });
1265 }
1266
1267 #[test]
1268 fn link_simple() {
1269 link_test(testdata().join("default/linking/simple"), |linker| {
1270 linker
1271 .assert_dependencies("default/linking/simple/Simple.t.sol:Lib", &[])
1272 .assert_dependencies(
1273 "default/linking/simple/Simple.t.sol:LibraryConsumer",
1274 &[(
1275 "default/linking/simple/Simple.t.sol:Lib",
1276 address!("0x5a443704dd4b594b382c22a083e2bd3090a6fef3"),
1277 )],
1278 )
1279 .assert_dependencies(
1280 "default/linking/simple/Simple.t.sol:SimpleLibraryLinkingTest",
1281 &[(
1282 "default/linking/simple/Simple.t.sol:Lib",
1283 address!("0x5a443704dd4b594b382c22a083e2bd3090a6fef3"),
1284 )],
1285 )
1286 .test_with_sender_and_nonce(Address::default(), 1);
1287 });
1288 }
1289
1290 #[test]
1291 fn link_nested() {
1292 link_test(testdata().join("default/linking/nested"), |linker| {
1293 linker
1294 .assert_dependencies("default/linking/nested/Nested.t.sol:Lib", &[])
1295 .assert_dependencies(
1296 "default/linking/nested/Nested.t.sol:NestedLib",
1297 &[(
1298 "default/linking/nested/Nested.t.sol:Lib",
1299 address!("0x5a443704dd4b594b382c22a083e2bd3090a6fef3"),
1300 )],
1301 )
1302 .assert_dependencies(
1303 "default/linking/nested/Nested.t.sol:LibraryConsumer",
1304 &[
1305 (
1308 "default/linking/nested/Nested.t.sol:Lib",
1309 Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3")
1310 .unwrap(),
1311 ),
1312 (
1313 "default/linking/nested/Nested.t.sol:NestedLib",
1314 Address::from_str("0x47e9Fbef8C83A1714F1951F142132E6e90F5fa5D")
1315 .unwrap(),
1316 ),
1317 ],
1318 )
1319 .assert_dependencies(
1320 "default/linking/nested/Nested.t.sol:NestedLibraryLinkingTest",
1321 &[
1322 (
1323 "default/linking/nested/Nested.t.sol:Lib",
1324 Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3")
1325 .unwrap(),
1326 ),
1327 (
1328 "default/linking/nested/Nested.t.sol:NestedLib",
1329 Address::from_str("0x47e9fbef8c83a1714f1951f142132e6e90f5fa5d")
1330 .unwrap(),
1331 ),
1332 ],
1333 )
1334 .test_with_sender_and_nonce(Address::default(), 1);
1335 });
1336 }
1337
1338 #[test]
1339 fn link_duplicate() {
1340 link_test(testdata().join("default/linking/duplicate"), |linker| {
1341 linker
1342 .assert_dependencies("default/linking/duplicate/Duplicate.t.sol:A", &[])
1343 .assert_dependencies("default/linking/duplicate/Duplicate.t.sol:B", &[])
1344 .assert_dependencies(
1345 "default/linking/duplicate/Duplicate.t.sol:C",
1346 &[(
1347 "default/linking/duplicate/Duplicate.t.sol:A",
1348 address!("0x5a443704dd4b594b382c22a083e2bd3090a6fef3"),
1349 )],
1350 )
1351 .assert_dependencies(
1352 "default/linking/duplicate/Duplicate.t.sol:D",
1353 &[(
1354 "default/linking/duplicate/Duplicate.t.sol:B",
1355 address!("0x5a443704dd4b594b382c22a083e2bd3090a6fef3"),
1356 )],
1357 )
1358 .assert_dependencies(
1359 "default/linking/duplicate/Duplicate.t.sol:E",
1360 &[
1361 (
1362 "default/linking/duplicate/Duplicate.t.sol:A",
1363 Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3")
1364 .unwrap(),
1365 ),
1366 (
1367 "default/linking/duplicate/Duplicate.t.sol:C",
1368 Address::from_str("0x47e9fbef8c83a1714f1951f142132e6e90f5fa5d")
1369 .unwrap(),
1370 ),
1371 ],
1372 )
1373 .assert_dependencies(
1374 "default/linking/duplicate/Duplicate.t.sol:LibraryConsumer",
1375 &[
1376 (
1377 "default/linking/duplicate/Duplicate.t.sol:A",
1378 Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3")
1379 .unwrap(),
1380 ),
1381 (
1382 "default/linking/duplicate/Duplicate.t.sol:B",
1383 Address::from_str("0x47e9fbef8c83a1714f1951f142132e6e90f5fa5d")
1384 .unwrap(),
1385 ),
1386 (
1387 "default/linking/duplicate/Duplicate.t.sol:C",
1388 Address::from_str("0x8be503bcded90ed42eff31f56199399b2b0154ca")
1389 .unwrap(),
1390 ),
1391 (
1392 "default/linking/duplicate/Duplicate.t.sol:D",
1393 Address::from_str("0x47c5e40890bce4a473a49d7501808b9633f29782")
1394 .unwrap(),
1395 ),
1396 (
1397 "default/linking/duplicate/Duplicate.t.sol:E",
1398 Address::from_str("0x29b2440db4a256b0c1e6d3b4cdcaa68e2440a08f")
1399 .unwrap(),
1400 ),
1401 ],
1402 )
1403 .assert_dependencies(
1404 "default/linking/duplicate/Duplicate.t.sol:DuplicateLibraryLinkingTest",
1405 &[
1406 (
1407 "default/linking/duplicate/Duplicate.t.sol:A",
1408 Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3")
1409 .unwrap(),
1410 ),
1411 (
1412 "default/linking/duplicate/Duplicate.t.sol:B",
1413 Address::from_str("0x47e9fbef8c83a1714f1951f142132e6e90f5fa5d")
1414 .unwrap(),
1415 ),
1416 (
1417 "default/linking/duplicate/Duplicate.t.sol:C",
1418 Address::from_str("0x8be503bcded90ed42eff31f56199399b2b0154ca")
1419 .unwrap(),
1420 ),
1421 (
1422 "default/linking/duplicate/Duplicate.t.sol:D",
1423 Address::from_str("0x47c5e40890bce4a473a49d7501808b9633f29782")
1424 .unwrap(),
1425 ),
1426 (
1427 "default/linking/duplicate/Duplicate.t.sol:E",
1428 Address::from_str("0x29b2440db4a256b0c1e6d3b4cdcaa68e2440a08f")
1429 .unwrap(),
1430 ),
1431 ],
1432 )
1433 .test_with_sender_and_nonce(Address::default(), 1);
1434 });
1435 }
1436
1437 #[test]
1438 fn link_cycle() {
1439 link_test(testdata().join("default/linking/cycle"), |linker| {
1440 linker
1441 .assert_dependencies(
1442 "default/linking/cycle/Cycle.t.sol:Foo",
1443 &[
1444 (
1445 "default/linking/cycle/Cycle.t.sol:Foo",
1446 Address::from_str("0x47e9Fbef8C83A1714F1951F142132E6e90F5fa5D")
1447 .unwrap(),
1448 ),
1449 (
1450 "default/linking/cycle/Cycle.t.sol:Bar",
1451 Address::from_str("0x5a443704dd4B594B382c22a083e2BD3090A6feF3")
1452 .unwrap(),
1453 ),
1454 ],
1455 )
1456 .assert_dependencies(
1457 "default/linking/cycle/Cycle.t.sol:Bar",
1458 &[
1459 (
1460 "default/linking/cycle/Cycle.t.sol:Foo",
1461 Address::from_str("0x47e9Fbef8C83A1714F1951F142132E6e90F5fa5D")
1462 .unwrap(),
1463 ),
1464 (
1465 "default/linking/cycle/Cycle.t.sol:Bar",
1466 Address::from_str("0x5a443704dd4B594B382c22a083e2BD3090A6feF3")
1467 .unwrap(),
1468 ),
1469 ],
1470 )
1471 .test_with_sender_and_nonce(Address::default(), 1);
1472 });
1473 }
1474
1475 #[test]
1476 #[ignore = "addresses depend on testdata utils internals for some reason"]
1477 fn link_create2_nested() {
1478 link_test(testdata().join("default/linking/nested"), |linker| {
1479 linker
1480 .assert_dependencies("default/linking/nested/Nested.t.sol:Lib", &[])
1481 .assert_dependencies(
1482 "default/linking/nested/Nested.t.sol:NestedLib",
1483 &[(
1484 "default/linking/nested/Nested.t.sol:Lib",
1485 address!("0x773253227cce756e50c3993ec6366b3ec27786f9"),
1486 )],
1487 )
1488 .assert_dependencies(
1489 "default/linking/nested/Nested.t.sol:LibraryConsumer",
1490 &[
1491 (
1494 "default/linking/nested/Nested.t.sol:Lib",
1495 Address::from_str("0x773253227cce756e50c3993ec6366b3ec27786f9")
1496 .unwrap(),
1497 ),
1498 (
1499 "default/linking/nested/Nested.t.sol:NestedLib",
1500 Address::from_str("0xac231df03403867b05d092c26fc91b6b83f4bebe")
1501 .unwrap(),
1502 ),
1503 ],
1504 )
1505 .assert_dependencies(
1506 "default/linking/nested/Nested.t.sol:NestedLibraryLinkingTest",
1507 &[
1508 (
1509 "default/linking/nested/Nested.t.sol:Lib",
1510 Address::from_str("0x773253227cce756e50c3993ec6366b3ec27786f9")
1511 .unwrap(),
1512 ),
1513 (
1514 "default/linking/nested/Nested.t.sol:NestedLib",
1515 Address::from_str("0xac231df03403867b05d092c26fc91b6b83f4bebe")
1516 .unwrap(),
1517 ),
1518 ],
1519 )
1520 .test_with_create2(
1521 Address::default(),
1522 fixed_bytes!(
1523 "19bf59b7b67ae8edcbc6e53616080f61fa99285c061450ad601b0bc40c9adfc9"
1524 ),
1525 );
1526 });
1527 }
1528
1529 #[test]
1530 fn link_samefile_union() {
1531 link_test(testdata().join("default/linking/samefile_union"), |linker| {
1532 linker
1533 .assert_dependencies("default/linking/samefile_union/Libs.sol:LInit", &[])
1534 .assert_dependencies("default/linking/samefile_union/Libs.sol:LRun", &[])
1535 .assert_dependencies(
1536 "default/linking/samefile_union/SameFileUnion.t.sol:UsesBoth",
1537 &[
1538 (
1539 "default/linking/samefile_union/Libs.sol:LInit",
1540 Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3")
1541 .unwrap(),
1542 ),
1543 (
1544 "default/linking/samefile_union/Libs.sol:LRun",
1545 Address::from_str("0x47e9fbef8c83a1714f1951f142132e6e90f5fa5d")
1546 .unwrap(),
1547 ),
1548 ],
1549 )
1550 .test_with_sender_and_nonce(Address::default(), 1);
1551 });
1552 }
1553
1554 #[test]
1555 fn link_create2_multiple_targets_deduplicates_shared_dependencies() {
1556 let test = LinkerTest::new(&testdata().join("default/linking/simple"), true);
1557 let linker = Linker::new(test.project.root(), test.output.artifact_ids().collect());
1558 let consumer = linker.contracts.keys().find(|id| id.name == "LibraryConsumer").unwrap();
1559 let test_contract =
1560 linker.contracts.keys().find(|id| id.name == "SimpleLibraryLinkingTest").unwrap();
1561
1562 let output = linker
1563 .link_with_create2(
1564 Libraries::default(),
1565 Address::ZERO,
1566 B256::with_last_byte(1),
1567 [consumer, test_contract],
1568 )
1569 .unwrap();
1570
1571 assert_eq!(output.libs_to_deploy.len(), 1);
1572 assert_eq!(output.libraries.libs.values().map(BTreeMap::len).sum::<usize>(), 1);
1573
1574 let linked_address = output
1575 .libraries
1576 .libs
1577 .values()
1578 .flat_map(|libraries| libraries.values())
1579 .next()
1580 .unwrap()
1581 .parse::<Address>()
1582 .unwrap();
1583 for target in [consumer, test_contract] {
1584 let bytecode = linker.link(target, &output.libraries).unwrap().bytecode.unwrap();
1585 assert!(
1586 bytecode
1587 .bytes()
1588 .unwrap()
1589 .windows(Address::len_bytes())
1590 .any(|window| { window == linked_address.as_slice() }),
1591 "{} was not linked to {linked_address}",
1592 target.name
1593 );
1594 }
1595 }
1596
1597 #[test]
1598 fn detailed_linking_includes_transitive_library_addresses() {
1599 let test = LinkerTest::new(&testdata().join("default/linking/nested"), true);
1600 let linker = Linker::new(test.project.root(), test.output.artifact_ids().collect());
1601 let consumer = linker.contracts.keys().find(|id| id.name == "LibraryConsumer").unwrap();
1602 let resolver = Resolver::new(&linker);
1603
1604 let create2 = linker
1605 .link_with_create2_detailed(Libraries::default(), Address::ZERO, B256::ZERO, [consumer])
1606 .unwrap();
1607 let libraries = create2.artifact_libraries.get(consumer).unwrap();
1608 assert_eq!(resolver.linked_library_addresses(consumer, libraries).unwrap().len(), 2);
1609
1610 let nonce = linker
1611 .link_with_nonce_or_address_detailed(Libraries::default(), Address::ZERO, 0, [consumer])
1612 .unwrap();
1613 let libraries = nonce.artifact_libraries.get(consumer).unwrap();
1614 assert_eq!(resolver.linked_library_addresses(consumer, libraries).unwrap().len(), 2);
1615 }
1616
1617 #[test]
1618 fn linking_preserves_transitive_profile_identity() {
1619 let test = LinkerTest::new(&testdata().join("default/linking/profile_nested"), true);
1620 let linker = Linker::new(test.project.root(), test.output.artifact_ids().collect());
1621 let find = |name| {
1622 linker
1623 .contracts
1624 .iter()
1625 .find(|(id, _)| id.name == name)
1626 .map(|(id, contract)| (id.clone(), contract.clone()))
1627 .unwrap()
1628 };
1629 let (inner_id, inner) = find("Inner");
1630 let (outer_id, outer) = find("Outer");
1631 let (consumer_id, consumer) = find("Consumer");
1632
1633 let mut contracts = linker.contracts.clone();
1634 let mut other_inner_id = inner_id.clone();
1635 other_inner_id.build_id = "other".to_string();
1636 other_inner_id.profile = "other".to_string();
1637 contracts.insert(other_inner_id.clone(), inner);
1638 let mut other_outer_id = outer_id.clone();
1639 other_outer_id.build_id = "other".to_string();
1640 other_outer_id.profile = "other".to_string();
1641 contracts.insert(other_outer_id.clone(), outer);
1642 let mut other_consumer_id = consumer_id.clone();
1643 other_consumer_id.build_id = "other".to_string();
1644 other_consumer_id.profile = "other".to_string();
1645 contracts.insert(other_consumer_id.clone(), consumer);
1646
1647 for id in [&other_inner_id, &other_outer_id] {
1648 let bytecode = contracts.get_mut(id).unwrap().bytecode.as_mut().unwrap().to_mut();
1649 match &mut bytecode.object {
1650 BytecodeObject::Bytecode(bytes) => {
1651 let mut distinct = bytes.to_vec();
1652 distinct.push(0);
1653 *bytes = distinct.into();
1654 }
1655 BytecodeObject::Unlinked(code) => code.push_str("00"),
1656 }
1657 }
1658
1659 let linker = Linker::new(test.project.root(), contracts);
1660 let consumers = [&consumer_id, &other_consumer_id];
1661 let profiles = [
1662 (&consumer_id, &outer_id, &inner_id),
1663 (&other_consumer_id, &other_outer_id, &other_inner_id),
1664 ];
1665 let assert_identity = |output: &DetailedLinkOutput| {
1666 assert_eq!(output.artifact_addresses.len(), 4);
1667 assert_eq!(
1668 output.output.libs_to_deploy,
1669 output
1670 .linked_libraries
1671 .iter()
1672 .map(|library| library.bytecode.clone())
1673 .collect::<Vec<_>>()
1674 );
1675
1676 for linked in &output.linked_libraries {
1677 assert_eq!(output.artifact_addresses[&linked.id], linked.address);
1678 assert_eq!(
1679 linker
1680 .linked_creation_bytecode(
1681 &linked.id,
1682 &output.artifact_libraries[&linked.id],
1683 )
1684 .unwrap(),
1685 linked.bytecode
1686 );
1687 }
1688
1689 for (index, &(consumer, outer, inner)) in profiles.iter().enumerate() {
1690 let sibling_outer = profiles[1 - index].1;
1691 let sibling_inner = profiles[1 - index].2;
1692 let outer_address = output.artifact_addresses[outer];
1693 let inner_address = output.artifact_addresses[inner];
1694
1695 let consumer_bytecode = linker
1696 .link(consumer, &output.artifact_libraries[consumer])
1697 .unwrap()
1698 .get_bytecode_bytes()
1699 .unwrap()
1700 .into_owned();
1701 assert!(
1702 consumer_bytecode
1703 .windows(Address::len_bytes())
1704 .any(|window| { window == outer_address.as_slice() })
1705 );
1706 assert!(!consumer_bytecode.windows(Address::len_bytes()).any(|window| {
1707 window == output.artifact_addresses[sibling_outer].as_slice()
1708 }));
1709
1710 let outer_bytecode = &output
1711 .linked_libraries
1712 .iter()
1713 .find(|library| library.id == *outer)
1714 .unwrap()
1715 .bytecode;
1716 assert!(
1717 outer_bytecode
1718 .windows(Address::len_bytes())
1719 .any(|window| window == inner_address.as_slice())
1720 );
1721 assert!(!outer_bytecode.windows(Address::len_bytes()).any(|window| {
1722 window == output.artifact_addresses[sibling_inner].as_slice()
1723 }));
1724 }
1725 };
1726
1727 let sender = address!("1000000000000000000000000000000000000000");
1728 let nonce = 7;
1729 let output = linker
1730 .link_with_nonce_or_address_detailed(Libraries::default(), sender, nonce, consumers)
1731 .unwrap();
1732 assert_identity(&output);
1733 for (index, library) in output.linked_libraries.iter().enumerate() {
1734 assert_eq!(library.address, sender.create(nonce + index as u64));
1735 }
1736
1737 let salt = B256::with_last_byte(1);
1738 let output = linker
1739 .link_with_create2_detailed(Libraries::default(), sender, salt, consumers)
1740 .unwrap();
1741 assert_identity(&output);
1742 for library in &output.linked_libraries {
1743 assert_eq!(library.address, sender.create2_from_code(salt, &library.bytecode));
1744 }
1745 }
1746
1747 #[test]
1748 fn linking_handles_library_key_collisions_across_profiles() {
1749 let test = LinkerTest::new(&testdata().join("default/linking/simple"), true);
1750 let linker = Linker::new(test.project.root(), test.output.artifact_ids().collect());
1751 let (library_id, library) = linker
1752 .contracts
1753 .iter()
1754 .find(|(id, _)| id.name == "Lib")
1755 .map(|(id, contract)| (id.clone(), contract.clone()))
1756 .unwrap();
1757 let (consumer_id, consumer) = linker
1758 .contracts
1759 .iter()
1760 .find(|(id, _)| id.name == "LibraryConsumer")
1761 .map(|(id, contract)| (id.clone(), contract.clone()))
1762 .unwrap();
1763
1764 let mut contracts = linker.contracts.clone();
1765 let mut other_library_id = library_id.clone();
1766 other_library_id.build_id = "other".to_string();
1767 other_library_id.profile = "other".to_string();
1768 contracts.insert(other_library_id.clone(), library);
1769 let mut other_consumer_id = consumer_id.clone();
1770 other_consumer_id.build_id = "other".to_string();
1771 other_consumer_id.profile = "other".to_string();
1772 contracts.insert(other_consumer_id.clone(), consumer);
1773
1774 let linker = Linker::new(test.project.root(), contracts.clone());
1775 let detailed = linker
1776 .link_with_create2_detailed(
1777 Libraries::default(),
1778 Address::ZERO,
1779 B256::ZERO,
1780 [&consumer_id, &other_consumer_id],
1781 )
1782 .unwrap();
1783 assert_eq!(detailed.linked_libraries.len(), 1);
1784 assert_eq!(detailed.artifact_addresses.len(), 2);
1785 assert_eq!(detailed.artifact_addresses[&library_id], detailed.linked_libraries[0].address);
1786 assert_eq!(
1787 detailed.artifact_addresses[&other_library_id],
1788 detailed.linked_libraries[0].address
1789 );
1790
1791 let output = linker
1792 .link_with_create2(
1793 Libraries::default(),
1794 Address::ZERO,
1795 B256::ZERO,
1796 [&consumer_id, &other_consumer_id],
1797 )
1798 .unwrap();
1799 assert_eq!(output.libs_to_deploy.len(), 1);
1800
1801 let Err(err) = linker.link_with_nonce_or_address(
1802 Libraries::default(),
1803 Address::ZERO,
1804 0,
1805 [&consumer_id, &other_consumer_id],
1806 ) else {
1807 panic!("expected conflicting library artifacts");
1808 };
1809 assert!(matches!(err, LinkerError::ConflictingLibraryArtifacts { .. }));
1810
1811 let other_library = contracts.get_mut(&other_library_id).unwrap();
1812 let bytecode = other_library.bytecode.as_mut().unwrap().to_mut();
1813 let mut bytes = bytecode.object.as_bytes().unwrap().to_vec();
1814 bytes.push(0);
1815 bytecode.object = BytecodeObject::Bytecode(bytes.into());
1816
1817 let linker = Linker::new(test.project.root(), contracts);
1818 let output = linker
1819 .link_with_create2_detailed(
1820 Libraries::default(),
1821 Address::ZERO,
1822 B256::ZERO,
1823 [&consumer_id, &other_consumer_id],
1824 )
1825 .unwrap();
1826 assert_eq!(output.linked_libraries.len(), 2);
1827 assert_ne!(output.linked_libraries[0].address, output.linked_libraries[1].address);
1828 for (target, library) in
1829 [(&consumer_id, &library_id), (&other_consumer_id, &other_library_id)]
1830 {
1831 let expected = output
1832 .linked_libraries
1833 .iter()
1834 .find(|linked| linked.id == *library)
1835 .unwrap()
1836 .address;
1837 let (file, name) = linker.convert_artifact_id_to_lib_path(library);
1838 let actual =
1839 output.artifact_libraries[target].libs[&file][&name].parse::<Address>().unwrap();
1840 assert_eq!(actual, expected);
1841 }
1842
1843 let Err(err) = linker.link_with_create2(
1844 Libraries::default(),
1845 Address::ZERO,
1846 B256::ZERO,
1847 [&consumer_id, &other_consumer_id],
1848 ) else {
1849 panic!("expected conflicting library artifacts");
1850 };
1851 assert!(matches!(err, LinkerError::ConflictingLibraryArtifacts { .. }));
1852
1853 let output = linker
1854 .link_with_nonce_or_address_detailed(
1855 Libraries::default(),
1856 Address::ZERO,
1857 0,
1858 [&consumer_id, &other_consumer_id],
1859 )
1860 .unwrap();
1861 assert_eq!(output.linked_libraries.len(), 2);
1862 assert_ne!(output.linked_libraries[0].address, output.linked_libraries[1].address);
1863 for (target, library) in
1864 [(&consumer_id, &library_id), (&other_consumer_id, &other_library_id)]
1865 {
1866 let expected = output
1867 .linked_libraries
1868 .iter()
1869 .find(|linked| linked.id == *library)
1870 .unwrap()
1871 .address;
1872 let (file, name) = linker.convert_artifact_id_to_lib_path(library);
1873 let actual =
1874 output.artifact_libraries[target].libs[&file][&name].parse::<Address>().unwrap();
1875 assert_eq!(actual, expected);
1876 }
1877
1878 let Err(err) = linker.link_with_nonce_or_address(
1879 Libraries::default(),
1880 Address::ZERO,
1881 0,
1882 [&consumer_id, &other_consumer_id],
1883 ) else {
1884 panic!("expected conflicting library artifacts");
1885 };
1886 assert!(matches!(err, LinkerError::ConflictingLibraryArtifacts { .. }));
1887
1888 let configured_address = address!("0000000000000000000000000000000000000001");
1889 let (file, name) = linker.convert_artifact_id_to_lib_path(&library_id);
1890 let mut libraries = Libraries::default();
1891 libraries.libs.entry(file).or_default().insert(name, configured_address.to_checksum(None));
1892
1893 let output = linker
1894 .link_with_create2(
1895 libraries.clone(),
1896 Address::ZERO,
1897 B256::ZERO,
1898 [&consumer_id, &other_consumer_id],
1899 )
1900 .unwrap();
1901 assert!(output.libs_to_deploy.is_empty());
1902 assert_eq!(output.library_addresses, [configured_address]);
1903
1904 let output = linker
1905 .link_with_nonce_or_address(
1906 libraries,
1907 Address::ZERO,
1908 0,
1909 [&consumer_id, &other_consumer_id],
1910 )
1911 .unwrap();
1912 assert!(output.libs_to_deploy.is_empty());
1913 assert_eq!(output.library_addresses, [configured_address]);
1914 }
1915
1916 #[test]
1917 fn linking_resolves_same_version_library_from_target_build() {
1918 let test = LinkerTest::new(&testdata().join("default/linking/simple"), true);
1919 let linker = Linker::new(test.project.root(), test.output.artifact_ids().collect());
1920 let (library_id, library) = linker
1921 .contracts
1922 .iter()
1923 .find(|(id, _)| id.name == "Lib")
1924 .map(|(id, contract)| (id.clone(), contract.clone()))
1925 .unwrap();
1926 let (consumer_id, consumer) = linker
1927 .contracts
1928 .iter()
1929 .find(|(id, _)| id.name == "LibraryConsumer")
1930 .map(|(id, contract)| (id.clone(), contract.clone()))
1931 .unwrap();
1932
1933 let mut contracts = linker.contracts.clone();
1934 let mut other_library_id = library_id;
1935 other_library_id.build_id = "other".to_string();
1936 other_library_id.profile = "other".to_string();
1937 contracts.insert(other_library_id, library);
1938 let mut ambiguous_consumer_id = consumer_id.clone();
1939 ambiguous_consumer_id.build_id = "ambiguous".to_string();
1940 ambiguous_consumer_id.profile = "ambiguous".to_string();
1941 contracts.insert(ambiguous_consumer_id.clone(), consumer);
1942
1943 let linker = Linker::new(test.project.root(), contracts);
1944 linker
1945 .link_with_create2(Libraries::default(), Address::ZERO, B256::ZERO, [&consumer_id])
1946 .unwrap();
1947
1948 let Err(err) = linker.link_with_create2(
1949 Libraries::default(),
1950 Address::ZERO,
1951 B256::ZERO,
1952 [&ambiguous_consumer_id],
1953 ) else {
1954 panic!("expected conflicting library artifacts");
1955 };
1956 assert!(matches!(err, LinkerError::ConflictingLibraryArtifacts { .. }));
1957 }
1958
1959 #[test]
1960 fn link_output_excludes_unreferenced_configured_libraries() {
1961 let test = LinkerTest::new(&testdata().join("default/linking/simple"), true);
1962 let linker = Linker::new(test.project.root(), test.output.artifact_ids().collect());
1963 let consumer = linker.contracts.keys().find(|id| id.name == "LibraryConsumer").unwrap();
1964 let unrelated = address!("0000000000000000000000000000000000000001");
1965 let mut libraries = Libraries::default();
1966 libraries
1967 .libs
1968 .entry("src/Unrelated.sol".into())
1969 .or_default()
1970 .insert("Unrelated".to_string(), unrelated.to_checksum(None));
1971
1972 let output =
1973 linker.link_with_create2(libraries, Address::ZERO, B256::ZERO, [consumer]).unwrap();
1974
1975 assert_eq!(output.library_addresses.len(), 1);
1976 assert!(!output.library_addresses.contains(&unrelated));
1977 }
1978
1979 #[test]
1980 fn exact_artifact_match_uses_configured_library_alias() {
1981 let test = LinkerTest::new(&testdata().join("default/linking/simple"), true);
1982 let linker = Linker::new(test.project.root(), test.output.artifact_ids().collect());
1983 let mut contracts = linker.contracts.clone();
1984 let (library_id, library) = contracts
1985 .iter()
1986 .find(|(id, _)| id.name == "Lib")
1987 .map(|(id, contract)| (id.clone(), contract.clone()))
1988 .unwrap();
1989 let mut alias_id = library_id.clone();
1990 alias_id.source = "./default/linking/simple/Simple.t.sol".into();
1991 contracts.insert(alias_id, library);
1992 let linker = Linker::new(test.project.root(), contracts);
1993 let consumer = linker.contracts.keys().find(|id| id.name == "LibraryConsumer").unwrap();
1994 let configured = address!("0000000000000000000000000000000000000001");
1995 let alias = PathBuf::from("./default/linking/simple/Simple.t.sol");
1996 let generated_key = PathBuf::from("default/linking/simple/Simple.t.sol");
1997 let mut libraries = Libraries::default();
1998 libraries
1999 .libs
2000 .entry(alias.clone())
2001 .or_default()
2002 .insert("Lib".to_string(), configured.to_checksum(None));
2003
2004 let output =
2005 linker.link_with_nonce_or_address(libraries, Address::ZERO, 1, [consumer]).unwrap();
2006 let bytecode = linker.link(consumer, &output.libraries).unwrap().bytecode.unwrap();
2007 let bytecode = bytecode.bytes().unwrap();
2008
2009 assert!(output.libs_to_deploy.is_empty());
2010 assert_eq!(output.library_addresses, [configured]);
2011 assert!(
2012 bytecode.windows(Address::len_bytes()).any(|window| window == configured.as_slice())
2013 );
2014 assert_eq!(output.libraries.libs.len(), 2);
2015 assert!(output.libraries.libs.contains_key(&alias));
2016 assert!(output.libraries.libs.contains_key(&generated_key));
2017
2018 let exact = address!("0000000000000000000000000000000000000002");
2019 let references = BTreeMap::from([(&library_id, BTreeSet::from([alias.clone()]))]);
2020 let mut libraries = output.libraries;
2021 libraries.libs.get_mut(&alias).unwrap().insert("Lib".into(), exact.to_checksum(None));
2022 linker.apply_configured_references(&references, &mut libraries).unwrap();
2023 assert_eq!(libraries.libs[&generated_key]["Lib"], exact.to_checksum(None));
2024
2025 libraries
2026 .libs
2027 .get_mut(&generated_key)
2028 .unwrap()
2029 .insert("Lib".into(), configured.to_checksum(None));
2030 let fallback = PathBuf::from("default/linking/simple/../simple/Simple.t.sol");
2031 let references = BTreeMap::from([(&library_id, BTreeSet::from([fallback]))]);
2032 let err = linker.apply_configured_references(&references, &mut libraries).unwrap_err();
2033 assert!(matches!(err, LinkerError::ConflictingLibraryArtifacts { .. }));
2034 }
2035
2036 #[test]
2037 fn partition_with_nonce_assigns_required_and_local_libraries() {
2038 let test = LinkerTest::new(&testdata().join("default/linking/samefile_union"), true);
2039 let linker = Linker::new(test.project.root(), test.output.artifact_ids().collect());
2040 let find = |name| linker.contracts.keys().find(|id| id.name == name).unwrap();
2041 let (target, required_id) = (find("UsesBoth"), find("LInit").clone());
2042 let sender = address!("0x1000000000000000000000000000000000000000");
2043 let local_deployer = address!("0x2000000000000000000000000000000000000000");
2044 let required = BTreeSet::from([required_id.clone()]);
2045 let (detailed, local) = linker
2046 .link_with_partition(Libraries::default(), sender, 7, local_deployer, &required, target)
2047 .unwrap();
2048
2049 assert_eq!(detailed.linked_libraries.len(), 2);
2050 assert_eq!(detailed.output.libs_to_deploy.len(), 1);
2051 assert_eq!(local.len(), 1);
2052 assert_eq!(
2053 detailed.linked_libraries.iter().find(|lib| lib.id == required_id).unwrap().address,
2054 sender.create(7)
2055 );
2056 assert_eq!(local[0].address, local_deployer.create(0));
2057 linker
2058 .ensure_linked(&linker.link(target, &detailed.output.libraries).unwrap(), target)
2059 .unwrap();
2060 for library in &detailed.linked_libraries {
2061 assert!(!library.bytecode.is_empty());
2062 }
2063
2064 let mut configured = Libraries::default();
2065 let (file, name) = linker.convert_artifact_id_to_lib_path(find("LRun"));
2066 configured.libs.entry(file).or_default().insert(name, Address::ZERO.to_checksum(None));
2067 let (configured, local) = linker
2068 .link_with_partition(configured, sender, 7, local_deployer, &required, target)
2069 .unwrap();
2070 assert_eq!(configured.linked_libraries.len(), 1);
2071 assert!(local.is_empty());
2072 }
2073
2074 #[test]
2075 fn partition_with_create2_assigns_required_and_local_libraries() {
2076 let test = LinkerTest::new(&testdata().join("default/linking/samefile_union"), true);
2077 let linker = Linker::new(test.project.root(), test.output.artifact_ids().collect());
2078 let find = |name| linker.contracts.keys().find(|id| id.name == name).unwrap();
2079 let (target, required_id) = (find("UsesBoth"), find("LInit").clone());
2080 let required = BTreeSet::from([required_id.clone()]);
2081 let deployer = address!("0x3000000000000000000000000000000000000000");
2082 let local_deployer = address!("0x4000000000000000000000000000000000000000");
2083 let salt = fixed_bytes!("19bf59b7b67ae8edcbc6e53616080f61fa99285c061450ad601b0bc40c9adfc9");
2084 let (detailed, local) = linker
2085 .link_with_create2_partition(
2086 Libraries::default(),
2087 deployer,
2088 salt,
2089 local_deployer,
2090 &required,
2091 target,
2092 )
2093 .unwrap();
2094
2095 assert_eq!(detailed.output.libs_to_deploy.len(), 1);
2096 assert_eq!(local.len(), 1);
2097 let onchain =
2098 detailed.linked_libraries.iter().find(|library| library.id == required_id).unwrap();
2099 assert_eq!(onchain.address, deployer.create2_from_code(salt, &onchain.bytecode));
2100 assert_eq!(detailed.output.libs_to_deploy[0], onchain.bytecode);
2101 assert_eq!(local[0].address, local_deployer.create(0));
2102 linker
2103 .ensure_linked(&linker.link(target, &detailed.output.libraries).unwrap(), target)
2104 .unwrap();
2105 }
2106
2107 #[test]
2108 fn partition_with_create2_keeps_transitive_order_deterministic() {
2109 let test = LinkerTest::new(&testdata().join("default/linking/nested"), true);
2110 let linker = Linker::new(test.project.root(), test.output.artifact_ids().collect());
2111 let find = |name| linker.contracts.keys().find(|id| id.name == name).unwrap();
2112 let target = find("LibraryConsumer");
2113 let required = BTreeSet::from([find("NestedLib").clone()]);
2114 let deployer = address!("0x3000000000000000000000000000000000000000");
2115 let local_deployer = address!("0x4000000000000000000000000000000000000000");
2116 let salt = fixed_bytes!("19bf59b7b67ae8edcbc6e53616080f61fa99285c061450ad601b0bc40c9adfc9");
2117 let link = || {
2118 linker
2119 .link_with_create2_partition(
2120 Libraries::default(),
2121 deployer,
2122 salt,
2123 local_deployer,
2124 &required,
2125 target,
2126 )
2127 .unwrap()
2128 };
2129 let (first, local) = link();
2130 let (second, _) = link();
2131
2132 assert!(local.is_empty());
2133 assert_eq!(first.output.libs_to_deploy.len(), 2);
2134 assert_eq!(
2135 first.linked_libraries.iter().map(|lib| (&lib.id, lib.address)).collect::<Vec<_>>(),
2136 second.linked_libraries.iter().map(|lib| (&lib.id, lib.address)).collect::<Vec<_>>()
2137 );
2138 assert_eq!(first.linked_libraries[0].id.name, "Lib");
2139 assert_eq!(first.linked_libraries[1].id.name, "NestedLib");
2140 for library in &first.linked_libraries {
2141 assert_eq!(library.address, deployer.create2_from_code(salt, &library.bytecode));
2142 }
2143 linker
2144 .ensure_linked(&linker.link(target, &first.output.libraries).unwrap(), target)
2145 .unwrap();
2146 }
2147
2148 #[test]
2149 fn linking_failure() {
2150 let linker = LinkerTest::new(&testdata().join("default/linking/simple"), true);
2151 let linker_instance =
2152 Linker::new(linker.project.root(), linker.output.artifact_ids().collect());
2153
2154 let mut libraries = Libraries::default();
2156 libraries.libs.entry("default/linking/simple/Simple.t.sol".into()).or_default().insert(
2157 "NonExistentLib".to_string(),
2158 "0x5a443704dd4b594b382c22a083e2bd3090a6fef3".to_string(),
2159 );
2160
2161 let artifact_id = linker_instance
2163 .contracts
2164 .keys()
2165 .find(|id| id.name == "LibraryConsumer")
2166 .expect("LibraryConsumer contract not found");
2167
2168 let contract = linker_instance.contracts.get(artifact_id).unwrap();
2169
2170 assert!(
2172 linker_instance.ensure_linked(contract, artifact_id).is_err(),
2173 "Expected artifact to have unlinked bytecode"
2174 );
2175 }
2176}