forked from foundry-rs/foundry
-
Notifications
You must be signed in to change notification settings - Fork 0
/
lib.rs
231 lines (203 loc) · 8.88 KB
/
lib.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
#[macro_use]
extern crate tracing;
use foundry_compilers::ProjectCompileOutput;
use foundry_config::{
validate_profiles, Config, FuzzConfig, InlineConfig, InlineConfigError, InlineConfigParser,
InvariantConfig, NatSpec,
};
use proptest::test_runner::{RngAlgorithm, TestRng, TestRunner};
use std::path::Path;
pub mod coverage;
pub mod gas_report;
pub mod link;
mod multi_runner;
pub use multi_runner::{MultiContractRunner, MultiContractRunnerBuilder};
mod runner;
pub use runner::ContractRunner;
pub mod result;
// TODO: remove
pub use foundry_common::traits::TestFilter;
pub use foundry_evm::*;
/// Metadata on how to run fuzz/invariant tests
#[derive(Clone, Debug, Default)]
pub struct TestOptions {
/// The base "fuzz" test configuration. To be used as a fallback in case
/// no more specific configs are found for a given run.
pub fuzz: FuzzConfig,
/// The base "invariant" test configuration. To be used as a fallback in case
/// no more specific configs are found for a given run.
pub invariant: InvariantConfig,
/// Contains per-test specific "fuzz" configurations.
pub inline_fuzz: InlineConfig<FuzzConfig>,
/// Contains per-test specific "invariant" configurations.
pub inline_invariant: InlineConfig<InvariantConfig>,
}
impl TestOptions {
/// Tries to create a new instance by detecting inline configurations from the project compile
/// output.
pub fn new(
output: &ProjectCompileOutput,
root: &Path,
profiles: Vec<String>,
base_fuzz: FuzzConfig,
base_invariant: InvariantConfig,
) -> Result<Self, InlineConfigError> {
let natspecs: Vec<NatSpec> = NatSpec::parse(output, root);
let mut inline_invariant = InlineConfig::<InvariantConfig>::default();
let mut inline_fuzz = InlineConfig::<FuzzConfig>::default();
for natspec in natspecs {
// Perform general validation
validate_profiles(&natspec, &profiles)?;
FuzzConfig::validate_configs(&natspec)?;
InvariantConfig::validate_configs(&natspec)?;
// Apply in-line configurations for the current profile
let configs: Vec<String> = natspec.current_profile_configs().collect();
let c: &str = &natspec.contract;
let f: &str = &natspec.function;
let line: String = natspec.debug_context();
match base_fuzz.try_merge(&configs) {
Ok(Some(conf)) => inline_fuzz.insert(c, f, conf),
Ok(None) => { /* No inline config found, do nothing */ }
Err(e) => Err(InlineConfigError { line: line.clone(), source: e })?,
}
match base_invariant.try_merge(&configs) {
Ok(Some(conf)) => inline_invariant.insert(c, f, conf),
Ok(None) => { /* No inline config found, do nothing */ }
Err(e) => Err(InlineConfigError { line: line.clone(), source: e })?,
}
}
Ok(Self { fuzz: base_fuzz, invariant: base_invariant, inline_fuzz, inline_invariant })
}
/// Returns a "fuzz" test runner instance. Parameters are used to select tight scoped fuzz
/// configs that apply for a contract-function pair. A fallback configuration is applied
/// if no specific setup is found for a given input.
///
/// - `contract_id` is the id of the test contract, expressed as a relative path from the
/// project root.
/// - `test_fn` is the name of the test function declared inside the test contract.
pub fn fuzz_runner<S>(&self, contract_id: S, test_fn: S) -> TestRunner
where
S: Into<String>,
{
let fuzz = self.fuzz_config(contract_id, test_fn);
self.fuzzer_with_cases(fuzz.runs)
}
/// Returns an "invariant" test runner instance. Parameters are used to select tight scoped fuzz
/// configs that apply for a contract-function pair. A fallback configuration is applied
/// if no specific setup is found for a given input.
///
/// - `contract_id` is the id of the test contract, expressed as a relative path from the
/// project root.
/// - `test_fn` is the name of the test function declared inside the test contract.
pub fn invariant_runner<S>(&self, contract_id: S, test_fn: S) -> TestRunner
where
S: Into<String>,
{
let invariant = self.invariant_config(contract_id, test_fn);
self.fuzzer_with_cases(invariant.runs)
}
/// Returns a "fuzz" configuration setup. Parameters are used to select tight scoped fuzz
/// configs that apply for a contract-function pair. A fallback configuration is applied
/// if no specific setup is found for a given input.
///
/// - `contract_id` is the id of the test contract, expressed as a relative path from the
/// project root.
/// - `test_fn` is the name of the test function declared inside the test contract.
pub fn fuzz_config<S>(&self, contract_id: S, test_fn: S) -> &FuzzConfig
where
S: Into<String>,
{
self.inline_fuzz.get(contract_id, test_fn).unwrap_or(&self.fuzz)
}
/// Returns an "invariant" configuration setup. Parameters are used to select tight scoped
/// invariant configs that apply for a contract-function pair. A fallback configuration is
/// applied if no specific setup is found for a given input.
///
/// - `contract_id` is the id of the test contract, expressed as a relative path from the
/// project root.
/// - `test_fn` is the name of the test function declared inside the test contract.
pub fn invariant_config<S>(&self, contract_id: S, test_fn: S) -> &InvariantConfig
where
S: Into<String>,
{
self.inline_invariant.get(contract_id, test_fn).unwrap_or(&self.invariant)
}
pub fn fuzzer_with_cases(&self, cases: u32) -> TestRunner {
// TODO: Add Options to modify the persistence
let config = proptest::test_runner::Config {
failure_persistence: None,
cases,
max_global_rejects: self.fuzz.max_test_rejects,
..Default::default()
};
if let Some(seed) = &self.fuzz.seed {
trace!(target: "forge::test", %seed, "building deterministic fuzzer");
let rng = TestRng::from_seed(RngAlgorithm::ChaCha, &seed.to_be_bytes::<32>());
TestRunner::new_with_rng(config, rng)
} else {
trace!(target: "forge::test", "building stochastic fuzzer");
TestRunner::new(config)
}
}
}
/// Builder utility to create a [`TestOptions`] instance.
#[derive(Default)]
#[must_use = "builders do nothing unless you call `build` on them"]
pub struct TestOptionsBuilder {
fuzz: Option<FuzzConfig>,
invariant: Option<InvariantConfig>,
profiles: Option<Vec<String>>,
}
impl TestOptionsBuilder {
/// Sets a [`FuzzConfig`] to be used as base "fuzz" configuration.
pub fn fuzz(mut self, conf: FuzzConfig) -> Self {
self.fuzz = Some(conf);
self
}
/// Sets a [`InvariantConfig`] to be used as base "invariant" configuration.
pub fn invariant(mut self, conf: InvariantConfig) -> Self {
self.invariant = Some(conf);
self
}
/// Sets available configuration profiles. Profiles are useful to validate existing in-line
/// configurations. This argument is necessary in case a `compile_output`is provided.
pub fn profiles(mut self, p: Vec<String>) -> Self {
self.profiles = Some(p);
self
}
/// Creates an instance of [`TestOptions`]. This takes care of creating "fuzz" and
/// "invariant" fallbacks, and extracting all inline test configs, if available.
///
/// `root` is a reference to the user's project root dir. This is essential
/// to determine the base path of generated contract identifiers. This is to provide correct
/// matchers for inline test configs.
pub fn build(
self,
output: &ProjectCompileOutput,
root: &Path,
) -> Result<TestOptions, InlineConfigError> {
let profiles: Vec<String> =
self.profiles.unwrap_or_else(|| vec![Config::selected_profile().into()]);
let base_fuzz = self.fuzz.unwrap_or_default();
let base_invariant = self.invariant.unwrap_or_default();
TestOptions::new(output, root, profiles, base_fuzz, base_invariant)
}
}
mod utils2 {
use alloy_primitives::Address;
use ethers_core::types::BlockId;
use ethers_providers::{Middleware, Provider};
use eyre::Context;
use foundry_common::types::{ToAlloy, ToEthers};
pub async fn next_nonce(
caller: Address,
provider_url: &str,
block: Option<BlockId>,
) -> eyre::Result<u64> {
let provider = Provider::try_from(provider_url)
.wrap_err_with(|| format!("bad fork_url provider: {provider_url}"))?;
let res = provider.get_transaction_count(caller.to_ethers(), block).await?.to_alloy();
res.try_into().map_err(Into::into)
}
}
pub use utils2::*;