1use crate::{
2 BasicTxDetails, Fuzzer,
3 invariant::{
4 FuzzRunIdentifiedContracts, TargetedContract, TargetedContractEvent, TargetedContracts,
5 },
6 strategies::literals::LiteralsDictionary,
7};
8use alloy_dyn_abi::{DynSolType, DynSolValue, EventExt, FunctionExt};
9use alloy_json_abi::Function;
10use alloy_primitives::{
11 Address, B256, Bytes, Log, U256,
12 map::{AddressIndexSet, AddressMap, B256IndexSet, HashMap, HashSet, IndexSet},
13};
14use foundry_common::{
15 ignore_metadata_hash,
16 mapping_slots::MappingSlots,
17 slot_identifier::{SlotIdentifier, SlotInfo},
18};
19use foundry_config::FuzzDictionaryConfig;
20use foundry_evm_core::{
21 bytecode::InstIter, eip2935::is_history_storage_address, utils::StateChangeset,
22};
23use revm::{
24 database::{CacheDB, DatabaseRef, DbAccount},
25 state::AccountInfo,
26};
27use std::{cell::RefCell, fmt, rc::Rc, sync::Arc};
28
29const PUSH_BYTE_ANALYSIS_LIMIT: usize = 24 * 1024;
34
35#[derive(Clone, Debug)]
37pub struct EvmFuzzState {
38 inner: Arc<FuzzDictionary>,
39 pub deployed_libs: Vec<Address>,
41}
42
43#[derive(Clone, Debug)]
49pub struct FuzzState {
50 inner: FuzzStateInner,
51 pub deployed_libs: Vec<Address>,
53}
54
55#[derive(Clone, Debug)]
56enum FuzzStateInner {
57 Stateless(Arc<FuzzDictionary>),
58 Invariant(Rc<RefCell<FuzzDictionary>>),
59}
60
61pub(crate) trait DictionaryRead: Clone + 'static {
62 fn deployed_libs(&self) -> &[Address];
63 fn with_dictionary<R>(&self, f: impl FnOnce(&FuzzDictionary) -> R) -> R;
64}
65
66impl EvmFuzzState {
67 #[cfg(test)]
68 pub(crate) fn test() -> Self {
69 Self::new(
70 &[],
71 &CacheDB::<revm::database::EmptyDB>::default(),
72 FuzzDictionaryConfig::default(),
73 None,
74 )
75 }
76
77 pub fn new<DB: DatabaseRef>(
78 deployed_libs: &[Address],
79 db: &CacheDB<DB>,
80 config: FuzzDictionaryConfig,
81 literals: Option<&LiteralsDictionary>,
82 ) -> Self {
83 let mut accs = db.cache.accounts.iter().collect::<Vec<_>>();
85 accs.sort_by_key(|(address, _)| *address);
86
87 let mut dictionary = FuzzDictionary::new(config);
89 dictionary.insert_db_values(accs);
90 if let Some(literals) = literals {
91 dictionary.literal_values = literals.clone();
92 }
93
94 Self { inner: Arc::new(dictionary), deployed_libs: deployed_libs.to_vec() }
95 }
96
97 pub fn stateless_worker(&self) -> FuzzState {
98 FuzzState {
99 inner: FuzzStateInner::Stateless(Arc::clone(&self.inner)),
100 deployed_libs: self.deployed_libs.clone(),
101 }
102 }
103
104 pub fn into_invariant(self) -> FuzzState {
105 FuzzState {
106 inner: FuzzStateInner::Invariant(Rc::new(RefCell::new((*self.inner).clone()))),
107 deployed_libs: self.deployed_libs,
108 }
109 }
110
111 pub fn fork(&self) -> Self {
112 Self { inner: Arc::clone(&self.inner), deployed_libs: self.deployed_libs.clone() }
113 }
114
115 pub fn collect_values(&mut self, values: impl IntoIterator<Item = B256>) {
116 let dict = Arc::make_mut(&mut self.inner);
117 for value in values {
118 dict.insert_value(value);
119 }
120 }
121
122 pub fn log_stats(&self) {
124 self.inner.log_stats();
125 }
126
127 #[cfg(test)]
129 pub(crate) fn seed_literals(&mut self, map: super::LiteralMaps) {
130 Arc::make_mut(&mut self.inner).seed_literals(map);
131 }
132}
133
134impl FuzzState {
135 pub fn snapshot(&self) -> EvmFuzzState {
136 EvmFuzzState {
137 inner: Arc::new(self.with_dictionary(Clone::clone)),
138 deployed_libs: self.deployed_libs.clone(),
139 }
140 }
141
142 pub fn collect_values(&self, values: impl IntoIterator<Item = B256>) {
143 let FuzzStateInner::Invariant(inner) = &self.inner else { return };
144 let mut dict = inner.borrow_mut();
145 for value in values {
146 dict.insert_value(value);
147 }
148 }
149
150 pub fn collect_fuzzer_values(&self, fuzzer: &mut Fuzzer) {
151 if fuzzer.collected_values.is_empty() {
152 return;
153 }
154
155 let FuzzStateInner::Invariant(inner) = &self.inner else { return };
156 let mut dict = inner.borrow_mut();
157 for value in fuzzer.collected_values.drain(..) {
158 dict.insert_value(value);
159 }
160 }
161
162 #[allow(clippy::too_many_arguments)]
165 pub fn collect_values_from_call(
166 &self,
167 fuzzed_contracts: &FuzzRunIdentifiedContracts,
168 tx: &BasicTxDetails,
169 result: &Bytes,
170 logs: &[Log],
171 state_changeset: &StateChangeset,
172 run_depth: u32,
173 mapping_slots: Option<&AddressMap<MappingSlots>>,
174 ) {
175 if logs.is_empty() && result.is_empty() && state_changeset.is_empty() {
176 return;
177 }
178
179 let FuzzStateInner::Invariant(inner) = &self.inner else { return };
180 let mut dict = inner.borrow_mut();
181 let targets = fuzzed_contracts.targets();
182 let (target_contract, target_function) = if logs.is_empty() && result.is_empty() {
183 (None, None)
184 } else {
185 targets.fuzzed_artifacts(tx)
186 };
187 if !logs.is_empty() {
188 dict.insert_logs_values(target_contract, logs, run_depth);
189 }
190 if !result.is_empty() {
191 dict.insert_result_values(target_function, result, run_depth);
192 }
193 dict.insert_new_state_values(state_changeset, &targets, mapping_slots);
194 }
195
196 pub fn collect_typed_cmp_values(&self, values: impl IntoIterator<Item = (u8, B256)>) {
200 let FuzzStateInner::Invariant(inner) = &self.inner else { return };
201 let mut dict = inner.borrow_mut();
202 for (width, value) in values {
203 dict.insert_persistent_value(value);
204 dict.insert_typed_cmp_value(width, value);
205 }
206 }
207
208 pub fn revert(&self) {
213 if let FuzzStateInner::Invariant(inner) = &self.inner {
214 inner.borrow_mut().revert();
215 }
216 }
217
218 pub fn log_stats(&self) {
220 self.with_dictionary(FuzzDictionary::log_stats);
221 }
222
223 pub fn deployed_libs(&self) -> &[Address] {
224 &self.deployed_libs
225 }
226
227 pub fn with_dictionary<R>(&self, f: impl FnOnce(&FuzzDictionary) -> R) -> R {
228 match &self.inner {
229 FuzzStateInner::Stateless(inner) => f(inner),
230 FuzzStateInner::Invariant(inner) => f(&inner.borrow()),
231 }
232 }
233}
234
235impl DictionaryRead for FuzzState {
236 fn deployed_libs(&self) -> &[Address] {
237 self.deployed_libs()
238 }
239
240 fn with_dictionary<R>(&self, f: impl FnOnce(&FuzzDictionary) -> R) -> R {
241 self.with_dictionary(f)
242 }
243}
244
245impl DictionaryRead for EvmFuzzState {
246 fn deployed_libs(&self) -> &[Address] {
247 &self.deployed_libs
248 }
249
250 fn with_dictionary<R>(&self, f: impl FnOnce(&FuzzDictionary) -> R) -> R {
251 f(&self.inner)
252 }
253}
254
255impl From<EvmFuzzState> for FuzzState {
256 fn from(state: EvmFuzzState) -> Self {
257 state.into_invariant()
258 }
259}
260
261const MAX_PERSISTENT_VALUES: usize = 2048;
265const MAX_SLOT_INFO_CACHE_ENTRIES: usize = 4096;
267
268#[derive(Clone)]
269pub struct FuzzDictionary {
270 state_values: B256IndexSet,
272 addresses: AddressIndexSet,
274 push_bytecode_hashes: B256IndexSet,
276 config: FuzzDictionaryConfig,
278 db_state_values: usize,
281 db_addresses: usize,
284 db_push_bytecode_hashes: usize,
287 sample_values: HashMap<DynSolType, B256IndexSet>,
290 literal_values: LiteralsDictionary,
292 samples_seeded: bool,
297 persistent_values: B256IndexSet,
299 slot_identifiers: HashMap<usize, SlotIdentifier>,
301 slot_info_cache: HashMap<(usize, B256), Option<SlotInfo>>,
303
304 misses: usize,
305 hits: usize,
306}
307
308impl fmt::Debug for FuzzDictionary {
309 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
310 f.debug_struct("FuzzDictionary")
311 .field("state_values", &self.state_values.len())
312 .field("addresses", &self.addresses)
313 .field("persistent_values", &self.persistent_values.len())
314 .finish()
315 }
316}
317
318impl Default for FuzzDictionary {
319 fn default() -> Self {
320 Self::new(Default::default())
321 }
322}
323
324impl FuzzDictionary {
325 pub fn new(mut config: FuzzDictionaryConfig) -> Self {
326 config.max_fuzz_dictionary_values = config.max_fuzz_dictionary_values.max(1);
327 let mut dictionary = Self {
328 config,
329 samples_seeded: false,
330
331 state_values: Default::default(),
332 addresses: Default::default(),
333 push_bytecode_hashes: Default::default(),
334 db_state_values: Default::default(),
335 db_addresses: Default::default(),
336 db_push_bytecode_hashes: Default::default(),
337 sample_values: Default::default(),
338 literal_values: Default::default(),
339 persistent_values: Default::default(),
340 slot_identifiers: Default::default(),
341 slot_info_cache: Default::default(),
342 misses: Default::default(),
343 hits: Default::default(),
344 };
345 dictionary.insert_value(B256::ZERO);
347 dictionary
348 }
349
350 #[cold]
353 fn seed_samples(&mut self) {
354 trace!("seeding `sample_values` from literal dictionary");
355 self.sample_values
356 .extend(self.literal_values.get().words.iter().map(|(k, v)| (k.clone(), v.clone())));
357 self.samples_seeded = true;
358 }
359
360 fn insert_db_values(&mut self, db_state: Vec<(&Address, &DbAccount)>) {
363 for (address, account) in db_state {
364 if is_history_storage_address(address) {
365 continue;
366 }
367
368 self.insert_value(address.into_word());
370 self.insert_push_bytes_values(address, &account.info);
372 if self.config.include_storage {
374 let mut values = account.storage.iter().collect::<Vec<_>>();
376 values.sort_unstable_by_key(|(slot, _)| **slot);
377 for (slot, value) in values {
378 self.insert_storage_value(slot, value, None);
379 }
380 }
381 }
382
383 if self.values().is_empty() {
386 self.insert_value(Address::random().into_word());
388 }
389
390 self.db_state_values = self.state_values.len();
393 self.db_addresses = self.addresses.len();
394 self.db_push_bytecode_hashes = self.push_bytecode_hashes.len();
395 }
396
397 fn insert_result_values(
399 &mut self,
400 function: Option<&Function>,
401 result: &Bytes,
402 run_depth: u32,
403 ) {
404 if let Some(function) = function
405 && !function.outputs.is_empty()
406 {
407 if let Ok(decoded_result) = function.abi_decode_output(result) {
409 self.insert_sample_values(decoded_result, run_depth);
410 }
411 }
412 }
413
414 fn insert_logs_values(
416 &mut self,
417 target_contract: Option<&TargetedContract>,
418 logs: &[Log],
419 run_depth: u32,
420 ) {
421 let mut samples = Vec::new();
422 for log in logs {
424 let log_decoded = if let Some(contract) = target_contract {
426 let matched_events = log
427 .topics()
428 .first()
429 .and_then(|selector| {
430 contract.event_lookup.by_topic(selector, log.topics().len() - 1)
431 })
432 .unwrap_or(&[]);
433 Self::decode_log_events(
434 matched_events,
435 contract.event_lookup.anonymous(),
436 log,
437 &mut samples,
438 )
439 } else {
440 false
441 };
442
443 if !log_decoded {
445 for &topic in log.topics() {
446 self.insert_value(topic);
447 }
448 let (chunks, rem) = log.data.data.as_chunks::<32>();
449 for chunk in chunks {
450 self.insert_value((*chunk).into());
451 }
452 if !rem.is_empty() {
453 self.insert_value(B256::right_padding_from(rem));
454 }
455 }
456 }
457
458 if !samples.is_empty() {
460 self.insert_sample_values(samples, run_depth);
461 }
462 }
463
464 fn decode_log_events(
465 matched_events: &[TargetedContractEvent],
466 anonymous_events: &[TargetedContractEvent],
467 log: &Log,
468 samples: &mut Vec<DynSolValue>,
469 ) -> bool {
470 let mut matched = matched_events.iter().peekable();
471 let mut anonymous = anonymous_events.iter().peekable();
472 while matched.peek().is_some() || anonymous.peek().is_some() {
473 let event = match (matched.peek(), anonymous.peek()) {
474 (Some(matched_event), Some(anonymous_event)) => {
475 if matched_event.order() < anonymous_event.order() {
476 matched.next().unwrap()
477 } else {
478 anonymous.next().unwrap()
479 }
480 }
481 (Some(_), None) => matched.next().unwrap(),
482 (None, Some(_)) => anonymous.next().unwrap(),
483 (None, None) => unreachable!(),
484 };
485 if let Ok(decoded_event) = event.event().decode_log(log) {
486 samples.extend(decoded_event.indexed);
487 samples.extend(decoded_event.body);
488 return true;
489 }
490 }
491 false
492 }
493
494 fn insert_new_state_values(
497 &mut self,
498 state_changeset: &StateChangeset,
499 targets: &TargetedContracts,
500 mapping_slots: Option<&AddressMap<MappingSlots>>,
501 ) {
502 for (address, account) in state_changeset {
503 if is_history_storage_address(address) {
504 continue;
505 }
506
507 self.insert_value(address.into_word());
509 self.insert_push_bytes_values(address, &account.info);
511 if self.config.include_storage && !account.storage.is_empty() {
513 let slot_identifier_key = targets.get(address).and_then(|contract| {
514 contract.storage_layout.as_ref().map(|layout| {
515 let key = Arc::as_ptr(layout) as usize;
516 self.slot_identifiers
517 .entry(key)
518 .or_insert_with(|| SlotIdentifier::new(Arc::clone(layout)));
519 key
520 })
521 });
522 trace!(
523 "{address:?} has mapping_slots {}",
524 mapping_slots.is_some_and(|m| m.contains_key(address))
525 );
526 let mapping_slots = mapping_slots.and_then(|m| m.get(address));
527 for (slot, value) in &account.storage {
528 let slot_info = slot_identifier_key.and_then(|key| {
529 let slot = B256::from(*slot);
530 let value_word = B256::from(value.present_value);
531 self.identify_storage_slot(key, slot, mapping_slots)
532 .filter(|slot_info| slot_info.decode(value_word).is_some())
533 });
534 self.insert_storage_value(slot, &value.present_value, slot_info);
535 }
536 }
537 }
538 }
539
540 fn identify_storage_slot(
541 &mut self,
542 key: usize,
543 slot: B256,
544 mapping_slots: Option<&MappingSlots>,
545 ) -> Option<SlotInfo> {
546 if mapping_slots.is_some() {
547 return self
548 .slot_identifiers
549 .get(&key)
550 .and_then(|identifier| identifier.identify(&slot, mapping_slots));
551 }
552
553 let cache_key = (key, slot);
554 if let Some(slot_info) = self.slot_info_cache.get(&cache_key) {
555 return slot_info.clone();
556 }
557
558 let slot_info =
559 self.slot_identifiers.get(&key).and_then(|identifier| identifier.identify(&slot, None));
560 if self.slot_info_cache.len() < MAX_SLOT_INFO_CACHE_ENTRIES {
561 self.slot_info_cache.insert(cache_key, slot_info.clone());
562 }
563 slot_info
564 }
565
566 fn insert_push_bytes_values(&mut self, address: &Address, account_info: &AccountInfo) {
570 if !self.config.include_push_bytes {
571 return;
572 }
573
574 let Some(code) = &account_info.code else {
575 return;
576 };
577 self.insert_address(*address);
578 if self.values_full() {
579 return;
580 }
581 if self.push_bytecode_hashes.insert(account_info.code_hash) {
582 self.collect_push_bytes(ignore_metadata_hash(code.original_byte_slice()));
583 }
584 }
585
586 fn collect_push_bytes(&mut self, code: &[u8]) {
587 let len = code.len().min(PUSH_BYTE_ANALYSIS_LIMIT);
588 let code = &code[..len];
589 let mut seen = HashSet::default();
590 for inst in InstIter::new(code) {
591 if self.values_full() {
592 break;
593 }
594 if !inst.immediate.is_empty()
596 && let Some(push_value) = U256::try_from_be_slice(inst.immediate)
597 && push_value != U256::ZERO
598 {
599 self.insert_push_value_u256(push_value, &mut seen);
600 }
601 }
602 }
603
604 fn insert_storage_value(&mut self, slot: &U256, value: &U256, slot_info: Option<SlotInfo>) {
607 let slot = B256::from(*slot);
608 let value_word = B256::from(*value);
609
610 self.insert_value(slot);
612
613 if let Some(slot_info) = slot_info {
614 trace!(?slot_info, "inserting typed storage value");
615 if !self.samples_seeded {
616 self.seed_samples();
617 }
618 self.sample_values
619 .entry(slot_info.slot_type.dyn_sol_type)
620 .or_default()
621 .insert(value_word);
622 } else {
623 self.insert_value_u256(*value);
624 }
625 }
626
627 fn insert_address(&mut self, address: Address) {
630 if self.addresses.len() < self.config.max_fuzz_dictionary_addresses {
631 self.addresses.insert(address);
632 }
633 }
634
635 fn insert_value(&mut self, value: B256) -> bool {
641 let insert = !self.values_full();
642 if insert {
643 let new_value = self.state_values.insert(value);
644 let counter = if new_value { &mut self.misses } else { &mut self.hits };
645 *counter += 1;
646 }
647 insert
648 }
649
650 fn insert_persistent_value(&mut self, value: B256) {
653 if self.persistent_values.len() >= MAX_PERSISTENT_VALUES {
654 return;
655 }
656 if !self.persistent_values.insert(value) {
657 return;
658 }
659 match self.state_values.get_index_of(&value) {
663 Some(index) if index < self.db_state_values => {}
665 Some(index) => {
667 self.state_values.move_index(index, self.db_state_values);
668 self.db_state_values += 1;
669 }
670 None => {
671 self.state_values.shift_insert(self.db_state_values, value);
672 self.db_state_values += 1;
673 }
674 }
675 }
676
677 fn insert_typed_cmp_value(&mut self, width: u8, value: B256) {
680 if !self.samples_seeded {
681 self.seed_samples();
682 }
683
684 const MAX_TYPED_CMP_PER_BUCKET: usize = 1024;
685
686 let native_type = match width {
687 8 => DynSolType::Uint(8),
688 16 => DynSolType::Uint(16),
689 32 => DynSolType::Uint(32),
690 64 => DynSolType::Uint(64),
691 _ => DynSolType::Uint(256),
692 };
693
694 let insert = |map: &mut HashMap<DynSolType, B256IndexSet>, ty: DynSolType, val: B256| {
695 let bucket = map.entry(ty).or_default();
696 if bucket.len() < MAX_TYPED_CMP_PER_BUCKET {
697 bucket.insert(val);
698 }
699 };
700
701 insert(&mut self.sample_values, native_type, value);
702
703 if width <= 64 {
704 insert(&mut self.sample_values, DynSolType::Uint(128), value);
705 insert(&mut self.sample_values, DynSolType::Uint(256), value);
706 insert(&mut self.sample_values, DynSolType::Int(256), value);
707 }
708 }
709
710 fn insert_value_u256(&mut self, value: U256) -> bool {
711 let one = U256::from(1);
713 let mut inserted = self.insert_value(value.into());
714 if !self.values_full() {
715 inserted |= self.insert_value((value.wrapping_sub(one)).into());
716 }
717 if !self.values_full() {
718 inserted |= self.insert_value((value.wrapping_add(one)).into());
719 }
720 inserted
721 }
722
723 fn insert_push_value_u256(&mut self, value: U256, seen: &mut HashSet<B256>) -> bool {
724 let one = U256::from(1);
726 let mut inserted = false;
727 for value in [value, value.wrapping_sub(one), value.wrapping_add(one)] {
728 if self.values_full() {
729 break;
730 }
731 let value = value.into();
732 if seen.insert(value) {
733 inserted |= self.insert_value(value);
734 }
735 }
736 inserted
737 }
738
739 fn values_full(&self) -> bool {
740 self.state_values.len() >= self.config.max_fuzz_dictionary_values
741 }
742
743 pub fn insert_sample_values(
747 &mut self,
748 sample_values: impl IntoIterator<Item = DynSolValue>,
749 limit: u32,
750 ) {
751 if !self.samples_seeded {
752 self.seed_samples();
753 }
754 for sample in sample_values {
755 if let (Some(sample_type), Some(sample_value)) = (sample.as_type(), sample.as_word()) {
756 if let Some(values) = self.sample_values.get_mut(&sample_type) {
757 if values.len() < limit as usize {
758 values.insert(sample_value);
759 } else {
760 self.insert_value(sample_value);
762 }
763 } else {
764 self.sample_values.entry(sample_type).or_default().insert(sample_value);
765 }
766 }
767 }
768 }
769
770 pub const fn values(&self) -> &B256IndexSet {
771 &self.state_values
772 }
773
774 pub fn len(&self) -> usize {
775 self.state_values.len()
776 }
777
778 pub fn is_empty(&self) -> bool {
779 self.state_values.is_empty()
780 }
781
782 #[inline]
787 pub fn samples(&self, param_type: &DynSolType) -> Option<&B256IndexSet> {
788 if !self.samples_seeded {
790 return self.literal_values.get().words.get(param_type);
791 }
792
793 self.sample_values.get(param_type)
794 }
795
796 #[inline]
798 pub fn ast_strings(&self) -> &IndexSet<String> {
799 &self.literal_values.get().strings
800 }
801
802 #[inline]
804 pub fn ast_bytes(&self) -> &IndexSet<Bytes> {
805 &self.literal_values.get().bytes
806 }
807
808 #[inline]
809 pub const fn addresses(&self) -> &AddressIndexSet {
810 &self.addresses
811 }
812
813 pub fn revert(&mut self) {
815 self.state_values.truncate(self.db_state_values);
816 self.addresses.truncate(self.db_addresses);
817 self.push_bytecode_hashes.truncate(self.db_push_bytecode_hashes);
818 }
819
820 pub fn log_stats(&self) {
821 trace!(
822 addresses.len = self.addresses.len(),
823 sample.len = self.sample_values.len(),
824 state.len = self.state_values.len(),
825 state.misses = self.misses,
826 state.hits = self.hits,
827 "FuzzDictionary stats",
828 );
829 }
830
831 #[cfg(test)]
832 pub(crate) fn seed_literals(&mut self, map: super::LiteralMaps) {
834 self.literal_values.set(map);
835 }
836}
837
838#[cfg(test)]
839mod tests {
840 use super::*;
841 use alloy_json_abi::{Event, JsonAbi};
842 use alloy_primitives::keccak256;
843 use foundry_evm_core::eip2935::HISTORY_STORAGE_ADDRESS;
844 use revm::{bytecode::Bytecode, database::EmptyDB};
845
846 fn account_with_code(raw: &'static [u8]) -> AccountInfo {
847 let code = Bytecode::new_raw(Bytes::from_static(raw));
848 AccountInfo {
849 code_hash: keccak256(code.original_byte_slice()),
850 code: Some(code),
851 ..Default::default()
852 }
853 }
854
855 #[test]
856 fn log_decoding_preserves_anonymous_event_priority() {
857 let mut abi = JsonAbi::new();
858 let anonymous_event =
859 Event::parse("event AEvent(bytes32 indexed topic, uint256 value) anonymous").unwrap();
860 let matched_event = Event::parse("event ZEvent(uint256 value)").unwrap();
861 let selector = matched_event.selector();
862 abi.events.entry(anonymous_event.name.clone()).or_default().push(anonymous_event);
863 abi.events.entry(matched_event.name.clone()).or_default().push(matched_event);
864 let contract = TargetedContract::new("Target".to_string(), abi);
865 let matched_events = contract.event_lookup.by_topic(&selector, 0).unwrap();
866 let word: B256 = U256::from(42).into();
867 let log = Log::new_unchecked(
868 Address::ZERO,
869 vec![selector],
870 Bytes::copy_from_slice(word.as_slice()),
871 );
872 let mut samples = Vec::new();
873
874 assert!(FuzzDictionary::decode_log_events(
875 matched_events,
876 contract.event_lookup.anonymous(),
877 &log,
878 &mut samples,
879 ));
880
881 assert_eq!(samples.len(), 2);
882 assert_eq!(samples[0], DynSolValue::FixedBytes(selector, 32));
883 assert_eq!(samples[1], DynSolValue::Uint(U256::from(42), 256));
884 }
885
886 #[test]
887 fn push_byte_collection_stops_when_dictionary_is_full() {
888 let mut dictionary = FuzzDictionary::new(FuzzDictionaryConfig {
889 max_fuzz_dictionary_values: 3,
890 ..Default::default()
891 });
892
893 dictionary.collect_push_bytes(&[0x60, 0x01, 0x60, 0x03]);
894
895 assert_eq!(dictionary.state_values.len(), 3);
896 assert!(dictionary.state_values.contains(&B256::from(U256::ZERO)));
897 assert!(dictionary.state_values.contains(&B256::from(U256::from(1))));
898 assert!(dictionary.state_values.contains(&B256::from(U256::from(2))));
899 assert!(!dictionary.state_values.contains(&B256::from(U256::from(3))));
900 }
901
902 #[test]
903 fn zero_value_capacity_keeps_only_required_seed() {
904 let mut dictionary = FuzzDictionary::new(FuzzDictionaryConfig {
905 max_fuzz_dictionary_values: 0,
906 ..Default::default()
907 });
908
909 assert_eq!(dictionary.config.max_fuzz_dictionary_values, 1);
910 assert_eq!(dictionary.state_values.as_slice(), &[B256::ZERO]);
911 assert!(!dictionary.insert_value(B256::from(U256::from(1))));
912 assert_eq!(dictionary.state_values.as_slice(), &[B256::ZERO]);
913 }
914
915 #[test]
916 fn duplicate_push_values_in_same_bytecode_are_collected_once() {
917 let mut dictionary = FuzzDictionary::default();
918
919 dictionary.collect_push_bytes(&[0x60, 0x01, 0x60, 0x01]);
920
921 assert!(dictionary.state_values.contains(&B256::from(U256::ZERO)));
922 assert!(dictionary.state_values.contains(&B256::from(U256::from(1))));
923 assert!(dictionary.state_values.contains(&B256::from(U256::from(2))));
924 assert_eq!(dictionary.hits, 1);
925 }
926
927 #[test]
928 fn duplicate_bytecode_push_bytes_are_collected_once() {
929 let mut dictionary = FuzzDictionary::default();
930 let account = account_with_code(&[0x60, 0x01]);
931
932 dictionary.insert_push_bytes_values(&Address::repeat_byte(0x11), &account);
933 let hits_after_first_scan = dictionary.hits;
934
935 dictionary.insert_push_bytes_values(&Address::repeat_byte(0x22), &account);
936
937 assert_eq!(dictionary.push_bytecode_hashes.len(), 1);
938 assert_eq!(dictionary.addresses.len(), 2);
939 assert_eq!(dictionary.hits, hits_after_first_scan);
940 }
941
942 #[test]
943 fn same_address_with_new_bytecode_is_scanned_again() {
944 let mut dictionary = FuzzDictionary::default();
945 let address = Address::repeat_byte(0x22);
946
947 dictionary.insert_push_bytes_values(&address, &account_with_code(&[0x60, 0x01]));
948 dictionary.insert_push_bytes_values(&address, &account_with_code(&[0x60, 0x04]));
949
950 assert_eq!(dictionary.addresses.len(), 1);
951 assert_eq!(dictionary.push_bytecode_hashes.len(), 2);
952 assert!(dictionary.state_values.contains(&B256::from(U256::from(1))));
953 assert!(dictionary.state_values.contains(&B256::from(U256::from(4))));
954 }
955
956 #[test]
957 fn no_code_account_does_not_block_later_push_byte_scan() {
958 let mut dictionary = FuzzDictionary::default();
959 let address = Address::repeat_byte(0x33);
960 let account_without_code = AccountInfo { code: None, ..Default::default() };
961
962 dictionary.insert_push_bytes_values(&address, &account_without_code);
963
964 assert!(!dictionary.addresses.contains(&address));
965 assert_eq!(dictionary.push_bytecode_hashes.len(), 0);
966
967 dictionary.insert_push_bytes_values(&address, &account_with_code(&[0x60, 0x04]));
968
969 assert!(dictionary.addresses.contains(&address));
970 assert_eq!(dictionary.push_bytecode_hashes.len(), 1);
971 assert!(dictionary.state_values.contains(&B256::from(U256::from(4))));
972 }
973
974 #[test]
975 fn revert_removes_runtime_bytecode_scan_cache() {
976 let mut dictionary = FuzzDictionary::default();
977 dictionary.db_state_values = dictionary.state_values.len();
978 dictionary.db_addresses = dictionary.addresses.len();
979 dictionary.db_push_bytecode_hashes = dictionary.push_bytecode_hashes.len();
980
981 let account = account_with_code(&[0x60, 0x01]);
982 dictionary.insert_push_bytes_values(&Address::repeat_byte(0x11), &account);
983 assert_eq!(dictionary.push_bytecode_hashes.len(), 1);
984
985 dictionary.revert();
986 assert_eq!(dictionary.push_bytecode_hashes.len(), 0);
987
988 dictionary.insert_push_bytes_values(&Address::repeat_byte(0x22), &account);
989 assert_eq!(dictionary.push_bytecode_hashes.len(), 1);
990 assert!(dictionary.state_values.contains(&B256::from(U256::from(1))));
991 }
992
993 #[test]
994 fn persistent_value_survives_revert() {
995 let mut dictionary = FuzzDictionary::default();
996 dictionary.db_state_values = dictionary.state_values.len();
997
998 let ephemeral = B256::from(U256::from(0xbeef_u64));
999 let persistent = B256::from(U256::from(0xcafe_u64));
1000 dictionary.insert_value(ephemeral);
1001 dictionary.insert_persistent_value(persistent);
1002
1003 dictionary.revert();
1004
1005 assert!(dictionary.state_values.contains(&persistent));
1006 assert!(!dictionary.state_values.contains(&ephemeral));
1007 assert!(dictionary.db_state_values <= dictionary.state_values.len());
1008
1009 let watermark = dictionary.db_state_values;
1011 let len = dictionary.state_values.len();
1012 dictionary.insert_persistent_value(B256::ZERO);
1013 assert_eq!(dictionary.db_state_values, watermark);
1014 assert_eq!(dictionary.state_values.len(), len);
1015 }
1016
1017 #[test]
1018 fn persistent_value_promotes_existing_ephemeral() {
1019 let mut dictionary = FuzzDictionary::default();
1020 dictionary.db_state_values = dictionary.state_values.len();
1021
1022 let ephemeral = B256::from(U256::from(0xbeef_u64));
1023 let value = B256::from(U256::from(0xdead_u64));
1024 dictionary.insert_value(ephemeral);
1025 dictionary.insert_value(value);
1026 dictionary.insert_persistent_value(value);
1027
1028 dictionary.revert();
1029
1030 assert!(dictionary.state_values.contains(&value));
1031 assert!(!dictionary.state_values.contains(&ephemeral));
1032 assert!(dictionary.db_state_values <= dictionary.state_values.len());
1033 }
1034
1035 #[test]
1036 fn history_storage_account_is_excluded_from_initial_dictionary() {
1037 let mut db = CacheDB::<EmptyDB>::default();
1038 let code = Bytecode::new_raw(Bytes::from_static(&[0x61, 0x01, 0x23, 0x00]));
1039 db.insert_account_info(
1040 HISTORY_STORAGE_ADDRESS,
1041 AccountInfo {
1042 code_hash: keccak256(code.original_byte_slice()),
1043 code: Some(code),
1044 ..Default::default()
1045 },
1046 );
1047 db.insert_account_storage(HISTORY_STORAGE_ADDRESS, U256::from(7), U256::from(0xdead_u64))
1048 .unwrap();
1049
1050 let state = EvmFuzzState::new(&[], &db, FuzzDictionaryConfig::default(), None);
1051
1052 state.with_dictionary(|dict| {
1053 assert!(!dict.values().contains(&HISTORY_STORAGE_ADDRESS.into_word()));
1054 assert!(!dict.values().contains(&B256::from(U256::from(0x123))));
1055 assert!(!dict.values().contains(&B256::from(U256::from(7))));
1056 assert!(!dict.values().contains(&B256::from(U256::from(0xdead_u64))));
1057 });
1058 }
1059
1060 #[test]
1061 fn worker_modes_share_seed_but_isolate_feedback() {
1062 let seed = EvmFuzzState::test();
1063 let readonly = seed.stateless_worker();
1064 let first = seed.clone().into_invariant();
1065 let second = seed.into_invariant();
1066 let transient = B256::from(U256::from(0xdead_u64));
1067 let persistent = B256::from(U256::from(0xbeef_u64));
1068
1069 readonly.collect_values([transient]);
1070 assert!(!readonly.with_dictionary(|dict| dict.state_values.contains(&transient)));
1071
1072 first.collect_values([transient]);
1073 first.collect_typed_cmp_values([(8, persistent)]);
1074 assert!(first.with_dictionary(|dict| dict.state_values.contains(&transient)));
1075 assert!(!second.with_dictionary(|dict| dict.state_values.contains(&transient)));
1076
1077 first.revert();
1078 assert!(!first.with_dictionary(|dict| dict.state_values.contains(&transient)));
1079 first.with_dictionary(|dict| {
1080 assert!(dict.state_values.contains(&persistent));
1081 assert!(dict.persistent_values.contains(&persistent));
1082 assert!(dict.sample_values[&DynSolType::Uint(8)].contains(&persistent));
1083 });
1084 }
1085}