foundry_evm_fuzz/invariant/
mod.rs1use alloy_json_abi::{Function, JsonAbi};
2use alloy_primitives::{Address, Selector, map::HashMap};
3use foundry_compilers::artifacts::StorageLayout;
4use itertools::Either;
5use parking_lot::Mutex;
6use std::{collections::BTreeMap, sync::Arc};
7
8mod call_override;
9pub use call_override::RandomCallGenerator;
10
11mod filters;
12use crate::BasicTxDetails;
13pub use filters::{ArtifactFilters, SenderFilters};
14use foundry_common::{ContractsByAddress, ContractsByArtifact};
15use foundry_evm_core::utils::{StateChangeset, get_function};
16
17#[derive(Clone, Debug)]
22pub struct FuzzRunIdentifiedContracts {
23 pub targets: Arc<Mutex<TargetedContracts>>,
25 pub is_updatable: bool,
27}
28
29impl FuzzRunIdentifiedContracts {
30 pub fn new(targets: TargetedContracts, is_updatable: bool) -> Self {
32 Self { targets: Arc::new(Mutex::new(targets)), is_updatable }
33 }
34
35 pub fn collect_created_contracts(
38 &self,
39 state_changeset: &StateChangeset,
40 project_contracts: &ContractsByArtifact,
41 setup_contracts: &ContractsByAddress,
42 artifact_filters: &ArtifactFilters,
43 created_contracts: &mut Vec<Address>,
44 ) -> eyre::Result<()> {
45 if !self.is_updatable {
46 return Ok(());
47 }
48
49 let mut targets = self.targets.lock();
50 for (address, account) in state_changeset {
51 if setup_contracts.contains_key(address) {
52 continue;
53 }
54 if !account.is_touched() {
55 continue;
56 }
57 let Some(code) = &account.info.code else {
58 continue;
59 };
60 if code.is_empty() {
61 continue;
62 }
63 let Some((artifact, contract)) =
64 project_contracts.find_by_deployed_code(code.original_byte_slice())
65 else {
66 continue;
67 };
68 let Some(functions) =
69 artifact_filters.get_targeted_functions(artifact, &contract.abi)?
70 else {
71 continue;
72 };
73 created_contracts.push(*address);
74 let contract = TargetedContract {
75 identifier: artifact.name.clone(),
76 abi: contract.abi.clone(),
77 targeted_functions: functions,
78 excluded_functions: Vec::new(),
79 storage_layout: contract.storage_layout.as_ref().map(Arc::clone),
80 };
81 targets.insert(*address, contract);
82 }
83 Ok(())
84 }
85
86 pub fn clear_created_contracts(&self, created_contracts: Vec<Address>) {
88 if !created_contracts.is_empty() {
89 let mut targets = self.targets.lock();
90 for addr in &created_contracts {
91 targets.remove(addr);
92 }
93 }
94 }
95}
96
97#[derive(Clone, Debug, Default)]
99pub struct TargetedContracts {
100 pub inner: BTreeMap<Address, TargetedContract>,
102}
103
104impl TargetedContracts {
105 pub fn new() -> Self {
107 Self::default()
108 }
109
110 pub fn fuzzed_artifacts(&self, tx: &BasicTxDetails) -> (Option<&JsonAbi>, Option<&Function>) {
114 match self.inner.get(&tx.call_details.target) {
115 Some(c) => (
116 Some(&c.abi),
117 c.abi.functions().find(|f| f.selector() == tx.call_details.calldata[..4]),
118 ),
119 None => (None, None),
120 }
121 }
122
123 pub fn fuzzed_functions(&self) -> impl Iterator<Item = (&Address, &Function)> {
126 self.inner
127 .iter()
128 .filter(|(_, c)| !c.abi.functions.is_empty())
129 .flat_map(|(contract, c)| c.abi_fuzzed_functions().map(move |f| (contract, f)))
130 }
131
132 pub fn can_replay(&self, tx: &BasicTxDetails) -> bool {
134 match self.inner.get(&tx.call_details.target) {
135 Some(c) => c.abi.functions().any(|f| f.selector() == tx.call_details.calldata[..4]),
136 None => false,
137 }
138 }
139
140 pub fn fuzzed_metric_key(&self, tx: &BasicTxDetails) -> Option<String> {
143 self.inner.get(&tx.call_details.target).and_then(|contract| {
144 contract
145 .abi
146 .functions()
147 .find(|f| f.selector() == tx.call_details.calldata[..4])
148 .map(|function| format!("{}.{}", contract.identifier.clone(), function.name))
149 })
150 }
151
152 pub fn get_storage_layouts(&self) -> HashMap<Address, Arc<StorageLayout>> {
154 self.inner
155 .iter()
156 .filter_map(|(addr, c)| {
157 c.storage_layout.as_ref().map(|layout| (*addr, Arc::clone(layout)))
158 })
159 .collect()
160 }
161}
162
163impl std::ops::Deref for TargetedContracts {
164 type Target = BTreeMap<Address, TargetedContract>;
165
166 fn deref(&self) -> &Self::Target {
167 &self.inner
168 }
169}
170
171impl std::ops::DerefMut for TargetedContracts {
172 fn deref_mut(&mut self) -> &mut Self::Target {
173 &mut self.inner
174 }
175}
176
177#[derive(Clone, Debug)]
179pub struct TargetedContract {
180 pub identifier: String,
182 pub abi: JsonAbi,
184 pub targeted_functions: Vec<Function>,
186 pub excluded_functions: Vec<Function>,
188 pub storage_layout: Option<Arc<StorageLayout>>,
190}
191
192impl TargetedContract {
193 pub fn new(identifier: String, abi: JsonAbi) -> Self {
195 Self {
196 identifier,
197 abi,
198 targeted_functions: Vec::new(),
199 excluded_functions: Vec::new(),
200 storage_layout: None,
201 }
202 }
203
204 pub fn with_project_contracts(mut self, project_contracts: &ContractsByArtifact) -> Self {
207 if let Some((src, name)) = self.identifier.split_once(':')
208 && let Some((_, contract_data)) = project_contracts.iter().find(|(artifact, _)| {
209 artifact.name == name && artifact.source.as_path().ends_with(src)
210 })
211 {
212 self.storage_layout = contract_data.storage_layout.as_ref().map(Arc::clone);
213 }
214 self
215 }
216
217 pub fn abi_fuzzed_functions(&self) -> impl Iterator<Item = &Function> {
221 if !self.targeted_functions.is_empty() {
222 Either::Left(self.targeted_functions.iter())
223 } else {
224 Either::Right(self.abi.functions().filter(|&func| {
225 !matches!(
226 func.state_mutability,
227 alloy_json_abi::StateMutability::Pure | alloy_json_abi::StateMutability::View
228 ) && !self.excluded_functions.contains(func)
229 }))
230 }
231 }
232
233 pub fn get_function(&self, selector: Selector) -> eyre::Result<&Function> {
235 get_function(&self.identifier, selector, &self.abi)
236 }
237
238 pub fn add_selectors(
240 &mut self,
241 selectors: impl IntoIterator<Item = Selector>,
242 should_exclude: bool,
243 ) -> eyre::Result<()> {
244 for selector in selectors {
245 if should_exclude {
246 self.excluded_functions.push(self.get_function(selector)?.clone());
247 } else {
248 self.targeted_functions.push(self.get_function(selector)?.clone());
249 }
250 }
251 Ok(())
252 }
253}
254
255#[derive(Clone, Debug)]
257pub struct InvariantContract<'a> {
258 pub address: Address,
260 pub invariant_function: &'a Function,
262 pub call_after_invariant: bool,
264 pub abi: &'a JsonAbi,
266}