foundry_evm_fuzz/strategies/
state.rs1use crate::{BasicTxDetails, invariant::FuzzRunIdentifiedContracts};
2use alloy_dyn_abi::{DynSolType, DynSolValue, EventExt, FunctionExt};
3use alloy_json_abi::{Function, JsonAbi};
4use alloy_primitives::{
5 Address, B256, Bytes, Log, U256,
6 map::{AddressIndexSet, AddressMap, B256IndexSet, HashMap},
7};
8use foundry_common::{
9 ignore_metadata_hash, mapping_slots::MappingSlots, slot_identifier::SlotIdentifier,
10};
11use foundry_compilers::artifacts::StorageLayout;
12use foundry_config::FuzzDictionaryConfig;
13use foundry_evm_core::{bytecode::InstIter, utils::StateChangeset};
14use parking_lot::{RawRwLock, RwLock, lock_api::RwLockReadGuard};
15use revm::{
16 database::{CacheDB, DatabaseRef, DbAccount},
17 state::AccountInfo,
18};
19use std::{collections::BTreeMap, fmt, sync::Arc};
20
21const PUSH_BYTE_ANALYSIS_LIMIT: usize = 24 * 1024;
26
27#[derive(Clone, Debug)]
31pub struct EvmFuzzState {
32 inner: Arc<RwLock<FuzzDictionary>>,
33 pub deployed_libs: Vec<Address>,
35 pub(crate) mapping_slots: Option<AddressMap<MappingSlots>>,
40}
41
42impl EvmFuzzState {
43 pub fn new<DB: DatabaseRef>(
44 db: &CacheDB<DB>,
45 config: FuzzDictionaryConfig,
46 deployed_libs: &[Address],
47 ) -> Self {
48 let mut accs = db.cache.accounts.iter().collect::<Vec<_>>();
50 accs.sort_by_key(|(address, _)| *address);
51
52 let mut dictionary = FuzzDictionary::new(config);
54 dictionary.insert_db_values(accs);
55 Self {
56 inner: Arc::new(RwLock::new(dictionary)),
57 deployed_libs: deployed_libs.to_vec(),
58 mapping_slots: None,
59 }
60 }
61
62 pub fn with_mapping_slots(mut self, mapping_slots: AddressMap<MappingSlots>) -> Self {
63 self.mapping_slots = Some(mapping_slots);
64 self
65 }
66
67 pub fn collect_values(&self, values: impl IntoIterator<Item = B256>) {
68 let mut dict = self.inner.write();
69 for value in values {
70 dict.insert_value(value);
71 }
72 }
73
74 pub fn collect_values_from_call(
77 &self,
78 fuzzed_contracts: &FuzzRunIdentifiedContracts,
79 tx: &BasicTxDetails,
80 result: &Bytes,
81 logs: &[Log],
82 state_changeset: &StateChangeset,
83 run_depth: u32,
84 ) {
85 let mut dict = self.inner.write();
86 {
87 let targets = fuzzed_contracts.targets.lock();
88 let (target_abi, target_function) = targets.fuzzed_artifacts(tx);
89 dict.insert_logs_values(target_abi, logs, run_depth);
90 dict.insert_result_values(target_function, result, run_depth);
91 let storage_layouts = targets.get_storage_layouts();
93 dict.insert_new_state_values(
94 state_changeset,
95 &storage_layouts,
96 self.mapping_slots.as_ref(),
97 );
98 }
99 }
100
101 pub fn revert(&self) {
106 self.inner.write().revert();
107 }
108
109 pub fn dictionary_read(&self) -> RwLockReadGuard<'_, RawRwLock, FuzzDictionary> {
110 self.inner.read()
111 }
112
113 pub fn log_stats(&self) {
115 self.inner.read().log_stats();
116 }
117}
118
119#[derive(Default)]
122pub struct FuzzDictionary {
123 state_values: B256IndexSet,
125 addresses: AddressIndexSet,
127 config: FuzzDictionaryConfig,
129 db_state_values: usize,
132 db_addresses: usize,
135 sample_values: HashMap<DynSolType, B256IndexSet>,
137
138 misses: usize,
139 hits: usize,
140}
141
142impl fmt::Debug for FuzzDictionary {
143 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144 f.debug_struct("FuzzDictionary")
145 .field("state_values", &self.state_values.len())
146 .field("addresses", &self.addresses)
147 .finish()
148 }
149}
150
151impl FuzzDictionary {
152 pub fn new(config: FuzzDictionaryConfig) -> Self {
153 let mut dictionary = Self { config, ..Default::default() };
154 dictionary.prefill();
155 dictionary
156 }
157
158 fn prefill(&mut self) {
160 self.insert_value(B256::ZERO);
161 }
162
163 fn insert_db_values(&mut self, db_state: Vec<(&Address, &DbAccount)>) {
166 for (address, account) in db_state {
167 self.insert_value(address.into_word());
169 self.insert_push_bytes_values(address, &account.info);
171 if self.config.include_storage {
173 let values = account.storage.iter().collect::<BTreeMap<_, _>>();
175 for (slot, value) in values {
176 self.insert_storage_value(slot, value, None, None);
177 }
178 }
179 }
180
181 if self.values().is_empty() {
184 self.insert_value(Address::random().into_word());
186 }
187
188 self.db_state_values = self.state_values.len();
191 self.db_addresses = self.addresses.len();
192 }
193
194 fn insert_result_values(
196 &mut self,
197 function: Option<&Function>,
198 result: &Bytes,
199 run_depth: u32,
200 ) {
201 if let Some(function) = function
202 && !function.outputs.is_empty()
203 {
204 if let Ok(decoded_result) = function.abi_decode_output(result) {
206 self.insert_sample_values(decoded_result, run_depth);
207 }
208 }
209 }
210
211 fn insert_logs_values(&mut self, abi: Option<&JsonAbi>, logs: &[Log], run_depth: u32) {
213 let mut samples = Vec::new();
214 for log in logs {
216 let mut log_decoded = false;
217 if let Some(abi) = abi {
219 for event in abi.events() {
220 if let Ok(decoded_event) = event.decode_log(log) {
221 samples.extend(decoded_event.indexed);
222 samples.extend(decoded_event.body);
223 log_decoded = true;
224 break;
225 }
226 }
227 }
228
229 if !log_decoded {
231 for &topic in log.topics() {
232 self.insert_value(topic);
233 }
234 let chunks = log.data.data.chunks_exact(32);
235 let rem = chunks.remainder();
236 for chunk in chunks {
237 self.insert_value(chunk.try_into().unwrap());
238 }
239 if !rem.is_empty() {
240 self.insert_value(B256::right_padding_from(rem));
241 }
242 }
243 }
244
245 self.insert_sample_values(samples, run_depth);
247 }
248
249 fn insert_new_state_values(
252 &mut self,
253 state_changeset: &StateChangeset,
254 storage_layouts: &HashMap<Address, Arc<StorageLayout>>,
255 mapping_slots: Option<&AddressMap<MappingSlots>>,
256 ) {
257 for (address, account) in state_changeset {
258 self.insert_value(address.into_word());
260 self.insert_push_bytes_values(address, &account.info);
262 if self.config.include_storage {
264 let storage_layout = storage_layouts.get(address).cloned();
265 trace!(
266 "{address:?} has mapping_slots {}",
267 mapping_slots.is_some_and(|m| m.contains_key(address))
268 );
269 let mapping_slots = mapping_slots.and_then(|m| m.get(address));
270 for (slot, value) in &account.storage {
271 self.insert_storage_value(
272 slot,
273 &value.present_value,
274 storage_layout.as_deref(),
275 mapping_slots,
276 );
277 }
278 }
279 }
280 }
281
282 fn insert_push_bytes_values(&mut self, address: &Address, account_info: &AccountInfo) {
286 if self.config.include_push_bytes
287 && !self.addresses.contains(address)
288 && let Some(code) = &account_info.code
289 {
290 self.insert_address(*address);
291 if !self.values_full() {
292 self.collect_push_bytes(ignore_metadata_hash(code.original_byte_slice()));
293 }
294 }
295 }
296
297 fn collect_push_bytes(&mut self, code: &[u8]) {
298 let len = code.len().min(PUSH_BYTE_ANALYSIS_LIMIT);
299 let code = &code[..len];
300 for inst in InstIter::new(code) {
301 if !inst.immediate.is_empty()
303 && let Some(push_value) = U256::try_from_be_slice(inst.immediate)
304 && push_value != U256::ZERO
305 {
306 self.insert_value_u256(push_value);
307 }
308 }
309 }
310
311 fn insert_storage_value(
314 &mut self,
315 slot: &U256,
316 value: &U256,
317 layout: Option<&StorageLayout>,
318 mapping_slots: Option<&MappingSlots>,
319 ) {
320 let slot = B256::from(*slot);
321 let value = B256::from(*value);
322
323 self.insert_value(slot);
325
326 if let Some(slot_identifier) =
328 layout.map(|l| SlotIdentifier::new(l.clone().into()))
329 && let Some(slot_info) = slot_identifier.identify(&slot, mapping_slots) && slot_info.decode(value).is_some()
331 {
332 trace!(?slot_info, "inserting typed storage value");
333 self.sample_values.entry(slot_info.slot_type.dyn_sol_type).or_default().insert(value);
334 } else {
335 self.insert_value_u256(value.into());
336 }
337 }
338
339 fn insert_address(&mut self, address: Address) {
342 if self.addresses.len() < self.config.max_fuzz_dictionary_addresses {
343 self.addresses.insert(address);
344 }
345 }
346
347 fn insert_value(&mut self, value: B256) -> bool {
353 let insert = !self.values_full();
354 if insert {
355 let new_value = self.state_values.insert(value);
356 let counter = if new_value { &mut self.misses } else { &mut self.hits };
357 *counter += 1;
358 }
359 insert
360 }
361
362 fn insert_value_u256(&mut self, value: U256) -> bool {
363 let one = U256::from(1);
365 self.insert_value(value.into())
366 | self.insert_value((value.wrapping_sub(one)).into())
367 | self.insert_value((value.wrapping_add(one)).into())
368 }
369
370 fn values_full(&self) -> bool {
371 self.state_values.len() >= self.config.max_fuzz_dictionary_values
372 }
373
374 pub fn insert_sample_values(
378 &mut self,
379 sample_values: impl IntoIterator<Item = DynSolValue>,
380 limit: u32,
381 ) {
382 for sample in sample_values {
383 if let (Some(sample_type), Some(sample_value)) = (sample.as_type(), sample.as_word()) {
384 if let Some(values) = self.sample_values.get_mut(&sample_type) {
385 if values.len() < limit as usize {
386 values.insert(sample_value);
387 } else {
388 self.insert_value(sample_value);
390 }
391 } else {
392 self.sample_values.entry(sample_type).or_default().insert(sample_value);
393 }
394 }
395 }
396 }
397
398 pub fn values(&self) -> &B256IndexSet {
399 &self.state_values
400 }
401
402 pub fn len(&self) -> usize {
403 self.state_values.len()
404 }
405
406 pub fn is_empty(&self) -> bool {
407 self.state_values.is_empty()
408 }
409
410 #[inline]
411 pub fn samples(&self, param_type: &DynSolType) -> Option<&B256IndexSet> {
412 self.sample_values.get(param_type)
413 }
414
415 #[inline]
416 pub fn addresses(&self) -> &AddressIndexSet {
417 &self.addresses
418 }
419
420 pub fn revert(&mut self) {
422 self.state_values.truncate(self.db_state_values);
423 self.addresses.truncate(self.db_addresses);
424 }
425
426 pub fn log_stats(&self) {
427 trace!(
428 addresses.len = self.addresses.len(),
429 sample.len = self.sample_values.len(),
430 state.len = self.state_values.len(),
431 state.misses = self.misses,
432 state.hits = self.hits,
433 "FuzzDictionary stats",
434 );
435 }
436}