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 storage_layout = storage_layouts.get(address).cloned();
323 trace!(
324 "{address:?} has mapping_slots {}",
325 mapping_slots.is_some_and(|m| m.contains_key(address))
326 );
327 let mapping_slots = mapping_slots.and_then(|m| m.get(address));
328 for (slot, value) in &account.storage {
329 self.insert_storage_value(
330 slot,
331 &value.present_value,
332 storage_layout.as_deref(),
333 mapping_slots,
334 );
335 }
336 }
337 }
338 }
339
340 fn insert_push_bytes_values(&mut self, address: &Address, account_info: &AccountInfo) {
344 if self.config.include_push_bytes
345 && !self.addresses.contains(address)
346 && let Some(code) = &account_info.code
347 {
348 self.insert_address(*address);
349 if !self.values_full() {
350 self.collect_push_bytes(ignore_metadata_hash(code.original_byte_slice()));
351 }
352 }
353 }
354
355 fn collect_push_bytes(&mut self, code: &[u8]) {
356 let len = code.len().min(PUSH_BYTE_ANALYSIS_LIMIT);
357 let code = &code[..len];
358 for inst in InstIter::new(code) {
359 if !inst.immediate.is_empty()
361 && let Some(push_value) = U256::try_from_be_slice(inst.immediate)
362 && push_value != U256::ZERO
363 {
364 self.insert_value_u256(push_value);
365 }
366 }
367 }
368
369 fn insert_storage_value(
372 &mut self,
373 slot: &U256,
374 value: &U256,
375 layout: Option<&StorageLayout>,
376 mapping_slots: Option<&MappingSlots>,
377 ) {
378 let slot = B256::from(*slot);
379 let value = B256::from(*value);
380
381 self.insert_value(slot);
383
384 if let Some(slot_identifier) =
386 layout.map(|l| SlotIdentifier::new(l.clone().into()))
387 && let Some(slot_info) = slot_identifier.identify(&slot, mapping_slots) && slot_info.decode(value).is_some()
389 {
390 trace!(?slot_info, "inserting typed storage value");
391 if !self.samples_seeded {
392 self.seed_samples();
393 }
394 self.sample_values.entry(slot_info.slot_type.dyn_sol_type).or_default().insert(value);
395 } else {
396 self.insert_value_u256(value.into());
397 }
398 }
399
400 fn insert_address(&mut self, address: Address) {
403 if self.addresses.len() < self.config.max_fuzz_dictionary_addresses {
404 self.addresses.insert(address);
405 }
406 }
407
408 fn insert_value(&mut self, value: B256) -> bool {
414 let insert = !self.values_full();
415 if insert {
416 let new_value = self.state_values.insert(value);
417 let counter = if new_value { &mut self.misses } else { &mut self.hits };
418 *counter += 1;
419 }
420 insert
421 }
422
423 fn insert_value_u256(&mut self, value: U256) -> bool {
424 let one = U256::from(1);
426 self.insert_value(value.into())
427 | self.insert_value((value.wrapping_sub(one)).into())
428 | self.insert_value((value.wrapping_add(one)).into())
429 }
430
431 fn values_full(&self) -> bool {
432 self.state_values.len() >= self.config.max_fuzz_dictionary_values
433 }
434
435 pub fn insert_sample_values(
439 &mut self,
440 sample_values: impl IntoIterator<Item = DynSolValue>,
441 limit: u32,
442 ) {
443 if !self.samples_seeded {
444 self.seed_samples();
445 }
446 for sample in sample_values {
447 if let (Some(sample_type), Some(sample_value)) = (sample.as_type(), sample.as_word()) {
448 if let Some(values) = self.sample_values.get_mut(&sample_type) {
449 if values.len() < limit as usize {
450 values.insert(sample_value);
451 } else {
452 self.insert_value(sample_value);
454 }
455 } else {
456 self.sample_values.entry(sample_type).or_default().insert(sample_value);
457 }
458 }
459 }
460 }
461
462 pub fn values(&self) -> &B256IndexSet {
463 &self.state_values
464 }
465
466 pub fn len(&self) -> usize {
467 self.state_values.len()
468 }
469
470 pub fn is_empty(&self) -> bool {
471 self.state_values.is_empty()
472 }
473
474 #[inline]
479 pub fn samples(&self, param_type: &DynSolType) -> Option<&B256IndexSet> {
480 if !self.samples_seeded {
482 return self.literal_values.get().words.get(param_type);
483 }
484
485 self.sample_values.get(param_type)
486 }
487
488 #[inline]
490 pub fn ast_strings(&self) -> &IndexSet<String> {
491 &self.literal_values.get().strings
492 }
493
494 #[inline]
496 pub fn ast_bytes(&self) -> &IndexSet<Bytes> {
497 &self.literal_values.get().bytes
498 }
499
500 #[inline]
501 pub fn addresses(&self) -> &AddressIndexSet {
502 &self.addresses
503 }
504
505 pub fn revert(&mut self) {
507 self.state_values.truncate(self.db_state_values);
508 self.addresses.truncate(self.db_addresses);
509 }
510
511 pub fn log_stats(&self) {
512 trace!(
513 addresses.len = self.addresses.len(),
514 sample.len = self.sample_values.len(),
515 state.len = self.state_values.len(),
516 state.misses = self.misses,
517 state.hits = self.hits,
518 "FuzzDictionary stats",
519 );
520 }
521
522 #[cfg(test)]
523 pub(crate) fn seed_literals(&mut self, map: super::LiteralMaps) {
525 self.literal_values.set(map);
526 }
527}