1use alloy_json_abi::{Event, Function, JsonAbi};
2use alloy_primitives::{Address, B256, Selector, map::HashMap};
3use foundry_compilers::artifacts::StorageLayout;
4use itertools::Either;
5use serde::{Deserialize, Serialize};
6use std::{
7 cell::{Cell, Ref, RefCell},
8 collections::BTreeMap,
9 fmt,
10 rc::Rc,
11 sync::Arc,
12};
13
14mod call_override;
15pub use call_override::RandomCallGenerator;
16
17mod filters;
18use crate::BasicTxDetails;
19pub use filters::{ArtifactFilters, SenderFilters};
20use foundry_common::{ContractsByAddress, ContractsByArtifact};
21use foundry_evm_core::utils::StateChangeset;
22
23type DynamicTargetArtifactMatchCache =
24 Rc<RefCell<HashMap<(Address, B256), Option<CachedTargetContract>>>>;
25type FuzzedFunction = (Address, Function);
26type FunctionLookup = HashMap<Selector, Function>;
27
28#[derive(Clone, Debug)]
33pub struct FuzzRunIdentifiedContracts {
34 targets: Rc<RefCell<TargetedContracts>>,
36 fuzzed_functions: Rc<RefCell<Vec<FuzzedFunction>>>,
38 fuzzed_functions_generation: Rc<Cell<u64>>,
40 pub is_updatable: bool,
42 artifact_matches: DynamicTargetArtifactMatchCache,
43}
44
45impl FuzzRunIdentifiedContracts {
46 pub fn new(targets: TargetedContracts, is_updatable: bool) -> Self {
48 let fuzzed_functions = Self::flatten_fuzzed_functions(&targets);
49 Self {
50 targets: Rc::new(RefCell::new(targets)),
51 fuzzed_functions: Rc::new(RefCell::new(fuzzed_functions)),
52 fuzzed_functions_generation: Rc::new(Cell::new(0)),
53 is_updatable,
54 artifact_matches: Rc::new(RefCell::new(HashMap::default())),
55 }
56 }
57
58 pub fn targets(&self) -> Ref<'_, TargetedContracts> {
60 self.targets.borrow()
61 }
62
63 pub fn fuzzed_functions(&self) -> Ref<'_, [FuzzedFunction]> {
65 Ref::map(self.fuzzed_functions.borrow(), Vec::as_slice)
66 }
67
68 pub fn fuzzed_functions_generation(&self) -> u64 {
70 self.fuzzed_functions_generation.get()
71 }
72
73 fn refresh_fuzzed_functions(&self) {
74 let fuzzed_functions = {
75 let targets = self.targets.borrow();
76 Self::flatten_fuzzed_functions(&targets)
77 };
78 *self.fuzzed_functions.borrow_mut() = fuzzed_functions;
79 self.fuzzed_functions_generation.set(self.fuzzed_functions_generation.get() + 1);
80 }
81
82 fn flatten_fuzzed_functions(targets: &TargetedContracts) -> Vec<FuzzedFunction> {
83 targets.fuzzed_functions().map(|(address, function)| (*address, function.clone())).collect()
84 }
85
86 pub fn collect_created_contracts(
89 &self,
90 state_changeset: &StateChangeset,
91 project_contracts: &ContractsByArtifact,
92 setup_contracts: &ContractsByAddress,
93 artifact_filters: &ArtifactFilters,
94 created_contracts: &mut Vec<Address>,
95 ) -> eyre::Result<()> {
96 if !self.is_updatable {
97 return Ok(());
98 }
99
100 let mut targets_changed = false;
101 for (address, account) in state_changeset {
102 if setup_contracts.contains_key(address) {
103 continue;
104 }
105 if !account.is_touched() {
106 continue;
107 }
108 let Some(code) = &account.info.code else {
109 continue;
110 };
111 if code.is_empty() {
112 continue;
113 }
114 let code_hash = code.hash_slow();
115 let code = code.original_byte_slice();
116 let Some(contract) = self.target_contract_for_code(
117 *address,
118 code_hash,
119 code,
120 project_contracts,
121 artifact_filters,
122 )?
123 else {
124 continue;
125 };
126 created_contracts.push(*address);
127 self.targets.borrow_mut().insert(*address, contract.into_targeted_contract());
128 targets_changed = true;
129 }
130 if targets_changed {
131 self.refresh_fuzzed_functions();
132 }
133 Ok(())
134 }
135
136 fn target_contract_for_code(
137 &self,
138 address: Address,
139 code_hash: B256,
140 code: &[u8],
141 project_contracts: &ContractsByArtifact,
142 artifact_filters: &ArtifactFilters,
143 ) -> eyre::Result<Option<CachedTargetContract>> {
144 let cache_key = (address, code_hash);
145 if let Some(cached_match) = self.artifact_matches.borrow().get(&cache_key) {
146 return Ok(cached_match.clone());
147 }
148
149 let cached_match = if let Some((artifact, contract_data)) =
150 project_contracts.find_by_deployed_code(code)
151 {
152 artifact_filters.get_targeted_functions(artifact, &contract_data.abi)?.map(
153 |targeted_functions| CachedTargetContract {
154 identifier: artifact.name.clone(),
155 abi: contract_data.abi.clone(),
156 targeted_functions,
157 storage_layout: contract_data.storage_layout.as_ref().map(Arc::clone),
158 event_lookup: Arc::new(TargetedContractEvents::new(&contract_data.abi)),
159 },
160 )
161 } else {
162 None
163 };
164 self.artifact_matches.borrow_mut().insert(cache_key, cached_match.clone());
165 Ok(cached_match)
166 }
167
168 pub fn clear_created_contracts(&self, created_contracts: Vec<Address>) {
170 let mut targets_changed = false;
171 if !created_contracts.is_empty() {
172 let mut targets = self.targets.borrow_mut();
173 for addr in &created_contracts {
174 targets_changed |= targets.remove(addr).is_some();
175 }
176 }
177 if targets_changed {
178 self.refresh_fuzzed_functions();
179 }
180 }
181}
182
183#[derive(Clone, Debug)]
184struct CachedTargetContract {
185 identifier: String,
186 abi: JsonAbi,
187 targeted_functions: Vec<Function>,
188 storage_layout: Option<Arc<StorageLayout>>,
189 event_lookup: Arc<TargetedContractEvents>,
190}
191
192impl CachedTargetContract {
193 fn into_targeted_contract(self) -> TargetedContract {
194 TargetedContract::from_parts(
195 self.identifier,
196 self.abi,
197 self.targeted_functions,
198 Vec::new(),
199 self.storage_layout,
200 self.event_lookup,
201 )
202 }
203}
204
205#[derive(Clone, Debug, Default)]
207pub struct TargetedContracts {
208 pub inner: BTreeMap<Address, TargetedContract>,
210}
211
212impl TargetedContracts {
213 pub fn new() -> Self {
215 Self::default()
216 }
217
218 pub fn fuzzed_artifacts(
222 &self,
223 tx: &BasicTxDetails,
224 ) -> (Option<&TargetedContract>, Option<&Function>) {
225 match self.inner.get(&tx.call_details.target) {
226 Some(c) => {
227 let function = tx
228 .call_details
229 .calldata
230 .get(..4)
231 .and_then(|selector| <[u8; 4]>::try_from(selector).ok())
232 .map(Selector::from)
233 .and_then(|selector| c.function_by_selector(selector));
234 (Some(c), function)
235 }
236 None => (None, None),
237 }
238 }
239
240 pub fn fuzzed_functions(&self) -> impl Iterator<Item = (&Address, &Function)> {
243 self.inner
244 .iter()
245 .filter(|(_, c)| !c.abi.functions.is_empty())
246 .flat_map(|(contract, c)| c.abi_fuzzed_functions().map(move |f| (contract, f)))
247 }
248
249 pub fn can_replay(&self, tx: &BasicTxDetails) -> bool {
251 match self.inner.get(&tx.call_details.target) {
252 Some(c) => tx
253 .call_details
254 .calldata
255 .get(..4)
256 .and_then(|selector| <[u8; 4]>::try_from(selector).ok())
257 .map(Selector::from)
258 .is_some_and(|selector| c.fuzzed_function_by_selector(selector).is_some()),
259 None => false,
260 }
261 }
262
263 pub fn fuzzed_metric_key(&self, tx: &BasicTxDetails) -> Option<String> {
266 tx.call_details
267 .calldata
268 .get(..4)
269 .and_then(|selector| <[u8; 4]>::try_from(selector).ok())
270 .map(Selector::from)
271 .and_then(|selector| {
272 self.fuzzed_metric_key_for_selector(tx.call_details.target, selector)
273 })
274 }
275
276 pub fn fuzzed_metric_key_for_selector(
279 &self,
280 target: Address,
281 selector: Selector,
282 ) -> Option<String> {
283 self.inner.get(&target).and_then(|contract| {
284 contract
285 .function_by_selector(selector)
286 .map(|function| format!("{}.{}", contract.identifier.as_str(), function.name))
287 })
288 }
289}
290
291impl std::ops::Deref for TargetedContracts {
292 type Target = BTreeMap<Address, TargetedContract>;
293
294 fn deref(&self) -> &Self::Target {
295 &self.inner
296 }
297}
298
299impl std::ops::DerefMut for TargetedContracts {
300 fn deref_mut(&mut self) -> &mut Self::Target {
301 &mut self.inner
302 }
303}
304
305#[derive(Clone, Debug)]
307pub struct TargetedContract {
308 pub identifier: String,
310 pub abi: JsonAbi,
312 pub targeted_functions: Vec<Function>,
314 pub excluded_functions: Vec<Function>,
316 pub storage_layout: Option<Arc<StorageLayout>>,
318 pub event_lookup: Arc<TargetedContractEvents>,
320 functions_by_selector: FunctionLookup,
321 fuzzed_functions_by_selector: FunctionLookup,
322}
323
324impl TargetedContract {
325 pub fn new(identifier: String, abi: JsonAbi) -> Self {
327 let event_lookup = Arc::new(TargetedContractEvents::new(&abi));
328 Self::from_parts(identifier, abi, Vec::new(), Vec::new(), None, event_lookup)
329 }
330
331 fn from_parts(
332 identifier: String,
333 abi: JsonAbi,
334 targeted_functions: Vec<Function>,
335 excluded_functions: Vec<Function>,
336 storage_layout: Option<Arc<StorageLayout>>,
337 event_lookup: Arc<TargetedContractEvents>,
338 ) -> Self {
339 let mut contract = Self {
340 identifier,
341 abi,
342 targeted_functions,
343 excluded_functions,
344 storage_layout,
345 event_lookup,
346 functions_by_selector: FunctionLookup::default(),
347 fuzzed_functions_by_selector: FunctionLookup::default(),
348 };
349 contract.rebuild_function_lookups();
350 contract
351 }
352
353 pub fn with_project_contracts(mut self, project_contracts: &ContractsByArtifact) -> Self {
356 if let Some((src, name)) = self.identifier.split_once(':')
357 && let Some((_, contract_data)) = project_contracts.iter().find(|(artifact, _)| {
358 artifact.name == name && artifact.source.as_path().ends_with(src)
359 })
360 {
361 self.storage_layout = contract_data.storage_layout.as_ref().map(Arc::clone);
362 }
363 self
364 }
365
366 pub fn abi_fuzzed_functions(&self) -> impl Iterator<Item = &Function> {
370 if self.targeted_functions.is_empty() {
371 Either::Right(self.abi.functions().filter(|&func| {
372 !matches!(
373 func.state_mutability,
374 alloy_json_abi::StateMutability::Pure | alloy_json_abi::StateMutability::View
375 ) && !self.excluded_functions.contains(func)
376 }))
377 } else {
378 Either::Left(
379 self.targeted_functions
380 .iter()
381 .filter(|func| !self.excluded_functions.contains(func)),
382 )
383 }
384 }
385
386 pub fn rebuild_function_lookups(&mut self) {
387 let functions_by_selector =
388 self.abi.functions().fold(FunctionLookup::default(), |mut functions, function| {
389 functions.entry(function.selector()).or_insert_with(|| function.clone());
390 functions
391 });
392 let fuzzed_functions_by_selector = self.abi_fuzzed_functions().fold(
393 FunctionLookup::default(),
394 |mut functions, function| {
395 functions.entry(function.selector()).or_insert_with(|| function.clone());
396 functions
397 },
398 );
399 self.functions_by_selector = functions_by_selector;
400 self.fuzzed_functions_by_selector = fuzzed_functions_by_selector;
401 }
402
403 pub fn function_by_selector(&self, selector: Selector) -> Option<&Function> {
405 self.functions_by_selector.get(&selector)
406 }
407
408 pub fn fuzzed_function_by_selector(&self, selector: Selector) -> Option<&Function> {
410 self.fuzzed_functions_by_selector.get(&selector)
411 }
412
413 pub fn get_function(&self, selector: Selector) -> eyre::Result<&Function> {
415 self.function_by_selector(selector)
416 .ok_or_else(|| eyre::eyre!("{} does not have the selector {selector}", self.identifier))
417 }
418
419 pub fn add_selectors(
421 &mut self,
422 selectors: impl IntoIterator<Item = Selector>,
423 should_exclude: bool,
424 ) -> eyre::Result<()> {
425 for selector in selectors {
426 if should_exclude {
427 self.excluded_functions.push(self.get_function(selector)?.clone());
428 } else {
429 self.targeted_functions.push(self.get_function(selector)?.clone());
430 }
431 }
432 self.rebuild_function_lookups();
433 Ok(())
434 }
435}
436
437#[derive(Clone, Debug, Default)]
439pub struct TargetedContractEvents {
440 by_topic: HashMap<(B256, usize), Vec<TargetedContractEvent>>,
441 anonymous: Vec<TargetedContractEvent>,
442}
443
444impl TargetedContractEvents {
445 fn new(abi: &JsonAbi) -> Self {
446 let mut events = Self::default();
447 for (order, event) in abi.events().enumerate() {
448 let event = TargetedContractEvent { order, event: event.clone() };
449 if event.event.anonymous {
450 events.anonymous.push(event);
451 } else {
452 let indexed_count = event.event.inputs.iter().filter(|input| input.indexed).count();
453 events
454 .by_topic
455 .entry((event.event.selector(), indexed_count))
456 .or_default()
457 .push(event);
458 }
459 }
460 events
461 }
462
463 pub fn by_topic(
464 &self,
465 selector: &B256,
466 indexed_count: usize,
467 ) -> Option<&[TargetedContractEvent]> {
468 self.by_topic.get(&(*selector, indexed_count)).map(Vec::as_slice)
469 }
470
471 pub fn anonymous(&self) -> &[TargetedContractEvent] {
472 &self.anonymous
473 }
474}
475
476#[derive(Clone, Debug)]
478pub struct TargetedContractEvent {
479 event: Event,
480 order: usize,
481}
482
483impl TargetedContractEvent {
484 pub const fn order(&self) -> usize {
485 self.order
486 }
487
488 pub const fn event(&self) -> &Event {
489 &self.event
490 }
491}
492
493#[derive(Clone, Debug)]
495pub struct InvariantContract<'a> {
496 pub address: Address,
498 pub name: &'a str,
500 pub invariant_fns: Vec<(&'a Function, bool)>,
504 pub anchor_idx: usize,
508 pub call_after_invariant: bool,
510 pub abi: &'a JsonAbi,
512}
513
514impl<'a> InvariantContract<'a> {
515 pub const fn new(
519 address: Address,
520 name: &'a str,
521 invariant_fns: Vec<(&'a Function, bool)>,
522 anchor_idx: usize,
523 call_after_invariant: bool,
524 abi: &'a JsonAbi,
525 ) -> Self {
526 Self { address, name, invariant_fns, anchor_idx, call_after_invariant, abi }
527 }
528
529 pub fn anchor(&self) -> &'a Function {
531 self.invariant_fns[self.anchor_idx].0
532 }
533
534 pub fn is_optimization(&self) -> bool {
536 is_optimization_invariant(self.anchor())
537 }
538}
539
540#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
546pub struct InvariantSettings {
547 pub target_contracts: BTreeMap<Address, String>,
549 pub target_selectors: BTreeMap<Address, Vec<Selector>>,
551 pub target_senders: Vec<Address>,
553 pub excluded_senders: Vec<Address>,
555 pub fail_on_revert: bool,
557}
558
559impl InvariantSettings {
560 pub fn new(
562 targeted_contracts: &TargetedContracts,
563 sender_filters: &SenderFilters,
564 fail_on_revert: bool,
565 ) -> Self {
566 let mut target_contracts = BTreeMap::new();
567 let mut target_selectors = BTreeMap::new();
568 for (addr, contract) in &targeted_contracts.inner {
569 target_contracts.insert(*addr, contract.identifier.clone());
570 target_selectors
571 .insert(*addr, contract.abi_fuzzed_functions().map(|f| f.selector()).collect());
572 }
573
574 let mut target_senders = sender_filters.targeted.clone();
575 target_senders.sort_unstable();
576
577 let mut excluded_senders = sender_filters.excluded.clone();
578 excluded_senders.sort_unstable();
579
580 Self {
581 target_contracts,
582 target_selectors,
583 target_senders,
584 excluded_senders,
585 fail_on_revert,
586 }
587 }
588
589 pub fn diff(&self, other: &Self) -> Option<String> {
592 let mut changes = Vec::new();
593
594 if self.target_contracts != other.target_contracts {
595 let added: Vec<_> = other
596 .target_contracts
597 .iter()
598 .filter(|(addr, _)| !self.target_contracts.contains_key(*addr))
599 .map(|(_, name)| name.as_str())
600 .collect();
601 let removed: Vec<_> = self
602 .target_contracts
603 .iter()
604 .filter(|(addr, _)| !other.target_contracts.contains_key(*addr))
605 .map(|(_, name)| name.as_str())
606 .collect();
607
608 if !added.is_empty() {
609 changes.push(format!("added target contracts: {}", added.join(", ")));
610 }
611 if !removed.is_empty() {
612 changes.push(format!("removed target contracts: {}", removed.join(", ")));
613 }
614 }
615
616 if self.target_selectors != other.target_selectors {
617 changes.push("target selectors changed".to_string());
618 }
619
620 if self.target_senders != other.target_senders {
621 changes.push("target senders changed".to_string());
622 }
623
624 if self.excluded_senders != other.excluded_senders {
625 changes.push("excluded senders changed".to_string());
626 }
627
628 if self.fail_on_revert != other.fail_on_revert {
629 changes.push(format!(
630 "fail_on_revert changed from {} to {}",
631 self.fail_on_revert, other.fail_on_revert
632 ));
633 }
634
635 if changes.is_empty() { None } else { Some(changes.join(", ")) }
636 }
637}
638
639impl fmt::Display for InvariantSettings {
640 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
641 write!(
642 f,
643 "targets: {}, selectors: {}, senders: {}, excluded: {}, fail_on_revert: {}",
644 self.target_contracts.len(),
645 self.target_selectors.values().map(|v| v.len()).sum::<usize>(),
646 self.target_senders.len(),
647 self.excluded_senders.len(),
648 self.fail_on_revert,
649 )
650 }
651}
652
653pub fn is_optimization_invariant(func: &Function) -> bool {
656 func.outputs.len() == 1 && func.outputs[0].ty == "int256"
657}
658
659#[cfg(test)]
660mod tests {
661 use super::*;
662 use crate::CallDetails;
663 use alloy_primitives::{Bytes, U256};
664 use foundry_compilers::{
665 ArtifactId,
666 artifacts::{
667 BytecodeObject, CompactBytecode, CompactContractBytecode, CompactDeployedBytecode,
668 },
669 };
670 use revm::{bytecode::Bytecode, state::Account};
671
672 fn abi_with_functions(functions: &[&str]) -> JsonAbi {
673 let mut abi = JsonAbi::new();
674 for function in functions {
675 let function = Function::parse(function).unwrap();
676 abi.functions.entry(function.name.clone()).or_default().push(function);
677 }
678 abi
679 }
680
681 fn targeted_contracts_with_functions(target: Address, functions: &[&str]) -> TargetedContracts {
682 let mut targets = TargetedContracts::new();
683 targets.inner.insert(
684 target,
685 TargetedContract::new("Target".to_string(), abi_with_functions(functions)),
686 );
687 targets
688 }
689
690 fn targeted_contracts_with_function(target: Address, function: Function) -> TargetedContracts {
691 let mut abi = JsonAbi::new();
692 abi.functions.entry(function.name.clone()).or_default().push(function);
693 let mut targets = TargetedContracts::new();
694 targets.inner.insert(target, TargetedContract::new("Target".to_string(), abi));
695 targets
696 }
697
698 fn tx(target: Address, calldata: impl Into<Bytes>) -> BasicTxDetails {
699 BasicTxDetails {
700 warp: None,
701 roll: None,
702 sender: Address::ZERO,
703 call_details: CallDetails { target, calldata: calldata.into(), value: None },
704 }
705 }
706
707 fn artifact_id(name: &str) -> ArtifactId {
708 ArtifactId {
709 path: format!("{name}.json").into(),
710 name: name.to_string(),
711 source: format!("{name}.sol").into(),
712 version: "0.8.30".parse().unwrap(),
713 build_id: "test".to_string(),
714 profile: "test".to_string(),
715 }
716 }
717
718 fn project_contracts_with_runtime_code_and_abi(
719 name: &str,
720 code: Bytes,
721 abi: JsonAbi,
722 ) -> ContractsByArtifact {
723 let deployed_bytecode = CompactDeployedBytecode {
724 bytecode: Some(CompactBytecode {
725 object: BytecodeObject::Bytecode(code),
726 source_map: None,
727 link_references: Default::default(),
728 }),
729 immutable_references: Default::default(),
730 };
731 let artifact = CompactContractBytecode {
732 abi: Some(abi),
733 bytecode: None,
734 deployed_bytecode: Some(deployed_bytecode),
735 };
736 ContractsByArtifact::new([(artifact_id(name), artifact)])
737 }
738
739 fn touched_account_with_code(code: Bytes) -> Account {
740 let mut account = Account::default();
741 account.info.balance = U256::ZERO;
742 account.info.code = Some(Bytecode::new_raw(code));
743 account.mark_touch();
744 account
745 }
746
747 #[test]
748 fn targeted_contracts_short_calldata_is_not_replayable_or_decodable() {
749 let target = Address::from([0x42; 20]);
750 let targets = targeted_contracts_with_function(target, Function::parse("foo()").unwrap());
751 let tx = tx(target, vec![0xde, 0xad, 0xbe]);
752
753 assert!(!targets.can_replay(&tx));
754 assert!(targets.fuzzed_artifacts(&tx).1.is_none());
755 assert!(targets.fuzzed_metric_key(&tx).is_none());
756 }
757
758 #[test]
759 fn abi_fuzzed_functions_filters_excluded_targeted_functions() {
760 let allowed = Function::parse("allowed()").unwrap();
761 let excluded = Function::parse("excluded()").unwrap();
762 let mut contract = TargetedContract::new("Target".to_string(), JsonAbi::new());
763 contract.targeted_functions = vec![allowed.clone(), excluded.clone()];
764 contract.excluded_functions = vec![excluded];
765
766 let selectors = contract.abi_fuzzed_functions().map(Function::selector).collect::<Vec<_>>();
767
768 assert_eq!(selectors, vec![allowed.selector()]);
769 }
770
771 #[test]
772 fn targeted_contracts_refresh_selector_lookup_after_filters() {
773 let target = Address::from([0x42; 20]);
774 let foo = Function::parse("foo()").unwrap();
775 let bar = Function::parse("bar()").unwrap();
776
777 let mut excluded = targeted_contracts_with_functions(target, &["foo()", "bar()"]);
778 excluded.inner.get_mut(&target).unwrap().add_selectors([foo.selector()], true).unwrap();
779 assert!(!excluded.can_replay(&tx(target, foo.selector().to_vec())));
780 assert!(excluded.can_replay(&tx(target, bar.selector().to_vec())));
781 assert_eq!(
782 excluded.fuzzed_artifacts(&tx(target, foo.selector().to_vec())).1.unwrap().name,
783 "foo"
784 );
785
786 let mut targeted = targeted_contracts_with_functions(target, &["foo()", "bar()"]);
787 targeted.inner.get_mut(&target).unwrap().add_selectors([foo.selector()], false).unwrap();
788 assert!(targeted.can_replay(&tx(target, foo.selector().to_vec())));
789 assert!(!targeted.can_replay(&tx(target, bar.selector().to_vec())));
790 assert_eq!(
791 targeted.fuzzed_metric_key_for_selector(target, bar.selector()).unwrap(),
792 "Target.bar"
793 );
794 }
795
796 #[test]
797 fn fuzz_run_identified_contracts_cache_fuzzed_functions_in_target_order() {
798 let first = Address::from([0x01; 20]);
799 let second = Address::from([0x02; 20]);
800 let mut targets = targeted_contracts_with_functions(second, &["bar()", "baz(uint256)"]);
801 targets.inner.insert(
802 first,
803 TargetedContract::new(
804 "First".to_string(),
805 abi_with_functions(&["foo()", "qux(address)"]),
806 ),
807 );
808 let expected = targets
809 .fuzzed_functions()
810 .map(|(address, function)| (*address, function.selector()))
811 .collect::<Vec<_>>();
812
813 let identified = FuzzRunIdentifiedContracts::new(targets, true);
814 let actual = identified
815 .fuzzed_functions()
816 .iter()
817 .map(|(address, function)| (*address, function.selector()))
818 .collect::<Vec<_>>();
819
820 assert_eq!(actual, expected);
821 }
822
823 #[test]
824 fn collect_created_contracts_caches_deployed_code_matches() {
825 let existing = Address::from([0x42; 20]);
826 let created = Address::from([0x43; 20]);
827 let setup = Address::from([0x44; 20]);
828 let untouched = Address::from([0x45; 20]);
829 let runtime_code = Bytes::from_static(&[0x60, 0x00, 0x56]);
830 let project_contracts = project_contracts_with_runtime_code_and_abi(
831 "DynamicTarget",
832 runtime_code.clone(),
833 JsonAbi::new(),
834 );
835 let mut targets = TargetedContracts::new();
836 for address in [existing, setup, untouched] {
837 targets.inner.insert(
838 address,
839 TargetedContract::new("AlreadyTargeted".to_string(), JsonAbi::new()),
840 );
841 }
842 let identified = FuzzRunIdentifiedContracts::new(targets, true);
843
844 let mut state_changeset = StateChangeset::default();
845 state_changeset.insert(existing, touched_account_with_code(runtime_code.clone()));
846 state_changeset.insert(setup, touched_account_with_code(runtime_code.clone()));
847 state_changeset.insert(untouched, Account::default());
848 state_changeset.insert(created, touched_account_with_code(runtime_code));
849 let mut created_contracts = Vec::new();
850 let setup_contracts =
851 ContractsByAddress::from([(setup, ("Setup".to_string(), JsonAbi::new()))]);
852
853 identified
854 .collect_created_contracts(
855 &state_changeset,
856 &project_contracts,
857 &setup_contracts,
858 &ArtifactFilters::default(),
859 &mut created_contracts,
860 )
861 .unwrap();
862
863 created_contracts.sort_unstable();
864 assert_eq!(created_contracts, vec![existing, created]);
865 let targets = identified.targets();
866 assert_eq!(targets[&existing].identifier, "DynamicTarget");
867 assert_eq!(targets[&created].identifier, "DynamicTarget");
868 assert_eq!(targets[&setup].identifier, "AlreadyTargeted");
869 assert_eq!(targets[&untouched].identifier, "AlreadyTargeted");
870 drop(targets);
871
872 identified
873 .collect_created_contracts(
874 &state_changeset,
875 &Default::default(),
876 &setup_contracts,
877 &ArtifactFilters::default(),
878 &mut created_contracts,
879 )
880 .unwrap();
881
882 created_contracts.sort_unstable();
883 assert_eq!(created_contracts, vec![existing, existing, created, created]);
884 }
885
886 #[test]
887 fn collect_and_clear_created_contracts_refresh_fuzzed_function_cache() {
888 let existing = Address::from([0x42; 20]);
889 let created = Address::from([0x43; 20]);
890 let runtime_code = Bytes::from_static(&[0x60, 0x00, 0x56]);
891 let project_contracts = project_contracts_with_runtime_code_and_abi(
892 "DynamicTarget",
893 runtime_code.clone(),
894 abi_with_functions(&["dynamic(uint256)"]),
895 );
896 let identified = FuzzRunIdentifiedContracts::new(
897 targeted_contracts_with_functions(existing, &["existing()"]),
898 true,
899 );
900
901 let initial = identified
902 .fuzzed_functions()
903 .iter()
904 .map(|(address, function)| (*address, function.selector()))
905 .collect::<Vec<_>>();
906 assert_eq!(initial, vec![(existing, Function::parse("existing()").unwrap().selector())]);
907 assert_eq!(identified.fuzzed_functions_generation(), 0);
908
909 let mut state_changeset = StateChangeset::default();
910 state_changeset.insert(created, touched_account_with_code(runtime_code));
911 let mut created_contracts = Vec::new();
912
913 identified
914 .collect_created_contracts(
915 &state_changeset,
916 &project_contracts,
917 &ContractsByAddress::default(),
918 &ArtifactFilters::default(),
919 &mut created_contracts,
920 )
921 .unwrap();
922
923 let with_created = identified
924 .fuzzed_functions()
925 .iter()
926 .map(|(address, function)| (*address, function.selector()))
927 .collect::<Vec<_>>();
928 assert_eq!(identified.fuzzed_functions_generation(), 1);
929 assert_eq!(
930 with_created,
931 vec![
932 (existing, Function::parse("existing()").unwrap().selector()),
933 (created, Function::parse("dynamic(uint256)").unwrap().selector()),
934 ]
935 );
936
937 identified.clear_created_contracts(created_contracts);
938 let cleared = identified
939 .fuzzed_functions()
940 .iter()
941 .map(|(address, function)| (*address, function.selector()))
942 .collect::<Vec<_>>();
943 assert_eq!(cleared, initial);
944 assert_eq!(identified.fuzzed_functions_generation(), 2);
945 }
946}