foundry_evm_fuzz/invariant/
mod.rsuse alloy_json_abi::{Function, JsonAbi};
use alloy_primitives::{Address, Bytes, Selector};
use itertools::Either;
use parking_lot::Mutex;
use std::{collections::BTreeMap, sync::Arc};
mod call_override;
pub use call_override::RandomCallGenerator;
mod filters;
pub use filters::{ArtifactFilters, SenderFilters};
use foundry_common::{ContractsByAddress, ContractsByArtifact};
use foundry_evm_core::utils::{get_function, StateChangeset};
#[derive(Clone, Debug)]
pub struct FuzzRunIdentifiedContracts {
pub targets: Arc<Mutex<TargetedContracts>>,
pub is_updatable: bool,
}
impl FuzzRunIdentifiedContracts {
pub fn new(targets: TargetedContracts, is_updatable: bool) -> Self {
Self { targets: Arc::new(Mutex::new(targets)), is_updatable }
}
pub fn collect_created_contracts(
&self,
state_changeset: &StateChangeset,
project_contracts: &ContractsByArtifact,
setup_contracts: &ContractsByAddress,
artifact_filters: &ArtifactFilters,
created_contracts: &mut Vec<Address>,
) -> eyre::Result<()> {
if !self.is_updatable {
return Ok(());
}
let mut targets = self.targets.lock();
for (address, account) in state_changeset {
if setup_contracts.contains_key(address) {
continue;
}
if !account.is_touched() {
continue;
}
let Some(code) = &account.info.code else {
continue;
};
if code.is_empty() {
continue;
}
let Some((artifact, contract)) =
project_contracts.find_by_deployed_code(code.original_byte_slice())
else {
continue;
};
let Some(functions) =
artifact_filters.get_targeted_functions(artifact, &contract.abi)?
else {
continue;
};
created_contracts.push(*address);
let contract = TargetedContract {
identifier: artifact.name.clone(),
abi: contract.abi.clone(),
targeted_functions: functions,
excluded_functions: Vec::new(),
};
targets.insert(*address, contract);
}
Ok(())
}
pub fn clear_created_contracts(&self, created_contracts: Vec<Address>) {
if !created_contracts.is_empty() {
let mut targets = self.targets.lock();
for addr in created_contracts.iter() {
targets.remove(addr);
}
}
}
}
#[derive(Clone, Debug, Default)]
pub struct TargetedContracts {
pub inner: BTreeMap<Address, TargetedContract>,
}
impl TargetedContracts {
pub fn new() -> Self {
Self::default()
}
pub fn fuzzed_artifacts(&self, tx: &BasicTxDetails) -> (Option<&JsonAbi>, Option<&Function>) {
match self.inner.get(&tx.call_details.target) {
Some(c) => (
Some(&c.abi),
c.abi.functions().find(|f| f.selector() == tx.call_details.calldata[..4]),
),
None => (None, None),
}
}
pub fn fuzzed_functions(&self) -> impl Iterator<Item = (&Address, &Function)> {
self.inner
.iter()
.filter(|(_, c)| !c.abi.functions.is_empty())
.flat_map(|(contract, c)| c.abi_fuzzed_functions().map(move |f| (contract, f)))
}
pub fn fuzzed_metric_key(&self, tx: &BasicTxDetails) -> Option<String> {
self.inner.get(&tx.call_details.target).and_then(|contract| {
contract
.abi
.functions()
.find(|f| f.selector() == tx.call_details.calldata[..4])
.map(|function| format!("{}.{}", contract.identifier.clone(), function.name))
})
}
}
impl std::ops::Deref for TargetedContracts {
type Target = BTreeMap<Address, TargetedContract>;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl std::ops::DerefMut for TargetedContracts {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
#[derive(Clone, Debug)]
pub struct TargetedContract {
pub identifier: String,
pub abi: JsonAbi,
pub targeted_functions: Vec<Function>,
pub excluded_functions: Vec<Function>,
}
impl TargetedContract {
pub fn new(identifier: String, abi: JsonAbi) -> Self {
Self { identifier, abi, targeted_functions: Vec::new(), excluded_functions: Vec::new() }
}
pub fn abi_fuzzed_functions(&self) -> impl Iterator<Item = &Function> {
if !self.targeted_functions.is_empty() {
Either::Left(self.targeted_functions.iter())
} else {
Either::Right(self.abi.functions().filter(|&func| {
!matches!(
func.state_mutability,
alloy_json_abi::StateMutability::Pure | alloy_json_abi::StateMutability::View
) && !self.excluded_functions.contains(func)
}))
}
}
pub fn get_function(&self, selector: Selector) -> eyre::Result<&Function> {
get_function(&self.identifier, selector, &self.abi)
}
pub fn add_selectors(
&mut self,
selectors: impl IntoIterator<Item = Selector>,
should_exclude: bool,
) -> eyre::Result<()> {
for selector in selectors {
if should_exclude {
self.excluded_functions.push(self.get_function(selector)?.clone());
} else {
self.targeted_functions.push(self.get_function(selector)?.clone());
}
}
Ok(())
}
}
#[derive(Clone, Debug)]
pub struct BasicTxDetails {
pub sender: Address,
pub call_details: CallDetails,
}
#[derive(Clone, Debug)]
pub struct CallDetails {
pub target: Address,
pub calldata: Bytes,
}
#[derive(Clone, Debug)]
pub struct InvariantContract<'a> {
pub address: Address,
pub invariant_function: &'a Function,
pub call_after_invariant: bool,
pub abi: &'a JsonAbi,
}