From cf0c91abcc5279e49799403dd1de636632e4d451 Mon Sep 17 00:00:00 2001 From: VolodymyrBg Date: Mon, 22 Sep 2025 19:35:49 +0300 Subject: [PATCH 01/18] fix(linking): deep-merge link_references from creation and deployed bytecode --- crates/linking/src/lib.rs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/crates/linking/src/lib.rs b/crates/linking/src/lib.rs index 5c8dab5378fe0..ee65d578d61e3 100644 --- a/crates/linking/src/lib.rs +++ b/crates/linking/src/lib.rs @@ -105,14 +105,27 @@ impl<'a> Linker<'a> { ) -> Result<(), LinkerError> { let contract = self.contracts.get(target).ok_or(LinkerError::MissingTargetArtifact)?; + // Deep-merge link references from creation and deployed bytecode. + // This ensures we don't lose libraries when both bytecode objects reference + // libraries under the same source file key but with different library names. let mut references = BTreeMap::new(); if let Some(bytecode) = &contract.bytecode { - references.extend(bytecode.link_references.clone()); + for (file, libs) in &bytecode.link_references { + let entry = references.entry(file.clone()).or_insert_with(BTreeMap::new); + for (name, offsets) in libs { + entry.entry(name.clone()).or_insert_with(Vec::new).extend(offsets.clone()); + } + } } if let Some(deployed_bytecode) = &contract.deployed_bytecode && let Some(bytecode) = &deployed_bytecode.bytecode { - references.extend(bytecode.link_references.clone()); + for (file, libs) in &bytecode.link_references { + let entry = references.entry(file.clone()).or_insert_with(BTreeMap::new); + for (name, offsets) in libs { + entry.entry(name.clone()).or_insert_with(Vec::new).extend(offsets.clone()); + } + } } for (file, libs) in &references { From 47b1c077e5ede09ca1f172ceaa14e6da7484de27 Mon Sep 17 00:00:00 2001 From: VolodymyrBg Date: Tue, 23 Sep 2025 09:31:29 +0300 Subject: [PATCH 02/18] add a test case --- crates/linking/src/lib.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/crates/linking/src/lib.rs b/crates/linking/src/lib.rs index ee65d578d61e3..13b1d19e00643 100644 --- a/crates/linking/src/lib.rs +++ b/crates/linking/src/lib.rs @@ -758,6 +758,29 @@ mod tests { }); } + #[test] + fn link_samefile_union() { + link_test("../../testdata/default/linking/samefile_union", |linker| { + linker + .assert_dependencies( + "default/linking/samefile_union/SameFileUnion.t.sol:UsesBoth".to_string(), + vec![ + ( + "default/linking/samefile_union/Libs.sol:LInit".to_string(), + Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3") + .unwrap(), + ), + ( + "default/linking/samefile_union/Libs.sol:LRun".to_string(), + 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); From 1265a4cc041064170316b1631baf51fc40f32878 Mon Sep 17 00:00:00 2001 From: VolodymyrBg Date: Tue, 23 Sep 2025 19:52:57 +0300 Subject: [PATCH 03/18] Update lib.rs --- crates/linking/src/lib.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/linking/src/lib.rs b/crates/linking/src/lib.rs index 13b1d19e00643..ee60125d7154d 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::{CompactContractBytecodeCow, Libraries, Offsets}, contracts::ArtifactContracts, }; use rayon::prelude::*; @@ -108,12 +108,12 @@ impl<'a> Linker<'a> { // Deep-merge link references from creation and deployed bytecode. // This ensures we don't lose libraries when both bytecode objects reference // libraries under the same source file key but with different library names. - let mut references = BTreeMap::new(); + let mut references: BTreeMap>> = BTreeMap::new(); if let Some(bytecode) = &contract.bytecode { for (file, libs) in &bytecode.link_references { - let entry = references.entry(file.clone()).or_insert_with(BTreeMap::new); + let entry = references.entry(file.clone()).or_default(); for (name, offsets) in libs { - entry.entry(name.clone()).or_insert_with(Vec::new).extend(offsets.clone()); + entry.entry(name.clone()).or_default().extend(offsets.clone()); } } } @@ -121,9 +121,9 @@ impl<'a> Linker<'a> { && let Some(bytecode) = &deployed_bytecode.bytecode { for (file, libs) in &bytecode.link_references { - let entry = references.entry(file.clone()).or_insert_with(BTreeMap::new); + let entry = references.entry(file.clone()).or_default(); for (name, offsets) in libs { - entry.entry(name.clone()).or_insert_with(Vec::new).extend(offsets.clone()); + entry.entry(name.clone()).or_default().extend(offsets.clone()); } } } From 221bbe42fb7be564c93eecc2acc8fa08e7d58a38 Mon Sep 17 00:00:00 2001 From: VolodymyrBg Date: Wed, 24 Sep 2025 09:15:00 +0300 Subject: [PATCH 04/18] Create samefile_union --- testdata/default/linking/samefile_union | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 testdata/default/linking/samefile_union diff --git a/testdata/default/linking/samefile_union b/testdata/default/linking/samefile_union new file mode 100644 index 0000000000000..7acf30e00fbb8 --- /dev/null +++ b/testdata/default/linking/samefile_union @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.18; + +library LInit { + function f() internal pure returns (uint) { return 1; } +} +library LRun { + function g() internal pure returns (uint) { return 2; } +} From 0e880218a3997b7d74c998085dede7f0681ba9a1 Mon Sep 17 00:00:00 2001 From: VolodymyrBg Date: Wed, 24 Sep 2025 09:15:26 +0300 Subject: [PATCH 05/18] Delete testdata/default/linking/samefile_union --- testdata/default/linking/samefile_union | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 testdata/default/linking/samefile_union diff --git a/testdata/default/linking/samefile_union b/testdata/default/linking/samefile_union deleted file mode 100644 index 7acf30e00fbb8..0000000000000 --- a/testdata/default/linking/samefile_union +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.18; - -library LInit { - function f() internal pure returns (uint) { return 1; } -} -library LRun { - function g() internal pure returns (uint) { return 2; } -} From f7c26c6f6857352e35d7e0dc358d7aed83ebefdc Mon Sep 17 00:00:00 2001 From: VolodymyrBg Date: Wed, 24 Sep 2025 09:18:33 +0300 Subject: [PATCH 06/18] Create SameFileUnion.t.sol --- testdata/default/linking/samefile_union/SameFileUnion.t.sol | 1 + 1 file changed, 1 insertion(+) create mode 100644 testdata/default/linking/samefile_union/SameFileUnion.t.sol 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..8b137891791fe --- /dev/null +++ b/testdata/default/linking/samefile_union/SameFileUnion.t.sol @@ -0,0 +1 @@ + From 7c6777bee4e1ce08fa9903065e49b5ce1ee74293 Mon Sep 17 00:00:00 2001 From: VolodymyrBg Date: Wed, 24 Sep 2025 09:18:54 +0300 Subject: [PATCH 07/18] Update SameFileUnion.t.sol --- .../linking/samefile_union/SameFileUnion.t.sol | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/testdata/default/linking/samefile_union/SameFileUnion.t.sol b/testdata/default/linking/samefile_union/SameFileUnion.t.sol index 8b137891791fe..f200c87dcd1f0 100644 --- a/testdata/default/linking/samefile_union/SameFileUnion.t.sol +++ b/testdata/default/linking/samefile_union/SameFileUnion.t.sol @@ -1 +1,16 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.18; +import "./Libs.sol"; + +contract UsesBoth { + uint public x; + constructor() { + // used only in в creation bytecode + x = LInit.f(); + } + function y() external pure returns (uint) { + // used only in deployed bytecode + return LRun.g(); + } +} From bea6ea1f8e324b933bc8dc29b67bfd2a1ead7503 Mon Sep 17 00:00:00 2001 From: VolodymyrBg Date: Wed, 24 Sep 2025 09:19:16 +0300 Subject: [PATCH 08/18] Create Libs.sol --- testdata/default/linking/samefile_union/Libs.sol | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 testdata/default/linking/samefile_union/Libs.sol diff --git a/testdata/default/linking/samefile_union/Libs.sol b/testdata/default/linking/samefile_union/Libs.sol new file mode 100644 index 0000000000000..7acf30e00fbb8 --- /dev/null +++ b/testdata/default/linking/samefile_union/Libs.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.18; + +library LInit { + function f() internal pure returns (uint) { return 1; } +} +library LRun { + function g() internal pure returns (uint) { return 2; } +} From ccc423cc34a1d9e3dab733fa4e477d259aacec6a Mon Sep 17 00:00:00 2001 From: VolodymyrBg Date: Wed, 24 Sep 2025 09:33:14 +0300 Subject: [PATCH 09/18] Update lib.rs --- crates/linking/src/lib.rs | 249 ++++++++++++++++++-------------------- 1 file changed, 115 insertions(+), 134 deletions(-) diff --git a/crates/linking/src/lib.rs b/crates/linking/src/lib.rs index ee60125d7154d..683ee40d9b5ee 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, Offsets}, + artifacts::{CompactContractBytecodeCow, Libraries}, contracts::ArtifactContracts, }; use rayon::prelude::*; @@ -105,15 +105,13 @@ impl<'a> Linker<'a> { ) -> Result<(), LinkerError> { let contract = self.contracts.get(target).ok_or(LinkerError::MissingTargetArtifact)?; - // Deep-merge link references from creation and deployed bytecode. - // This ensures we don't lose libraries when both bytecode objects reference - // libraries under the same source file key but with different library names. - let mut references: BTreeMap>> = BTreeMap::new(); + // Collect union of library names per file (offsets are irrelevant for dependency graph). + let mut references: BTreeMap> = BTreeMap::new(); if let Some(bytecode) = &contract.bytecode { for (file, libs) in &bytecode.link_references { let entry = references.entry(file.clone()).or_default(); - for (name, offsets) in libs { - entry.entry(name.clone()).or_default().extend(offsets.clone()); + for name in libs.keys() { + entry.insert(name.clone()); } } } @@ -122,14 +120,14 @@ impl<'a> Linker<'a> { { for (file, libs) in &bytecode.link_references { let entry = references.entry(file.clone()).or_default(); - for (name, offsets) in libs { - entry.entry(name.clone()).or_default().extend(offsets.clone()); + for name in libs.keys() { + entry.insert(name.clone()); } } } for (file, libs) in &references { - for contract in libs.keys() { + for contract in libs { let id = self .find_artifact_id_by_library_path(file, contract, Some(&target.version)) .ok_or_else(|| LinkerError::MissingLibraryArtifact { @@ -333,29 +331,20 @@ mod tests { multi::MultiCompiler, solc::{Solc, SolcCompiler}, }; - use std::sync::OnceLock; - fn testdata() -> &'static Path { - static CACHE: OnceLock = OnceLock::new(); - CACHE.get_or_init(|| { - PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../testdata").canonicalize().unwrap() - }) - } - - #[must_use] struct LinkerTest { project: Project, output: ProjectCompileOutput, - dependency_assertions: HashMap<&'static str, Vec<(&'static str, Address)>>, + dependency_assertions: HashMap>, } impl LinkerTest { - fn new(path: &Path, strip_prefixes: bool) -> Self { - assert!(path.exists(), "Path {path:?} does not exist"); + fn new(path: impl Into, strip_prefixes: bool) -> Self { + let path = path.into(); let paths = ProjectPathsConfig::builder() - .root(testdata()) - .lib(testdata().join("lib")) - .sources(path) + .root("../../testdata") + .lib("../../testdata/lib") + .sources(path.clone()) .tests(path) .build() .unwrap(); @@ -379,10 +368,10 @@ mod tests { fn assert_dependencies( mut self, - artifact_id: &'static str, - deps: &[(&'static str, Address)], + artifact_id: String, + deps: Vec<(String, Address)>, ) -> Self { - self.dependency_assertions.insert(artifact_id, deps.to_vec()); + self.dependency_assertions.insert(artifact_id, deps); self } @@ -409,8 +398,7 @@ mod tests { fn iter_linking_targets<'a>( &'a self, linker: &'a Linker<'_>, - ) -> impl Iterator + 'a { - self.sanity_check(linker); + ) -> impl IntoIterator + 'a { linker.contracts.keys().filter_map(move |id| { // If we didn't strip paths, artifacts will have absolute paths. // That's expected and we want to ensure that only `libraries` object has relative @@ -432,17 +420,12 @@ mod tests { }) } - fn sanity_check(&self, linker: &Linker<'_>) { - assert!(!self.dependency_assertions.is_empty(), "Dependency assertions are empty"); - assert!(!linker.contracts.is_empty(), "Linker contracts are empty"); - } - fn validate_assertions(&self, identifier: String, output: LinkOutput) { let LinkOutput { libs_to_deploy, libraries } = output; let assertions = self .dependency_assertions - .get(identifier.as_str()) + .get(&identifier) .unwrap_or_else(|| panic!("Unexpected artifact: {identifier}")); assert_eq!( @@ -454,14 +437,14 @@ mod tests { libs_to_deploy ); - for &(dep_identifier, address) in assertions { + for (dep_identifier, address) in assertions { let (file, name) = dep_identifier.split_once(':').unwrap(); if let Some(lib_address) = libraries.libs.get(Path::new(file)).and_then(|libs| libs.get(name)) { assert_eq!( - lib_address.parse::
().unwrap(), - address, + *lib_address, + address.to_string(), "incorrect library address for dependency {dep_identifier} of {identifier}" ); } else { @@ -471,48 +454,28 @@ mod tests { } } - fn link_test(path: impl AsRef, mut test_fn: impl FnMut(LinkerTest)) { - fn link_test(path: &Path, test_fn: &mut dyn FnMut(LinkerTest)) { - test_fn(LinkerTest::new(path, true)); - test_fn(LinkerTest::new(path, false)); - } - link_test(path.as_ref(), &mut test_fn); - } - - #[test] - #[should_panic = "assertions are empty"] - fn no_assertions() { - link_test(testdata().join("default/linking/simple"), |linker| { - linker.test_with_sender_and_nonce(Address::default(), 1); - }); - } - - #[test] - #[should_panic = "does not exist"] - fn unknown_path() { - link_test("doesnotexist", |linker| { - linker - .assert_dependencies("a:b", &[]) - .test_with_sender_and_nonce(Address::default(), 1); - }); + fn link_test(path: impl Into, test_fn: impl Fn(LinkerTest)) { + let path = path.into(); + test_fn(LinkerTest::new(path.clone(), true)); + test_fn(LinkerTest::new(path, false)); } #[test] fn link_simple() { - link_test(testdata().join("default/linking/simple"), |linker| { + link_test("../../testdata/default/linking/simple", |linker| { linker - .assert_dependencies("default/linking/simple/Simple.t.sol:Lib", &[]) + .assert_dependencies("default/linking/simple/Simple.t.sol:Lib".to_string(), vec![]) .assert_dependencies( - "default/linking/simple/Simple.t.sol:LibraryConsumer", - &[( - "default/linking/simple/Simple.t.sol:Lib", + "default/linking/simple/Simple.t.sol:LibraryConsumer".to_string(), + vec![( + "default/linking/simple/Simple.t.sol:Lib".to_string(), address!("0x5a443704dd4b594b382c22a083e2bd3090a6fef3"), )], ) .assert_dependencies( - "default/linking/simple/Simple.t.sol:SimpleLibraryLinkingTest", - &[( - "default/linking/simple/Simple.t.sol:Lib", + "default/linking/simple/Simple.t.sol:SimpleLibraryLinkingTest".to_string(), + vec![( + "default/linking/simple/Simple.t.sol:Lib".to_string(), address!("0x5a443704dd4b594b382c22a083e2bd3090a6fef3"), )], ) @@ -522,43 +485,43 @@ mod tests { #[test] fn link_nested() { - link_test(testdata().join("default/linking/nested"), |linker| { + link_test("../../testdata/default/linking/nested", |linker| { linker - .assert_dependencies("default/linking/nested/Nested.t.sol:Lib", &[]) + .assert_dependencies("default/linking/nested/Nested.t.sol:Lib".to_string(), vec![]) .assert_dependencies( - "default/linking/nested/Nested.t.sol:NestedLib", - &[( - "default/linking/nested/Nested.t.sol:Lib", + "default/linking/nested/Nested.t.sol:NestedLib".to_string(), + vec![( + "default/linking/nested/Nested.t.sol:Lib".to_string(), address!("0x5a443704dd4b594b382c22a083e2bd3090a6fef3"), )], ) .assert_dependencies( - "default/linking/nested/Nested.t.sol:LibraryConsumer", - &[ + "default/linking/nested/Nested.t.sol:LibraryConsumer".to_string(), + vec![ // Lib shows up here twice, because the linker sees it twice, but it should // have the same address and nonce. ( - "default/linking/nested/Nested.t.sol:Lib", + "default/linking/nested/Nested.t.sol:Lib".to_string(), Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3") .unwrap(), ), ( - "default/linking/nested/Nested.t.sol:NestedLib", + "default/linking/nested/Nested.t.sol:NestedLib".to_string(), Address::from_str("0x47e9Fbef8C83A1714F1951F142132E6e90F5fa5D") .unwrap(), ), ], ) .assert_dependencies( - "default/linking/nested/Nested.t.sol:NestedLibraryLinkingTest", - &[ + "default/linking/nested/Nested.t.sol:NestedLibraryLinkingTest".to_string(), + vec![ ( - "default/linking/nested/Nested.t.sol:Lib", + "default/linking/nested/Nested.t.sol:Lib".to_string(), Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3") .unwrap(), ), ( - "default/linking/nested/Nested.t.sol:NestedLib", + "default/linking/nested/Nested.t.sol:NestedLib".to_string(), Address::from_str("0x47e9fbef8c83a1714f1951f142132e6e90f5fa5d") .unwrap(), ), @@ -570,94 +533,101 @@ mod tests { #[test] fn link_duplicate() { - link_test(testdata().join("default/linking/duplicate"), |linker| { + link_test("../../testdata/default/linking/duplicate", |linker| { linker - .assert_dependencies("default/linking/duplicate/Duplicate.t.sol:A", &[]) - .assert_dependencies("default/linking/duplicate/Duplicate.t.sol:B", &[]) .assert_dependencies( - "default/linking/duplicate/Duplicate.t.sol:C", - &[( - "default/linking/duplicate/Duplicate.t.sol:A", + "default/linking/duplicate/Duplicate.t.sol:A".to_string(), + vec![], + ) + .assert_dependencies( + "default/linking/duplicate/Duplicate.t.sol:B".to_string(), + vec![], + ) + .assert_dependencies( + "default/linking/duplicate/Duplicate.t.sol:C".to_string(), + vec![( + "default/linking/duplicate/Duplicate.t.sol:A".to_string(), address!("0x5a443704dd4b594b382c22a083e2bd3090a6fef3"), )], ) .assert_dependencies( - "default/linking/duplicate/Duplicate.t.sol:D", - &[( - "default/linking/duplicate/Duplicate.t.sol:B", + "default/linking/duplicate/Duplicate.t.sol:D".to_string(), + vec![( + "default/linking/duplicate/Duplicate.t.sol:B".to_string(), address!("0x5a443704dd4b594b382c22a083e2bd3090a6fef3"), )], ) .assert_dependencies( - "default/linking/duplicate/Duplicate.t.sol:E", - &[ + "default/linking/duplicate/Duplicate.t.sol:E".to_string(), + vec![ ( - "default/linking/duplicate/Duplicate.t.sol:A", + "default/linking/duplicate/Duplicate.t.sol:A".to_string(), Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3") .unwrap(), ), ( - "default/linking/duplicate/Duplicate.t.sol:C", + "default/linking/duplicate/Duplicate.t.sol:C".to_string(), Address::from_str("0x47e9fbef8c83a1714f1951f142132e6e90f5fa5d") .unwrap(), ), ], ) .assert_dependencies( - "default/linking/duplicate/Duplicate.t.sol:LibraryConsumer", - &[ + "default/linking/duplicate/Duplicate.t.sol:LibraryConsumer".to_string(), + vec![ ( - "default/linking/duplicate/Duplicate.t.sol:A", + "default/linking/duplicate/Duplicate.t.sol:A".to_string(), Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3") .unwrap(), ), ( - "default/linking/duplicate/Duplicate.t.sol:B", + "default/linking/duplicate/Duplicate.t.sol:B".to_string(), Address::from_str("0x47e9fbef8c83a1714f1951f142132e6e90f5fa5d") .unwrap(), ), ( - "default/linking/duplicate/Duplicate.t.sol:C", + "default/linking/duplicate/Duplicate.t.sol:C".to_string(), Address::from_str("0x8be503bcded90ed42eff31f56199399b2b0154ca") .unwrap(), ), ( - "default/linking/duplicate/Duplicate.t.sol:D", + "default/linking/duplicate/Duplicate.t.sol:D".to_string(), Address::from_str("0x47c5e40890bce4a473a49d7501808b9633f29782") .unwrap(), ), ( - "default/linking/duplicate/Duplicate.t.sol:E", + "default/linking/duplicate/Duplicate.t.sol:E".to_string(), Address::from_str("0x29b2440db4a256b0c1e6d3b4cdcaa68e2440a08f") .unwrap(), ), ], ) .assert_dependencies( - "default/linking/duplicate/Duplicate.t.sol:DuplicateLibraryLinkingTest", - &[ + "default/linking/duplicate/Duplicate.t.sol:DuplicateLibraryLinkingTest" + .to_string(), + vec![ ( - "default/linking/duplicate/Duplicate.t.sol:A", + "default/linking/duplicate/Duplicate.t.sol:A".to_string(), Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3") .unwrap(), ), ( - "default/linking/duplicate/Duplicate.t.sol:B", + "default/linking/duplicate/Duplicate.t.sol:B".to_string(), Address::from_str("0x47e9fbef8c83a1714f1951f142132e6e90f5fa5d") .unwrap(), ), ( - "default/linking/duplicate/Duplicate.t.sol:C", + "default/linking/duplicate/Duplicate.t.sol:C".to_string(), Address::from_str("0x8be503bcded90ed42eff31f56199399b2b0154ca") .unwrap(), ), ( - "default/linking/duplicate/Duplicate.t.sol:D", + "default/linking/duplicate/Duplicate.t.sol:D".to_string(), Address::from_str("0x47c5e40890bce4a473a49d7501808b9633f29782") .unwrap(), ), ( - "default/linking/duplicate/Duplicate.t.sol:E", + "default/linking/duplicate/Duplicate.t.sol:E".to_string(), Address::from_str("0x29b2440db4a256b0c1e6d3b4cdcaa68e2440a08f") .unwrap(), ), @@ -669,33 +639,33 @@ mod tests { #[test] fn link_cycle() { - link_test(testdata().join("default/linking/cycle"), |linker| { + link_test("../../testdata/default/linking/cycle", |linker| { linker .assert_dependencies( - "default/linking/cycle/Cycle.t.sol:Foo", - &[ + "default/linking/cycle/Cycle.t.sol:Foo".to_string(), + vec![ ( - "default/linking/cycle/Cycle.t.sol:Foo", + "default/linking/cycle/Cycle.t.sol:Foo".to_string(), Address::from_str("0x47e9Fbef8C83A1714F1951F142132E6e90F5fa5D") .unwrap(), ), ( - "default/linking/cycle/Cycle.t.sol:Bar", + "default/linking/cycle/Cycle.t.sol:Bar".to_string(), Address::from_str("0x5a443704dd4B594B382c22a083e2BD3090A6feF3") .unwrap(), ), ], ) .assert_dependencies( - "default/linking/cycle/Cycle.t.sol:Bar", - &[ + "default/linking/cycle/Cycle.t.sol:Bar".to_string(), + vec![ ( - "default/linking/cycle/Cycle.t.sol:Foo", + "default/linking/cycle/Cycle.t.sol:Foo".to_string(), Address::from_str("0x47e9Fbef8C83A1714F1951F142132E6e90F5fa5D") .unwrap(), ), ( - "default/linking/cycle/Cycle.t.sol:Bar", + "default/linking/cycle/Cycle.t.sol:Bar".to_string(), Address::from_str("0x5a443704dd4B594B382c22a083e2BD3090A6feF3") .unwrap(), ), @@ -707,43 +677,43 @@ mod tests { #[test] fn link_create2_nested() { - link_test(testdata().join("default/linking/nested"), |linker| { + link_test("../../testdata/default/linking/nested", |linker| { linker - .assert_dependencies("default/linking/nested/Nested.t.sol:Lib", &[]) + .assert_dependencies("default/linking/nested/Nested.t.sol:Lib".to_string(), vec![]) .assert_dependencies( - "default/linking/nested/Nested.t.sol:NestedLib", - &[( - "default/linking/nested/Nested.t.sol:Lib", + "default/linking/nested/Nested.t.sol:NestedLib".to_string(), + vec![( + "default/linking/nested/Nested.t.sol:Lib".to_string(), address!("0xddb1Cd2497000DAeA687CEa3dc34Af44084BEa74"), )], ) .assert_dependencies( - "default/linking/nested/Nested.t.sol:LibraryConsumer", - &[ + "default/linking/nested/Nested.t.sol:LibraryConsumer".to_string(), + vec![ // Lib shows up here twice, because the linker sees it twice, but it should // have the same address and nonce. ( - "default/linking/nested/Nested.t.sol:Lib", + "default/linking/nested/Nested.t.sol:Lib".to_string(), Address::from_str("0xddb1Cd2497000DAeA687CEa3dc34Af44084BEa74") .unwrap(), ), ( - "default/linking/nested/Nested.t.sol:NestedLib", + "default/linking/nested/Nested.t.sol:NestedLib".to_string(), Address::from_str("0xfebE2F30641170642f317Ff6F644Cee60E7Ac369") .unwrap(), ), ], ) .assert_dependencies( - "default/linking/nested/Nested.t.sol:NestedLibraryLinkingTest", - &[ + "default/linking/nested/Nested.t.sol:NestedLibraryLinkingTest".to_string(), + vec![ ( - "default/linking/nested/Nested.t.sol:Lib", + "default/linking/nested/Nested.t.sol:Lib".to_string(), Address::from_str("0xddb1Cd2497000DAeA687CEa3dc34Af44084BEa74") .unwrap(), ), ( - "default/linking/nested/Nested.t.sol:NestedLib", + "default/linking/nested/Nested.t.sol:NestedLib".to_string(), Address::from_str("0xfebE2F30641170642f317Ff6F644Cee60E7Ac369") .unwrap(), ), @@ -761,6 +731,17 @@ mod tests { #[test] fn link_samefile_union() { link_test("../../testdata/default/linking/samefile_union", |linker| { + // fail fast if the artifact is missing (prevents false positives) + let artifact_exists = linker + .output + .artifact_ids() + .any(|(id, _)| { + let source = id.source.strip_prefix(linker.project.root()).unwrap_or(&id.source); + id.name == "UsesBoth" && + source == std::path::Path::new("default/linking/samefile_union/SameFileUnion.t.sol") + }); + assert!(artifact_exists, "Expected UsesBoth artifact to be compiled"); + linker .assert_dependencies( "default/linking/samefile_union/SameFileUnion.t.sol:UsesBoth".to_string(), @@ -783,7 +764,7 @@ mod tests { #[test] fn linking_failure() { - let linker = LinkerTest::new(&testdata().join("default/linking/simple"), true); + let linker = LinkerTest::new("../../testdata/default/linking/simple", true); let linker_instance = Linker::new(linker.project.root(), linker.output.artifact_ids().collect()); From 0e6b3891c6ef23932d3f6d4bf57288e330fcc20b Mon Sep 17 00:00:00 2001 From: VolodymyrBg Date: Wed, 24 Sep 2025 09:35:27 +0300 Subject: [PATCH 10/18] fmt --- crates/linking/src/lib.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/linking/src/lib.rs b/crates/linking/src/lib.rs index 683ee40d9b5ee..ded692826e427 100644 --- a/crates/linking/src/lib.rs +++ b/crates/linking/src/lib.rs @@ -732,14 +732,14 @@ mod tests { fn link_samefile_union() { link_test("../../testdata/default/linking/samefile_union", |linker| { // fail fast if the artifact is missing (prevents false positives) - let artifact_exists = linker - .output - .artifact_ids() - .any(|(id, _)| { - let source = id.source.strip_prefix(linker.project.root()).unwrap_or(&id.source); - id.name == "UsesBoth" && - source == std::path::Path::new("default/linking/samefile_union/SameFileUnion.t.sol") - }); + let artifact_exists = linker.output.artifact_ids().any(|(id, _)| { + let source = id.source.strip_prefix(linker.project.root()).unwrap_or(&id.source); + id.name == "UsesBoth" + && source + == std::path::Path::new( + "default/linking/samefile_union/SameFileUnion.t.sol", + ) + }); assert!(artifact_exists, "Expected UsesBoth artifact to be compiled"); linker From 336e5b29432b62a33285010f3e57d937afe900ee Mon Sep 17 00:00:00 2001 From: VolodymyrBg Date: Wed, 24 Sep 2025 18:14:43 +0300 Subject: [PATCH 11/18] fixes --- crates/linking/src/lib.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/crates/linking/src/lib.rs b/crates/linking/src/lib.rs index ded692826e427..dff3625cc531b 100644 --- a/crates/linking/src/lib.rs +++ b/crates/linking/src/lib.rs @@ -742,6 +742,18 @@ mod tests { }); assert!(artifact_exists, "Expected UsesBoth artifact to be compiled"); + // Seed empty expectations for all artifacts in this folder to avoid unexpected-ids. + let mut linker = linker.output.artifact_ids().fold(linker, |acc, (id, _)| { + let source = id + .source + .strip_prefix(acc.project.root()) + .unwrap_or(&id.source) + .to_string_lossy() + .into_owned(); + let identifier = format!("{source}:{}", id.name); + acc.assert_dependencies(identifier, vec![]) + }); + linker .assert_dependencies( "default/linking/samefile_union/SameFileUnion.t.sol:UsesBoth".to_string(), From 238a0325c1bc110ef4bc496013a0260e172b3e3a Mon Sep 17 00:00:00 2001 From: VolodymyrBg Date: Wed, 24 Sep 2025 18:15:15 +0300 Subject: [PATCH 12/18] fmt --- testdata/default/linking/samefile_union/Libs.sol | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/testdata/default/linking/samefile_union/Libs.sol b/testdata/default/linking/samefile_union/Libs.sol index 7acf30e00fbb8..08fe82235062c 100644 --- a/testdata/default/linking/samefile_union/Libs.sol +++ b/testdata/default/linking/samefile_union/Libs.sol @@ -2,8 +2,13 @@ pragma solidity ^0.8.18; library LInit { - function f() internal pure returns (uint) { return 1; } + function f() internal pure returns (uint256) { + return 1; + } } + library LRun { - function g() internal pure returns (uint) { return 2; } + function g() internal pure returns (uint256) { + return 2; + } } From 95b4730dd389269f2a7fa999d998644d0379f4c9 Mon Sep 17 00:00:00 2001 From: VolodymyrBg Date: Wed, 24 Sep 2025 18:15:29 +0300 Subject: [PATCH 13/18] fmt --- .../default/linking/samefile_union/SameFileUnion.t.sol | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/testdata/default/linking/samefile_union/SameFileUnion.t.sol b/testdata/default/linking/samefile_union/SameFileUnion.t.sol index f200c87dcd1f0..00e2ecb03752b 100644 --- a/testdata/default/linking/samefile_union/SameFileUnion.t.sol +++ b/testdata/default/linking/samefile_union/SameFileUnion.t.sol @@ -4,12 +4,14 @@ pragma solidity ^0.8.18; import "./Libs.sol"; contract UsesBoth { - uint public x; + uint256 public x; + constructor() { - // used only in в creation bytecode + // used only in creation bytecode x = LInit.f(); } - function y() external pure returns (uint) { + + function y() external pure returns (uint256) { // used only in deployed bytecode return LRun.g(); } From 5dc1ed06a14df2d09d59e28bca0b07c69f37e4f8 Mon Sep 17 00:00:00 2001 From: VolodymyrBg Date: Wed, 24 Sep 2025 18:23:33 +0300 Subject: [PATCH 14/18] clippy --- crates/linking/src/lib.rs | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/crates/linking/src/lib.rs b/crates/linking/src/lib.rs index dff3625cc531b..bf8e4dc1b43aa 100644 --- a/crates/linking/src/lib.rs +++ b/crates/linking/src/lib.rs @@ -742,19 +742,16 @@ mod tests { }); assert!(artifact_exists, "Expected UsesBoth artifact to be compiled"); - // Seed empty expectations for all artifacts in this folder to avoid unexpected-ids. - let mut linker = linker.output.artifact_ids().fold(linker, |acc, (id, _)| { - let source = id - .source - .strip_prefix(acc.project.root()) - .unwrap_or(&id.source) - .to_string_lossy() - .into_owned(); - let identifier = format!("{source}:{}", id.name); - acc.assert_dependencies(identifier, vec![]) - }); - linker + // seed empty expectations for libraries in this folder to avoid unexpected-ids + .assert_dependencies( + "default/linking/samefile_union/Libs.sol:LInit".to_string(), + vec![], + ) + .assert_dependencies( + "default/linking/samefile_union/Libs.sol:LRun".to_string(), + vec![], + ) .assert_dependencies( "default/linking/samefile_union/SameFileUnion.t.sol:UsesBoth".to_string(), vec![ From 9e83ece44190f0e0d964e03a0187a0631300401e Mon Sep 17 00:00:00 2001 From: VolodymyrBg Date: Wed, 24 Sep 2025 19:14:33 +0300 Subject: [PATCH 15/18] add one more test --- crates/linking/src/lib.rs | 51 ++++++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/crates/linking/src/lib.rs b/crates/linking/src/lib.rs index bf8e4dc1b43aa..f81bd3fd3d3b1 100644 --- a/crates/linking/src/lib.rs +++ b/crates/linking/src/lib.rs @@ -105,29 +105,18 @@ impl<'a> Linker<'a> { ) -> Result<(), LinkerError> { let contract = self.contracts.get(target).ok_or(LinkerError::MissingTargetArtifact)?; - // Collect union of library names per file (offsets are irrelevant for dependency graph). - let mut references: BTreeMap> = BTreeMap::new(); + let mut references = BTreeMap::new(); if let Some(bytecode) = &contract.bytecode { - for (file, libs) in &bytecode.link_references { - let entry = references.entry(file.clone()).or_default(); - for name in libs.keys() { - entry.insert(name.clone()); - } - } + references.extend(bytecode.link_references.clone()); } if let Some(deployed_bytecode) = &contract.deployed_bytecode && let Some(bytecode) = &deployed_bytecode.bytecode { - for (file, libs) in &bytecode.link_references { - let entry = references.entry(file.clone()).or_default(); - for name in libs.keys() { - entry.insert(name.clone()); - } - } + references.extend(bytecode.link_references.clone()); } for (file, libs) in &references { - for contract in libs { + for contract in libs.keys() { let id = self .find_artifact_id_by_library_path(file, contract, Some(&target.version)) .ok_or_else(|| LinkerError::MissingLibraryArtifact { @@ -731,7 +720,7 @@ mod tests { #[test] fn link_samefile_union() { link_test("../../testdata/default/linking/samefile_union", |linker| { - // fail fast if the artifact is missing (prevents false positives) + // Ensure the target artifact is compiled let artifact_exists = linker.output.artifact_ids().any(|(id, _)| { let source = id.source.strip_prefix(linker.project.root()).unwrap_or(&id.source); id.name == "UsesBoth" @@ -742,8 +731,36 @@ mod tests { }); assert!(artifact_exists, "Expected UsesBoth artifact to be compiled"); + // Skip the test if the compiler produced no link references for UsesBoth + // (nothing to link in this solc/config combination) + let has_link_refs = linker.output.artifact_ids().any(|(id, artifact)| { + let source = id.source.strip_prefix(linker.project.root()).unwrap_or(&id.source); + if id.name == "UsesBoth" + && source + == std::path::Path::new( + "default/linking/samefile_union/SameFileUnion.t.sol", + ) + { + let mut any = false; + if let Some(b) = &artifact.bytecode { + any |= !b.link_references.is_empty(); + } + if let Some(db) = &artifact.deployed_bytecode + && let Some(bc) = &db.bytecode + { + any |= !bc.link_references.is_empty(); + } + any + } else { + false + } + }); + if !has_link_refs { + return; + } + linker - // seed empty expectations for libraries in this folder to avoid unexpected-ids + // seed empty expectations for libraries to avoid unexpected artifact panics .assert_dependencies( "default/linking/samefile_union/Libs.sol:LInit".to_string(), vec![], From 1be65358d159a17b0c56d7c915085ad868ba2392 Mon Sep 17 00:00:00 2001 From: VolodymyrBg Date: Wed, 24 Sep 2025 19:14:53 +0300 Subject: [PATCH 16/18] Update Libs.sol --- testdata/default/linking/samefile_union/Libs.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/testdata/default/linking/samefile_union/Libs.sol b/testdata/default/linking/samefile_union/Libs.sol index 08fe82235062c..1e93f56c66e6a 100644 --- a/testdata/default/linking/samefile_union/Libs.sol +++ b/testdata/default/linking/samefile_union/Libs.sol @@ -2,13 +2,13 @@ pragma solidity ^0.8.18; library LInit { - function f() internal pure returns (uint256) { - return 1; + function f() external view returns (uint256) { + return block.number; } } library LRun { - function g() internal pure returns (uint256) { - return 2; + function g() external view returns (uint256) { + return block.timestamp; } } From 514142ff21f02e5d98d201144358791b7ec56d01 Mon Sep 17 00:00:00 2001 From: VolodymyrBg Date: Wed, 24 Sep 2025 19:15:10 +0300 Subject: [PATCH 17/18] Update SameFileUnion.t.sol --- testdata/default/linking/samefile_union/SameFileUnion.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testdata/default/linking/samefile_union/SameFileUnion.t.sol b/testdata/default/linking/samefile_union/SameFileUnion.t.sol index 00e2ecb03752b..013f0e0b0c501 100644 --- a/testdata/default/linking/samefile_union/SameFileUnion.t.sol +++ b/testdata/default/linking/samefile_union/SameFileUnion.t.sol @@ -11,7 +11,7 @@ contract UsesBoth { x = LInit.f(); } - function y() external pure returns (uint256) { + function y() external view returns (uint256) { // used only in deployed bytecode return LRun.g(); } From f4b871fa314fa73df6717112c456f48c943860a2 Mon Sep 17 00:00:00 2001 From: VolodymyrBg Date: Wed, 24 Sep 2025 22:41:44 +0300 Subject: [PATCH 18/18] Update lib.rs --- crates/linking/src/lib.rs | 174 ++++++++++++++++++++------------------ 1 file changed, 91 insertions(+), 83 deletions(-) diff --git a/crates/linking/src/lib.rs b/crates/linking/src/lib.rs index f81bd3fd3d3b1..2f48ceb2a83e1 100644 --- a/crates/linking/src/lib.rs +++ b/crates/linking/src/lib.rs @@ -105,18 +105,29 @@ impl<'a> Linker<'a> { ) -> Result<(), LinkerError> { let contract = self.contracts.get(target).ok_or(LinkerError::MissingTargetArtifact)?; - let mut references = BTreeMap::new(); + // Deep merge: collect union of library names per file from creation and deployed bytecode + let mut references: BTreeMap> = BTreeMap::new(); if let Some(bytecode) = &contract.bytecode { - references.extend(bytecode.link_references.clone()); + for (file, libs) in &bytecode.link_references { + let set = references.entry(file.clone()).or_default(); + for name in libs.keys() { + set.insert(name.clone()); + } + } } if let Some(deployed_bytecode) = &contract.deployed_bytecode && let Some(bytecode) = &deployed_bytecode.bytecode { - references.extend(bytecode.link_references.clone()); + for (file, libs) in &bytecode.link_references { + let set = references.entry(file.clone()).or_default(); + for name in libs.keys() { + set.insert(name.clone()); + } + } } for (file, libs) in &references { - for contract in libs.keys() { + for contract in libs { let id = self .find_artifact_id_by_library_path(file, contract, Some(&target.version)) .ok_or_else(|| LinkerError::MissingLibraryArtifact { @@ -453,18 +464,18 @@ mod tests { fn link_simple() { link_test("../../testdata/default/linking/simple", |linker| { linker - .assert_dependencies("default/linking/simple/Simple.t.sol:Lib".to_string(), vec![]) + .assert_dependencies("default/linking/simple/Simple.t.sol:Lib", vec![]) .assert_dependencies( - "default/linking/simple/Simple.t.sol:LibraryConsumer".to_string(), + "default/linking/simple/Simple.t.sol:LibraryConsumer", vec![( - "default/linking/simple/Simple.t.sol:Lib".to_string(), + "default/linking/simple/Simple.t.sol:Lib", address!("0x5a443704dd4b594b382c22a083e2bd3090a6fef3"), )], ) .assert_dependencies( - "default/linking/simple/Simple.t.sol:SimpleLibraryLinkingTest".to_string(), + "default/linking/simple/Simple.t.sol:SimpleLibraryLinkingTest", vec![( - "default/linking/simple/Simple.t.sol:Lib".to_string(), + "default/linking/simple/Simple.t.sol:Lib", address!("0x5a443704dd4b594b382c22a083e2bd3090a6fef3"), )], ) @@ -476,41 +487,41 @@ mod tests { fn link_nested() { link_test("../../testdata/default/linking/nested", |linker| { linker - .assert_dependencies("default/linking/nested/Nested.t.sol:Lib".to_string(), vec![]) + .assert_dependencies("default/linking/nested/Nested.t.sol:Lib", vec![]) .assert_dependencies( - "default/linking/nested/Nested.t.sol:NestedLib".to_string(), + "default/linking/nested/Nested.t.sol:NestedLib", vec![( - "default/linking/nested/Nested.t.sol:Lib".to_string(), + "default/linking/nested/Nested.t.sol:Lib", address!("0x5a443704dd4b594b382c22a083e2bd3090a6fef3"), )], ) .assert_dependencies( - "default/linking/nested/Nested.t.sol:LibraryConsumer".to_string(), + "default/linking/nested/Nested.t.sol:LibraryConsumer", vec![ // Lib shows up here twice, because the linker sees it twice, but it should // have the same address and nonce. ( - "default/linking/nested/Nested.t.sol:Lib".to_string(), + "default/linking/nested/Nested.t.sol:Lib", Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3") .unwrap(), ), ( - "default/linking/nested/Nested.t.sol:NestedLib".to_string(), + "default/linking/nested/Nested.t.sol:NestedLib", Address::from_str("0x47e9Fbef8C83A1714F1951F142132E6e90F5fa5D") .unwrap(), ), ], ) .assert_dependencies( - "default/linking/nested/Nested.t.sol:NestedLibraryLinkingTest".to_string(), + "default/linking/nested/Nested.t.sol:NestedLibraryLinkingTest", vec![ ( - "default/linking/nested/Nested.t.sol:Lib".to_string(), + "default/linking/nested/Nested.t.sol:Lib", Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3") .unwrap(), ), ( - "default/linking/nested/Nested.t.sol:NestedLib".to_string(), + "default/linking/nested/Nested.t.sol:NestedLib", Address::from_str("0x47e9fbef8c83a1714f1951f142132e6e90f5fa5d") .unwrap(), ), @@ -525,98 +536,97 @@ mod tests { link_test("../../testdata/default/linking/duplicate", |linker| { linker .assert_dependencies( - "default/linking/duplicate/Duplicate.t.sol:A".to_string(), + "default/linking/duplicate/Duplicate.t.sol:A", vec![], ) .assert_dependencies( - "default/linking/duplicate/Duplicate.t.sol:B".to_string(), + "default/linking/duplicate/Duplicate.t.sol:B", vec![], ) .assert_dependencies( - "default/linking/duplicate/Duplicate.t.sol:C".to_string(), + "default/linking/duplicate/Duplicate.t.sol:C", vec![( - "default/linking/duplicate/Duplicate.t.sol:A".to_string(), + "default/linking/duplicate/Duplicate.t.sol:A", address!("0x5a443704dd4b594b382c22a083e2bd3090a6fef3"), )], ) .assert_dependencies( - "default/linking/duplicate/Duplicate.t.sol:D".to_string(), + "default/linking/duplicate/Duplicate.t.sol:D", vec![( - "default/linking/duplicate/Duplicate.t.sol:B".to_string(), + "default/linking/duplicate/Duplicate.t.sol:B", address!("0x5a443704dd4b594b382c22a083e2bd3090a6fef3"), )], ) .assert_dependencies( - "default/linking/duplicate/Duplicate.t.sol:E".to_string(), + "default/linking/duplicate/Duplicate.t.sol:E", vec![ ( - "default/linking/duplicate/Duplicate.t.sol:A".to_string(), + "default/linking/duplicate/Duplicate.t.sol:A", Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3") .unwrap(), ), ( - "default/linking/duplicate/Duplicate.t.sol:C".to_string(), + "default/linking/duplicate/Duplicate.t.sol:C", Address::from_str("0x47e9fbef8c83a1714f1951f142132e6e90f5fa5d") .unwrap(), ), ], ) .assert_dependencies( - "default/linking/duplicate/Duplicate.t.sol:LibraryConsumer".to_string(), + "default/linking/duplicate/Duplicate.t.sol:LibraryConsumer", vec![ ( - "default/linking/duplicate/Duplicate.t.sol:A".to_string(), + "default/linking/duplicate/Duplicate.t.sol:A", Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3") .unwrap(), ), ( - "default/linking/duplicate/Duplicate.t.sol:B".to_string(), + "default/linking/duplicate/Duplicate.t.sol:B", Address::from_str("0x47e9fbef8c83a1714f1951f142132e6e90f5fa5d") .unwrap(), ), ( - "default/linking/duplicate/Duplicate.t.sol:C".to_string(), + "default/linking/duplicate/Duplicate.t.sol:C", Address::from_str("0x8be503bcded90ed42eff31f56199399b2b0154ca") .unwrap(), ), ( - "default/linking/duplicate/Duplicate.t.sol:D".to_string(), + "default/linking/duplicate/Duplicate.t.sol:D", Address::from_str("0x47c5e40890bce4a473a49d7501808b9633f29782") .unwrap(), ), ( - "default/linking/duplicate/Duplicate.t.sol:E".to_string(), + "default/linking/duplicate/Duplicate.t.sol:E", Address::from_str("0x29b2440db4a256b0c1e6d3b4cdcaa68e2440a08f") .unwrap(), ), ], ) .assert_dependencies( - "default/linking/duplicate/Duplicate.t.sol:DuplicateLibraryLinkingTest" - .to_string(), + "default/linking/duplicate/Duplicate.t.sol:DuplicateLibraryLinkingTest", vec![ ( - "default/linking/duplicate/Duplicate.t.sol:A".to_string(), + "default/linking/duplicate/Duplicate.t.sol:A", Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3") .unwrap(), ), ( - "default/linking/duplicate/Duplicate.t.sol:B".to_string(), + "default/linking/duplicate/Duplicate.t.sol:B", Address::from_str("0x47e9fbef8c83a1714f1951f142132e6e90f5fa5d") .unwrap(), ), ( - "default/linking/duplicate/Duplicate.t.sol:C".to_string(), + "default/linking/duplicate/Duplicate.t.sol:C", Address::from_str("0x8be503bcded90ed42eff31f56199399b2b0154ca") .unwrap(), ), ( - "default/linking/duplicate/Duplicate.t.sol:D".to_string(), + "default/linking/duplicate/Duplicate.t.sol:D", Address::from_str("0x47c5e40890bce4a473a49d7501808b9633f29782") .unwrap(), ), ( - "default/linking/duplicate/Duplicate.t.sol:E".to_string(), + "default/linking/duplicate/Duplicate.t.sol:E", Address::from_str("0x29b2440db4a256b0c1e6d3b4cdcaa68e2440a08f") .unwrap(), ), @@ -631,30 +641,30 @@ mod tests { link_test("../../testdata/default/linking/cycle", |linker| { linker .assert_dependencies( - "default/linking/cycle/Cycle.t.sol:Foo".to_string(), + "default/linking/cycle/Cycle.t.sol:Foo", vec![ ( - "default/linking/cycle/Cycle.t.sol:Foo".to_string(), + "default/linking/cycle/Cycle.t.sol:Foo", Address::from_str("0x47e9Fbef8C83A1714F1951F142132E6e90F5fa5D") .unwrap(), ), ( - "default/linking/cycle/Cycle.t.sol:Bar".to_string(), + "default/linking/cycle/Cycle.t.sol:Bar", Address::from_str("0x5a443704dd4B594B382c22a083e2BD3090A6feF3") .unwrap(), ), ], ) .assert_dependencies( - "default/linking/cycle/Cycle.t.sol:Bar".to_string(), + "default/linking/cycle/Cycle.t.sol:Bar", vec![ ( - "default/linking/cycle/Cycle.t.sol:Foo".to_string(), + "default/linking/cycle/Cycle.t.sol:Foo", Address::from_str("0x47e9Fbef8C83A1714F1951F142132E6e90F5fa5D") .unwrap(), ), ( - "default/linking/cycle/Cycle.t.sol:Bar".to_string(), + "default/linking/cycle/Cycle.t.sol:Bar", Address::from_str("0x5a443704dd4B594B382c22a083e2BD3090A6feF3") .unwrap(), ), @@ -668,41 +678,41 @@ mod tests { fn link_create2_nested() { link_test("../../testdata/default/linking/nested", |linker| { linker - .assert_dependencies("default/linking/nested/Nested.t.sol:Lib".to_string(), vec![]) + .assert_dependencies("default/linking/nested/Nested.t.sol:Lib", vec![]) .assert_dependencies( - "default/linking/nested/Nested.t.sol:NestedLib".to_string(), + "default/linking/nested/Nested.t.sol:NestedLib", vec![( - "default/linking/nested/Nested.t.sol:Lib".to_string(), + "default/linking/nested/Nested.t.sol:Lib", address!("0xddb1Cd2497000DAeA687CEa3dc34Af44084BEa74"), )], ) .assert_dependencies( - "default/linking/nested/Nested.t.sol:LibraryConsumer".to_string(), + "default/linking/nested/Nested.t.sol:LibraryConsumer", vec![ // Lib shows up here twice, because the linker sees it twice, but it should // have the same address and nonce. ( - "default/linking/nested/Nested.t.sol:Lib".to_string(), + "default/linking/nested/Nested.t.sol:Lib", Address::from_str("0xddb1Cd2497000DAeA687CEa3dc34Af44084BEa74") .unwrap(), ), ( - "default/linking/nested/Nested.t.sol:NestedLib".to_string(), + "default/linking/nested/Nested.t.sol:NestedLib", Address::from_str("0xfebE2F30641170642f317Ff6F644Cee60E7Ac369") .unwrap(), ), ], ) .assert_dependencies( - "default/linking/nested/Nested.t.sol:NestedLibraryLinkingTest".to_string(), + "default/linking/nested/Nested.t.sol:NestedLibraryLinkingTest", vec![ ( - "default/linking/nested/Nested.t.sol:Lib".to_string(), + "default/linking/nested/Nested.t.sol:Lib", Address::from_str("0xddb1Cd2497000DAeA687CEa3dc34Af44084BEa74") .unwrap(), ), ( - "default/linking/nested/Nested.t.sol:NestedLib".to_string(), + "default/linking/nested/Nested.t.sol:NestedLib", Address::from_str("0xfebE2F30641170642f317Ff6F644Cee60E7Ac369") .unwrap(), ), @@ -731,54 +741,52 @@ mod tests { }); assert!(artifact_exists, "Expected UsesBoth artifact to be compiled"); - // Skip the test if the compiler produced no link references for UsesBoth - // (nothing to link in this solc/config combination) - let has_link_refs = linker.output.artifact_ids().any(|(id, artifact)| { + // Skip the test unless BOTH LInit (creation) and LRun (runtime) link refs are present. + let both_refs_present = linker.output.artifact_ids().any(|(id, artifact)| { let source = id.source.strip_prefix(linker.project.root()).unwrap_or(&id.source); - if id.name == "UsesBoth" - && source - == std::path::Path::new( + if id.name != "UsesBoth" + || source + != std::path::Path::new( "default/linking/samefile_union/SameFileUnion.t.sol", ) { - let mut any = false; - if let Some(b) = &artifact.bytecode { - any |= !b.link_references.is_empty(); + return false; + } + + use std::collections::BTreeSet as Set; + let mut names: Set = Set::new(); + if let Some(b) = &artifact.bytecode { + for libs in b.link_references.values() { + names.extend(libs.keys().cloned()); } - if let Some(db) = &artifact.deployed_bytecode - && let Some(bc) = &db.bytecode - { - any |= !bc.link_references.is_empty(); + } + if let Some(db) = &artifact.deployed_bytecode + && let Some(bc) = &db.bytecode + { + for libs in bc.link_references.values() { + names.extend(libs.keys().cloned()); } - any - } else { - false } + names.contains("LInit") && names.contains("LRun") }); - if !has_link_refs { + if !both_refs_present { return; } linker // seed empty expectations for libraries to avoid unexpected artifact panics + .assert_dependencies("default/linking/samefile_union/Libs.sol:LInit", vec![]) + .assert_dependencies("default/linking/samefile_union/Libs.sol:LRun", vec![]) .assert_dependencies( - "default/linking/samefile_union/Libs.sol:LInit".to_string(), - vec![], - ) - .assert_dependencies( - "default/linking/samefile_union/Libs.sol:LRun".to_string(), - vec![], - ) - .assert_dependencies( - "default/linking/samefile_union/SameFileUnion.t.sol:UsesBoth".to_string(), + "default/linking/samefile_union/SameFileUnion.t.sol:UsesBoth", vec![ ( - "default/linking/samefile_union/Libs.sol:LInit".to_string(), + "default/linking/samefile_union/Libs.sol:LInit", Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3") .unwrap(), ), ( - "default/linking/samefile_union/Libs.sol:LRun".to_string(), + "default/linking/samefile_union/Libs.sol:LRun", Address::from_str("0x47e9fbef8c83a1714f1951f142132e6e90f5fa5d") .unwrap(), ),