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_deployed_code(&self, code: &[u8]) -> Option<ArtifactWithContractRef<'_>> {
234 self.find_by_code(code, 0.15, false, ContractData::deployed_bytecode)
235 }
236
237 fn find_by_code(
240 &self,
241 code: &[u8],
242 accepted_score: f64,
243 strip_ctor_args: bool,
244 get: impl Fn(&ContractData) -> Option<&Bytes>,
245 ) -> Option<ArtifactWithContractRef<'_>> {
246 self.iter()
247 .filter_map(|(id, contract)| {
248 if let Some(deployed_bytecode) = get(contract) {
249 let mut code = code;
250 if strip_ctor_args && code.len() > deployed_bytecode.len() {
251 if let Some(constructor) = contract.abi.constructor() {
253 let constructor_args = &code[deployed_bytecode.len()..];
254 if constructor.abi_decode_input(constructor_args).is_ok() {
255 code = &code[..deployed_bytecode.len()]
258 }
259 }
260 };
261
262 let score = bytecode_diff_score(deployed_bytecode.as_ref(), code);
263 (score <= accepted_score).then_some((score, (id, contract)))
264 } else {
265 None
266 }
267 })
268 .min_by(|(score1, _), (score2, _)| score1.total_cmp(score2))
269 .map(|(_, data)| data)
270 }
271
272 pub fn find_by_deployed_code_exact(&self, code: &[u8]) -> Option<ArtifactWithContractRef<'_>> {
275 self.find_by_deployed_code_exact_inner(code, false)
276 }
277
278 pub fn find_by_deployed_code_exact_unique(
280 &self,
281 code: &[u8],
282 ) -> Option<ArtifactWithContractRef<'_>> {
283 self.find_by_deployed_code_exact_inner(code, true)
284 }
285
286 fn find_by_deployed_code_exact_inner(
287 &self,
288 code: &[u8],
289 unique: bool,
290 ) -> Option<ArtifactWithContractRef<'_>> {
291 if code.is_empty() {
293 return None;
294 }
295
296 let mut partial_match = None;
297 let mut unique_match = None;
298 let matched = self.iter().find(|(id, contract)| {
299 let Some(deployed_bytecode) = &contract.deployed_bytecode else {
300 return false;
301 };
302 let Some(deployed_code) = &deployed_bytecode.object else {
303 return false;
304 };
305
306 let len = match deployed_code {
307 BytecodeObject::Bytecode(bytes) => bytes.len(),
308 BytecodeObject::Unlinked(bytes) => bytes.len() / 2,
309 };
310
311 if len != code.len() {
312 return false;
313 }
314
315 let mut ignored = deployed_bytecode
317 .immutable_references
318 .values()
319 .chain(deployed_bytecode.link_references.values().flat_map(|v| v.values()))
320 .flatten()
321 .cloned()
322 .collect::<Vec<_>>();
323
324 let has_call_protection = match deployed_code {
329 BytecodeObject::Bytecode(bytes) => {
330 bytes.starts_with(&CALL_PROTECTION_BYTECODE_PREFIX)
331 }
332 BytecodeObject::Unlinked(bytes) => {
333 if let Ok(bytes) =
334 Bytes::from_str(&bytes[..CALL_PROTECTION_BYTECODE_PREFIX.len() * 2])
335 {
336 bytes.starts_with(&CALL_PROTECTION_BYTECODE_PREFIX)
337 } else {
338 false
339 }
340 }
341 };
342
343 if has_call_protection {
344 ignored.push(Offsets { start: 1, length: 20 });
345 }
346
347 let metadata_start = find_metadata_start(code);
348
349 if let Some(metadata) = metadata_start {
350 ignored.push(Offsets {
351 start: metadata as u32,
352 length: (code.len() - metadata) as u32,
353 });
354 }
355
356 normalize_offsets(&mut ignored);
358
359 let mut left = 0;
360 for offset in ignored {
361 let right = offset.start as usize;
362
363 let matched = match deployed_code {
364 BytecodeObject::Bytecode(bytes) => bytes[left..right] == code[left..right],
365 BytecodeObject::Unlinked(bytes) => {
366 if let Ok(bytes) = Bytes::from_str(&bytes[left * 2..right * 2]) {
367 bytes == code[left..right]
368 } else {
369 false
370 }
371 }
372 };
373
374 if !matched {
375 return false;
376 }
377
378 left = right + offset.length as usize;
379 }
380
381 let is_partial = if left < code.len() {
382 match deployed_code {
383 BytecodeObject::Bytecode(bytes) => bytes[left..] == code[left..],
384 BytecodeObject::Unlinked(bytes) => {
385 if let Ok(bytes) = Bytes::from_str(&bytes[left * 2..]) {
386 bytes == code[left..]
387 } else {
388 false
389 }
390 }
391 }
392 } else {
393 true
394 };
395
396 if !is_partial {
397 return false;
398 }
399
400 let Some(metadata) = metadata_start else {
401 if unique && unique_match.is_none() {
402 unique_match = Some((*id, *contract));
403 return false;
404 }
405 return true;
406 };
407
408 let exact_match = match deployed_code {
409 BytecodeObject::Bytecode(bytes) => bytes[metadata..] == code[metadata..],
410 BytecodeObject::Unlinked(bytes) => {
411 if let Ok(bytes) = Bytes::from_str(&bytes[metadata * 2..]) {
412 bytes == code[metadata..]
413 } else {
414 false
415 }
416 }
417 };
418
419 if exact_match {
420 if unique && unique_match.is_none() {
421 unique_match = Some((*id, *contract));
422 false
423 } else {
424 true
425 }
426 } else {
427 partial_match = Some((*id, *contract));
428 false
429 }
430 });
431
432 if unique {
433 matched.is_none().then_some(unique_match).flatten()
434 } else {
435 matched.or(partial_match)
436 }
437 }
438
439 pub fn find_by_name_or_identifier(
442 &self,
443 id: &str,
444 ) -> Result<Option<ArtifactWithContractRef<'_>>> {
445 let mut iter =
446 self.iter().filter(|(artifact, _)| artifact.name == id || artifact.identifier() == id);
447 let first = iter.next();
448 if first.is_some() && iter.next().is_some() {
449 eyre::bail!("{id} has more than one implementation.");
450 }
451
452 Ok(first)
453 }
454
455 pub fn find_abi_by_name_or_src_path(&self, name_or_path: &str) -> Option<(JsonAbi, String)> {
459 self.iter()
460 .find(|(artifact, _)| {
461 artifact.name == name_or_path || artifact.source == Path::new(name_or_path)
462 })
463 .map(|(_, contract)| (contract.abi.clone(), contract.name.clone()))
464 }
465
466 pub fn flatten(&self) -> (BTreeMap<Selector, Function>, BTreeMap<B256, Event>, JsonAbi) {
468 let mut funcs = BTreeMap::new();
469 let mut events = BTreeMap::new();
470 let mut errors_abi = JsonAbi::new();
471 for contract in self.values() {
472 for func in contract.abi.functions() {
473 funcs.insert(func.selector(), func.clone());
474 }
475 for event in contract.abi.events() {
476 events.insert(event.selector(), event.clone());
477 }
478 for error in contract.abi.errors() {
479 errors_abi.errors.entry(error.name.clone()).or_default().push(error.clone());
480 }
481 }
482 (funcs, events, errors_abi)
483 }
484}
485
486impl From<ProjectCompileOutput> for ContractsByArtifact {
487 fn from(value: ProjectCompileOutput) -> Self {
488 Self::new(value.into_artifacts().map(|(id, ar)| {
489 (
490 id,
491 CompactContractBytecode {
492 abi: ar.abi,
493 bytecode: ar.bytecode,
494 deployed_bytecode: ar.deployed_bytecode,
495 },
496 )
497 }))
498 }
499}
500
501impl Deref for ContractsByArtifact {
502 type Target = BTreeMap<ArtifactId, ContractData>;
503
504 fn deref(&self) -> &Self::Target {
505 &self.0
506 }
507}
508
509pub type ContractsByAddress = BTreeMap<Address, (String, JsonAbi)>;
511
512pub fn matches_contract_creation(contract: &ContractData, creation_code: &[u8]) -> bool {
518 let Some(bytecode) = contract.bytecode() else { return false };
519 let Some(arguments) = creation_code.strip_prefix(bytecode.as_ref()) else { return false };
520 match contract.abi.constructor() {
521 Some(constructor) => constructor
522 .abi_decode_input(arguments)
523 .ok()
524 .and_then(|values| constructor.abi_encode_input(&values).ok())
525 .is_some_and(|encoded| encoded == arguments),
526 None => arguments.is_empty(),
527 }
528}
529
530pub fn bytecode_diff_score<'a>(mut a: &'a [u8], mut b: &'a [u8]) -> f64 {
531 if a.len() < b.len() {
533 std::mem::swap(&mut a, &mut b);
534 }
535
536 let mut n_different_bytes = a.len() - b.len();
538
539 if n_different_bytes > 32 && n_different_bytes * 10 > a.len() {
544 return 1.0;
545 }
546
547 n_different_bytes += unsafe { count_different_bytes(a, b) };
550
551 n_different_bytes as f64 / a.len() as f64
552}
553
554const unsafe fn count_different_bytes(a: &[u8], b: &[u8]) -> usize {
560 let a_ptr = a.as_ptr();
565 let b_ptr = b.as_ptr();
566 let len = b.len();
567
568 let mut sum = 0;
569 let mut i = 0;
570 while i < len {
571 sum += unsafe { *a_ptr.add(i) != *b_ptr.add(i) } as usize;
573 i += 1;
574 }
575 sum
576}
577
578pub fn get_contract_name(id: &str) -> &str {
597 id.rsplit(':').next().unwrap_or(id)
598}
599
600pub fn get_file_name(id: &str) -> &str {
612 id.split(':').next().unwrap_or(id)
613}
614
615pub fn compact_to_contract(contract: CompactContractBytecode) -> Result<ContractBytecodeSome> {
617 Ok(ContractBytecodeSome {
618 abi: contract.abi.ok_or_else(|| eyre::eyre!("No contract abi"))?,
619 bytecode: contract.bytecode.ok_or_else(|| eyre::eyre!("No contract bytecode"))?.into(),
620 deployed_bytecode: contract
621 .deployed_bytecode
622 .ok_or_else(|| eyre::eyre!("No contract deployed bytecode"))?
623 .into(),
624 })
625}
626
627pub fn find_target_path(project: &Project, identifier: &PathOrContractInfo) -> Result<PathBuf> {
629 match identifier {
630 PathOrContractInfo::Path(path) => Ok(canonicalized(project.root().join(path))),
631 PathOrContractInfo::ContractInfo(info) => {
632 if let Some(path) = info.path.as_ref() {
633 let path = canonicalized(project.root().join(path));
634 if !path.is_file() {
635 eyre::bail!(
636 "Could not find source file for contract `{}` at {}",
637 info.name,
638 path.strip_prefix(project.root()).unwrap_or(&path).display()
639 );
640 }
641 return Ok(path);
642 }
643 let path = project.find_contract_path(&info.name)?;
647 Ok(path)
648 }
649 }
650}
651
652pub fn find_matching_contract_artifact(
654 output: &mut ProjectCompileOutput,
655 target_path: &Path,
656 target_name: Option<&str>,
657) -> eyre::Result<ConfigurableContractArtifact> {
658 if let Some(name) = target_name {
659 if let Some(artifact) = output.remove(target_path, name) {
660 return Ok(artifact);
661 }
662
663 let target_path = canonicalized(target_path);
664 let matching_source = output.artifact_ids().find_map(|(id, _artifact)| {
665 (id.name == name && canonicalized(&id.source) == target_path).then(|| id.source.clone())
666 });
667
668 matching_source
669 .and_then(|source| output.remove(&source, name))
670 .ok_or_eyre(format!("Could not find artifact `{name}` in the compiled artifacts"))
671 } else {
672 let possible_targets = output
673 .artifact_ids()
674 .filter(|(id, _artifact)| id.source == target_path)
675 .collect::<Vec<_>>();
676
677 if possible_targets.is_empty() {
678 eyre::bail!(
679 "Could not find artifact linked to source `{target_path:?}` in the compiled artifacts"
680 );
681 }
682
683 let (target_id, target_artifact) = possible_targets[0].clone();
684 if possible_targets.len() == 1 {
685 return Ok(target_artifact.clone());
686 }
687
688 if !target_id.name.contains('.')
692 && possible_targets.iter().any(|(id, _)| id.name != target_id.name)
693 {
694 eyre::bail!(
695 "Multiple contracts found in the same file, please specify the target <path>:<contract> or <contract>"
696 );
697 }
698
699 let artifact = possible_targets
702 .iter()
703 .find_map(|(id, artifact)| (id.profile == "default").then_some(*artifact))
704 .unwrap_or(target_artifact);
705
706 Ok(artifact.clone())
707 }
708}
709
710#[cfg(test)]
711mod tests {
712 use super::*;
713 use alloy_dyn_abi::DynSolValue;
714 use alloy_primitives::U256;
715 use semver::Version;
716
717 fn deployed_artifact(name: &str, code: Bytes) -> (ArtifactId, CompactContractBytecode) {
718 deployed_artifact_with_immutables(name, code, Default::default())
719 }
720
721 fn deployed_artifact_with_immutables(
722 name: &str,
723 code: Bytes,
724 immutable_references: BTreeMap<String, Vec<Offsets>>,
725 ) -> (ArtifactId, CompactContractBytecode) {
726 (
727 ArtifactId {
728 path: format!("out/{name}.json").into(),
729 name: name.to_owned(),
730 source: format!("src/{name}.sol").into(),
731 version: Version::new(0, 8, 30),
732 build_id: String::new(),
733 profile: "default".to_owned(),
734 },
735 CompactContractBytecode {
736 abi: Some(Default::default()),
737 bytecode: None,
738 deployed_bytecode: Some(CompactDeployedBytecode {
739 bytecode: Some(CompactBytecode {
740 object: BytecodeObject::Bytecode(code),
741 source_map: None,
742 link_references: Default::default(),
743 }),
744 immutable_references,
745 }),
746 },
747 )
748 }
749
750 #[test]
751 fn exact_creation_match_requires_canonical_constructor_suffix() {
752 let abi = JsonAbi::parse(["constructor(uint256 value)"]).unwrap();
753 let bytecode = Bytes::from_static(&[0x60, 0x00]);
754 let contract = ContractData {
755 name: "C".to_owned(),
756 abi,
757 bytecode: Some(BytecodeData {
758 object: Some(BytecodeObject::Bytecode(bytecode.clone())),
759 link_references: BTreeMap::new(),
760 immutable_references: BTreeMap::new(),
761 }),
762 deployed_bytecode: None,
763 storage_layout: None,
764 };
765 let arguments = contract
766 .abi
767 .constructor()
768 .unwrap()
769 .abi_encode_input(&[DynSolValue::Uint(U256::from(1), 256)])
770 .unwrap();
771 let creation = [bytecode.as_ref(), &arguments].concat();
772
773 assert!(matches_contract_creation(&contract, &creation));
774 assert!(!matches_contract_creation(&contract, &creation[..creation.len() - 1]));
775 assert!(!matches_contract_creation(&contract, &[creation, vec![0]].concat()));
776 }
777
778 #[test]
779 fn bytecode_diffing() {
780 assert_eq!(bytecode_diff_score(b"a", b"a"), 0.0);
781 assert_eq!(bytecode_diff_score(b"a", b"b"), 1.0);
782
783 let a_100 = &b"a".repeat(100)[..];
784 assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(100)), 1.0);
785 assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(99)), 1.0);
786 assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(101)), 1.0);
787 assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(120)), 1.0);
788 assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(1000)), 1.0);
789
790 let a_99 = &b"a".repeat(99)[..];
791 assert!(bytecode_diff_score(a_100, a_99) <= 0.01);
792 }
793
794 #[test]
795 fn find_by_deployed_code_exact_with_empty_deployed() {
796 let contracts = ContractsByArtifact::new(vec![]);
797
798 assert!(contracts.find_by_deployed_code_exact(&[]).is_none());
799 }
800
801 #[test]
802 fn find_by_deployed_code_exact_unique_rejects_ambiguity() {
803 let code = Bytes::from_static(&[0x60, 0x00]);
804 let contracts = ContractsByArtifact::new([
805 deployed_artifact("A", code.clone()),
806 deployed_artifact("B", code.clone()),
807 ]);
808
809 assert!(contracts.find_by_deployed_code_exact_unique(&code).is_none());
810
811 let contracts = ContractsByArtifact::new([deployed_artifact("A", code.clone())]);
812 assert_eq!(contracts.find_by_deployed_code_exact_unique(&code).unwrap().0.name, "A");
813 }
814
815 #[test]
816 fn find_by_deployed_code_exact_unique_rejects_partial_metadata_match() {
817 let artifact_code = Bytes::from_static(&[0x60, 0x00, 0xa0, 0x00, 0x01]);
818 let deployed_code = Bytes::from_static(&[0x60, 0x00, 0xf6, 0x00, 0x01]);
819 let contracts = ContractsByArtifact::new([deployed_artifact("A", artifact_code)]);
820
821 assert!(contracts.find_by_deployed_code_exact(&deployed_code).is_some());
822 assert!(contracts.find_by_deployed_code_exact_unique(&deployed_code).is_none());
823 }
824
825 #[test]
827 fn find_by_deployed_code_exact_handles_overlapping_ignored_ranges() {
828 let mut code = vec![0x73u8];
830 code.extend(std::iter::repeat_n(0u8, 20));
831 code.extend_from_slice(&[0xAA, 0xBB, 0xCC, 0xDD]);
833 let code = Bytes::from(code);
834
835 let immutable_references =
837 BTreeMap::from([("someImmutable".to_string(), vec![Offsets { start: 5, length: 3 }])]);
838
839 let contracts = ContractsByArtifact::new([deployed_artifact_with_immutables(
840 "LibWithEarlyImmutable",
841 code.clone(),
842 immutable_references,
843 )]);
844
845 assert!(contracts.find_by_deployed_code_exact(&code).is_some());
847 }
848
849 #[test]
850 fn normalize_offsets_merges_overlaps_and_adjacency() {
851 let mut offsets = vec![Offsets { start: 1, length: 20 }, Offsets { start: 5, length: 3 }];
853 normalize_offsets(&mut offsets);
854 assert_eq!(offsets, vec![Offsets { start: 1, length: 20 }]);
855
856 let mut offsets = vec![Offsets { start: 1, length: 20 }, Offsets { start: 15, length: 15 }];
858 normalize_offsets(&mut offsets);
859 assert_eq!(offsets, vec![Offsets { start: 1, length: 29 }]);
860
861 let mut offsets = vec![Offsets { start: 1, length: 20 }, Offsets { start: 21, length: 4 }];
863 normalize_offsets(&mut offsets);
864 assert_eq!(offsets, vec![Offsets { start: 1, length: 24 }]);
865
866 let mut offsets = vec![Offsets { start: 1, length: 5 }, Offsets { start: 10, length: 5 }];
868 normalize_offsets(&mut offsets);
869 assert_eq!(
870 offsets,
871 vec![Offsets { start: 1, length: 5 }, Offsets { start: 10, length: 5 }]
872 );
873
874 let mut offsets = vec![Offsets { start: 10, length: 5 }, Offsets { start: 1, length: 5 }];
876 normalize_offsets(&mut offsets);
877 assert_eq!(
878 offsets,
879 vec![Offsets { start: 1, length: 5 }, Offsets { start: 10, length: 5 }]
880 );
881 }
882}