diff --git a/crates/linking/src/lib.rs b/crates/linking/src/lib.rs index 33fad7418ac2f..0066ab7c83843 100644 --- a/crates/linking/src/lib.rs +++ b/crates/linking/src/lib.rs @@ -8,7 +8,7 @@ use alloy_primitives::{Address, B256, Bytes}; use foundry_compilers::{ Artifact, ArtifactId, - artifacts::{CompactContractBytecodeCow, Libraries}, + artifacts::{CompactBytecode, CompactContractBytecodeCow, Libraries}, contracts::ArtifactContracts, }; use rayon::prelude::*; @@ -105,23 +105,28 @@ impl<'a> Linker<'a> { ) -> Result<(), LinkerError> { let contract = self.contracts.get(target).ok_or(LinkerError::MissingTargetArtifact)?; - let mut references = BTreeMap::new(); + let mut references: BTreeMap> = BTreeMap::new(); + let mut extend = |bytecode: &CompactBytecode| { + for (file, libs) in &bytecode.link_references { + references.entry(file.clone()).or_default().extend(libs.keys().cloned()); + } + }; if let Some(bytecode) = &contract.bytecode { - references.extend(bytecode.link_references.clone()); + extend(bytecode); } if let Some(deployed_bytecode) = &contract.deployed_bytecode && let Some(bytecode) = &deployed_bytecode.bytecode { - references.extend(bytecode.link_references.clone()); + extend(bytecode); } - for (file, libs) in &references { - for contract in libs.keys() { + for (file, libs) in references { + for name in libs { let id = self - .find_artifact_id_by_library_path(file, contract, Some(&target.version)) + .find_artifact_id_by_library_path(&file, &name, Some(&target.version)) .ok_or_else(|| LinkerError::MissingLibraryArtifact { - file: file.to_string(), - name: contract.to_string(), + file: file.clone(), + name, })?; if deps.insert(id) { self.collect_dependencies(id, deps)?; @@ -744,6 +749,31 @@ mod tests { }); } + #[test] + fn link_samefile_union() { + link_test(testdata().join("default/linking/samefile_union"), |linker| { + linker + .assert_dependencies("default/linking/samefile_union/Libs.sol:LInit", &[]) + .assert_dependencies("default/linking/samefile_union/Libs.sol:LRun", &[]) + .assert_dependencies( + "default/linking/samefile_union/SameFileUnion.t.sol:UsesBoth", + &[ + ( + "default/linking/samefile_union/Libs.sol:LInit", + Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3") + .unwrap(), + ), + ( + "default/linking/samefile_union/Libs.sol:LRun", + Address::from_str("0x47e9fbef8c83a1714f1951f142132e6e90f5fa5d") + .unwrap(), + ), + ], + ) + .test_with_sender_and_nonce(Address::default(), 1); + }); + } + #[test] fn linking_failure() { let linker = LinkerTest::new(&testdata().join("default/linking/simple"), true); diff --git a/testdata/default/linking/samefile_union/Libs.sol b/testdata/default/linking/samefile_union/Libs.sol new file mode 100644 index 0000000000000..1e93f56c66e6a --- /dev/null +++ b/testdata/default/linking/samefile_union/Libs.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.18; + +library LInit { + function f() external view returns (uint256) { + return block.number; + } +} + +library LRun { + function g() external view returns (uint256) { + return block.timestamp; + } +} diff --git a/testdata/default/linking/samefile_union/SameFileUnion.t.sol b/testdata/default/linking/samefile_union/SameFileUnion.t.sol new file mode 100644 index 0000000000000..013f0e0b0c501 --- /dev/null +++ b/testdata/default/linking/samefile_union/SameFileUnion.t.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.18; + +import "./Libs.sol"; + +contract UsesBoth { + uint256 public x; + + constructor() { + // used only in creation bytecode + x = LInit.f(); + } + + function y() external view returns (uint256) { + // used only in deployed bytecode + return LRun.g(); + } +}