foundry_evm_fuzz/strategies/
state.rs1use crate::{
2 BasicTxDetails, invariant::FuzzRunIdentifiedContracts, strategies::literals::LiteralsDictionary,
3};
4use alloy_dyn_abi::{DynSolType, DynSolValue, EventExt, FunctionExt};
5use alloy_json_abi::{Function, JsonAbi};
6use alloy_primitives::{
7 Address, B256, Bytes, Log, U256,
8 map::{AddressIndexSet, AddressMap, B256IndexSet, HashMap, IndexSet},
9};
10use foundry_common::{
11 ignore_metadata_hash, mapping_slots::MappingSlots, slot_identifier::SlotIdentifier,
12};
13use foundry_compilers::artifacts::StorageLayout;
14use foundry_config::FuzzDictionaryConfig;
15use foundry_evm_core::{bytecode::InstIter, utils::StateChangeset};
16use parking_lot::{RawRwLock, RwLock, lock_api::RwLockReadGuard};
17use revm::{
18 database::{CacheDB, DatabaseRef, DbAccount},
19 state::AccountInfo,
20};
21use std::{collections::BTreeMap, fmt, sync::Arc};
22
23const PUSH_BYTE_ANALYSIS_LIMIT: usize = 24 * 1024;
28
29#[derive(Clone, Debug)]
33pub struct EvmFuzzState {
34 inner: Arc<RwLock<FuzzDictionary>>,
35 pub deployed_libs: Vec<Address>,
37 pub(crate) mapping_slots: Option<AddressMap<MappingSlots>>,
42}
43
44impl EvmFuzzState {
45 #[cfg(test)]
46 pub(crate) fn test() -> Self {
47 Self::new(
48 &[],
49 &CacheDB::<revm::database::EmptyDB>::default(),
50 FuzzDictionaryConfig::default(),
51 None,
52 )
53 }
54
55 pub fn new<DB: DatabaseRef>(
56 deployed_libs: &[Address],
57 db: &CacheDB<DB>,
58 config: FuzzDictionaryConfig,
59 literals: Option<&LiteralsDictionary>,
60 ) -> Self {
61 let mut accs = db.cache.accounts.iter().collect::<Vec<_>>();
63 accs.sort_by_key(|(address, _)| *address);
64
65 let mut dictionary = FuzzDictionary::new(config);
67 dictionary.insert_db_values(accs);
68 if let Some(literals) = literals {
69 dictionary.literal_values = literals.clone();
70 }
71
72 Self {
73 inner: Arc::new(RwLock::new(dictionary)),
74 deployed_libs: deployed_libs.to_vec(),
75 mapping_slots: None,
76 }
77 }
78
79 pub fn with_mapping_slots(mut self, mapping_slots: AddressMap<MappingSlots>) -> Self {
80 self.mapping_slots = Some(mapping_slots);
81 self
82 }
83
84 pub fn collect_values(&self, values: impl IntoIterator<Item = B256>) {
85 let mut dict = self.inner.write();
86 for value in values {
87 dict.insert_value(value);
88 }
89 }
90
91 pub fn collect_values_from_call(
94 &self,
95 fuzzed_contracts: &FuzzRunIdentifiedContracts,
96 tx: &BasicTxDetails,
97 result: &Bytes,
98 logs: &[Log],
99 state_changeset: &StateChangeset,
100 run_depth: u32,
101 ) {
102 let mut dict = self.inner.write();
103 {
104 let targets = fuzzed_contracts.targets.lock();
105 let (target_abi, target_function) = targets.fuzzed_artifacts(tx);
106 dict.insert_logs_values(target_abi, logs, run_depth);
107 dict.insert_result_values(target_function, result, run_depth);
108 let storage_layouts = targets.get_storage_layouts();
110 dict.insert_new_state_values(
111 state_changeset,
112 &storage_layouts,
113 self.mapping_slots.as_ref(),
114 );
115 }
116 }
117
118 pub fn revert(&self) {
123 self.inner.write().revert();
124 }
125
126 pub fn dictionary_read(&self) -> RwLockReadGuard<'_, RawRwLock, FuzzDictionary> {
127 self.inner.read()
128 }
129
130 pub fn log_stats(&self) {
132 self.inner.read().log_stats();
133 }
134
135 #[cfg(test)]
137 pub(crate) fn seed_literals(&self, map: super::LiteralMaps) {
138 self.inner.write().seed_literals(map);
139 }
140}
141
142pub struct FuzzDictionary {
145 state_values: B256IndexSet,
147 addresses: AddressIndexSet,
149 config: FuzzDictionaryConfig,
151 db_state_values: usize,
154 db_addresses: usize,
157 sample_values: HashMap<DynSolType, B256IndexSet>,
160 literal_values: LiteralsDictionary,
162 samples_seeded: bool,
167
168 misses: usize,
169 hits: usize,
170}
171
172impl fmt::Debug for FuzzDictionary {
173 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174 f.debug_struct("FuzzDictionary")
175 .field("state_values", &self.state_values.len())
176 .field("addresses", &self.addresses)
177 .finish()
178 }
179}
180
181impl Default for FuzzDictionary {
182 fn default() -> Self {
183 Self::new(Default::default())
184 }
185}
186
187impl FuzzDictionary {
188 pub fn new(config: FuzzDictionaryConfig) -> Self {
189 let mut dictionary = Self {
190 config,
191 samples_seeded: false,
192
193 state_values: Default::default(),
194 addresses: Default::default(),
195 db_state_values: Default::default(),
196 db_addresses: Default::default(),
197 sample_values: Default::default(),
198 literal_values: Default::default(),
199 misses: Default::default(),
200 hits: Default::default(),
201 };
202 dictionary.prefill();
203 dictionary
204 }
205
206 fn prefill(&mut self) {
208 self.insert_value(B256::ZERO);
209 }
210
211 #[cold]
214 fn seed_samples(&mut self) {
215 trace!("seeding `sample_values` from literal dictionary");
216 self.sample_values
217 .extend(self.literal_values.get().words.iter().map(|(k, v)| (k.clone(), v.clone())));
218 self.samples_seeded = true;
219 }
220
221 fn insert_db_values(&mut self, db_state: Vec<(&Address, &DbAccount)>) {
224 for (address, account) in db_state {
225 self.insert_value(address.into_word());
227 self.insert_push_bytes_values(address, &account.info);
229 if self.config.include_storage {
231 let values = account.storage.iter().collect::<BTreeMap<_, _>>();
233 for (slot, value) in values {
234 self.insert_storage_value(slot, value, None, None);
235 }
236 }
237 }
238
239 if self.values().is_empty() {
242 self.insert_value(Address::random().into_word());
244 }
245
246 self.db_state_values = self.state_values.len();
249 self.db_addresses = self.addresses.len();
250 }
251
252 fn insert_result_values(
254 &mut self,
255 function: Option<&Function>,
256 result: &Bytes,
257 run_depth: u32,
258 ) {
259 if let Some(function) = function
260 && !function.outputs.is_empty()
261 {
262 if let Ok(decoded_result) = function.abi_decode_output(result) {
264 self.insert_sample_values(decoded_result, run_depth);
265 }
266 }
267 }
268
269 fn insert_logs_values(&mut self, abi: Option<&JsonAbi>, logs: &[Log], run_depth: u32) {
271 let mut samples = Vec::new();
272 for log in logs {
274 let mut log_decoded = false;
275 if let Some(abi) = abi {
277 for event in abi.events() {
278 if let Ok(decoded_event) = event.decode_log(log) {
279 samples.extend(decoded_event.indexed);
280 samples.extend(decoded_event.body);
281 log_decoded = true;
282 break;
283 }
284 }
285 }
286
287 if !log_decoded {
289 for &topic in log.topics() {
290 self.insert_value(topic);
291 }
292 let chunks = log.data.data.chunks_exact(32);
293 let rem = chunks.remainder();
294 for chunk in chunks {
295 self.insert_value(chunk.try_into().unwrap());
296 }
297 if !rem.is_empty() {
298 self.insert_value(B256::right_padding_from(rem));
299 }
300 }
301 }
302
303 self.insert_sample_values(samples, run_depth);
305 }
306
307 fn insert_new_state_values(
310 &mut self,
311 state_changeset: &StateChangeset,
312 storage_layouts: &HashMap<Address, Arc<StorageLayout>>,
313 mapping_slots: Option<&AddressMap<MappingSlots>>,
314 ) {
315 for (address, account) in state_changeset {
316 self.insert_value(address.into_word());
318 self.insert_push_bytes_values(address, &account.info);
320 if self.config.include_storage {
322 let slot_identifier =
323 storage_layouts.get(address).map(|layout| SlotIdentifier::new(layout.clone()));
324 trace!(
325 "{address:?} has mapping_slots {}",
326 mapping_slots.is_some_and(|m| m.contains_key(address))
327 );
328 let mapping_slots = mapping_slots.and_then(|m| m.get(address));
329 for (slot, value) in &account.storage {
330 self.insert_storage_value(
331 slot,
332 &value.present_value,
333 slot_identifier.as_ref(),
334 mapping_slots,
335 );
336 }
337 }
338 }
339 }
340
341 fn insert_push_bytes_values(&mut self, address: &Address, account_info: &AccountInfo) {
345 if self.config.include_push_bytes
346 && !self.addresses.contains(address)
347 && let Some(code) = &account_info.code
348 {
349 self.insert_address(*address);
350 if !self.values_full() {
351 self.collect_push_bytes(ignore_metadata_hash(code.original_byte_slice()));
352 }
353 }
354 }
355
356 fn collect_push_bytes(&mut self, code: &[u8]) {
357 let len = code.len().min(PUSH_BYTE_ANALYSIS_LIMIT);
358 let code = &code[..len];
359 for inst in InstIter::new(code) {
360 if !inst.immediate.is_empty()
362 && let Some(push_value) = U256::try_from_be_slice(inst.immediate)
363 && push_value != U256::ZERO
364 {
365 self.insert_value_u256(push_value);
366 }
367 }
368 }
369
370 fn insert_storage_value(
373 &mut self,
374 slot: &U256,
375 value: &U256,
376 slot_identifier: Option<&SlotIdentifier>,
377 mapping_slots: Option<&MappingSlots>,
378 ) {
379 let slot = B256::from(*slot);
380 let value = B256::from(*value);
381
382 self.insert_value(slot);
384
385 if let Some(slot_identifier) = slot_identifier
387 && let Some(slot_info) = slot_identifier.identify(&slot, mapping_slots)
389 && slot_info.decode(value).is_some()
390 {
391 trace!(?slot_info, "inserting typed storage value");
392 if !self.samples_seeded {
393 self.seed_samples();
394 }
395 self.sample_values.entry(slot_info.slot_type.dyn_sol_type).or_default().insert(value);
396 } else {
397 self.insert_value_u256(value.into());
398 }
399 }
400
401 fn insert_address(&mut self, address: Address) {
404 if self.addresses.len() < self.config.max_fuzz_dictionary_addresses {
405 self.addresses.insert(address);
406 }
407 }
408
409 fn insert_value(&mut self, value: B256) -> bool {
415 let insert = !self.values_full();
416 if insert {
417 let new_value = self.state_values.insert(value);
418 let counter = if new_value { &mut self.misses } else { &mut self.hits };
419 *counter += 1;
420 }
421 insert
422 }
423
424 fn insert_value_u256(&mut self, value: U256) -> bool {
425 let one = U256::from(1);
427 self.insert_value(value.into())
428 | self.insert_value((value.wrapping_sub(one)).into())
429 | self.insert_value((value.wrapping_add(one)).into())
430 }
431
432 fn values_full(&self) -> bool {
433 self.state_values.len() >= self.config.max_fuzz_dictionary_values
434 }
435
436 pub fn insert_sample_values(
440 &mut self,
441 sample_values: impl IntoIterator<Item = DynSolValue>,
442 limit: u32,
443 ) {
444 if !self.samples_seeded {
445 self.seed_samples();
446 }
447 for sample in sample_values {
448 if let (Some(sample_type), Some(sample_value)) = (sample.as_type(), sample.as_word()) {
449 if let Some(values) = self.sample_values.get_mut(&sample_type) {
450 if values.len() < limit as usize {
451 values.insert(sample_value);
452 } else {
453 self.insert_value(sample_value);
455 }
456 } else {
457 self.sample_values.entry(sample_type).or_default().insert(sample_value);
458 }
459 }
460 }
461 }
462
463 pub fn values(&self) -> &B256IndexSet {
464 &self.state_values
465 }
466
467 pub fn len(&self) -> usize {
468 self.state_values.len()
469 }
470
471 pub fn is_empty(&self) -> bool {
472 self.state_values.is_empty()
473 }
474
475 #[inline]
480 pub fn samples(&self, param_type: &DynSolType) -> Option<&B256IndexSet> {
481 if !self.samples_seeded {
483 return self.literal_values.get().words.get(param_type);
484 }
485
486 self.sample_values.get(param_type)
487 }
488
489 #[inline]
491 pub fn ast_strings(&self) -> &IndexSet<String> {
492 &self.literal_values.get().strings
493 }
494
495 #[inline]
497 pub fn ast_bytes(&self) -> &IndexSet<Bytes> {
498 &self.literal_values.get().bytes
499 }
500
501 #[inline]
502 pub fn addresses(&self) -> &AddressIndexSet {
503 &self.addresses
504 }
505
506 pub fn revert(&mut self) {
508 self.state_values.truncate(self.db_state_values);
509 self.addresses.truncate(self.db_addresses);
510 }
511
512 pub fn log_stats(&self) {
513 trace!(
514 addresses.len = self.addresses.len(),
515 sample.len = self.sample_values.len(),
516 state.len = self.state_values.len(),
517 state.misses = self.misses,
518 state.hits = self.hits,
519 "FuzzDictionary stats",
520 );
521 }
522
523 #[cfg(test)]
524 pub(crate) fn seed_literals(&mut self, map: super::LiteralMaps) {
526 self.literal_values.set(map);
527 }
528}