foundry_evm_fuzz/strategies/
state.rs1use crate::invariant::{BasicTxDetails, 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, B256IndexSet, HashMap},
7};
8use foundry_common::ignore_metadata_hash;
9use foundry_config::FuzzDictionaryConfig;
10use foundry_evm_core::utils::StateChangeset;
11use parking_lot::{RawRwLock, RwLock, lock_api::RwLockReadGuard};
12use revm::{
13 bytecode::opcode,
14 database::{CacheDB, DatabaseRef, DbAccount},
15 state::AccountInfo,
16};
17use std::{collections::BTreeMap, fmt, sync::Arc};
18
19const PUSH_BYTE_ANALYSIS_LIMIT: usize = 24 * 1024;
24
25#[derive(Clone, Debug)]
29pub struct EvmFuzzState {
30 inner: Arc<RwLock<FuzzDictionary>>,
31 pub deployed_libs: Vec<Address>,
33}
34
35impl EvmFuzzState {
36 pub fn new<DB: DatabaseRef>(
37 db: &CacheDB<DB>,
38 config: FuzzDictionaryConfig,
39 deployed_libs: &[Address],
40 ) -> Self {
41 let mut accs = db.cache.accounts.iter().collect::<Vec<_>>();
43 accs.sort_by_key(|(address, _)| *address);
44
45 let mut dictionary = FuzzDictionary::new(config);
47 dictionary.insert_db_values(accs);
48 Self { inner: Arc::new(RwLock::new(dictionary)), deployed_libs: deployed_libs.to_vec() }
49 }
50
51 pub fn collect_values(&self, values: impl IntoIterator<Item = B256>) {
52 let mut dict = self.inner.write();
53 for value in values {
54 dict.insert_value(value);
55 }
56 }
57
58 pub fn collect_values_from_call(
61 &self,
62 fuzzed_contracts: &FuzzRunIdentifiedContracts,
63 tx: &BasicTxDetails,
64 result: &Bytes,
65 logs: &[Log],
66 state_changeset: &StateChangeset,
67 run_depth: u32,
68 ) {
69 let mut dict = self.inner.write();
70 {
71 let targets = fuzzed_contracts.targets.lock();
72 let (target_abi, target_function) = targets.fuzzed_artifacts(tx);
73 dict.insert_logs_values(target_abi, logs, run_depth);
74 dict.insert_result_values(target_function, result, run_depth);
75 }
76 dict.insert_new_state_values(state_changeset);
77 }
78
79 pub fn revert(&self) {
84 self.inner.write().revert();
85 }
86
87 pub fn dictionary_read(&self) -> RwLockReadGuard<'_, RawRwLock, FuzzDictionary> {
88 self.inner.read()
89 }
90
91 pub fn log_stats(&self) {
93 self.inner.read().log_stats();
94 }
95}
96
97#[derive(Default)]
100pub struct FuzzDictionary {
101 state_values: B256IndexSet,
103 addresses: AddressIndexSet,
105 config: FuzzDictionaryConfig,
107 db_state_values: usize,
110 db_addresses: usize,
113 sample_values: HashMap<DynSolType, B256IndexSet>,
115
116 misses: usize,
117 hits: usize,
118}
119
120impl fmt::Debug for FuzzDictionary {
121 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122 f.debug_struct("FuzzDictionary")
123 .field("state_values", &self.state_values.len())
124 .field("addresses", &self.addresses)
125 .finish()
126 }
127}
128
129impl FuzzDictionary {
130 pub fn new(config: FuzzDictionaryConfig) -> Self {
131 let mut dictionary = Self { config, ..Default::default() };
132 dictionary.prefill();
133 dictionary
134 }
135
136 fn prefill(&mut self) {
138 self.insert_value(B256::ZERO);
139 }
140
141 fn insert_db_values(&mut self, db_state: Vec<(&Address, &DbAccount)>) {
144 for (address, account) in db_state {
145 self.insert_value(address.into_word());
147 self.insert_push_bytes_values(address, &account.info);
149 if self.config.include_storage {
151 let values = account.storage.iter().collect::<BTreeMap<_, _>>();
153 for (slot, value) in values {
154 self.insert_storage_value(slot, value);
155 }
156 }
157 }
158
159 if self.values().is_empty() {
162 self.insert_value(Address::random().into_word());
164 }
165
166 self.db_state_values = self.state_values.len();
169 self.db_addresses = self.addresses.len();
170 }
171
172 fn insert_result_values(
174 &mut self,
175 function: Option<&Function>,
176 result: &Bytes,
177 run_depth: u32,
178 ) {
179 if let Some(function) = function
180 && !function.outputs.is_empty()
181 {
182 if let Ok(decoded_result) = function.abi_decode_output(result) {
184 self.insert_sample_values(decoded_result, run_depth);
185 }
186 }
187 }
188
189 fn insert_logs_values(&mut self, abi: Option<&JsonAbi>, logs: &[Log], run_depth: u32) {
191 let mut samples = Vec::new();
192 for log in logs {
194 let mut log_decoded = false;
195 if let Some(abi) = abi {
197 for event in abi.events() {
198 if let Ok(decoded_event) = event.decode_log(log) {
199 samples.extend(decoded_event.indexed);
200 samples.extend(decoded_event.body);
201 log_decoded = true;
202 break;
203 }
204 }
205 }
206
207 if !log_decoded {
209 for &topic in log.topics() {
210 self.insert_value(topic);
211 }
212 let chunks = log.data.data.chunks_exact(32);
213 let rem = chunks.remainder();
214 for chunk in chunks {
215 self.insert_value(chunk.try_into().unwrap());
216 }
217 if !rem.is_empty() {
218 self.insert_value(B256::right_padding_from(rem));
219 }
220 }
221 }
222
223 self.insert_sample_values(samples, run_depth);
225 }
226
227 fn insert_new_state_values(&mut self, state_changeset: &StateChangeset) {
230 for (address, account) in state_changeset {
231 self.insert_value(address.into_word());
233 self.insert_push_bytes_values(address, &account.info);
235 if self.config.include_storage {
237 for (slot, value) in &account.storage {
238 self.insert_storage_value(slot, &value.present_value);
239 }
240 }
241 }
242 }
243
244 fn insert_push_bytes_values(&mut self, address: &Address, account_info: &AccountInfo) {
248 if self.config.include_push_bytes && !self.addresses.contains(address) {
249 if let Some(code) = &account_info.code {
251 self.insert_address(*address);
252 self.collect_push_bytes(ignore_metadata_hash(code.original_byte_slice()));
253 }
254 }
255 }
256
257 fn collect_push_bytes(&mut self, code: &[u8]) {
258 let mut i = 0;
259 let len = code.len().min(PUSH_BYTE_ANALYSIS_LIMIT);
260 while i < len {
261 let op = code[i];
262 if (opcode::PUSH1..=opcode::PUSH32).contains(&op) {
263 let push_size = (op - opcode::PUSH1 + 1) as usize;
264 let push_start = i + 1;
265 let push_end = push_start + push_size;
266 if push_start > code.len() || push_end > code.len() {
269 break;
270 }
271
272 let push_value = U256::try_from_be_slice(&code[push_start..push_end]).unwrap();
273 if push_value != U256::ZERO {
274 self.insert_value(push_value.into());
276
277 self.insert_value((push_value - U256::from(1)).into());
279
280 if push_value != U256::MAX {
281 self.insert_value((push_value + U256::from(1)).into());
282 }
283 }
284
285 i += push_size;
286 }
287 i += 1;
288 }
289 }
290
291 fn insert_storage_value(&mut self, storage_slot: &U256, storage_value: &U256) {
294 self.insert_value(B256::from(*storage_slot));
295 self.insert_value(B256::from(*storage_value));
296 if *storage_value != U256::ZERO {
298 let below_value = storage_value - U256::from(1);
299 self.insert_value(B256::from(below_value));
300 }
301 if *storage_value != U256::MAX {
302 let above_value = storage_value + U256::from(1);
303 self.insert_value(B256::from(above_value));
304 }
305 }
306
307 fn insert_address(&mut self, address: Address) {
310 if self.addresses.len() < self.config.max_fuzz_dictionary_addresses {
311 self.addresses.insert(address);
312 }
313 }
314
315 fn insert_value(&mut self, value: B256) {
318 if self.state_values.len() < self.config.max_fuzz_dictionary_values {
319 let new_value = self.state_values.insert(value);
320 let counter = if new_value { &mut self.misses } else { &mut self.hits };
321 *counter += 1;
322 }
323 }
324
325 pub fn insert_sample_values(
329 &mut self,
330 sample_values: impl IntoIterator<Item = DynSolValue>,
331 limit: u32,
332 ) {
333 for sample in sample_values {
334 if let (Some(sample_type), Some(sample_value)) = (sample.as_type(), sample.as_word()) {
335 if let Some(values) = self.sample_values.get_mut(&sample_type) {
336 if values.len() < limit as usize {
337 values.insert(sample_value);
338 } else {
339 self.insert_value(sample_value);
341 }
342 } else {
343 self.sample_values.entry(sample_type).or_default().insert(sample_value);
344 }
345 }
346 }
347 }
348
349 pub fn values(&self) -> &B256IndexSet {
350 &self.state_values
351 }
352
353 pub fn len(&self) -> usize {
354 self.state_values.len()
355 }
356
357 pub fn is_empty(&self) -> bool {
358 self.state_values.is_empty()
359 }
360
361 #[inline]
362 pub fn samples(&self, param_type: &DynSolType) -> Option<&B256IndexSet> {
363 self.sample_values.get(param_type)
364 }
365
366 #[inline]
367 pub fn addresses(&self) -> &AddressIndexSet {
368 &self.addresses
369 }
370
371 pub fn revert(&mut self) {
373 self.state_values.truncate(self.db_state_values);
374 self.addresses.truncate(self.db_addresses);
375 }
376
377 pub fn log_stats(&self) {
378 trace!(
379 addresses.len = self.addresses.len(),
380 sample.len = self.sample_values.len(),
381 state.len = self.state_values.len(),
382 state.misses = self.misses,
383 state.hits = self.hits,
384 "FuzzDictionary stats",
385 );
386 }
387}