1use crate::{compile::PathOrContractInfo, find_metadata_start, strip_bytecode_placeholders};
4use alloy_dyn_abi::JsonAbiExt;
5use alloy_json_abi::{Event, Function, JsonAbi};
6use alloy_primitives::{Address, B256, Bytes, Selector, address, hex};
7use eyre::{OptionExt, Result};
8use foundry_compilers::{
9 ArtifactId, Project, ProjectCompileOutput,
10 artifacts::{
11 BytecodeObject, CompactBytecode, CompactContractBytecode, CompactContractBytecodeCow,
12 CompactDeployedBytecode, ConfigurableContractArtifact, ContractBytecodeSome, Offsets,
13 StorageLayout,
14 },
15 utils::canonicalized,
16};
17use std::{
18 collections::BTreeMap,
19 ops::Deref,
20 path::{Path, PathBuf},
21 str::FromStr,
22 sync::Arc,
23};
24
25const CALL_PROTECTION_BYTECODE_PREFIX: [u8; 21] =
30 hex!("730000000000000000000000000000000000000000");
31
32pub const LIBRARY_DEPLOYER: Address = address!("0x1F95D37F27EA0dEA9C252FC09D5A6eaA97647353");
36
37#[expect(missing_docs)]
39#[derive(Debug, Clone)]
40pub struct BytecodeData {
41 pub object: Option<BytecodeObject>,
42 pub link_references: BTreeMap<String, BTreeMap<String, Vec<Offsets>>>,
43 pub immutable_references: BTreeMap<String, Vec<Offsets>>,
44}
45
46impl BytecodeData {
47 fn bytes(&self) -> Option<&Bytes> {
48 self.object.as_ref().and_then(|b| b.as_bytes())
49 }
50}
51
52fn normalize_offsets(offsets: &mut Vec<Offsets>) {
54 offsets.sort_by_key(|o| o.start);
55
56 let mut merged: Vec<Offsets> = Vec::with_capacity(offsets.len());
57 for offset in offsets.drain(..) {
58 if let Some(last) = merged.last_mut() {
59 let last_end = last.start as u64 + last.length as u64;
60 if offset.start as u64 <= last_end {
61 let this_end = offset.start as u64 + offset.length as u64;
62 if this_end > last_end {
63 last.length = (this_end - last.start as u64) as u32;
64 }
65 continue;
66 }
67 }
68 merged.push(offset);
69 }
70 *offsets = merged;
71}
72
73impl From<CompactBytecode> for BytecodeData {
74 fn from(bytecode: CompactBytecode) -> Self {
75 Self {
76 object: Some(bytecode.object),
77 link_references: bytecode.link_references,
78 immutable_references: BTreeMap::new(),
79 }
80 }
81}
82
83impl From<CompactDeployedBytecode> for BytecodeData {
84 fn from(bytecode: CompactDeployedBytecode) -> Self {
85 let (object, link_references) = if let Some(compact) = bytecode.bytecode {
86 (Some(compact.object), compact.link_references)
87 } else {
88 (None, BTreeMap::new())
89 };
90 Self { object, link_references, immutable_references: bytecode.immutable_references }
91 }
92}
93
94#[derive(Debug)]
96pub struct ContractData {
97 pub name: String,
99 pub abi: JsonAbi,
101 pub bytecode: Option<BytecodeData>,
103 pub deployed_bytecode: Option<BytecodeData>,
105 pub storage_layout: Option<Arc<StorageLayout>>,
107}
108
109impl ContractData {
110 pub fn bytecode(&self) -> Option<&Bytes> {
112 self.bytecode.as_ref()?.bytes().filter(|b| !b.is_empty())
113 }
114
115 pub fn deployed_bytecode(&self) -> Option<&Bytes> {
117 self.deployed_bytecode.as_ref()?.bytes().filter(|b| !b.is_empty())
118 }
119
120 pub fn bytecode_without_placeholders(&self) -> Option<Bytes> {
122 strip_bytecode_placeholders(self.bytecode.as_ref()?.object.as_ref()?)
123 }
124
125 pub fn deployed_bytecode_without_placeholders(&self) -> Option<Bytes> {
127 strip_bytecode_placeholders(self.deployed_bytecode.as_ref()?.object.as_ref()?)
128 }
129}
130
131pub struct ContractsByArtifactBuilder<'a> {
134 artifacts: BTreeMap<ArtifactId, CompactContractBytecodeCow<'a>>,
136 storage_layouts: BTreeMap<ArtifactId, StorageLayout>,
138}
139
140impl<'a> ContractsByArtifactBuilder<'a> {
141 pub fn new(
143 artifacts: impl IntoIterator<Item = (ArtifactId, CompactContractBytecodeCow<'a>)>,
144 ) -> Self {
145 Self { artifacts: artifacts.into_iter().collect(), storage_layouts: BTreeMap::new() }
146 }
147
148 pub fn with_output(self, output: &ProjectCompileOutput, base: &Path) -> Self {
150 self.with_storage_layouts(output.artifact_ids().filter_map(|(id, artifact)| {
151 artifact
152 .storage_layout
153 .as_ref()
154 .map(|layout| (id.with_stripped_file_prefixes(base), layout.clone()))
155 }))
156 }
157
158 pub fn with_storage_layouts(
160 mut self,
161 layouts: impl IntoIterator<Item = (ArtifactId, StorageLayout)>,
162 ) -> Self {
163 self.storage_layouts.extend(layouts);
164 self
165 }
166
167 pub fn build(self) -> ContractsByArtifact {
169 let map = self
170 .artifacts
171 .into_iter()
172 .filter_map(|(id, artifact)| {
173 let name = id.name.clone();
174 let CompactContractBytecodeCow { abi, bytecode, deployed_bytecode } = artifact;
175
176 Some((
177 id.clone(),
178 ContractData {
179 name,
180 abi: abi?.into_owned(),
181 bytecode: bytecode.map(|b| b.into_owned().into()),
182 deployed_bytecode: deployed_bytecode.map(|b| b.into_owned().into()),
183 storage_layout: self.storage_layouts.get(&id).map(|l| Arc::new(l.clone())),
184 },
185 ))
186 })
187 .collect();
188
189 ContractsByArtifact(Arc::new(map))
190 }
191}
192
193type ArtifactWithContractRef<'a> = (&'a ArtifactId, &'a ContractData);
194
195#[derive(Clone, Default, Debug)]
197pub struct ContractsByArtifact(Arc<BTreeMap<ArtifactId, ContractData>>);
198
199impl ContractsByArtifact {
200 pub fn new(artifacts: impl IntoIterator<Item = (ArtifactId, CompactContractBytecode)>) -> Self {
202 let map = artifacts
203 .into_iter()
204 .filter_map(|(id, artifact)| {
205 let name = id.name.clone();
206 let CompactContractBytecode { abi, bytecode, deployed_bytecode } = artifact;
207 Some((
208 id,
209 ContractData {
210 name,
211 abi: abi?,
212 bytecode: bytecode.map(Into::into),
213 deployed_bytecode: deployed_bytecode.map(Into::into),
214 storage_layout: None,
215 },
216 ))
217 })
218 .collect();
219 Self(Arc::new(map))
220 }
221
222 pub fn clear(&mut self) {
224 *self = Self::default();
225 }
226
227 pub fn find_by_creation_code(&self, code: &[u8]) -> Option<ArtifactWithContractRef<'_>> {
229 self.find_by_code(code, 0.1, true, ContractData::bytecode)
230 }
231
232 pub fn find_by_creation_code_prefix(&self, code: &[u8]) -> Option<ArtifactWithContractRef<'_>> {
235 self.iter().find(|(_, contract)| {
236 contract.bytecode().is_some_and(|bytecode| code.starts_with(bytecode))
237 })
238 }
239
240 pub fn find_by_deployed_code(&self, code: &[u8]) -> Option<ArtifactWithContractRef<'_>> {
242 self.find_by_code(code, 0.15, false, ContractData::deployed_bytecode)
243 }
244
245 fn find_by_code(
248 &self,
249 code: &[u8],
250 accepted_score: f64,
251 strip_ctor_args: bool,
252 get: impl Fn(&ContractData) -> Option<&Bytes>,
253 ) -> Option<ArtifactWithContractRef<'_>> {
254 self.iter()
255 .filter_map(|(id, contract)| {
256 if let Some(deployed_bytecode) = get(contract) {
257 let mut code = code;
258 if strip_ctor_args && code.len() > deployed_bytecode.len() {
259 if let Some(constructor) = contract.abi.constructor() {
261 let constructor_args = &code[deployed_bytecode.len()..];
262 if constructor.abi_decode_input(constructor_args).is_ok() {
263 code = &code[..deployed_bytecode.len()]
266 }
267 }
268 };
269
270 let score = bytecode_diff_score(deployed_bytecode.as_ref(), code);
271 (score <= accepted_score).then_some((score, (id, contract)))
272 } else {
273 None
274 }
275 })
276 .min_by(|(score1, _), (score2, _)| score1.total_cmp(score2))
277 .map(|(_, data)| data)
278 }
279
280 pub fn find_by_deployed_code_exact(&self, code: &[u8]) -> Option<ArtifactWithContractRef<'_>> {
283 self.find_by_deployed_code_exact_preferred(code, |_| true)
284 }
285
286 pub fn find_by_deployed_code_exact_preferred(
289 &self,
290 code: &[u8],
291 preferred: impl Fn(&ArtifactId) -> bool,
292 ) -> Option<ArtifactWithContractRef<'_>> {
293 self.find_by_deployed_code_exact_inner(code, false, preferred).0
294 }
295
296 pub fn find_by_deployed_code_exact_unique(
298 &self,
299 code: &[u8],
300 ) -> Option<ArtifactWithContractRef<'_>> {
301 self.find_by_deployed_code_exact_inner(code, true, |_| false).0
302 }
303
304 pub fn find_by_deployed_code_exact_unambiguous(
307 &self,
308 code: &[u8],
309 ) -> Result<Option<ArtifactWithContractRef<'_>>> {
310 let (matched, ambiguous_partial) =
311 self.find_by_deployed_code_exact_inner(code, false, |_| true);
312 if ambiguous_partial {
313 eyre::bail!(
314 "Multiple local contracts match the deployed bytecode with different metadata. Specify the contract as <path>:<contract>"
315 );
316 }
317 Ok(matched)
318 }
319
320 fn find_by_deployed_code_exact_inner(
321 &self,
322 code: &[u8],
323 unique: bool,
324 preferred: impl Fn(&ArtifactId) -> bool,
325 ) -> (Option<ArtifactWithContractRef<'_>>, bool) {
326 if code.is_empty() {
328 return (None, false);
329 }
330
331 let mut partial_match = None;
332 let mut exact_match = None;
333 let mut ambiguous_partial = false;
334 'contracts: for (id, contract) in self.iter() {
335 let Some(deployed_bytecode) = &contract.deployed_bytecode else {
336 continue;
337 };
338 let Some(deployed_code) = &deployed_bytecode.object else {
339 continue;
340 };
341
342 let len = match deployed_code {
343 BytecodeObject::Bytecode(bytes) => bytes.len(),
344 BytecodeObject::Unlinked(bytes) => bytes.len() / 2,
345 };
346
347 let is_vyper = id.source.extension().is_some_and(|extension| extension == "vy");
351 let code = if is_vyper {
352 let Some(runtime) = code.get(..len) else { continue };
353 runtime
354 } else {
355 code
356 };
357 if len == 0 || len != code.len() {
358 continue;
359 }
360
361 let mut ignored = deployed_bytecode
363 .immutable_references
364 .values()
365 .chain(deployed_bytecode.link_references.values().flat_map(|v| v.values()))
366 .flatten()
367 .cloned()
368 .collect::<Vec<_>>();
369
370 let has_call_protection = !is_vyper
375 && match deployed_code {
376 BytecodeObject::Bytecode(bytes) => {
377 bytes.starts_with(&CALL_PROTECTION_BYTECODE_PREFIX)
378 }
379 BytecodeObject::Unlinked(bytes) => {
380 if let Ok(bytes) =
381 Bytes::from_str(&bytes[..CALL_PROTECTION_BYTECODE_PREFIX.len() * 2])
382 {
383 bytes.starts_with(&CALL_PROTECTION_BYTECODE_PREFIX)
384 } else {
385 false
386 }
387 }
388 };
389
390 if has_call_protection {
391 ignored.push(Offsets { start: 1, length: 20 });
392 }
393
394 let metadata_start = if is_vyper { None } else { find_metadata_start(code) };
395
396 if let Some(metadata) = metadata_start {
399 ignored.push(Offsets {
400 start: metadata as u32,
401 length: (code.len() - 2 - metadata) as u32,
402 });
403 }
404
405 normalize_offsets(&mut ignored);
407
408 let mut left = 0;
409 for offset in ignored {
410 let right = offset.start as usize;
411
412 let matched = match deployed_code {
413 BytecodeObject::Bytecode(bytes) => bytes[left..right] == code[left..right],
414 BytecodeObject::Unlinked(bytes) => {
415 if let Ok(bytes) = Bytes::from_str(&bytes[left * 2..right * 2]) {
416 bytes == code[left..right]
417 } else {
418 false
419 }
420 }
421 };
422
423 if !matched {
424 continue 'contracts;
425 }
426
427 left = right + offset.length as usize;
428 }
429
430 let is_partial = if left < code.len() {
431 match deployed_code {
432 BytecodeObject::Bytecode(bytes) => bytes[left..] == code[left..],
433 BytecodeObject::Unlinked(bytes) => {
434 if let Ok(bytes) = Bytes::from_str(&bytes[left * 2..]) {
435 bytes == code[left..]
436 } else {
437 false
438 }
439 }
440 }
441 } else {
442 true
443 };
444
445 if !is_partial {
446 continue;
447 }
448
449 let matches_metadata = metadata_start.is_none_or(|metadata| match deployed_code {
450 BytecodeObject::Bytecode(bytes) => bytes[metadata..] == code[metadata..],
451 BytecodeObject::Unlinked(bytes) => Bytes::from_str(&bytes[metadata * 2..])
452 .is_ok_and(|bytes| bytes == code[metadata..]),
453 });
454
455 if matches_metadata {
456 if unique && exact_match.is_some() {
457 return (None, false);
458 }
459 if exact_match.is_none() || preferred(id) {
460 exact_match = Some((id, contract));
461 }
462 if !unique && preferred(id) {
463 return (exact_match, false);
464 }
465 } else {
466 ambiguous_partial |= partial_match.is_some();
467 if preferred(id) || partial_match.is_none_or(|(id, _)| !preferred(id)) {
468 partial_match = Some((id, contract));
469 }
470 }
471 }
472
473 let ambiguous_partial = exact_match.is_none() && ambiguous_partial;
474 (if unique { exact_match } else { exact_match.or(partial_match) }, ambiguous_partial)
475 }
476
477 pub fn find_by_name_or_identifier(
480 &self,
481 id: &str,
482 ) -> Result<Option<ArtifactWithContractRef<'_>>> {
483 let mut iter =
484 self.iter().filter(|(artifact, _)| artifact.name == id || artifact.identifier() == id);
485 let first = iter.next();
486 if first.is_some() && iter.next().is_some() {
487 eyre::bail!("{id} has more than one implementation.");
488 }
489
490 Ok(first)
491 }
492
493 pub fn find_abi_by_name_or_src_path(&self, name_or_path: &str) -> Option<(JsonAbi, String)> {
497 self.iter()
498 .find(|(artifact, _)| {
499 artifact.name == name_or_path || artifact.source == Path::new(name_or_path)
500 })
501 .map(|(_, contract)| (contract.abi.clone(), contract.name.clone()))
502 }
503
504 pub fn flatten(&self) -> (BTreeMap<Selector, Function>, BTreeMap<B256, Event>, JsonAbi) {
506 let mut funcs = BTreeMap::new();
507 let mut events = BTreeMap::new();
508 let mut errors_abi = JsonAbi::new();
509 for contract in self.values() {
510 for func in contract.abi.functions() {
511 funcs.insert(func.selector(), func.clone());
512 }
513 for event in contract.abi.events() {
514 events.insert(event.selector(), event.clone());
515 }
516 for error in contract.abi.errors() {
517 errors_abi.errors.entry(error.name.clone()).or_default().push(error.clone());
518 }
519 }
520 (funcs, events, errors_abi)
521 }
522}
523
524impl From<ProjectCompileOutput> for ContractsByArtifact {
525 fn from(value: ProjectCompileOutput) -> Self {
526 Self::new(value.into_artifacts().map(|(id, ar)| {
527 (
528 id,
529 CompactContractBytecode {
530 abi: ar.abi,
531 bytecode: ar.bytecode,
532 deployed_bytecode: ar.deployed_bytecode,
533 },
534 )
535 }))
536 }
537}
538
539impl Deref for ContractsByArtifact {
540 type Target = BTreeMap<ArtifactId, ContractData>;
541
542 fn deref(&self) -> &Self::Target {
543 &self.0
544 }
545}
546
547pub type ContractsByAddress = BTreeMap<Address, (String, JsonAbi)>;
549
550pub fn matches_contract_creation(contract: &ContractData, creation_code: &[u8]) -> bool {
556 let Some(bytecode) = contract.bytecode() else { return false };
557 let Some(arguments) = creation_code.strip_prefix(bytecode.as_ref()) else { return false };
558 match contract.abi.constructor() {
559 Some(constructor) => constructor
560 .abi_decode_input(arguments)
561 .ok()
562 .and_then(|values| constructor.abi_encode_input(&values).ok())
563 .is_some_and(|encoded| encoded == arguments),
564 None => arguments.is_empty(),
565 }
566}
567
568pub fn bytecode_diff_score<'a>(mut a: &'a [u8], mut b: &'a [u8]) -> f64 {
569 if a.len() < b.len() {
571 std::mem::swap(&mut a, &mut b);
572 }
573
574 let mut n_different_bytes = a.len() - b.len();
576
577 if n_different_bytes > 32 && n_different_bytes * 10 > a.len() {
582 return 1.0;
583 }
584
585 n_different_bytes += unsafe { count_different_bytes(a, b) };
588
589 n_different_bytes as f64 / a.len() as f64
590}
591
592const unsafe fn count_different_bytes(a: &[u8], b: &[u8]) -> usize {
598 let a_ptr = a.as_ptr();
603 let b_ptr = b.as_ptr();
604 let len = b.len();
605
606 let mut sum = 0;
607 let mut i = 0;
608 while i < len {
609 sum += unsafe { *a_ptr.add(i) != *b_ptr.add(i) } as usize;
611 i += 1;
612 }
613 sum
614}
615
616pub fn get_contract_name(id: &str) -> &str {
635 id.rsplit(':').next().unwrap_or(id)
636}
637
638pub fn get_file_name(id: &str) -> &str {
650 id.split(':').next().unwrap_or(id)
651}
652
653pub fn compact_to_contract(contract: CompactContractBytecode) -> Result<ContractBytecodeSome> {
655 Ok(ContractBytecodeSome {
656 abi: contract.abi.ok_or_else(|| eyre::eyre!("No contract abi"))?,
657 bytecode: contract.bytecode.ok_or_else(|| eyre::eyre!("No contract bytecode"))?.into(),
658 deployed_bytecode: contract
659 .deployed_bytecode
660 .ok_or_else(|| eyre::eyre!("No contract deployed bytecode"))?
661 .into(),
662 })
663}
664
665pub fn find_target_path(project: &Project, identifier: &PathOrContractInfo) -> Result<PathBuf> {
667 match identifier {
668 PathOrContractInfo::Path(path) => Ok(canonicalized(project.root().join(path))),
669 PathOrContractInfo::ContractInfo(info) => {
670 if let Some(path) = info.path.as_ref() {
671 let path = canonicalized(project.root().join(path));
672 if !path.is_file() {
673 eyre::bail!(
674 "Could not find source file for contract `{}` at {}",
675 info.name,
676 path.strip_prefix(project.root()).unwrap_or(&path).display()
677 );
678 }
679 return Ok(path);
680 }
681 let path = project.find_contract_path(&info.name)?;
685 Ok(path)
686 }
687 }
688}
689
690pub fn find_matching_contract_artifact(
692 output: &mut ProjectCompileOutput,
693 target_path: &Path,
694 target_name: Option<&str>,
695) -> eyre::Result<ConfigurableContractArtifact> {
696 if let Some(name) = target_name {
697 if let Some(artifact) = output.remove(target_path, name) {
698 return Ok(artifact);
699 }
700
701 let target_path = canonicalized(target_path);
702 let matching_source = output.artifact_ids().find_map(|(id, _artifact)| {
703 (id.name == name && canonicalized(&id.source) == target_path).then(|| id.source.clone())
704 });
705
706 matching_source
707 .and_then(|source| output.remove(&source, name))
708 .ok_or_eyre(format!("Could not find artifact `{name}` in the compiled artifacts"))
709 } else {
710 let possible_targets = output
711 .artifact_ids()
712 .filter(|(id, _artifact)| id.source == target_path)
713 .collect::<Vec<_>>();
714
715 if possible_targets.is_empty() {
716 eyre::bail!(
717 "Could not find artifact linked to source `{target_path:?}` in the compiled artifacts"
718 );
719 }
720
721 let (target_id, target_artifact) = possible_targets[0].clone();
722 if possible_targets.len() == 1 {
723 return Ok(target_artifact.clone());
724 }
725
726 if !target_id.name.contains('.')
730 && possible_targets.iter().any(|(id, _)| id.name != target_id.name)
731 {
732 eyre::bail!(
733 "Multiple contracts found in the same file, please specify the target <path>:<contract> or <contract>"
734 );
735 }
736
737 let artifact = possible_targets
740 .iter()
741 .find_map(|(id, artifact)| (id.profile == "default").then_some(*artifact))
742 .unwrap_or(target_artifact);
743
744 Ok(artifact.clone())
745 }
746}
747
748#[cfg(test)]
749mod tests {
750 use super::*;
751 use alloy_dyn_abi::DynSolValue;
752 use alloy_primitives::U256;
753 use semver::Version;
754
755 fn deployed_artifact(name: &str, code: Bytes) -> (ArtifactId, CompactContractBytecode) {
756 deployed_artifact_with_immutables(name, code, Default::default())
757 }
758
759 fn deployed_artifact_with_immutables(
760 name: &str,
761 code: Bytes,
762 immutable_references: BTreeMap<String, Vec<Offsets>>,
763 ) -> (ArtifactId, CompactContractBytecode) {
764 (
765 ArtifactId {
766 path: format!("out/{name}.json").into(),
767 name: name.to_owned(),
768 source: format!("src/{name}.sol").into(),
769 version: Version::new(0, 8, 30),
770 build_id: String::new(),
771 profile: "default".to_owned(),
772 },
773 CompactContractBytecode {
774 abi: Some(Default::default()),
775 bytecode: None,
776 deployed_bytecode: Some(CompactDeployedBytecode {
777 bytecode: Some(CompactBytecode {
778 object: BytecodeObject::Bytecode(code),
779 source_map: None,
780 link_references: Default::default(),
781 }),
782 immutable_references,
783 }),
784 },
785 )
786 }
787
788 #[test]
789 fn exact_creation_match_requires_canonical_constructor_suffix() {
790 let abi = JsonAbi::parse(["constructor(uint256 value)"]).unwrap();
791 let bytecode = Bytes::from_static(&[0x60, 0x00]);
792 let contract = ContractData {
793 name: "C".to_owned(),
794 abi,
795 bytecode: Some(BytecodeData {
796 object: Some(BytecodeObject::Bytecode(bytecode.clone())),
797 link_references: BTreeMap::new(),
798 immutable_references: BTreeMap::new(),
799 }),
800 deployed_bytecode: None,
801 storage_layout: None,
802 };
803 let arguments = contract
804 .abi
805 .constructor()
806 .unwrap()
807 .abi_encode_input(&[DynSolValue::Uint(U256::ONE, 256)])
808 .unwrap();
809 let creation = [bytecode.as_ref(), &arguments].concat();
810
811 assert!(matches_contract_creation(&contract, &creation));
812 assert!(!matches_contract_creation(&contract, &creation[..creation.len() - 1]));
813 assert!(!matches_contract_creation(&contract, &[creation, vec![0]].concat()));
814 }
815
816 #[test]
817 fn bytecode_diffing() {
818 assert_eq!(bytecode_diff_score(b"a", b"a"), 0.0);
819 assert_eq!(bytecode_diff_score(b"a", b"b"), 1.0);
820
821 let a_100 = &b"a".repeat(100)[..];
822 assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(100)), 1.0);
823 assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(99)), 1.0);
824 assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(101)), 1.0);
825 assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(120)), 1.0);
826 assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(1000)), 1.0);
827
828 let a_99 = &b"a".repeat(99)[..];
829 assert!(bytecode_diff_score(a_100, a_99) <= 0.01);
830 }
831
832 #[test]
833 fn find_by_deployed_code_exact_with_empty_deployed() {
834 let contracts = ContractsByArtifact::new(vec![]);
835
836 assert!(contracts.find_by_deployed_code_exact(&[]).is_none());
837 }
838
839 #[test]
840 fn find_by_deployed_code_exact_unique_rejects_ambiguity() {
841 let code = Bytes::from_static(&[0x60, 0x00]);
842 let contracts = ContractsByArtifact::new([
843 deployed_artifact("A", code.clone()),
844 deployed_artifact("B", code.clone()),
845 ]);
846
847 assert!(contracts.find_by_deployed_code_exact_unique(&code).is_none());
848
849 let contracts = ContractsByArtifact::new([deployed_artifact("A", code.clone())]);
850 assert_eq!(contracts.find_by_deployed_code_exact_unique(&code).unwrap().0.name, "A");
851 }
852
853 #[test]
854 fn find_by_deployed_code_exact_unique_rejects_partial_metadata_match() {
855 let artifact_code = Bytes::from_static(&[0x60, 0x00, 0xa0, 0x00, 0x01]);
856 let deployed_code = Bytes::from_static(&[0x60, 0x00, 0xf6, 0x00, 0x01]);
857 let contracts = ContractsByArtifact::new([deployed_artifact("A", artifact_code)]);
858
859 assert!(contracts.find_by_deployed_code_exact(&deployed_code).is_some());
860 assert!(contracts.find_by_deployed_code_exact_unique(&deployed_code).is_none());
861 }
862
863 #[test]
864 fn find_by_deployed_code_exact_unambiguous_preserves_single_partial() {
865 let artifact_code = Bytes::from_static(&[0x60, 0x00, 0xa0, 0x00, 0x01]);
866 let deployed_code = Bytes::from_static(&[0x60, 0x00, 0xf6, 0x00, 0x01]);
867 let contracts = ContractsByArtifact::new([deployed_artifact("A", artifact_code)]);
868
869 assert_eq!(
870 contracts
871 .find_by_deployed_code_exact_unambiguous(&deployed_code)
872 .unwrap()
873 .unwrap()
874 .0
875 .name,
876 "A"
877 );
878 assert!(contracts.find_by_deployed_code_exact_unambiguous(&[0x00]).unwrap().is_none());
879 }
880
881 #[test]
882 fn find_by_deployed_code_exact_unambiguous_prefers_exact_over_ambiguous_partial() {
883 let partial_code = Bytes::from_static(&[0x60, 0x00, 0xa0, 0x00, 0x01]);
884 let deployed_code = Bytes::from_static(&[0x60, 0x00, 0xf6, 0x00, 0x01]);
885 for exact_name in ["A", "Z"] {
886 let contracts = ContractsByArtifact::new([
887 deployed_artifact("B", partial_code.clone()),
888 deployed_artifact("C", partial_code.clone()),
889 ]);
890 assert!(contracts.find_by_deployed_code_exact(&deployed_code).is_some());
891 assert!(contracts.find_by_deployed_code_exact_unique(&deployed_code).is_none());
892 assert_eq!(
893 contracts
894 .find_by_deployed_code_exact_unambiguous(&deployed_code)
895 .unwrap_err()
896 .to_string(),
897 "Multiple local contracts match the deployed bytecode with different metadata. Specify the contract as <path>:<contract>"
898 );
899
900 let contracts = ContractsByArtifact::new([
901 deployed_artifact(exact_name, deployed_code.clone()),
902 deployed_artifact("B", partial_code.clone()),
903 deployed_artifact("C", partial_code.clone()),
904 ]);
905 assert_eq!(
906 contracts
907 .find_by_deployed_code_exact_unambiguous(&deployed_code)
908 .unwrap()
909 .unwrap()
910 .0
911 .name,
912 exact_name
913 );
914 }
915 }
916
917 #[test]
919 fn find_by_deployed_code_exact_handles_overlapping_ignored_ranges() {
920 let mut code = vec![0x73u8];
922 code.extend(std::iter::repeat_n(0u8, 20));
923 code.extend_from_slice(&[0xAA, 0xBB, 0xCC, 0xDD]);
925 let code = Bytes::from(code);
926
927 let immutable_references =
929 BTreeMap::from([("someImmutable".to_string(), vec![Offsets { start: 5, length: 3 }])]);
930
931 let contracts = ContractsByArtifact::new([deployed_artifact_with_immutables(
932 "LibWithEarlyImmutable",
933 code.clone(),
934 immutable_references,
935 )]);
936
937 assert!(contracts.find_by_deployed_code_exact(&code).is_some());
939 }
940
941 #[test]
942 fn find_by_deployed_code_exact_rejects_forged_metadata_length() {
943 let contracts = ContractsByArtifact::new([deployed_artifact(
945 "A",
946 Bytes::from_static(&[0x60, 0x00, 0x60, 0x01, 0xa0, 0x00, 0x01]),
947 )]);
948
949 assert!(
951 contracts
952 .find_by_deployed_code_exact(&[0x60, 0x00, 0x60, 0x01, 0xf6, 0x00, 0x01])
953 .is_some()
954 );
955 assert!(
957 contracts
958 .find_by_deployed_code_exact(&[0x60, 0x00, 0x60, 0x02, 0xa0, 0x00, 0x04])
959 .is_none()
960 );
961 }
962
963 #[test]
964 fn normalize_offsets_merges_overlaps_and_adjacency() {
965 let mut offsets = vec![Offsets { start: 1, length: 20 }, Offsets { start: 5, length: 3 }];
967 normalize_offsets(&mut offsets);
968 assert_eq!(offsets, vec![Offsets { start: 1, length: 20 }]);
969
970 let mut offsets = vec![Offsets { start: 1, length: 20 }, Offsets { start: 15, length: 15 }];
972 normalize_offsets(&mut offsets);
973 assert_eq!(offsets, vec![Offsets { start: 1, length: 29 }]);
974
975 let mut offsets = vec![Offsets { start: 1, length: 20 }, Offsets { start: 21, length: 4 }];
977 normalize_offsets(&mut offsets);
978 assert_eq!(offsets, vec![Offsets { start: 1, length: 24 }]);
979
980 let mut offsets = vec![Offsets { start: 1, length: 5 }, Offsets { start: 10, length: 5 }];
982 normalize_offsets(&mut offsets);
983 assert_eq!(
984 offsets,
985 vec![Offsets { start: 1, length: 5 }, Offsets { start: 10, length: 5 }]
986 );
987
988 let mut offsets = vec![Offsets { start: 10, length: 5 }, Offsets { start: 1, length: 5 }];
990 normalize_offsets(&mut offsets);
991 assert_eq!(
992 offsets,
993 vec![Offsets { start: 1, length: 5 }, Offsets { start: 10, length: 5 }]
994 );
995 }
996}