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 let mut accounts = state_changeset.iter().collect::<Vec<_>>();
502 accounts.sort_unstable_by_key(|(address, _)| **address);
503 for (address, account) in accounts {
504 if is_history_storage_address(address) {
505 continue;
506 }
507
508 self.insert_value(address.into_word());
510 self.insert_push_bytes_values(address, &account.info);
512 if self.config.include_storage && !account.storage.is_empty() {
514 let slot_identifier_key = targets.get(address).and_then(|contract| {
515 contract.storage_layout.as_ref().map(|layout| {
516 let key = Arc::as_ptr(layout) as usize;
517 self.slot_identifiers
518 .entry(key)
519 .or_insert_with(|| SlotIdentifier::new(Arc::clone(layout)));
520 key
521 })
522 });
523 trace!(
524 "{address:?} has mapping_slots {}",
525 mapping_slots.is_some_and(|m| m.contains_key(address))
526 );
527 let mapping_slots = mapping_slots.and_then(|m| m.get(address));
528 let mut storage = account.storage.iter().collect::<Vec<_>>();
530 storage.sort_unstable_by_key(|(slot, _)| **slot);
531 for (slot, value) in storage {
532 let slot_info = slot_identifier_key.and_then(|key| {
533 let slot = B256::from(*slot);
534 let value_word = B256::from(value.present_value());
535 self.identify_storage_slot(key, slot, mapping_slots)
536 .filter(|slot_info| slot_info.decode(value_word).is_some())
537 });
538 self.insert_storage_value(slot, &value.present_value(), slot_info);
539 }
540 }
541 }
542 }
543
544 fn identify_storage_slot(
545 &mut self,
546 key: usize,
547 slot: B256,
548 mapping_slots: Option<&MappingSlots>,
549 ) -> Option<SlotInfo> {
550 if mapping_slots.is_some() {
551 return self
552 .slot_identifiers
553 .get(&key)
554 .and_then(|identifier| identifier.identify(&slot, mapping_slots));
555 }
556
557 let cache_key = (key, slot);
558 if let Some(slot_info) = self.slot_info_cache.get(&cache_key) {
559 return slot_info.clone();
560 }
561
562 let slot_info =
563 self.slot_identifiers.get(&key).and_then(|identifier| identifier.identify(&slot, None));
564 if self.slot_info_cache.len() < MAX_SLOT_INFO_CACHE_ENTRIES {
565 self.slot_info_cache.insert(cache_key, slot_info.clone());
566 }
567 slot_info
568 }
569
570 fn insert_push_bytes_values(&mut self, address: &Address, account_info: &AccountInfo) {
574 if !self.config.include_push_bytes {
575 return;
576 }
577
578 let Some(code) = &account_info.code else {
579 return;
580 };
581 self.insert_address(*address);
582 if self.values_full() {
583 return;
584 }
585 if self.push_bytecode_hashes.insert(account_info.code_hash()) {
586 self.collect_push_bytes(ignore_metadata_hash(code.original_byte_slice()));
587 }
588 }
589
590 fn collect_push_bytes(&mut self, code: &[u8]) {
591 let len = code.len().min(PUSH_BYTE_ANALYSIS_LIMIT);
592 let code = &code[..len];
593 let mut seen = HashSet::default();
594 for inst in InstIter::new(code) {
595 if self.values_full() {
596 break;
597 }
598 if !inst.immediate.is_empty()
600 && let Some(push_value) = U256::try_from_be_slice(inst.immediate)
601 && !push_value.is_zero()
602 {
603 self.insert_push_value_u256(push_value, &mut seen);
604 }
605 }
606 }
607
608 fn insert_storage_value(&mut self, slot: &U256, value: &U256, slot_info: Option<SlotInfo>) {
611 let slot = B256::from(*slot);
612 let value_word = B256::from(*value);
613
614 self.insert_value(slot);
616
617 if let Some(slot_info) = slot_info {
618 trace!(?slot_info, "inserting typed storage value");
619 if !self.samples_seeded {
620 self.seed_samples();
621 }
622 self.sample_values
623 .entry(slot_info.slot_type.dyn_sol_type)
624 .or_default()
625 .insert(value_word);
626 } else {
627 self.insert_value_u256(*value);
628 }
629 }
630
631 fn insert_address(&mut self, address: Address) {
634 if self.addresses.len() < self.config.max_fuzz_dictionary_addresses {
635 self.addresses.insert(address);
636 }
637 }
638
639 fn insert_value(&mut self, value: B256) -> bool {
645 let insert = !self.values_full();
646 if insert {
647 let new_value = self.state_values.insert(value);
648 let counter = if new_value { &mut self.misses } else { &mut self.hits };
649 *counter += 1;
650 }
651 insert
652 }
653
654 fn insert_persistent_value(&mut self, value: B256) {
657 if self.persistent_values.len() >= MAX_PERSISTENT_VALUES {
658 return;
659 }
660 if !self.persistent_values.insert(value) {
661 return;
662 }
663 match self.state_values.get_index_of(&value) {
667 Some(index) if index < self.db_state_values => {}
669 Some(index) => {
671 self.state_values.move_index(index, self.db_state_values);
672 self.db_state_values += 1;
673 }
674 None => {
675 self.state_values.shift_insert(self.db_state_values, value);
676 self.db_state_values += 1;
677 }
678 }
679 }
680
681 fn insert_typed_cmp_value(&mut self, width: u8, value: B256) {
684 if !self.samples_seeded {
685 self.seed_samples();
686 }
687
688 const MAX_TYPED_CMP_PER_BUCKET: usize = 1024;
689
690 let native_type = match width {
691 8 => DynSolType::Uint(8),
692 16 => DynSolType::Uint(16),
693 32 => DynSolType::Uint(32),
694 64 => DynSolType::Uint(64),
695 _ => DynSolType::Uint(256),
696 };
697
698 let insert = |map: &mut HashMap<DynSolType, B256IndexSet>, ty: DynSolType, val: B256| {
699 let bucket = map.entry(ty).or_default();
700 if bucket.len() < MAX_TYPED_CMP_PER_BUCKET {
701 bucket.insert(val);
702 }
703 };
704
705 insert(&mut self.sample_values, native_type, value);
706
707 if width <= 64 {
708 insert(&mut self.sample_values, DynSolType::Uint(128), value);
709 insert(&mut self.sample_values, DynSolType::Uint(256), value);
710 insert(&mut self.sample_values, DynSolType::Int(256), value);
711 }
712 }
713
714 fn insert_value_u256(&mut self, value: U256) -> bool {
715 let one = U256::ONE;
717 let mut inserted = self.insert_value(value.into());
718 if !self.values_full() {
719 inserted |= self.insert_value((value.wrapping_sub(one)).into());
720 }
721 if !self.values_full() {
722 inserted |= self.insert_value((value.wrapping_add(one)).into());
723 }
724 inserted
725 }
726
727 fn insert_push_value_u256(&mut self, value: U256, seen: &mut HashSet<B256>) -> bool {
728 let one = U256::ONE;
730 let mut inserted = false;
731 for value in [value, value.wrapping_sub(one), value.wrapping_add(one)] {
732 if self.values_full() {
733 break;
734 }
735 let value = value.into();
736 if seen.insert(value) {
737 inserted |= self.insert_value(value);
738 }
739 }
740 inserted
741 }
742
743 fn values_full(&self) -> bool {
744 self.state_values.len() >= self.config.max_fuzz_dictionary_values
745 }
746
747 pub fn insert_sample_values(
751 &mut self,
752 sample_values: impl IntoIterator<Item = DynSolValue>,
753 limit: u32,
754 ) {
755 if !self.samples_seeded {
756 self.seed_samples();
757 }
758 for sample in sample_values {
759 if let (Some(sample_type), Some(sample_value)) = (sample.as_type(), sample.as_word()) {
760 if let Some(values) = self.sample_values.get_mut(&sample_type) {
761 if values.len() < limit as usize {
762 values.insert(sample_value);
763 } else {
764 self.insert_value(sample_value);
766 }
767 } else {
768 self.sample_values.entry(sample_type).or_default().insert(sample_value);
769 }
770 }
771 }
772 }
773
774 pub const fn values(&self) -> &B256IndexSet {
775 &self.state_values
776 }
777
778 pub fn len(&self) -> usize {
779 self.state_values.len()
780 }
781
782 pub fn is_empty(&self) -> bool {
783 self.state_values.is_empty()
784 }
785
786 #[inline]
791 pub fn samples(&self, param_type: &DynSolType) -> Option<&B256IndexSet> {
792 if !self.samples_seeded {
794 return self.literal_values.get().words.get(param_type);
795 }
796
797 self.sample_values.get(param_type)
798 }
799
800 #[inline]
802 pub fn ast_strings(&self) -> &IndexSet<String> {
803 &self.literal_values.get().strings
804 }
805
806 #[inline]
808 pub fn ast_bytes(&self) -> &IndexSet<Bytes> {
809 &self.literal_values.get().bytes
810 }
811
812 #[inline]
813 pub const fn addresses(&self) -> &AddressIndexSet {
814 &self.addresses
815 }
816
817 pub fn revert(&mut self) {
819 self.state_values.truncate(self.db_state_values);
820 self.addresses.truncate(self.db_addresses);
821 self.push_bytecode_hashes.truncate(self.db_push_bytecode_hashes);
822 }
823
824 pub fn log_stats(&self) {
825 trace!(
826 addresses.len = self.addresses.len(),
827 sample.len = self.sample_values.len(),
828 state.len = self.state_values.len(),
829 state.misses = self.misses,
830 state.hits = self.hits,
831 "FuzzDictionary stats",
832 );
833 }
834
835 #[cfg(test)]
836 pub(crate) fn seed_literals(&mut self, map: super::LiteralMaps) {
838 self.literal_values.set(map);
839 }
840}
841
842#[cfg(test)]
843mod tests {
844
845 use super::*;
846 use alloy_json_abi::{Event, JsonAbi};
847 use foundry_evm_core::eip2935::HISTORY_STORAGE_ADDRESS;
848 use revm::{
849 bytecode::Bytecode,
850 state::{Account, EvmStorageSlot, TransactionId},
851 };
852
853 fn account_with_code(raw: &'static [u8]) -> AccountInfo {
854 AccountInfo::default().with_code(Bytecode::new_raw(Bytes::from_static(raw)))
855 }
856
857 #[test]
858 fn log_decoding_preserves_anonymous_event_priority() {
859 let mut abi = JsonAbi::new();
860 let anonymous_event =
861 Event::parse("event AEvent(bytes32 indexed topic, uint256 value) anonymous").unwrap();
862 let matched_event = Event::parse("event ZEvent(uint256 value)").unwrap();
863 let selector = matched_event.selector();
864 abi.events.entry(anonymous_event.name.clone()).or_default().push(anonymous_event);
865 abi.events.entry(matched_event.name.clone()).or_default().push(matched_event);
866 let contract = TargetedContract::new("Target".to_string(), abi);
867 let matched_events = contract.event_lookup.by_topic(&selector, 0).unwrap();
868 let word: B256 = U256::from(42).into();
869 let log = Log::new_unchecked(Address::ZERO, vec![selector], Bytes::from(word));
870 let mut samples = Vec::new();
871
872 assert!(FuzzDictionary::decode_log_events(
873 matched_events,
874 contract.event_lookup.anonymous(),
875 &log,
876 &mut samples,
877 ));
878
879 assert_eq!(samples.len(), 2);
880 assert_eq!(samples[0], DynSolValue::FixedBytes(selector, 32));
881 assert_eq!(samples[1], DynSolValue::Uint(U256::from(42), 256));
882 }
883
884 #[test]
885 fn push_byte_collection_stops_when_dictionary_is_full() {
886 let mut dictionary = FuzzDictionary::new(FuzzDictionaryConfig {
887 max_fuzz_dictionary_values: 3,
888 ..Default::default()
889 });
890
891 dictionary.collect_push_bytes(&[0x60, 0x01, 0x60, 0x03]);
892
893 assert_eq!(dictionary.len(), 3);
894 assert!(dictionary.state_values.contains(&B256::ZERO));
895 assert!(dictionary.state_values.contains(&B256::with_last_byte(1)));
896 assert!(dictionary.state_values.contains(&B256::with_last_byte(2)));
897 assert!(!dictionary.state_values.contains(&B256::with_last_byte(3)));
898 }
899
900 #[test]
901 fn zero_value_capacity_keeps_only_required_seed() {
902 let mut dictionary = FuzzDictionary::new(FuzzDictionaryConfig {
903 max_fuzz_dictionary_values: 0,
904 ..Default::default()
905 });
906
907 assert_eq!(dictionary.config.max_fuzz_dictionary_values, 1);
908 assert_eq!(dictionary.state_values.as_slice(), &[B256::ZERO]);
909 assert!(!dictionary.insert_value(B256::with_last_byte(1)));
910 assert_eq!(dictionary.state_values.as_slice(), &[B256::ZERO]);
911 }
912
913 #[test]
914 fn duplicate_push_values_in_same_bytecode_are_collected_once() {
915 let mut dictionary = FuzzDictionary::default();
916
917 dictionary.collect_push_bytes(&[0x60, 0x01, 0x60, 0x01]);
918
919 assert!(dictionary.state_values.contains(&B256::ZERO));
920 assert!(dictionary.state_values.contains(&B256::with_last_byte(1)));
921 assert!(dictionary.state_values.contains(&B256::with_last_byte(2)));
922 assert_eq!(dictionary.hits, 1);
923 }
924
925 #[test]
926 fn duplicate_bytecode_push_bytes_are_collected_once() {
927 let mut dictionary = FuzzDictionary::default();
928 let account = account_with_code(&[0x60, 0x01]);
929
930 dictionary.insert_push_bytes_values(&Address::repeat_byte(0x11), &account);
931 let hits_after_first_scan = dictionary.hits;
932
933 dictionary.insert_push_bytes_values(&Address::repeat_byte(0x22), &account);
934
935 assert_eq!(dictionary.push_bytecode_hashes.len(), 1);
936 assert_eq!(dictionary.addresses.len(), 2);
937 assert_eq!(dictionary.hits, hits_after_first_scan);
938 }
939
940 #[test]
941 fn same_address_with_new_bytecode_is_scanned_again() {
942 let mut dictionary = FuzzDictionary::default();
943 let address = Address::repeat_byte(0x22);
944
945 dictionary.insert_push_bytes_values(&address, &account_with_code(&[0x60, 0x01]));
946 dictionary.insert_push_bytes_values(&address, &account_with_code(&[0x60, 0x04]));
947
948 assert_eq!(dictionary.addresses.len(), 1);
949 assert_eq!(dictionary.push_bytecode_hashes.len(), 2);
950 assert!(dictionary.state_values.contains(&B256::with_last_byte(1)));
951 assert!(dictionary.state_values.contains(&B256::with_last_byte(4)));
952 }
953
954 #[test]
955 fn no_code_account_does_not_block_later_push_byte_scan() {
956 let mut dictionary = FuzzDictionary::default();
957 let address = Address::repeat_byte(0x33);
958 let account_without_code = AccountInfo { code: None, ..Default::default() };
959
960 dictionary.insert_push_bytes_values(&address, &account_without_code);
961
962 assert!(!dictionary.addresses.contains(&address));
963 assert_eq!(dictionary.push_bytecode_hashes.len(), 0);
964
965 dictionary.insert_push_bytes_values(&address, &account_with_code(&[0x60, 0x04]));
966
967 assert!(dictionary.addresses.contains(&address));
968 assert_eq!(dictionary.push_bytecode_hashes.len(), 1);
969 assert!(dictionary.state_values.contains(&B256::with_last_byte(4)));
970 }
971
972 #[test]
973 fn revert_removes_runtime_bytecode_scan_cache() {
974 let mut dictionary = FuzzDictionary::default();
975 dictionary.db_state_values = dictionary.state_values.len();
976 dictionary.db_addresses = dictionary.addresses.len();
977 dictionary.db_push_bytecode_hashes = dictionary.push_bytecode_hashes.len();
978
979 let account = account_with_code(&[0x60, 0x01]);
980 dictionary.insert_push_bytes_values(&Address::repeat_byte(0x11), &account);
981 assert_eq!(dictionary.push_bytecode_hashes.len(), 1);
982
983 dictionary.revert();
984 assert_eq!(dictionary.push_bytecode_hashes.len(), 0);
985
986 dictionary.insert_push_bytes_values(&Address::repeat_byte(0x22), &account);
987 assert_eq!(dictionary.push_bytecode_hashes.len(), 1);
988 assert!(dictionary.state_values.contains(&B256::with_last_byte(1)));
989 }
990
991 #[test]
992 fn persistent_value_survives_revert() {
993 let mut dictionary = FuzzDictionary::default();
994 dictionary.db_state_values = dictionary.state_values.len();
995
996 let ephemeral = B256::from(U256::from(0xbeef_u64));
997 let persistent = B256::from(U256::from(0xcafe_u64));
998 dictionary.insert_value(ephemeral);
999 dictionary.insert_persistent_value(persistent);
1000
1001 dictionary.revert();
1002
1003 assert!(dictionary.state_values.contains(&persistent));
1004 assert!(!dictionary.state_values.contains(&ephemeral));
1005 assert!(dictionary.db_state_values <= dictionary.state_values.len());
1006
1007 let watermark = dictionary.db_state_values;
1009 let len = dictionary.state_values.len();
1010 dictionary.insert_persistent_value(B256::ZERO);
1011 assert_eq!(dictionary.db_state_values, watermark);
1012 assert_eq!(dictionary.state_values.len(), len);
1013 }
1014
1015 #[test]
1016 fn persistent_value_promotes_existing_ephemeral() {
1017 let mut dictionary = FuzzDictionary::default();
1018 dictionary.db_state_values = dictionary.state_values.len();
1019
1020 let ephemeral = B256::from(U256::from(0xbeef_u64));
1021 let value = B256::from(U256::from(0xdead_u64));
1022 dictionary.insert_value(ephemeral);
1023 dictionary.insert_value(value);
1024 dictionary.insert_persistent_value(value);
1025
1026 dictionary.revert();
1027
1028 assert!(dictionary.state_values.contains(&value));
1029 assert!(!dictionary.state_values.contains(&ephemeral));
1030 assert!(dictionary.db_state_values <= dictionary.state_values.len());
1031 }
1032
1033 #[test]
1034 fn history_storage_account_is_excluded_from_initial_dictionary() {
1035 let mut db = InMemoryDB::default();
1036 let code = Bytecode::new_raw(Bytes::from_static(&[0x61, 0x01, 0x23, 0x00]));
1037 db.insert_account_info(HISTORY_STORAGE_ADDRESS, AccountInfo::default().with_code(code));
1038 db.insert_account_storage(HISTORY_STORAGE_ADDRESS, U256::from(7), U256::from(0xdead_u64))
1039 .unwrap();
1040
1041 let state = EvmFuzzState::new(&[], &db, FuzzDictionaryConfig::default(), None);
1042
1043 state.with_dictionary(|dict| {
1044 assert!(!dict.values().contains(&HISTORY_STORAGE_ADDRESS.into_word()));
1045 assert!(!dict.values().contains(&B256::from(U256::from(0x123))));
1046 assert!(!dict.values().contains(&B256::with_last_byte(7)));
1047 assert!(!dict.values().contains(&B256::from(U256::from(0xdead_u64))));
1048 });
1049 }
1050
1051 #[test]
1052 fn worker_modes_share_seed_but_isolate_feedback() {
1053 let seed = EvmFuzzState::test();
1054 let readonly = seed.stateless_worker();
1055 let first = seed.clone().into_invariant();
1056 let second = seed.into_invariant();
1057 let transient = B256::from(U256::from(0xdead_u64));
1058 let persistent = B256::from(U256::from(0xbeef_u64));
1059
1060 readonly.collect_values([transient]);
1061 assert!(!readonly.with_dictionary(|dict| dict.state_values.contains(&transient)));
1062
1063 first.collect_values([transient]);
1064 first.collect_typed_cmp_values([(8, persistent)]);
1065 assert!(first.with_dictionary(|dict| dict.state_values.contains(&transient)));
1066 assert!(!second.with_dictionary(|dict| dict.state_values.contains(&transient)));
1067
1068 first.revert();
1069 assert!(!first.with_dictionary(|dict| dict.state_values.contains(&transient)));
1070 first.with_dictionary(|dict| {
1071 assert!(dict.state_values.contains(&persistent));
1072 assert!(dict.persistent_values.contains(&persistent));
1073 assert!(dict.sample_values[&DynSolType::Uint(8)].contains(&persistent));
1074 });
1075 }
1076
1077 #[test]
1078 fn new_state_values_are_inserted_in_sorted_order() {
1079 let entry =
1082 |i: u64, j: u64| (U256::from(i * 0x100 + j), U256::from(i * 0x10000 + j * 0x10));
1083 let address = |i: u64| Address::repeat_byte(i as u8 * 0x11);
1084
1085 let mut state_changeset = StateChangeset::default();
1086 for i in [3, 1, 4, 2] {
1087 let storage = [3, 1, 2].map(|j| {
1088 let (slot, value) = entry(i, j);
1089 (slot, EvmStorageSlot::new(value, TransactionId::ZERO))
1090 });
1091 state_changeset
1092 .insert(address(i), Account::default().with_storage(storage.into_iter()));
1093 }
1094
1095 let mut dictionary = FuzzDictionary::default();
1096 dictionary.insert_new_state_values(&state_changeset, &TargetedContracts::new(), None);
1097
1098 let mut expected = vec![B256::ZERO];
1099 for i in 1..=4 {
1100 expected.push(address(i).into_word());
1101 for j in 1..=3 {
1102 let (slot, value) = entry(i, j);
1103 expected
1104 .extend([slot, value, value - U256::ONE, value + U256::ONE].map(B256::from));
1105 }
1106 }
1107 assert_eq!(dictionary.state_values.as_slice(), expected.as_slice());
1108 }
1109}