foundry_evm_fuzz/invariant/
mod.rs
1use alloy_json_abi::{Function, JsonAbi};
2use alloy_primitives::{Address, Bytes, Selector};
3use itertools::Either;
4use parking_lot::Mutex;
5use serde::{Deserialize, Serialize};
6use std::{collections::BTreeMap, sync::Arc};
7
8mod call_override;
9pub use call_override::RandomCallGenerator;
10
11mod filters;
12pub use filters::{ArtifactFilters, SenderFilters};
13use foundry_common::{ContractsByAddress, ContractsByArtifact};
14use foundry_evm_core::utils::{StateChangeset, get_function};
15
16#[derive(Clone, Debug)]
21pub struct FuzzRunIdentifiedContracts {
22 pub targets: Arc<Mutex<TargetedContracts>>,
24 pub is_updatable: bool,
26}
27
28impl FuzzRunIdentifiedContracts {
29 pub fn new(targets: TargetedContracts, is_updatable: bool) -> Self {
31 Self { targets: Arc::new(Mutex::new(targets)), is_updatable }
32 }
33
34 pub fn collect_created_contracts(
37 &self,
38 state_changeset: &StateChangeset,
39 project_contracts: &ContractsByArtifact,
40 setup_contracts: &ContractsByAddress,
41 artifact_filters: &ArtifactFilters,
42 created_contracts: &mut Vec<Address>,
43 ) -> eyre::Result<()> {
44 if !self.is_updatable {
45 return Ok(());
46 }
47
48 let mut targets = self.targets.lock();
49 for (address, account) in state_changeset {
50 if setup_contracts.contains_key(address) {
51 continue;
52 }
53 if !account.is_touched() {
54 continue;
55 }
56 let Some(code) = &account.info.code else {
57 continue;
58 };
59 if code.is_empty() {
60 continue;
61 }
62 let Some((artifact, contract)) =
63 project_contracts.find_by_deployed_code(code.original_byte_slice())
64 else {
65 continue;
66 };
67 let Some(functions) =
68 artifact_filters.get_targeted_functions(artifact, &contract.abi)?
69 else {
70 continue;
71 };
72 created_contracts.push(*address);
73 let contract = TargetedContract {
74 identifier: artifact.name.clone(),
75 abi: contract.abi.clone(),
76 targeted_functions: functions,
77 excluded_functions: Vec::new(),
78 };
79 targets.insert(*address, contract);
80 }
81 Ok(())
82 }
83
84 pub fn clear_created_contracts(&self, created_contracts: Vec<Address>) {
86 if !created_contracts.is_empty() {
87 let mut targets = self.targets.lock();
88 for addr in &created_contracts {
89 targets.remove(addr);
90 }
91 }
92 }
93}
94
95#[derive(Clone, Debug, Default)]
97pub struct TargetedContracts {
98 pub inner: BTreeMap<Address, TargetedContract>,
100}
101
102impl TargetedContracts {
103 pub fn new() -> Self {
105 Self::default()
106 }
107
108 pub fn fuzzed_artifacts(&self, tx: &BasicTxDetails) -> (Option<&JsonAbi>, Option<&Function>) {
112 match self.inner.get(&tx.call_details.target) {
113 Some(c) => (
114 Some(&c.abi),
115 c.abi.functions().find(|f| f.selector() == tx.call_details.calldata[..4]),
116 ),
117 None => (None, None),
118 }
119 }
120
121 pub fn fuzzed_functions(&self) -> impl Iterator<Item = (&Address, &Function)> {
124 self.inner
125 .iter()
126 .filter(|(_, c)| !c.abi.functions.is_empty())
127 .flat_map(|(contract, c)| c.abi_fuzzed_functions().map(move |f| (contract, f)))
128 }
129
130 pub fn can_replay(&self, tx: &BasicTxDetails) -> bool {
132 match self.inner.get(&tx.call_details.target) {
133 Some(c) => c.abi.functions().any(|f| f.selector() == tx.call_details.calldata[..4]),
134 None => false,
135 }
136 }
137
138 pub fn fuzzed_metric_key(&self, tx: &BasicTxDetails) -> Option<String> {
141 self.inner.get(&tx.call_details.target).and_then(|contract| {
142 contract
143 .abi
144 .functions()
145 .find(|f| f.selector() == tx.call_details.calldata[..4])
146 .map(|function| format!("{}.{}", contract.identifier.clone(), function.name))
147 })
148 }
149}
150
151impl std::ops::Deref for TargetedContracts {
152 type Target = BTreeMap<Address, TargetedContract>;
153
154 fn deref(&self) -> &Self::Target {
155 &self.inner
156 }
157}
158
159impl std::ops::DerefMut for TargetedContracts {
160 fn deref_mut(&mut self) -> &mut Self::Target {
161 &mut self.inner
162 }
163}
164
165#[derive(Clone, Debug)]
167pub struct TargetedContract {
168 pub identifier: String,
170 pub abi: JsonAbi,
172 pub targeted_functions: Vec<Function>,
174 pub excluded_functions: Vec<Function>,
176}
177
178impl TargetedContract {
179 pub fn new(identifier: String, abi: JsonAbi) -> Self {
181 Self { identifier, abi, targeted_functions: Vec::new(), excluded_functions: Vec::new() }
182 }
183
184 pub fn abi_fuzzed_functions(&self) -> impl Iterator<Item = &Function> {
188 if !self.targeted_functions.is_empty() {
189 Either::Left(self.targeted_functions.iter())
190 } else {
191 Either::Right(self.abi.functions().filter(|&func| {
192 !matches!(
193 func.state_mutability,
194 alloy_json_abi::StateMutability::Pure | alloy_json_abi::StateMutability::View
195 ) && !self.excluded_functions.contains(func)
196 }))
197 }
198 }
199
200 pub fn get_function(&self, selector: Selector) -> eyre::Result<&Function> {
202 get_function(&self.identifier, selector, &self.abi)
203 }
204
205 pub fn add_selectors(
207 &mut self,
208 selectors: impl IntoIterator<Item = Selector>,
209 should_exclude: bool,
210 ) -> eyre::Result<()> {
211 for selector in selectors {
212 if should_exclude {
213 self.excluded_functions.push(self.get_function(selector)?.clone());
214 } else {
215 self.targeted_functions.push(self.get_function(selector)?.clone());
216 }
217 }
218 Ok(())
219 }
220}
221
222#[derive(Clone, Debug, Serialize, Deserialize)]
224pub struct BasicTxDetails {
225 pub sender: Address,
227 pub call_details: CallDetails,
229}
230
231#[derive(Clone, Debug, Serialize, Deserialize)]
233pub struct CallDetails {
234 pub target: Address,
236 pub calldata: Bytes,
238}
239
240#[derive(Clone, Debug)]
242pub struct InvariantContract<'a> {
243 pub address: Address,
245 pub invariant_function: &'a Function,
247 pub call_after_invariant: bool,
249 pub abi: &'a JsonAbi,
251}