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