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)]
45pub struct InvariantFuzzState {
46 inner: Rc<RefCell<FuzzDictionary>>,
47 pub deployed_libs: Vec<Address>,
49}
50
51pub trait FuzzStateReader: Clone + 'static {
52 fn deployed_libs(&self) -> &[Address];
53 fn with_dictionary<R>(&self, f: impl FnOnce(&FuzzDictionary) -> R) -> R;
54}
55
56impl EvmFuzzState {
57 #[cfg(test)]
58 pub(crate) fn test() -> Self {
59 Self::new(
60 &[],
61 &CacheDB::<revm::database::EmptyDB>::default(),
62 FuzzDictionaryConfig::default(),
63 None,
64 )
65 }
66
67 pub fn new<DB: DatabaseRef>(
68 deployed_libs: &[Address],
69 db: &CacheDB<DB>,
70 config: FuzzDictionaryConfig,
71 literals: Option<&LiteralsDictionary>,
72 ) -> Self {
73 let mut accs = db.cache.accounts.iter().collect::<Vec<_>>();
75 accs.sort_by_key(|(address, _)| *address);
76
77 let mut dictionary = FuzzDictionary::new(config);
79 dictionary.insert_db_values(accs);
80 if let Some(literals) = literals {
81 dictionary.literal_values = literals.clone();
82 }
83
84 Self { inner: Arc::new(dictionary), deployed_libs: deployed_libs.to_vec() }
85 }
86
87 pub fn into_invariant(self) -> InvariantFuzzState {
88 InvariantFuzzState {
89 inner: Rc::new(RefCell::new((*self.inner).clone())),
90 deployed_libs: self.deployed_libs,
91 }
92 }
93
94 pub fn fork(&self) -> Self {
95 Self { inner: Arc::clone(&self.inner), deployed_libs: self.deployed_libs.clone() }
96 }
97
98 pub fn collect_values(&mut self, values: impl IntoIterator<Item = B256>) {
99 let dict = Arc::make_mut(&mut self.inner);
100 for value in values {
101 dict.insert_value(value);
102 }
103 }
104
105 pub fn log_stats(&self) {
107 self.inner.log_stats();
108 }
109
110 #[cfg(test)]
112 pub(crate) fn seed_literals(&mut self, map: super::LiteralMaps) {
113 Arc::make_mut(&mut self.inner).seed_literals(map);
114 }
115}
116
117impl FuzzStateReader for EvmFuzzState {
118 fn deployed_libs(&self) -> &[Address] {
119 &self.deployed_libs
120 }
121
122 fn with_dictionary<R>(&self, f: impl FnOnce(&FuzzDictionary) -> R) -> R {
123 f(&self.inner)
124 }
125}
126
127impl InvariantFuzzState {
128 pub fn snapshot(&self) -> EvmFuzzState {
129 EvmFuzzState {
130 inner: Arc::new(self.inner.borrow().clone()),
131 deployed_libs: self.deployed_libs.clone(),
132 }
133 }
134
135 pub fn collect_values(&self, values: impl IntoIterator<Item = B256>) {
136 let mut dict = self.inner.borrow_mut();
137 for value in values {
138 dict.insert_value(value);
139 }
140 }
141
142 pub fn collect_fuzzer_values(&self, fuzzer: &mut Fuzzer) {
143 if fuzzer.collected_values.is_empty() {
144 return;
145 }
146
147 let mut dict = self.inner.borrow_mut();
148 for value in fuzzer.collected_values.drain(..) {
149 dict.insert_value(value);
150 }
151 }
152
153 #[allow(clippy::too_many_arguments)]
156 pub fn collect_values_from_call(
157 &self,
158 fuzzed_contracts: &FuzzRunIdentifiedContracts,
159 tx: &BasicTxDetails,
160 result: &Bytes,
161 logs: &[Log],
162 state_changeset: &StateChangeset,
163 run_depth: u32,
164 mapping_slots: Option<&AddressMap<MappingSlots>>,
165 ) {
166 if logs.is_empty() && result.is_empty() && state_changeset.is_empty() {
167 return;
168 }
169
170 let mut dict = self.inner.borrow_mut();
171 let targets = fuzzed_contracts.targets();
172 let (target_contract, target_function) = if logs.is_empty() && result.is_empty() {
173 (None, None)
174 } else {
175 targets.fuzzed_artifacts(tx)
176 };
177 if !logs.is_empty() {
178 dict.insert_logs_values(target_contract, logs, run_depth);
179 }
180 if !result.is_empty() {
181 dict.insert_result_values(target_function, result, run_depth);
182 }
183 dict.insert_new_state_values(state_changeset, &targets, mapping_slots);
184 }
185
186 pub fn collect_typed_cmp_values(&self, values: impl IntoIterator<Item = (u8, B256)>) {
190 let mut dict = self.inner.borrow_mut();
191 for (width, value) in values {
192 dict.insert_persistent_value(value);
193 dict.insert_typed_cmp_value(width, value);
194 }
195 }
196
197 pub fn revert(&self) {
202 self.inner.borrow_mut().revert();
203 }
204
205 pub fn log_stats(&self) {
207 self.inner.borrow().log_stats();
208 }
209}
210
211impl FuzzStateReader for InvariantFuzzState {
212 fn deployed_libs(&self) -> &[Address] {
213 &self.deployed_libs
214 }
215
216 fn with_dictionary<R>(&self, f: impl FnOnce(&FuzzDictionary) -> R) -> R {
217 f(&self.inner.borrow())
218 }
219}
220
221impl From<EvmFuzzState> for InvariantFuzzState {
222 fn from(state: EvmFuzzState) -> Self {
223 state.into_invariant()
224 }
225}
226
227const MAX_PERSISTENT_VALUES: usize = 2048;
231const MAX_SLOT_INFO_CACHE_ENTRIES: usize = 4096;
233
234#[derive(Clone)]
235pub struct FuzzDictionary {
236 state_values: B256IndexSet,
238 addresses: AddressIndexSet,
240 push_bytecode_hashes: B256IndexSet,
242 config: FuzzDictionaryConfig,
244 db_state_values: usize,
247 db_addresses: usize,
250 db_push_bytecode_hashes: usize,
253 sample_values: HashMap<DynSolType, B256IndexSet>,
256 literal_values: LiteralsDictionary,
258 samples_seeded: bool,
263 persistent_values: B256IndexSet,
265 slot_identifiers: HashMap<usize, SlotIdentifier>,
267 slot_info_cache: HashMap<(usize, B256), Option<SlotInfo>>,
269
270 misses: usize,
271 hits: usize,
272}
273
274impl fmt::Debug for FuzzDictionary {
275 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
276 f.debug_struct("FuzzDictionary")
277 .field("state_values", &self.state_values.len())
278 .field("addresses", &self.addresses)
279 .field("persistent_values", &self.persistent_values.len())
280 .finish()
281 }
282}
283
284impl Default for FuzzDictionary {
285 fn default() -> Self {
286 Self::new(Default::default())
287 }
288}
289
290impl FuzzDictionary {
291 pub fn new(config: FuzzDictionaryConfig) -> Self {
292 let mut dictionary = Self {
293 config,
294 samples_seeded: false,
295
296 state_values: Default::default(),
297 addresses: Default::default(),
298 push_bytecode_hashes: Default::default(),
299 db_state_values: Default::default(),
300 db_addresses: Default::default(),
301 db_push_bytecode_hashes: Default::default(),
302 sample_values: Default::default(),
303 literal_values: Default::default(),
304 persistent_values: Default::default(),
305 slot_identifiers: Default::default(),
306 slot_info_cache: Default::default(),
307 misses: Default::default(),
308 hits: Default::default(),
309 };
310 dictionary.prefill();
311 dictionary
312 }
313
314 fn prefill(&mut self) {
316 self.insert_value(B256::ZERO);
317 }
318
319 #[cold]
322 fn seed_samples(&mut self) {
323 trace!("seeding `sample_values` from literal dictionary");
324 self.sample_values
325 .extend(self.literal_values.get().words.iter().map(|(k, v)| (k.clone(), v.clone())));
326 self.samples_seeded = true;
327 }
328
329 fn insert_db_values(&mut self, db_state: Vec<(&Address, &DbAccount)>) {
332 for (address, account) in db_state {
333 if is_history_storage_address(address) {
334 continue;
335 }
336
337 self.insert_value(address.into_word());
339 self.insert_push_bytes_values(address, &account.info);
341 if self.config.include_storage {
343 let mut values = account.storage.iter().collect::<Vec<_>>();
345 values.sort_unstable_by_key(|(slot, _)| **slot);
346 for (slot, value) in values {
347 self.insert_storage_value(slot, value, None);
348 }
349 }
350 }
351
352 if self.values().is_empty() {
355 self.insert_value(Address::random().into_word());
357 }
358
359 self.db_state_values = self.state_values.len();
362 self.db_addresses = self.addresses.len();
363 self.db_push_bytecode_hashes = self.push_bytecode_hashes.len();
364 }
365
366 fn insert_result_values(
368 &mut self,
369 function: Option<&Function>,
370 result: &Bytes,
371 run_depth: u32,
372 ) {
373 if let Some(function) = function
374 && !function.outputs.is_empty()
375 {
376 if let Ok(decoded_result) = function.abi_decode_output(result) {
378 self.insert_sample_values(decoded_result, run_depth);
379 }
380 }
381 }
382
383 fn insert_logs_values(
385 &mut self,
386 target_contract: Option<&TargetedContract>,
387 logs: &[Log],
388 run_depth: u32,
389 ) {
390 let mut samples = Vec::new();
391 for log in logs {
393 let log_decoded = if let Some(contract) = target_contract {
395 let matched_events = log
396 .topics()
397 .first()
398 .and_then(|selector| {
399 contract.event_lookup.by_topic(selector, log.topics().len() - 1)
400 })
401 .unwrap_or(&[]);
402 Self::decode_log_events(
403 matched_events,
404 contract.event_lookup.anonymous(),
405 log,
406 &mut samples,
407 )
408 } else {
409 false
410 };
411
412 if !log_decoded {
414 for &topic in log.topics() {
415 self.insert_value(topic);
416 }
417 let (chunks, rem) = log.data.data.as_chunks::<32>();
418 for chunk in chunks {
419 self.insert_value((*chunk).into());
420 }
421 if !rem.is_empty() {
422 self.insert_value(B256::right_padding_from(rem));
423 }
424 }
425 }
426
427 if !samples.is_empty() {
429 self.insert_sample_values(samples, run_depth);
430 }
431 }
432
433 fn decode_log_events(
434 matched_events: &[TargetedContractEvent],
435 anonymous_events: &[TargetedContractEvent],
436 log: &Log,
437 samples: &mut Vec<DynSolValue>,
438 ) -> bool {
439 let mut matched = matched_events.iter().peekable();
440 let mut anonymous = anonymous_events.iter().peekable();
441 while matched.peek().is_some() || anonymous.peek().is_some() {
442 let event = match (matched.peek(), anonymous.peek()) {
443 (Some(matched_event), Some(anonymous_event)) => {
444 if matched_event.order() < anonymous_event.order() {
445 matched.next().unwrap()
446 } else {
447 anonymous.next().unwrap()
448 }
449 }
450 (Some(_), None) => matched.next().unwrap(),
451 (None, Some(_)) => anonymous.next().unwrap(),
452 (None, None) => unreachable!(),
453 };
454 if let Ok(decoded_event) = event.event().decode_log(log) {
455 samples.extend(decoded_event.indexed);
456 samples.extend(decoded_event.body);
457 return true;
458 }
459 }
460 false
461 }
462
463 fn insert_new_state_values(
466 &mut self,
467 state_changeset: &StateChangeset,
468 targets: &TargetedContracts,
469 mapping_slots: Option<&AddressMap<MappingSlots>>,
470 ) {
471 for (address, account) in state_changeset {
472 if is_history_storage_address(address) {
473 continue;
474 }
475
476 self.insert_value(address.into_word());
478 self.insert_push_bytes_values(address, &account.info);
480 if self.config.include_storage && !account.storage.is_empty() {
482 let slot_identifier_key = targets.get(address).and_then(|contract| {
483 contract.storage_layout.as_ref().map(|layout| {
484 let key = Arc::as_ptr(layout) as usize;
485 self.slot_identifiers
486 .entry(key)
487 .or_insert_with(|| SlotIdentifier::new(Arc::clone(layout)));
488 key
489 })
490 });
491 trace!(
492 "{address:?} has mapping_slots {}",
493 mapping_slots.is_some_and(|m| m.contains_key(address))
494 );
495 let mapping_slots = mapping_slots.and_then(|m| m.get(address));
496 for (slot, value) in &account.storage {
497 let slot_info = slot_identifier_key.and_then(|key| {
498 let slot = B256::from(*slot);
499 let value_word = B256::from(value.present_value);
500 self.identify_storage_slot(key, slot, mapping_slots)
501 .filter(|slot_info| slot_info.decode(value_word).is_some())
502 });
503 self.insert_storage_value(slot, &value.present_value, slot_info);
504 }
505 }
506 }
507 }
508
509 fn identify_storage_slot(
510 &mut self,
511 key: usize,
512 slot: B256,
513 mapping_slots: Option<&MappingSlots>,
514 ) -> Option<SlotInfo> {
515 if mapping_slots.is_some() {
516 return self
517 .slot_identifiers
518 .get(&key)
519 .and_then(|identifier| identifier.identify(&slot, mapping_slots));
520 }
521
522 let cache_key = (key, slot);
523 if let Some(slot_info) = self.slot_info_cache.get(&cache_key) {
524 return slot_info.clone();
525 }
526
527 let slot_info =
528 self.slot_identifiers.get(&key).and_then(|identifier| identifier.identify(&slot, None));
529 if self.slot_info_cache.len() < MAX_SLOT_INFO_CACHE_ENTRIES {
530 self.slot_info_cache.insert(cache_key, slot_info.clone());
531 }
532 slot_info
533 }
534
535 fn insert_push_bytes_values(&mut self, address: &Address, account_info: &AccountInfo) {
539 if !self.config.include_push_bytes {
540 return;
541 }
542
543 let Some(code) = &account_info.code else {
544 return;
545 };
546 self.insert_address(*address);
547 if self.values_full() {
548 return;
549 }
550 if self.push_bytecode_hashes.insert(account_info.code_hash) {
551 self.collect_push_bytes(ignore_metadata_hash(code.original_byte_slice()));
552 }
553 }
554
555 fn collect_push_bytes(&mut self, code: &[u8]) {
556 let len = code.len().min(PUSH_BYTE_ANALYSIS_LIMIT);
557 let code = &code[..len];
558 let mut seen = HashSet::default();
559 for inst in InstIter::new(code) {
560 if self.values_full() {
561 break;
562 }
563 if !inst.immediate.is_empty()
565 && let Some(push_value) = U256::try_from_be_slice(inst.immediate)
566 && push_value != U256::ZERO
567 {
568 self.insert_push_value_u256(push_value, &mut seen);
569 }
570 }
571 }
572
573 fn insert_storage_value(&mut self, slot: &U256, value: &U256, slot_info: Option<SlotInfo>) {
576 let slot = B256::from(*slot);
577 let value_word = B256::from(*value);
578
579 self.insert_value(slot);
581
582 if let Some(slot_info) = slot_info {
583 trace!(?slot_info, "inserting typed storage value");
584 if !self.samples_seeded {
585 self.seed_samples();
586 }
587 self.sample_values
588 .entry(slot_info.slot_type.dyn_sol_type)
589 .or_default()
590 .insert(value_word);
591 } else {
592 self.insert_value_u256(*value);
593 }
594 }
595
596 fn insert_address(&mut self, address: Address) {
599 if self.addresses.len() < self.config.max_fuzz_dictionary_addresses {
600 self.addresses.insert(address);
601 }
602 }
603
604 fn insert_value(&mut self, value: B256) -> bool {
610 let insert = !self.values_full();
611 if insert {
612 let new_value = self.state_values.insert(value);
613 let counter = if new_value { &mut self.misses } else { &mut self.hits };
614 *counter += 1;
615 }
616 insert
617 }
618
619 fn insert_persistent_value(&mut self, value: B256) {
622 if self.persistent_values.len() >= MAX_PERSISTENT_VALUES {
623 return;
624 }
625 if self.persistent_values.insert(value) && self.state_values.insert(value) {
626 self.db_state_values += 1;
627 }
628 }
629
630 fn insert_typed_cmp_value(&mut self, width: u8, value: B256) {
633 if !self.samples_seeded {
634 self.seed_samples();
635 }
636
637 const MAX_TYPED_CMP_PER_BUCKET: usize = 1024;
638
639 let native_type = match width {
640 8 => DynSolType::Uint(8),
641 16 => DynSolType::Uint(16),
642 32 => DynSolType::Uint(32),
643 64 => DynSolType::Uint(64),
644 _ => DynSolType::Uint(256),
645 };
646
647 let insert = |map: &mut HashMap<DynSolType, B256IndexSet>, ty: DynSolType, val: B256| {
648 let bucket = map.entry(ty).or_default();
649 if bucket.len() < MAX_TYPED_CMP_PER_BUCKET {
650 bucket.insert(val);
651 }
652 };
653
654 insert(&mut self.sample_values, native_type, value);
655
656 if width <= 64 {
657 insert(&mut self.sample_values, DynSolType::Uint(128), value);
658 insert(&mut self.sample_values, DynSolType::Uint(256), value);
659 insert(&mut self.sample_values, DynSolType::Int(256), value);
660 }
661 }
662
663 fn insert_value_u256(&mut self, value: U256) -> bool {
664 let one = U256::from(1);
666 let mut inserted = self.insert_value(value.into());
667 if !self.values_full() {
668 inserted |= self.insert_value((value.wrapping_sub(one)).into());
669 }
670 if !self.values_full() {
671 inserted |= self.insert_value((value.wrapping_add(one)).into());
672 }
673 inserted
674 }
675
676 fn insert_push_value_u256(&mut self, value: U256, seen: &mut HashSet<B256>) -> bool {
677 let one = U256::from(1);
679 let mut inserted = false;
680 for value in [value, value.wrapping_sub(one), value.wrapping_add(one)] {
681 if self.values_full() {
682 break;
683 }
684 let value = value.into();
685 if seen.insert(value) {
686 inserted |= self.insert_value(value);
687 }
688 }
689 inserted
690 }
691
692 fn values_full(&self) -> bool {
693 self.state_values.len() >= self.config.max_fuzz_dictionary_values
694 }
695
696 pub fn insert_sample_values(
700 &mut self,
701 sample_values: impl IntoIterator<Item = DynSolValue>,
702 limit: u32,
703 ) {
704 if !self.samples_seeded {
705 self.seed_samples();
706 }
707 for sample in sample_values {
708 if let (Some(sample_type), Some(sample_value)) = (sample.as_type(), sample.as_word()) {
709 if let Some(values) = self.sample_values.get_mut(&sample_type) {
710 if values.len() < limit as usize {
711 values.insert(sample_value);
712 } else {
713 self.insert_value(sample_value);
715 }
716 } else {
717 self.sample_values.entry(sample_type).or_default().insert(sample_value);
718 }
719 }
720 }
721 }
722
723 pub const fn values(&self) -> &B256IndexSet {
724 &self.state_values
725 }
726
727 pub fn len(&self) -> usize {
728 self.state_values.len()
729 }
730
731 pub fn is_empty(&self) -> bool {
732 self.state_values.is_empty()
733 }
734
735 #[inline]
740 pub fn samples(&self, param_type: &DynSolType) -> Option<&B256IndexSet> {
741 if !self.samples_seeded {
743 return self.literal_values.get().words.get(param_type);
744 }
745
746 self.sample_values.get(param_type)
747 }
748
749 #[inline]
751 pub fn ast_strings(&self) -> &IndexSet<String> {
752 &self.literal_values.get().strings
753 }
754
755 #[inline]
757 pub fn ast_bytes(&self) -> &IndexSet<Bytes> {
758 &self.literal_values.get().bytes
759 }
760
761 #[inline]
762 pub const fn addresses(&self) -> &AddressIndexSet {
763 &self.addresses
764 }
765
766 pub fn revert(&mut self) {
768 self.state_values.truncate(self.db_state_values);
769 self.addresses.truncate(self.db_addresses);
770 self.push_bytecode_hashes.truncate(self.db_push_bytecode_hashes);
771 }
772
773 pub fn log_stats(&self) {
774 trace!(
775 addresses.len = self.addresses.len(),
776 sample.len = self.sample_values.len(),
777 state.len = self.state_values.len(),
778 state.misses = self.misses,
779 state.hits = self.hits,
780 "FuzzDictionary stats",
781 );
782 }
783
784 #[cfg(test)]
785 pub(crate) fn seed_literals(&mut self, map: super::LiteralMaps) {
787 self.literal_values.set(map);
788 }
789}
790
791#[cfg(test)]
792mod tests {
793 use super::*;
794 use alloy_json_abi::{Event, JsonAbi};
795 use alloy_primitives::keccak256;
796 use foundry_evm_core::eip2935::HISTORY_STORAGE_ADDRESS;
797 use revm::{bytecode::Bytecode, database::EmptyDB};
798
799 fn account_with_code(raw: &'static [u8]) -> AccountInfo {
800 let code = Bytecode::new_raw(Bytes::from_static(raw));
801 AccountInfo {
802 code_hash: keccak256(code.original_byte_slice()),
803 code: Some(code),
804 ..Default::default()
805 }
806 }
807
808 #[test]
809 fn log_decoding_preserves_anonymous_event_priority() {
810 let mut abi = JsonAbi::new();
811 let anonymous_event =
812 Event::parse("event AEvent(bytes32 indexed topic, uint256 value) anonymous").unwrap();
813 let matched_event = Event::parse("event ZEvent(uint256 value)").unwrap();
814 let selector = matched_event.selector();
815 abi.events.entry(anonymous_event.name.clone()).or_default().push(anonymous_event);
816 abi.events.entry(matched_event.name.clone()).or_default().push(matched_event);
817 let contract = TargetedContract::new("Target".to_string(), abi);
818 let matched_events = contract.event_lookup.by_topic(&selector, 0).unwrap();
819 let word: B256 = U256::from(42).into();
820 let log = Log::new_unchecked(
821 Address::ZERO,
822 vec![selector],
823 Bytes::copy_from_slice(word.as_slice()),
824 );
825 let mut samples = Vec::new();
826
827 assert!(FuzzDictionary::decode_log_events(
828 matched_events,
829 contract.event_lookup.anonymous(),
830 &log,
831 &mut samples,
832 ));
833
834 assert_eq!(samples.len(), 2);
835 assert_eq!(samples[0], DynSolValue::FixedBytes(selector, 32));
836 assert_eq!(samples[1], DynSolValue::Uint(U256::from(42), 256));
837 }
838
839 #[test]
840 fn push_byte_collection_stops_when_dictionary_is_full() {
841 let mut dictionary = FuzzDictionary::new(FuzzDictionaryConfig {
842 max_fuzz_dictionary_values: 3,
843 ..Default::default()
844 });
845
846 dictionary.collect_push_bytes(&[0x60, 0x01, 0x60, 0x03]);
847
848 assert_eq!(dictionary.state_values.len(), 3);
849 assert!(dictionary.state_values.contains(&B256::from(U256::ZERO)));
850 assert!(dictionary.state_values.contains(&B256::from(U256::from(1))));
851 assert!(dictionary.state_values.contains(&B256::from(U256::from(2))));
852 assert!(!dictionary.state_values.contains(&B256::from(U256::from(3))));
853 }
854
855 #[test]
856 fn duplicate_push_values_in_same_bytecode_are_collected_once() {
857 let mut dictionary = FuzzDictionary::default();
858
859 dictionary.collect_push_bytes(&[0x60, 0x01, 0x60, 0x01]);
860
861 assert!(dictionary.state_values.contains(&B256::from(U256::ZERO)));
862 assert!(dictionary.state_values.contains(&B256::from(U256::from(1))));
863 assert!(dictionary.state_values.contains(&B256::from(U256::from(2))));
864 assert_eq!(dictionary.hits, 1);
865 }
866
867 #[test]
868 fn duplicate_bytecode_push_bytes_are_collected_once() {
869 let mut dictionary = FuzzDictionary::default();
870 let account = account_with_code(&[0x60, 0x01]);
871
872 dictionary.insert_push_bytes_values(&Address::repeat_byte(0x11), &account);
873 let hits_after_first_scan = dictionary.hits;
874
875 dictionary.insert_push_bytes_values(&Address::repeat_byte(0x22), &account);
876
877 assert_eq!(dictionary.push_bytecode_hashes.len(), 1);
878 assert_eq!(dictionary.addresses.len(), 2);
879 assert_eq!(dictionary.hits, hits_after_first_scan);
880 }
881
882 #[test]
883 fn same_address_with_new_bytecode_is_scanned_again() {
884 let mut dictionary = FuzzDictionary::default();
885 let address = Address::repeat_byte(0x22);
886
887 dictionary.insert_push_bytes_values(&address, &account_with_code(&[0x60, 0x01]));
888 dictionary.insert_push_bytes_values(&address, &account_with_code(&[0x60, 0x04]));
889
890 assert_eq!(dictionary.addresses.len(), 1);
891 assert_eq!(dictionary.push_bytecode_hashes.len(), 2);
892 assert!(dictionary.state_values.contains(&B256::from(U256::from(1))));
893 assert!(dictionary.state_values.contains(&B256::from(U256::from(4))));
894 }
895
896 #[test]
897 fn no_code_account_does_not_block_later_push_byte_scan() {
898 let mut dictionary = FuzzDictionary::default();
899 let address = Address::repeat_byte(0x33);
900 let account_without_code = AccountInfo { code: None, ..Default::default() };
901
902 dictionary.insert_push_bytes_values(&address, &account_without_code);
903
904 assert!(!dictionary.addresses.contains(&address));
905 assert_eq!(dictionary.push_bytecode_hashes.len(), 0);
906
907 dictionary.insert_push_bytes_values(&address, &account_with_code(&[0x60, 0x04]));
908
909 assert!(dictionary.addresses.contains(&address));
910 assert_eq!(dictionary.push_bytecode_hashes.len(), 1);
911 assert!(dictionary.state_values.contains(&B256::from(U256::from(4))));
912 }
913
914 #[test]
915 fn revert_removes_runtime_bytecode_scan_cache() {
916 let mut dictionary = FuzzDictionary::default();
917 dictionary.db_state_values = dictionary.state_values.len();
918 dictionary.db_addresses = dictionary.addresses.len();
919 dictionary.db_push_bytecode_hashes = dictionary.push_bytecode_hashes.len();
920
921 let account = account_with_code(&[0x60, 0x01]);
922 dictionary.insert_push_bytes_values(&Address::repeat_byte(0x11), &account);
923 assert_eq!(dictionary.push_bytecode_hashes.len(), 1);
924
925 dictionary.revert();
926 assert_eq!(dictionary.push_bytecode_hashes.len(), 0);
927
928 dictionary.insert_push_bytes_values(&Address::repeat_byte(0x22), &account);
929 assert_eq!(dictionary.push_bytecode_hashes.len(), 1);
930 assert!(dictionary.state_values.contains(&B256::from(U256::from(1))));
931 }
932
933 #[test]
934 fn history_storage_account_is_excluded_from_initial_dictionary() {
935 let mut db = CacheDB::<EmptyDB>::default();
936 let code = Bytecode::new_raw(Bytes::from_static(&[0x61, 0x01, 0x23, 0x00]));
937 db.insert_account_info(
938 HISTORY_STORAGE_ADDRESS,
939 AccountInfo {
940 code_hash: keccak256(code.original_byte_slice()),
941 code: Some(code),
942 ..Default::default()
943 },
944 );
945 db.insert_account_storage(HISTORY_STORAGE_ADDRESS, U256::from(7), U256::from(0xdead_u64))
946 .unwrap();
947
948 let state = EvmFuzzState::new(&[], &db, FuzzDictionaryConfig::default(), None);
949
950 state.with_dictionary(|dict| {
951 assert!(!dict.values().contains(&HISTORY_STORAGE_ADDRESS.into_word()));
952 assert!(!dict.values().contains(&B256::from(U256::from(0x123))));
953 assert!(!dict.values().contains(&B256::from(U256::from(7))));
954 assert!(!dict.values().contains(&B256::from(U256::from(0xdead_u64))));
955 });
956 }
957}