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_preferred(code, |_| true)
276 }
277
278 pub fn find_by_deployed_code_exact_preferred(
281 &self,
282 code: &[u8],
283 preferred: impl Fn(&ArtifactId) -> bool,
284 ) -> Option<ArtifactWithContractRef<'_>> {
285 self.find_by_deployed_code_exact_inner(code, false, preferred).0
286 }
287
288 pub fn find_by_deployed_code_exact_unique(
290 &self,
291 code: &[u8],
292 ) -> Option<ArtifactWithContractRef<'_>> {
293 self.find_by_deployed_code_exact_inner(code, true, |_| false).0
294 }
295
296 pub fn find_by_deployed_code_exact_unambiguous(
299 &self,
300 code: &[u8],
301 ) -> Result<Option<ArtifactWithContractRef<'_>>> {
302 let (matched, ambiguous_partial) =
303 self.find_by_deployed_code_exact_inner(code, false, |_| true);
304 if ambiguous_partial {
305 eyre::bail!(
306 "Multiple local contracts match the deployed bytecode with different metadata. Specify the contract as <path>:<contract>"
307 );
308 }
309 Ok(matched)
310 }
311
312 fn find_by_deployed_code_exact_inner(
313 &self,
314 code: &[u8],
315 unique: bool,
316 preferred: impl Fn(&ArtifactId) -> bool,
317 ) -> (Option<ArtifactWithContractRef<'_>>, bool) {
318 if code.is_empty() {
320 return (None, false);
321 }
322
323 let mut partial_match = None;
324 let mut exact_match = None;
325 let mut ambiguous_partial = false;
326 'contracts: for (id, contract) in self.iter() {
327 let Some(deployed_bytecode) = &contract.deployed_bytecode else {
328 continue;
329 };
330 let Some(deployed_code) = &deployed_bytecode.object else {
331 continue;
332 };
333
334 let len = match deployed_code {
335 BytecodeObject::Bytecode(bytes) => bytes.len(),
336 BytecodeObject::Unlinked(bytes) => bytes.len() / 2,
337 };
338
339 let is_vyper = id.source.extension().is_some_and(|extension| extension == "vy");
343 let code = if is_vyper {
344 let Some(runtime) = code.get(..len) else { continue };
345 runtime
346 } else {
347 code
348 };
349 if len == 0 || len != code.len() {
350 continue;
351 }
352
353 let mut ignored = deployed_bytecode
355 .immutable_references
356 .values()
357 .chain(deployed_bytecode.link_references.values().flat_map(|v| v.values()))
358 .flatten()
359 .cloned()
360 .collect::<Vec<_>>();
361
362 let has_call_protection = !is_vyper
367 && match deployed_code {
368 BytecodeObject::Bytecode(bytes) => {
369 bytes.starts_with(&CALL_PROTECTION_BYTECODE_PREFIX)
370 }
371 BytecodeObject::Unlinked(bytes) => {
372 if let Ok(bytes) =
373 Bytes::from_str(&bytes[..CALL_PROTECTION_BYTECODE_PREFIX.len() * 2])
374 {
375 bytes.starts_with(&CALL_PROTECTION_BYTECODE_PREFIX)
376 } else {
377 false
378 }
379 }
380 };
381
382 if has_call_protection {
383 ignored.push(Offsets { start: 1, length: 20 });
384 }
385
386 let metadata_start = if is_vyper { None } else { find_metadata_start(code) };
387
388 if let Some(metadata) = metadata_start {
389 ignored.push(Offsets {
390 start: metadata as u32,
391 length: (code.len() - metadata) as u32,
392 });
393 }
394
395 normalize_offsets(&mut ignored);
397
398 let mut left = 0;
399 for offset in ignored {
400 let right = offset.start as usize;
401
402 let matched = match deployed_code {
403 BytecodeObject::Bytecode(bytes) => bytes[left..right] == code[left..right],
404 BytecodeObject::Unlinked(bytes) => {
405 if let Ok(bytes) = Bytes::from_str(&bytes[left * 2..right * 2]) {
406 bytes == code[left..right]
407 } else {
408 false
409 }
410 }
411 };
412
413 if !matched {
414 continue 'contracts;
415 }
416
417 left = right + offset.length as usize;
418 }
419
420 let is_partial = if left < code.len() {
421 match deployed_code {
422 BytecodeObject::Bytecode(bytes) => bytes[left..] == code[left..],
423 BytecodeObject::Unlinked(bytes) => {
424 if let Ok(bytes) = Bytes::from_str(&bytes[left * 2..]) {
425 bytes == code[left..]
426 } else {
427 false
428 }
429 }
430 }
431 } else {
432 true
433 };
434
435 if !is_partial {
436 continue;
437 }
438
439 let matches_metadata = metadata_start.is_none_or(|metadata| match deployed_code {
440 BytecodeObject::Bytecode(bytes) => bytes[metadata..] == code[metadata..],
441 BytecodeObject::Unlinked(bytes) => Bytes::from_str(&bytes[metadata * 2..])
442 .is_ok_and(|bytes| bytes == code[metadata..]),
443 });
444
445 if matches_metadata {
446 if unique && exact_match.is_some() {
447 return (None, false);
448 }
449 if exact_match.is_none() || preferred(id) {
450 exact_match = Some((id, contract));
451 }
452 if !unique && preferred(id) {
453 return (exact_match, false);
454 }
455 } else {
456 ambiguous_partial |= partial_match.is_some();
457 if preferred(id) || partial_match.is_none_or(|(id, _)| !preferred(id)) {
458 partial_match = Some((id, contract));
459 }
460 }
461 }
462
463 let ambiguous_partial = exact_match.is_none() && ambiguous_partial;
464 (if unique { exact_match } else { exact_match.or(partial_match) }, ambiguous_partial)
465 }
466
467 pub fn find_by_name_or_identifier(
470 &self,
471 id: &str,
472 ) -> Result<Option<ArtifactWithContractRef<'_>>> {
473 let mut iter =
474 self.iter().filter(|(artifact, _)| artifact.name == id || artifact.identifier() == id);
475 let first = iter.next();
476 if first.is_some() && iter.next().is_some() {
477 eyre::bail!("{id} has more than one implementation.");
478 }
479
480 Ok(first)
481 }
482
483 pub fn find_abi_by_name_or_src_path(&self, name_or_path: &str) -> Option<(JsonAbi, String)> {
487 self.iter()
488 .find(|(artifact, _)| {
489 artifact.name == name_or_path || artifact.source == Path::new(name_or_path)
490 })
491 .map(|(_, contract)| (contract.abi.clone(), contract.name.clone()))
492 }
493
494 pub fn flatten(&self) -> (BTreeMap<Selector, Function>, BTreeMap<B256, Event>, JsonAbi) {
496 let mut funcs = BTreeMap::new();
497 let mut events = BTreeMap::new();
498 let mut errors_abi = JsonAbi::new();
499 for contract in self.values() {
500 for func in contract.abi.functions() {
501 funcs.insert(func.selector(), func.clone());
502 }
503 for event in contract.abi.events() {
504 events.insert(event.selector(), event.clone());
505 }
506 for error in contract.abi.errors() {
507 errors_abi.errors.entry(error.name.clone()).or_default().push(error.clone());
508 }
509 }
510 (funcs, events, errors_abi)
511 }
512}
513
514impl From<ProjectCompileOutput> for ContractsByArtifact {
515 fn from(value: ProjectCompileOutput) -> Self {
516 Self::new(value.into_artifacts().map(|(id, ar)| {
517 (
518 id,
519 CompactContractBytecode {
520 abi: ar.abi,
521 bytecode: ar.bytecode,
522 deployed_bytecode: ar.deployed_bytecode,
523 },
524 )
525 }))
526 }
527}
528
529impl Deref for ContractsByArtifact {
530 type Target = BTreeMap<ArtifactId, ContractData>;
531
532 fn deref(&self) -> &Self::Target {
533 &self.0
534 }
535}
536
537pub type ContractsByAddress = BTreeMap<Address, (String, JsonAbi)>;
539
540pub fn matches_contract_creation(contract: &ContractData, creation_code: &[u8]) -> bool {
546 let Some(bytecode) = contract.bytecode() else { return false };
547 let Some(arguments) = creation_code.strip_prefix(bytecode.as_ref()) else { return false };
548 match contract.abi.constructor() {
549 Some(constructor) => constructor
550 .abi_decode_input(arguments)
551 .ok()
552 .and_then(|values| constructor.abi_encode_input(&values).ok())
553 .is_some_and(|encoded| encoded == arguments),
554 None => arguments.is_empty(),
555 }
556}
557
558pub fn bytecode_diff_score<'a>(mut a: &'a [u8], mut b: &'a [u8]) -> f64 {
559 if a.len() < b.len() {
561 std::mem::swap(&mut a, &mut b);
562 }
563
564 let mut n_different_bytes = a.len() - b.len();
566
567 if n_different_bytes > 32 && n_different_bytes * 10 > a.len() {
572 return 1.0;
573 }
574
575 n_different_bytes += unsafe { count_different_bytes(a, b) };
578
579 n_different_bytes as f64 / a.len() as f64
580}
581
582const unsafe fn count_different_bytes(a: &[u8], b: &[u8]) -> usize {
588 let a_ptr = a.as_ptr();
593 let b_ptr = b.as_ptr();
594 let len = b.len();
595
596 let mut sum = 0;
597 let mut i = 0;
598 while i < len {
599 sum += unsafe { *a_ptr.add(i) != *b_ptr.add(i) } as usize;
601 i += 1;
602 }
603 sum
604}
605
606pub fn get_contract_name(id: &str) -> &str {
625 id.rsplit(':').next().unwrap_or(id)
626}
627
628pub fn get_file_name(id: &str) -> &str {
640 id.split(':').next().unwrap_or(id)
641}
642
643pub fn compact_to_contract(contract: CompactContractBytecode) -> Result<ContractBytecodeSome> {
645 Ok(ContractBytecodeSome {
646 abi: contract.abi.ok_or_else(|| eyre::eyre!("No contract abi"))?,
647 bytecode: contract.bytecode.ok_or_else(|| eyre::eyre!("No contract bytecode"))?.into(),
648 deployed_bytecode: contract
649 .deployed_bytecode
650 .ok_or_else(|| eyre::eyre!("No contract deployed bytecode"))?
651 .into(),
652 })
653}
654
655pub fn find_target_path(project: &Project, identifier: &PathOrContractInfo) -> Result<PathBuf> {
657 match identifier {
658 PathOrContractInfo::Path(path) => Ok(canonicalized(project.root().join(path))),
659 PathOrContractInfo::ContractInfo(info) => {
660 if let Some(path) = info.path.as_ref() {
661 let path = canonicalized(project.root().join(path));
662 if !path.is_file() {
663 eyre::bail!(
664 "Could not find source file for contract `{}` at {}",
665 info.name,
666 path.strip_prefix(project.root()).unwrap_or(&path).display()
667 );
668 }
669 return Ok(path);
670 }
671 let path = project.find_contract_path(&info.name)?;
675 Ok(path)
676 }
677 }
678}
679
680pub fn find_matching_contract_artifact(
682 output: &mut ProjectCompileOutput,
683 target_path: &Path,
684 target_name: Option<&str>,
685) -> eyre::Result<ConfigurableContractArtifact> {
686 if let Some(name) = target_name {
687 if let Some(artifact) = output.remove(target_path, name) {
688 return Ok(artifact);
689 }
690
691 let target_path = canonicalized(target_path);
692 let matching_source = output.artifact_ids().find_map(|(id, _artifact)| {
693 (id.name == name && canonicalized(&id.source) == target_path).then(|| id.source.clone())
694 });
695
696 matching_source
697 .and_then(|source| output.remove(&source, name))
698 .ok_or_eyre(format!("Could not find artifact `{name}` in the compiled artifacts"))
699 } else {
700 let possible_targets = output
701 .artifact_ids()
702 .filter(|(id, _artifact)| id.source == target_path)
703 .collect::<Vec<_>>();
704
705 if possible_targets.is_empty() {
706 eyre::bail!(
707 "Could not find artifact linked to source `{target_path:?}` in the compiled artifacts"
708 );
709 }
710
711 let (target_id, target_artifact) = possible_targets[0].clone();
712 if possible_targets.len() == 1 {
713 return Ok(target_artifact.clone());
714 }
715
716 if !target_id.name.contains('.')
720 && possible_targets.iter().any(|(id, _)| id.name != target_id.name)
721 {
722 eyre::bail!(
723 "Multiple contracts found in the same file, please specify the target <path>:<contract> or <contract>"
724 );
725 }
726
727 let artifact = possible_targets
730 .iter()
731 .find_map(|(id, artifact)| (id.profile == "default").then_some(*artifact))
732 .unwrap_or(target_artifact);
733
734 Ok(artifact.clone())
735 }
736}
737
738#[cfg(test)]
739mod tests {
740 use super::*;
741 use alloy_dyn_abi::DynSolValue;
742 use alloy_primitives::U256;
743 use semver::Version;
744
745 fn deployed_artifact(name: &str, code: Bytes) -> (ArtifactId, CompactContractBytecode) {
746 deployed_artifact_with_immutables(name, code, Default::default())
747 }
748
749 fn deployed_artifact_with_immutables(
750 name: &str,
751 code: Bytes,
752 immutable_references: BTreeMap<String, Vec<Offsets>>,
753 ) -> (ArtifactId, CompactContractBytecode) {
754 (
755 ArtifactId {
756 path: format!("out/{name}.json").into(),
757 name: name.to_owned(),
758 source: format!("src/{name}.sol").into(),
759 version: Version::new(0, 8, 30),
760 build_id: String::new(),
761 profile: "default".to_owned(),
762 },
763 CompactContractBytecode {
764 abi: Some(Default::default()),
765 bytecode: None,
766 deployed_bytecode: Some(CompactDeployedBytecode {
767 bytecode: Some(CompactBytecode {
768 object: BytecodeObject::Bytecode(code),
769 source_map: None,
770 link_references: Default::default(),
771 }),
772 immutable_references,
773 }),
774 },
775 )
776 }
777
778 #[test]
779 fn exact_creation_match_requires_canonical_constructor_suffix() {
780 let abi = JsonAbi::parse(["constructor(uint256 value)"]).unwrap();
781 let bytecode = Bytes::from_static(&[0x60, 0x00]);
782 let contract = ContractData {
783 name: "C".to_owned(),
784 abi,
785 bytecode: Some(BytecodeData {
786 object: Some(BytecodeObject::Bytecode(bytecode.clone())),
787 link_references: BTreeMap::new(),
788 immutable_references: BTreeMap::new(),
789 }),
790 deployed_bytecode: None,
791 storage_layout: None,
792 };
793 let arguments = contract
794 .abi
795 .constructor()
796 .unwrap()
797 .abi_encode_input(&[DynSolValue::Uint(U256::ONE, 256)])
798 .unwrap();
799 let creation = [bytecode.as_ref(), &arguments].concat();
800
801 assert!(matches_contract_creation(&contract, &creation));
802 assert!(!matches_contract_creation(&contract, &creation[..creation.len() - 1]));
803 assert!(!matches_contract_creation(&contract, &[creation, vec![0]].concat()));
804 }
805
806 #[test]
807 fn bytecode_diffing() {
808 assert_eq!(bytecode_diff_score(b"a", b"a"), 0.0);
809 assert_eq!(bytecode_diff_score(b"a", b"b"), 1.0);
810
811 let a_100 = &b"a".repeat(100)[..];
812 assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(100)), 1.0);
813 assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(99)), 1.0);
814 assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(101)), 1.0);
815 assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(120)), 1.0);
816 assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(1000)), 1.0);
817
818 let a_99 = &b"a".repeat(99)[..];
819 assert!(bytecode_diff_score(a_100, a_99) <= 0.01);
820 }
821
822 #[test]
823 fn find_by_deployed_code_exact_with_empty_deployed() {
824 let contracts = ContractsByArtifact::new(vec![]);
825
826 assert!(contracts.find_by_deployed_code_exact(&[]).is_none());
827 }
828
829 #[test]
830 fn find_by_deployed_code_exact_unique_rejects_ambiguity() {
831 let code = Bytes::from_static(&[0x60, 0x00]);
832 let contracts = ContractsByArtifact::new([
833 deployed_artifact("A", code.clone()),
834 deployed_artifact("B", code.clone()),
835 ]);
836
837 assert!(contracts.find_by_deployed_code_exact_unique(&code).is_none());
838
839 let contracts = ContractsByArtifact::new([deployed_artifact("A", code.clone())]);
840 assert_eq!(contracts.find_by_deployed_code_exact_unique(&code).unwrap().0.name, "A");
841 }
842
843 #[test]
844 fn find_by_deployed_code_exact_unique_rejects_partial_metadata_match() {
845 let artifact_code = Bytes::from_static(&[0x60, 0x00, 0xa0, 0x00, 0x01]);
846 let deployed_code = Bytes::from_static(&[0x60, 0x00, 0xf6, 0x00, 0x01]);
847 let contracts = ContractsByArtifact::new([deployed_artifact("A", artifact_code)]);
848
849 assert!(contracts.find_by_deployed_code_exact(&deployed_code).is_some());
850 assert!(contracts.find_by_deployed_code_exact_unique(&deployed_code).is_none());
851 }
852
853 #[test]
854 fn find_by_deployed_code_exact_unambiguous_preserves_single_partial() {
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_eq!(
860 contracts
861 .find_by_deployed_code_exact_unambiguous(&deployed_code)
862 .unwrap()
863 .unwrap()
864 .0
865 .name,
866 "A"
867 );
868 assert!(contracts.find_by_deployed_code_exact_unambiguous(&[0x00]).unwrap().is_none());
869 }
870
871 #[test]
872 fn find_by_deployed_code_exact_unambiguous_prefers_exact_over_ambiguous_partial() {
873 let partial_code = Bytes::from_static(&[0x60, 0x00, 0xa0, 0x00, 0x01]);
874 let deployed_code = Bytes::from_static(&[0x60, 0x00, 0xf6, 0x00, 0x01]);
875 for exact_name in ["A", "Z"] {
876 let contracts = ContractsByArtifact::new([
877 deployed_artifact("B", partial_code.clone()),
878 deployed_artifact("C", partial_code.clone()),
879 ]);
880 assert!(contracts.find_by_deployed_code_exact(&deployed_code).is_some());
881 assert!(contracts.find_by_deployed_code_exact_unique(&deployed_code).is_none());
882 assert_eq!(
883 contracts
884 .find_by_deployed_code_exact_unambiguous(&deployed_code)
885 .unwrap_err()
886 .to_string(),
887 "Multiple local contracts match the deployed bytecode with different metadata. Specify the contract as <path>:<contract>"
888 );
889
890 let contracts = ContractsByArtifact::new([
891 deployed_artifact(exact_name, deployed_code.clone()),
892 deployed_artifact("B", partial_code.clone()),
893 deployed_artifact("C", partial_code.clone()),
894 ]);
895 assert_eq!(
896 contracts
897 .find_by_deployed_code_exact_unambiguous(&deployed_code)
898 .unwrap()
899 .unwrap()
900 .0
901 .name,
902 exact_name
903 );
904 }
905 }
906
907 #[test]
909 fn find_by_deployed_code_exact_handles_overlapping_ignored_ranges() {
910 let mut code = vec![0x73u8];
912 code.extend(std::iter::repeat_n(0u8, 20));
913 code.extend_from_slice(&[0xAA, 0xBB, 0xCC, 0xDD]);
915 let code = Bytes::from(code);
916
917 let immutable_references =
919 BTreeMap::from([("someImmutable".to_string(), vec![Offsets { start: 5, length: 3 }])]);
920
921 let contracts = ContractsByArtifact::new([deployed_artifact_with_immutables(
922 "LibWithEarlyImmutable",
923 code.clone(),
924 immutable_references,
925 )]);
926
927 assert!(contracts.find_by_deployed_code_exact(&code).is_some());
929 }
930
931 #[test]
932 fn normalize_offsets_merges_overlaps_and_adjacency() {
933 let mut offsets = vec![Offsets { start: 1, length: 20 }, Offsets { start: 5, length: 3 }];
935 normalize_offsets(&mut offsets);
936 assert_eq!(offsets, vec![Offsets { start: 1, length: 20 }]);
937
938 let mut offsets = vec![Offsets { start: 1, length: 20 }, Offsets { start: 15, length: 15 }];
940 normalize_offsets(&mut offsets);
941 assert_eq!(offsets, vec![Offsets { start: 1, length: 29 }]);
942
943 let mut offsets = vec![Offsets { start: 1, length: 20 }, Offsets { start: 21, length: 4 }];
945 normalize_offsets(&mut offsets);
946 assert_eq!(offsets, vec![Offsets { start: 1, length: 24 }]);
947
948 let mut offsets = vec![Offsets { start: 1, length: 5 }, Offsets { start: 10, length: 5 }];
950 normalize_offsets(&mut offsets);
951 assert_eq!(
952 offsets,
953 vec![Offsets { start: 1, length: 5 }, Offsets { start: 10, length: 5 }]
954 );
955
956 let mut offsets = vec![Offsets { start: 10, length: 5 }, Offsets { start: 1, length: 5 }];
958 normalize_offsets(&mut offsets);
959 assert_eq!(
960 offsets,
961 vec![Offsets { start: 1, length: 5 }, Offsets { start: 10, length: 5 }]
962 );
963 }
964}