Skip to main content

foundry_common/
traits.rs

1//! Commonly used traits.
2
3use alloy_json_abi::Function;
4use alloy_primitives::Bytes;
5use alloy_sol_types::SolError;
6use std::{fmt, path::Path};
7
8/// Test filter.
9pub trait TestFilter: Send + Sync {
10    /// Returns whether the test should be included.
11    fn matches_test(&self, test_signature: &str) -> bool;
12
13    /// Returns whether the contract should be included.
14    fn matches_contract(&self, contract_name: &str) -> bool;
15
16    /// Returns a contract with the given path should be included.
17    fn matches_path(&self, path: &Path) -> bool;
18
19    /// Returns whether the test should be included for the given contract.
20    ///
21    /// `contract_id` is the full artifact identifier (`path:Contract`).
22    fn matches_test_function_kind_in_contract(
23        &self,
24        _contract_id: &str,
25        func: &Function,
26        kind: TestFunctionKind,
27    ) -> bool {
28        kind.is_any_test() && self.matches_test(&func.signature())
29    }
30
31    /// Returns whether the test should be included for the given contract.
32    ///
33    /// `contract_id` is the full artifact identifier (`path:Contract`).
34    fn matches_test_function_in_contract(&self, contract_id: &str, func: &Function) -> bool {
35        self.matches_test_function_kind_in_contract(contract_id, func, func.test_function_kind())
36    }
37}
38
39impl<'a> dyn TestFilter + 'a {
40    /// Returns `true` if the function kind is runnable and matches the given filter.
41    pub fn matches_test_function_kind(&self, func: &Function, kind: TestFunctionKind) -> bool {
42        kind.is_any_test() && self.matches_test(&func.signature())
43    }
44
45    /// Returns `true` if the function is a test function that matches the given filter.
46    pub fn matches_test_function(&self, func: &Function) -> bool {
47        self.matches_test_function_kind(func, func.test_function_kind())
48    }
49}
50
51/// A test filter that filters out nothing.
52#[derive(Clone, Debug, Default)]
53pub struct EmptyTestFilter(());
54impl TestFilter for EmptyTestFilter {
55    fn matches_test(&self, _test_signature: &str) -> bool {
56        true
57    }
58
59    fn matches_contract(&self, _contract_name: &str) -> bool {
60        true
61    }
62
63    fn matches_path(&self, _path: &Path) -> bool {
64        true
65    }
66}
67
68/// Extension trait for `Function`.
69pub trait TestFunctionExt {
70    /// Returns the kind of test function.
71    fn test_function_kind(&self) -> TestFunctionKind {
72        TestFunctionKind::classify(self.tfe_as_str(), self.tfe_has_inputs(), false)
73    }
74
75    /// Returns `true` if this function is a `setUp` function.
76    fn is_setup(&self) -> bool {
77        self.test_function_kind().is_setup()
78    }
79
80    /// Returns `true` if this function is a unit, fuzz, or invariant test.
81    fn is_any_test(&self) -> bool {
82        self.test_function_kind().is_any_test()
83    }
84
85    /// Returns `true` if this function is a test that should fail.
86    fn is_any_test_fail(&self) -> bool {
87        self.test_function_kind().is_any_test_fail()
88    }
89
90    /// Returns `true` if this function is a unit test.
91    fn is_unit_test(&self) -> bool {
92        matches!(self.test_function_kind(), TestFunctionKind::UnitTest { .. })
93    }
94
95    /// Returns `true` if this function is a `beforeTestSetup` function.
96    fn is_before_test_setup(&self) -> bool {
97        self.tfe_as_str().eq_ignore_ascii_case("beforetestsetup")
98    }
99
100    /// Returns `true` if this function is a fuzz test.
101    fn is_fuzz_test(&self) -> bool {
102        self.test_function_kind().is_fuzz_test()
103    }
104
105    /// Returns `true` if this function is an invariant test.
106    fn is_invariant_test(&self) -> bool {
107        self.test_function_kind().is_invariant_test()
108    }
109
110    /// Returns `true` if this function is a symbolic test.
111    fn is_symbolic_test(&self) -> bool {
112        self.test_function_kind().is_symbolic_test()
113    }
114
115    /// Returns `true` if this function is an `afterInvariant` function.
116    fn is_after_invariant(&self) -> bool {
117        self.test_function_kind().is_after_invariant()
118    }
119
120    /// Returns `true` if this function is a `fixture` function.
121    fn is_fixture(&self) -> bool {
122        self.test_function_kind().is_fixture()
123    }
124
125    /// Returns `true` if this function is test reserved function.
126    fn is_reserved(&self) -> bool {
127        self.is_any_test()
128            || self.is_setup()
129            || self.is_before_test_setup()
130            || self.is_after_invariant()
131            || self.is_fixture()
132    }
133
134    #[doc(hidden)]
135    fn tfe_as_str(&self) -> &str;
136    #[doc(hidden)]
137    fn tfe_has_inputs(&self) -> bool;
138}
139
140impl TestFunctionExt for Function {
141    fn tfe_as_str(&self) -> &str {
142        self.name.as_str()
143    }
144
145    fn tfe_has_inputs(&self) -> bool {
146        !self.inputs.is_empty()
147    }
148}
149
150impl TestFunctionExt for String {
151    fn tfe_as_str(&self) -> &str {
152        self
153    }
154
155    fn tfe_has_inputs(&self) -> bool {
156        false
157    }
158}
159
160impl TestFunctionExt for str {
161    fn tfe_as_str(&self) -> &str {
162        self
163    }
164
165    fn tfe_has_inputs(&self) -> bool {
166        false
167    }
168}
169
170/// Test function kind.
171#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
172pub enum TestFunctionKind {
173    /// `setUp`.
174    Setup,
175    /// `test*`. `should_fail` is `true` for `testFail*`.
176    UnitTest { should_fail: bool },
177    /// `test*`, with arguments. `should_fail` is `true` for `testFail*`.
178    FuzzTest { should_fail: bool },
179    /// `invariant*` or `statefulFuzz*`.
180    InvariantTest,
181    /// `table*`, with arguments.
182    TableTest,
183    /// `check*` or `prove*`, when selected by symbolic test mode.
184    SymbolicTest,
185    /// `afterInvariant`.
186    AfterInvariant,
187    /// `fixture*`.
188    Fixture,
189    /// Unknown kind.
190    Unknown,
191}
192
193impl TestFunctionKind {
194    /// Classify a function.
195    pub fn classify(name: &str, has_inputs: bool, symbolic_entrypoints: bool) -> Self {
196        match () {
197            _ if name.starts_with("test") => {
198                let should_fail = name.starts_with("testFail");
199                if has_inputs {
200                    Self::FuzzTest { should_fail }
201                } else {
202                    Self::UnitTest { should_fail }
203                }
204            }
205            _ if name.starts_with("invariant") || name.starts_with("statefulFuzz") => {
206                Self::InvariantTest
207            }
208            _ if name.starts_with("table") => Self::TableTest,
209            _ if symbolic_entrypoints
210                && (name.starts_with("check") || name.starts_with("prove")) =>
211            {
212                Self::SymbolicTest
213            }
214            _ if name.eq_ignore_ascii_case("setup") && !has_inputs => Self::Setup,
215            _ if name.eq_ignore_ascii_case("afterinvariant") => Self::AfterInvariant,
216            _ if name.starts_with("fixture") => Self::Fixture,
217            _ => Self::Unknown,
218        }
219    }
220
221    /// Returns the name of the function kind.
222    pub const fn name(&self) -> &'static str {
223        match self {
224            Self::Setup => "setUp",
225            Self::UnitTest { should_fail: false } => "test",
226            Self::UnitTest { should_fail: true } => "testFail",
227            Self::FuzzTest { should_fail: false } => "fuzz",
228            Self::FuzzTest { should_fail: true } => "fuzz fail",
229            Self::InvariantTest => "invariant",
230            Self::TableTest => "table",
231            Self::SymbolicTest => "symbolic",
232            Self::AfterInvariant => "afterInvariant",
233            Self::Fixture => "fixture",
234            Self::Unknown => "unknown",
235        }
236    }
237
238    /// Returns `true` if this function is a `setUp` function.
239    #[inline]
240    pub const fn is_setup(&self) -> bool {
241        matches!(self, Self::Setup)
242    }
243
244    /// Returns `true` if this function is a unit, fuzz, or invariant test.
245    #[inline]
246    pub const fn is_any_test(&self) -> bool {
247        matches!(
248            self,
249            Self::UnitTest { .. }
250                | Self::FuzzTest { .. }
251                | Self::TableTest
252                | Self::InvariantTest
253                | Self::SymbolicTest
254        )
255    }
256
257    /// Returns `true` if this function is a test that should fail.
258    #[inline]
259    pub const fn is_any_test_fail(&self) -> bool {
260        matches!(self, Self::UnitTest { should_fail: true } | Self::FuzzTest { should_fail: true })
261    }
262
263    /// Returns `true` if this function is a unit test.
264    #[inline]
265    pub const fn is_unit_test(&self) -> bool {
266        matches!(self, Self::UnitTest { .. })
267    }
268
269    /// Returns `true` if this function is a fuzz test.
270    #[inline]
271    pub const fn is_fuzz_test(&self) -> bool {
272        matches!(self, Self::FuzzTest { .. })
273    }
274
275    /// Returns `true` if this function is an invariant test.
276    #[inline]
277    pub const fn is_invariant_test(&self) -> bool {
278        matches!(self, Self::InvariantTest)
279    }
280
281    /// Returns `true` if this function is a table test.
282    #[inline]
283    pub const fn is_table_test(&self) -> bool {
284        matches!(self, Self::TableTest)
285    }
286
287    /// Returns `true` if this function is a symbolic test.
288    #[inline]
289    pub const fn is_symbolic_test(&self) -> bool {
290        matches!(self, Self::SymbolicTest)
291    }
292
293    /// Returns `true` if this function is an `afterInvariant` function.
294    #[inline]
295    pub const fn is_after_invariant(&self) -> bool {
296        matches!(self, Self::AfterInvariant)
297    }
298
299    /// Returns `true` if this function is a `fixture` function.
300    #[inline]
301    pub const fn is_fixture(&self) -> bool {
302        matches!(self, Self::Fixture)
303    }
304
305    /// Returns `true` if this function kind is known.
306    #[inline]
307    pub const fn is_known(&self) -> bool {
308        !matches!(self, Self::Unknown)
309    }
310
311    /// Returns `true` if this function kind is unknown.
312    #[inline]
313    pub const fn is_unknown(&self) -> bool {
314        matches!(self, Self::Unknown)
315    }
316}
317
318impl fmt::Display for TestFunctionKind {
319    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
320        self.name().fmt(f)
321    }
322}
323
324/// An extension trait for `std::error::Error` for ABI encoding.
325pub trait ErrorExt: std::error::Error {
326    /// ABI-encodes the error using `Revert(string)`.
327    fn abi_encode_revert(&self) -> Bytes;
328}
329
330impl<T: std::error::Error> ErrorExt for T {
331    fn abi_encode_revert(&self) -> Bytes {
332        alloy_sol_types::Revert::from(self.to_string()).abi_encode().into()
333    }
334}
335
336#[cfg(test)]
337mod tests {
338    use super::*;
339
340    #[test]
341    fn test_setup_classification() {
342        // setUp() with no params should be classified as Setup
343        assert_eq!(TestFunctionKind::classify("setUp", false, false), TestFunctionKind::Setup);
344
345        // setUp(bytes memory) with params should NOT be classified as Setup
346        // This is common in Gnosis Safe/Zodiac modules
347        assert_eq!(TestFunctionKind::classify("setUp", true, false), TestFunctionKind::Unknown);
348    }
349
350    #[test]
351    fn test_symbolic_classification() {
352        assert_eq!(
353            TestFunctionKind::classify("checkSymbolic", true, true),
354            TestFunctionKind::SymbolicTest
355        );
356        assert_eq!(
357            TestFunctionKind::classify("proveSymbolic", false, true),
358            TestFunctionKind::SymbolicTest
359        );
360        assert_eq!(
361            TestFunctionKind::classify("checkSymbolic", true, false),
362            TestFunctionKind::Unknown
363        );
364    }
365}