diff --git a/Cargo.toml b/Cargo.toml index 5c126f0e6..a4795627f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,7 @@ alloy-json-abi = { version = "1.3", features = ["serde_json"] } alloy-primitives = { version = "1.3", features = ["serde", "rand"] } cfg-if = "1.0" dunce = "1.0" +memchr = "2.7" memmap2 = "0.9" path-slash = "0.2" rayon = "1.11" diff --git a/crates/artifacts/solc/Cargo.toml b/crates/artifacts/solc/Cargo.toml index d0a5a2047..b1f39e515 100644 --- a/crates/artifacts/solc/Cargo.toml +++ b/crates/artifacts/solc/Cargo.toml @@ -19,6 +19,7 @@ foundry-compilers-core.workspace = true alloy-json-abi.workspace = true alloy-primitives.workspace = true +memchr.workspace = true semver.workspace = true serde_json.workspace = true serde.workspace = true diff --git a/crates/artifacts/solc/src/bytecode.rs b/crates/artifacts/solc/src/bytecode.rs index b88121d9e..5866dcdf6 100644 --- a/crates/artifacts/solc/src/bytecode.rs +++ b/crates/artifacts/solc/src/bytecode.rs @@ -304,17 +304,7 @@ impl BytecodeObject { /// See also: pub fn link_fully_qualified(&mut self, name: &str, addr: Address) -> &mut Self { if let Self::Unlinked(unlinked) = self { - let place_holder = utils::library_hash_placeholder(name); - // the address as hex without prefix - let hex_addr = hex::encode(addr); - - // the library placeholder used to be the fully qualified name of the library instead of - // the hash. This is also still supported by `solc` so we handle this as well - let fully_qualified_placeholder = utils::library_fully_qualified_placeholder(name); - - *unlinked = unlinked - .replace(&format!("__{fully_qualified_placeholder}__"), &hex_addr) - .replace(&format!("__{place_holder}__"), &hex_addr) + link(unlinked, name, addr); } self } @@ -386,6 +376,43 @@ impl AsRef<[u8]> for BytecodeObject { } } +/// Reference: +fn link(unlinked: &mut String, name: &str, addr: Address) { + const LEN: usize = 40; + + let mut refs = vec![]; + let mut find = |needle: &str| { + assert_eq!(needle.len(), LEN, "{needle:?}"); + refs.extend(memchr::memmem::find_iter(unlinked.as_bytes(), needle)); + }; + + let placeholder = utils::library_hash_placeholder(name); + find(&format!("__{placeholder}__")); + + // The library placeholder used to be the fully qualified name of the library instead of + // the hash. This is also still supported by `solc` so we handle this as well. + let fully_qualified_placeholder = utils::library_fully_qualified_placeholder(name); + find(&format!("__{fully_qualified_placeholder}__")); + + if refs.is_empty() { + debug!("no references found while linking {name} -> {addr}"); + return; + } + + // The address as hex without prefix. + let mut buffer = hex::Buffer::<20, false>::new(); + let hex_addr = &*buffer.format(&addr); + assert_eq!(hex_addr.len(), LEN, "{hex_addr:?}"); + + // The ranges are non-overlapping, so we don't need to sort, and can iterate in whatever order + // because of equal lengths. + // SAFETY: We're replacing LEN bytes at a time, and all the indexes come from the same string. + let unlinked = unsafe { unlinked.as_bytes_mut() }; + for &idx in &refs { + unlinked[idx..idx + LEN].copy_from_slice(hex_addr.as_bytes()); + } +} + /// This will serialize the bytecode data without a `0x` prefix, which the `ethers::types::Bytes` /// adds by default. ///