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