1use crate::invariant::{BasicTxDetails, FuzzRunIdentifiedContracts};
2use alloy_dyn_abi::{DynSolType, DynSolValue, EventExt, FunctionExt};
3use alloy_json_abi::{Function, JsonAbi};
4use alloy_primitives::{
5 map::{AddressIndexSet, B256IndexSet, HashMap},
6 Address, Bytes, Log, B256, U256,
7};
8use foundry_config::FuzzDictionaryConfig;
9use foundry_evm_core::utils::StateChangeset;
10use parking_lot::{lock_api::RwLockReadGuard, RawRwLock, RwLock};
11use revm::{
12 db::{CacheDB, DatabaseRef, DbAccount},
13 interpreter::opcode,
14 primitives::AccountInfo,
15};
16use std::{collections::BTreeMap, fmt, sync::Arc};
1718/// The maximum number of bytes we will look at in bytecodes to find push bytes (24 KiB).
19///
20/// This is to limit the performance impact of fuzz tests that might deploy arbitrarily sized
21/// bytecode (as is the case with Solmate).
22const PUSH_BYTE_ANALYSIS_LIMIT: usize = 24 * 1024;
2324/// A set of arbitrary 32 byte data from the VM used to generate values for the strategy.
25///
26/// Wrapped in a shareable container.
27#[derive(Clone, Debug)]
28pub struct EvmFuzzState {
29 inner: Arc<RwLock<FuzzDictionary>>,
30/// Addresses of external libraries deployed in test setup, excluded from fuzz test inputs.
31pub deployed_libs: Vec<Address>,
32}
3334impl EvmFuzzState {
35pub fn new<DB: DatabaseRef>(
36 db: &CacheDB<DB>,
37 config: FuzzDictionaryConfig,
38 deployed_libs: &[Address],
39 ) -> Self {
40// Sort accounts to ensure deterministic dictionary generation from the same setUp state.
41let mut accs = db.accounts.iter().collect::<Vec<_>>();
42accs.sort_by_key(|(address, _)| *address);
4344// Create fuzz dictionary and insert values from db state.
45let mut dictionary = FuzzDictionary::new(config);
46dictionary.insert_db_values(accs);
47Self { inner: Arc::new(RwLock::new(dictionary)), deployed_libs: deployed_libs.to_vec() }
48 }
4950pub fn collect_values(&self, values: impl IntoIterator<Item = B256>) {
51let mut dict = self.inner.write();
52for value in values {
53 dict.insert_value(value);
54 }
55 }
5657/// Collects state changes from a [StateChangeset] and logs into an [EvmFuzzState] according to
58 /// the given [FuzzDictionaryConfig].
59pub fn collect_values_from_call(
60&self,
61 fuzzed_contracts: &FuzzRunIdentifiedContracts,
62 tx: &BasicTxDetails,
63 result: &Bytes,
64 logs: &[Log],
65 state_changeset: &StateChangeset,
66 run_depth: u32,
67 ) {
68let mut dict = self.inner.write();
69 {
70let targets = fuzzed_contracts.targets.lock();
71let (target_abi, target_function) = targets.fuzzed_artifacts(tx);
72dict.insert_logs_values(target_abi, logs, run_depth);
73dict.insert_result_values(target_function, result, run_depth);
74 }
75dict.insert_new_state_values(state_changeset);
76 }
7778/// Removes all newly added entries from the dictionary.
79 ///
80 /// Should be called between fuzz/invariant runs to avoid accumulating data derived from fuzz
81 /// inputs.
82pub fn revert(&self) {
83self.inner.write().revert();
84 }
8586pub fn dictionary_read(&self) -> RwLockReadGuard<'_, RawRwLock, FuzzDictionary> {
87self.inner.read()
88 }
8990/// Logs stats about the current state.
91pub fn log_stats(&self) {
92self.inner.read().log_stats();
93 }
94}
9596// We're using `IndexSet` to have a stable element order when restoring persisted state, as well as
97// for performance when iterating over the sets.
98#[derive(Default)]
99pub struct FuzzDictionary {
100/// Collected state values.
101state_values: B256IndexSet,
102/// Addresses that already had their PUSH bytes collected.
103addresses: AddressIndexSet,
104/// Configuration for the dictionary.
105config: FuzzDictionaryConfig,
106/// Number of state values initially collected from db.
107 /// Used to revert new collected values at the end of each run.
108db_state_values: usize,
109/// Number of address values initially collected from db.
110 /// Used to revert new collected addresses at the end of each run.
111db_addresses: usize,
112/// Sample typed values that are collected from call result and used across invariant runs.
113sample_values: HashMap<DynSolType, B256IndexSet>,
114115 misses: usize,
116 hits: usize,
117}
118119impl fmt::Debugfor FuzzDictionary {
120fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121f.debug_struct("FuzzDictionary")
122 .field("state_values", &self.state_values.len())
123 .field("addresses", &self.addresses)
124 .finish()
125 }
126}
127128impl FuzzDictionary {
129pub fn new(config: FuzzDictionaryConfig) -> Self {
130let mut dictionary = Self { config, ..Default::default() };
131dictionary.prefill();
132dictionary133 }
134135/// Insert common values into the dictionary at initialization.
136fn prefill(&mut self) {
137self.insert_value(B256::ZERO);
138 }
139140/// Insert values from initial db state into fuzz dictionary.
141 /// These values are persisted across invariant runs.
142fn insert_db_values(&mut self, db_state: Vec<(&Address, &DbAccount)>) {
143for (address, account) in db_state {
144// Insert basic account information
145self.insert_value(address.into_word());
146// Insert push bytes
147self.insert_push_bytes_values(address, &account.info);
148// Insert storage values.
149if self.config.include_storage {
150// Sort storage values before inserting to ensure deterministic dictionary.
151let values = account.storage.iter().collect::<BTreeMap<_, _>>();
152for (slot, value) in values {
153self.insert_storage_value(slot, value);
154 }
155 }
156 }
157158// We need at least some state data if DB is empty,
159 // otherwise we can't select random data for state fuzzing.
160if self.values().is_empty() {
161// Prefill with a random address.
162self.insert_value(Address::random().into_word());
163 }
164165// Record number of values and addresses inserted from db to be used for reverting at the
166 // end of each run.
167self.db_state_values = self.state_values.len();
168self.db_addresses = self.addresses.len();
169 }
170171/// Insert values collected from call result into fuzz dictionary.
172fn insert_result_values(
173&mut self,
174 function: Option<&Function>,
175 result: &Bytes,
176 run_depth: u32,
177 ) {
178if let Some(function) = function {
179if !function.outputs.is_empty() {
180// Decode result and collect samples to be used in subsequent fuzz runs.
181if let Ok(decoded_result) = function.abi_decode_output(result, false) {
182self.insert_sample_values(decoded_result, run_depth);
183 }
184 }
185 }
186 }
187188/// Insert values from call log topics and data into fuzz dictionary.
189fn insert_logs_values(&mut self, abi: Option<&JsonAbi>, logs: &[Log], run_depth: u32) {
190let mut samples = Vec::new();
191// Decode logs with known events and collect samples from indexed fields and event body.
192for log in logs {
193let mut log_decoded = false;
194// Try to decode log with events from contract abi.
195if let Some(abi) = abi {
196for event in abi.events() {
197if let Ok(decoded_event) = event.decode_log(log, false) {
198 samples.extend(decoded_event.indexed);
199 samples.extend(decoded_event.body);
200 log_decoded = true;
201break;
202 }
203 }
204 }
205206// If we weren't able to decode event then we insert raw data in fuzz dictionary.
207if !log_decoded {
208for &topic in log.topics() {
209self.insert_value(topic);
210 }
211let chunks = log.data.data.chunks_exact(32);
212let rem = chunks.remainder();
213for chunk in chunks {
214self.insert_value(chunk.try_into().unwrap());
215 }
216if !rem.is_empty() {
217self.insert_value(B256::right_padding_from(rem));
218 }
219 }
220 }
221222// Insert samples collected from current call in fuzz dictionary.
223self.insert_sample_values(samples, run_depth);
224 }
225226/// Insert values from call state changeset into fuzz dictionary.
227 /// These values are removed at the end of current run.
228fn insert_new_state_values(&mut self, state_changeset: &StateChangeset) {
229for (address, account) in state_changeset {
230// Insert basic account information.
231self.insert_value(address.into_word());
232// Insert push bytes.
233self.insert_push_bytes_values(address, &account.info);
234// Insert storage values.
235if self.config.include_storage {
236for (slot, value) in &account.storage {
237self.insert_storage_value(slot, &value.present_value);
238 }
239 }
240 }
241 }
242243/// Insert values from push bytes into fuzz dictionary.
244 /// Values are collected only once for a given address.
245 /// If values are newly collected then they are removed at the end of current run.
246fn insert_push_bytes_values(&mut self, address: &Address, account_info: &AccountInfo) {
247if self.config.include_push_bytes && !self.addresses.contains(address) {
248// Insert push bytes
249if let Some(code) = &account_info.code {
250self.insert_address(*address);
251self.collect_push_bytes(code.bytes_slice());
252 }
253 }
254 }
255256fn collect_push_bytes(&mut self, code: &[u8]) {
257let mut i = 0;
258let len = code.len().min(PUSH_BYTE_ANALYSIS_LIMIT);
259while i < len {
260let op = code[i];
261if (opcode::PUSH1..=opcode::PUSH32).contains(&op) {
262let push_size = (op - opcode::PUSH1 + 1) as usize;
263let push_start = i + 1;
264let push_end = push_start + push_size;
265// As a precaution, if a fuzz test deploys malformed bytecode (such as using
266 // `CREATE2`) this will terminate the loop early.
267if push_start > code.len() || push_end > code.len() {
268break;
269 }
270271let push_value = U256::try_from_be_slice(&code[push_start..push_end]).unwrap();
272if push_value != U256::ZERO {
273// Never add 0 to the dictionary as it's always present.
274self.insert_value(push_value.into());
275276// Also add the value below and above the push value to the dictionary.
277self.insert_value((push_value - U256::from(1)).into());
278279if push_value != U256::MAX {
280self.insert_value((push_value + U256::from(1)).into());
281 }
282 }
283284 i += push_size;
285 }
286 i += 1;
287 }
288 }
289290/// Insert values from single storage slot and storage value into fuzz dictionary.
291 /// If storage values are newly collected then they are removed at the end of current run.
292fn insert_storage_value(&mut self, storage_slot: &U256, storage_value: &U256) {
293self.insert_value(B256::from(*storage_slot));
294self.insert_value(B256::from(*storage_value));
295// also add the value below and above the storage value to the dictionary.
296if *storage_value != U256::ZERO {
297let below_value = storage_value - U256::from(1);
298self.insert_value(B256::from(below_value));
299 }
300if *storage_value != U256::MAX {
301let above_value = storage_value + U256::from(1);
302self.insert_value(B256::from(above_value));
303 }
304 }
305306/// Insert address into fuzz dictionary.
307 /// If address is newly collected then it is removed by index at the end of current run.
308fn insert_address(&mut self, address: Address) {
309if self.addresses.len() < self.config.max_fuzz_dictionary_addresses {
310self.addresses.insert(address);
311 }
312 }
313314/// Insert raw value into fuzz dictionary.
315 /// If value is newly collected then it is removed by index at the end of current run.
316fn insert_value(&mut self, value: B256) {
317if self.state_values.len() < self.config.max_fuzz_dictionary_values {
318let new_value = self.state_values.insert(value);
319let counter = if new_value { &mut self.misses } else { &mut self.hits };
320*counter += 1;
321 }
322 }
323324/// Insert sample values that are reused across multiple runs.
325 /// The number of samples is limited to invariant run depth.
326 /// If collected samples limit is reached then values are inserted as regular values.
327pub fn insert_sample_values(
328&mut self,
329 sample_values: impl IntoIterator<Item = DynSolValue>,
330 limit: u32,
331 ) {
332for sample in sample_values {
333if let (Some(sample_type), Some(sample_value)) = (sample.as_type(), sample.as_word()) {
334if let Some(values) = self.sample_values.get_mut(&sample_type) {
335if values.len() < limit as usize {
336 values.insert(sample_value);
337 } else {
338// Insert as state value (will be removed at the end of the run).
339self.insert_value(sample_value);
340 }
341 } else {
342self.sample_values.entry(sample_type).or_default().insert(sample_value);
343 }
344 }
345 }
346 }
347348pub fn values(&self) -> &B256IndexSet {
349&self.state_values
350 }
351352pub fn len(&self) -> usize {
353self.state_values.len()
354 }
355356pub fn is_empty(&self) -> bool {
357self.state_values.is_empty()
358 }
359360#[inline]
361pub fn samples(&self, param_type: &DynSolType) -> Option<&B256IndexSet> {
362self.sample_values.get(param_type)
363 }
364365#[inline]
366pub fn addresses(&self) -> &AddressIndexSet {
367&self.addresses
368 }
369370/// Revert values and addresses collected during the run by truncating to initial db len.
371pub fn revert(&mut self) {
372self.state_values.truncate(self.db_state_values);
373self.addresses.truncate(self.db_addresses);
374 }
375376pub fn log_stats(&self) {
377trace!(
378 addresses.len = self.addresses.len(),
379 sample.len = self.sample_values.len(),
380 state.len = self.state_values.len(),
381 state.misses = self.misses,
382 state.hits = self.hits,
383"FuzzDictionary stats",
384 );
385 }
386}