1use crate::{
4 ContractRunner, TestFilter,
5 progress::TestsProgress,
6 result::{SuiteResult, SymbolicCounterexampleArtifact, SymbolicCounterexampleArtifactKind},
7 runner::{
8 ContractRunnerContext, InvariantCampaignScope, count_runnable_invariant_campaign_anchors,
9 },
10 symbolic_regression::SYMBOLIC_REGRESSION_MARKER,
11};
12use alloy_json_abi::{Function, JsonAbi};
13use alloy_primitives::{Address, Bytes, U256};
14use eyre::Result;
15use foundry_cli::opts::configure_pcx_from_compile_output;
16use foundry_common::{
17 ContractsByArtifact, ContractsByArtifactBuilder, EmptyTestFilter, LIBRARY_DEPLOYER,
18 TestFunctionKind, get_contract_name,
19};
20use foundry_compilers::{
21 Artifact, ArtifactId, Compiler, ProjectCompileOutput,
22 artifacts::{Contract, Libraries},
23};
24use foundry_config::{Config, InlineConfig};
25use foundry_evm::{
26 backend::Backend,
27 core::evm::{EvmEnvFor, FoundryEvmNetwork, SpecFor, TxEnvFor},
28 decode::RevertDecoder,
29 executors::{EarlyExit, Executor, ExecutorBuilder, ReplayObservation, ShowmapDomain},
30 fork::CreateFork,
31 fuzz::{
32 BasicTxDetails,
33 strategies::{EnumBounds, LiteralsDictionary},
34 },
35 inspectors::{CheatsConfig, EdgeIndexMap},
36 opts::EvmOpts,
37 traces::{InternalTraceMode, TraceRequirements},
38};
39use foundry_evm_networks::NetworkVariant;
40
41use foundry_linking::{DetailedLinkOutput, LinkOutput, Linker, LinkerError, Resolver};
42use rayon::prelude::*;
43use std::{
44 borrow::Borrow,
45 collections::{BTreeMap, BTreeSet},
46 ops::{Deref, DerefMut},
47 path::{Path, PathBuf},
48 sync::{Arc, Mutex, mpsc},
49 time::Instant,
50};
51
52#[derive(Debug, Clone)]
53pub struct TestContract {
54 pub abi: JsonAbi,
55 pub bytecode: Bytes,
56 pub library_addresses: BTreeSet<Address>,
57}
58
59pub type DeployableContracts = BTreeMap<ArtifactId, TestContract>;
60
61#[derive(Clone, Debug)]
64pub struct MultiContractRunner<FEN: FoundryEvmNetwork> {
65 pub contracts: DeployableContracts,
68 pub known_contracts: ContractsByArtifact,
70 pub revert_decoder: RevertDecoder,
72 pub libs_to_deploy: Vec<Bytes>,
74 pub library_addresses: Vec<Address>,
76 pub library_deployment: LibraryDeployment,
78 pub libraries: Libraries,
80 pub analysis: Arc<solar::sema::Compiler>,
82 pub fuzz_literals: LiteralsDictionary,
84 pub invariant_literals: LiteralsDictionary,
86 pub enum_bounds: EnumBounds,
88
89 pub fork: Option<CreateFork>,
91
92 pub tcfg: TestRunnerConfig<FEN>,
94}
95
96#[derive(Clone, Copy, Debug)]
98pub enum LibraryDeployment {
99 Nonce,
100 Create2 { deployer: Address, salt: alloy_primitives::B256 },
101}
102
103impl<FEN: FoundryEvmNetwork> Deref for MultiContractRunner<FEN> {
104 type Target = TestRunnerConfig<FEN>;
105
106 fn deref(&self) -> &Self::Target {
107 &self.tcfg
108 }
109}
110
111impl<FEN: FoundryEvmNetwork> DerefMut for MultiContractRunner<FEN> {
112 fn deref_mut(&mut self) -> &mut Self::Target {
113 &mut self.tcfg
114 }
115}
116
117impl<FEN: FoundryEvmNetwork> MultiContractRunner<FEN> {
118 fn test_function_matcher(&self) -> TestFunctionMatcher<'_> {
119 TestFunctionMatcher::new(
120 &self.config,
121 &self.inline_config,
122 self.tcfg.symbolic_artifact_replay.as_ref(),
123 )
124 }
125
126 pub fn matching_contracts<'a: 'b, 'b>(
128 &'a self,
129 filter: &'b dyn TestFilter,
130 ) -> impl Iterator<Item = (&'a ArtifactId, &'a TestContract)> + 'b {
131 let matcher = self.test_function_matcher();
132 self.contracts.iter().filter(move |&(id, c)| matcher.matches_contract(filter, id, &c.abi))
133 }
134
135 pub fn matching_test_functions<'a: 'b, 'b>(
137 &'a self,
138 filter: &'b dyn TestFilter,
139 ) -> impl Iterator<Item = &'a Function> + 'b {
140 let matcher = self.test_function_matcher();
141 self.matching_contracts(filter).flat_map(move |(id, c)| {
142 let identifier = id.identifier();
143 let generated_symbolic_regression = is_generated_symbolic_regression_contract(&c.abi);
144 c.abi.functions().filter(move |func| {
145 matcher.matches_test_function(
146 filter,
147 &identifier,
148 func,
149 generated_symbolic_regression,
150 )
151 })
152 })
153 }
154
155 pub fn all_test_functions<'a: 'b, 'b>(
157 &'a self,
158 filter: &'b dyn TestFilter,
159 ) -> impl Iterator<Item = &'a Function> + 'b {
160 let matcher = self.test_function_matcher();
161 self.contracts
162 .iter()
163 .filter(|(id, _)| filter.matches_path(&id.source) && filter.matches_contract(&id.name))
164 .flat_map(move |(id, c)| {
165 let identifier = id.identifier();
166 let generated_symbolic_regression =
167 is_generated_symbolic_regression_contract(&c.abi);
168 c.abi.functions().filter(move |func| {
169 matcher
170 .test_function_kind(&identifier, func, generated_symbolic_regression)
171 .is_any_test()
172 })
173 })
174 }
175
176 pub fn list(&self, filter: &dyn TestFilter) -> BTreeMap<String, BTreeMap<String, Vec<String>>> {
178 self.list_with(filter, |func| func.name.clone())
179 }
180
181 pub(crate) fn list_signatures(
182 &self,
183 filter: &dyn TestFilter,
184 ) -> BTreeMap<String, BTreeMap<String, Vec<String>>> {
185 self.list_with(filter, |func| func.signature())
186 }
187
188 fn list_with(
189 &self,
190 filter: &dyn TestFilter,
191 format_test: impl Fn(&Function) -> String + Copy,
192 ) -> BTreeMap<String, BTreeMap<String, Vec<String>>> {
193 let matcher = self.test_function_matcher();
194 self.matching_contracts(filter)
195 .map(move |(id, c)| {
196 let source = id.source.as_path().display().to_string();
197 let name = id.name.clone();
198 let identifier = id.identifier();
199 let generated_symbolic_regression =
200 is_generated_symbolic_regression_contract(&c.abi);
201 let tests = c
202 .abi
203 .functions()
204 .filter(|func| {
205 let kind = matcher.test_function_kind(
206 &identifier,
207 func,
208 generated_symbolic_regression,
209 );
210 (!self.tcfg.fuzz_only
211 || matches!(
212 kind,
213 TestFunctionKind::FuzzTest { .. } | TestFunctionKind::InvariantTest
214 ))
215 && filter.matches_test_function_kind_in_contract(
216 &identifier,
217 func,
218 kind,
219 )
220 })
221 .map(format_test)
222 .collect::<Vec<_>>();
223 (source, name, tests)
224 })
225 .filter(|(_, _, tests)| !tests.is_empty())
226 .fold(BTreeMap::new(), |mut acc, (source, name, tests)| {
227 acc.entry(source).or_default().insert(name, tests);
228 acc
229 })
230 }
231
232 pub fn test_collect(
238 &mut self,
239 filter: &dyn TestFilter,
240 ) -> Result<BTreeMap<String, SuiteResult>> {
241 Ok(self.test_iter(filter)?.collect())
242 }
243
244 pub fn test_iter(
250 &mut self,
251 filter: &dyn TestFilter,
252 ) -> Result<impl Iterator<Item = (String, SuiteResult)>> {
253 let (tx, rx) = mpsc::channel();
254 self.test(filter, tx, false)?;
255 Ok(rx.into_iter())
256 }
257
258 pub fn test(
265 &mut self,
266 filter: &dyn TestFilter,
267 tx: mpsc::Sender<(String, SuiteResult)>,
268 show_progress: bool,
269 ) -> Result<()> {
270 let tokio_handle = tokio::runtime::Handle::current();
271 trace!("running all tests");
272
273 let db = Backend::spawn(self.fork.take())?;
275
276 let find_timer = Instant::now();
277 let contracts = self.matching_contracts(filter).collect::<Vec<_>>();
278 let find_time = find_timer.elapsed();
279 debug!(
280 "Found {} test contracts out of {} in {:?}",
281 contracts.len(),
282 self.contracts.len(),
283 find_time,
284 );
285 let num_invariant_campaign_anchors = contracts
286 .iter()
287 .map(|(id, contract)| {
288 count_runnable_invariant_campaign_anchors(
289 &contract.abi,
290 filter,
291 InvariantCampaignScope {
292 config: &self.tcfg.config,
293 inline_config: &self.tcfg.inline_config,
294 contract_name: &id.identifier(),
295 all_override_networks: &self.tcfg.multi_network.all_override_networks,
296 pass_network: self.tcfg.multi_network.pass_network.as_ref(),
297 },
298 )
299 })
300 .sum();
301
302 if show_progress {
303 let tests_progress = TestsProgress::new(contracts.len(), rayon::current_num_threads());
304 let results: Vec<(String, SuiteResult)> = contracts
306 .par_iter()
307 .map(|&(id, contract)| {
308 let _guard = tokio_handle.enter();
309 tests_progress.inner.lock().start_suite_progress(&id.identifier());
310
311 let result = self.run_test_suite(
312 id,
313 contract,
314 &db,
315 filter,
316 ContractRunnerContext {
317 progress: Some(&tests_progress),
318 tokio_handle: tokio_handle.clone(),
319 num_invariant_campaign_anchors,
320 },
321 );
322
323 tests_progress
324 .inner
325 .lock()
326 .end_suite_progress(&id.identifier(), result.summary());
327
328 (id.identifier(), result)
329 })
330 .collect();
331
332 tests_progress.inner.lock().clear();
333
334 for result in &results {
335 let _ = tx.send(result.to_owned());
336 }
337 } else {
338 contracts.par_iter().for_each(|&(id, contract)| {
339 let _guard = tokio_handle.enter();
340 let result = self.run_test_suite(
341 id,
342 contract,
343 &db,
344 filter,
345 ContractRunnerContext {
346 progress: None,
347 tokio_handle: tokio_handle.clone(),
348 num_invariant_campaign_anchors,
349 },
350 );
351 let _ = tx.send((id.identifier(), result));
352 })
353 }
354
355 Ok(())
356 }
357
358 fn run_test_suite(
359 &self,
360 artifact_id: &ArtifactId,
361 contract: &TestContract,
362 db: &Backend<FEN>,
363 filter: &dyn TestFilter,
364 context: ContractRunnerContext<'_>,
365 ) -> SuiteResult {
366 let identifier = artifact_id.identifier();
367 let span_name = if enabled!(tracing::Level::TRACE) {
368 identifier.as_str()
369 } else {
370 get_contract_name(&identifier)
371 };
372 let span = debug_span!("suite", name = %span_name);
373 let span_local = span.clone();
374 let _guard = span_local.enter();
375
376 debug!("start executing all tests in contract");
377
378 let executor = self.tcfg.executor(
379 self.known_contracts.clone(),
380 self.analysis.clone(),
381 artifact_id,
382 db.clone(),
383 );
384 let runner = ContractRunner::new(&identifier, contract, executor, span, self, context);
385 let r = runner.run_tests(filter);
386
387 debug!(duration=?r.duration, "executed all tests in contract");
388
389 r
390 }
391}
392
393#[derive(Clone, Debug, Default)]
401pub struct MultiNetworkConfig {
402 pub all_override_networks: Vec<NetworkVariant>,
405 pub pass_network: Option<NetworkVariant>,
410}
411
412#[derive(Clone, Debug)]
415pub struct ShowmapConfig {
416 pub out_dir: PathBuf,
418 pub approach: String,
420 pub trial: String,
422 pub per_input: bool,
424 pub domain: ShowmapDomain,
426 pub corpus_dir: Option<PathBuf>,
429 pub emit_files: bool,
431}
432
433pub type FuzzMinimizeEdgeIndices = Arc<Mutex<BTreeMap<String, Arc<Mutex<EdgeIndexMap>>>>>;
434
435#[derive(Clone, Copy, Debug, PartialEq, Eq)]
437pub enum FuzzMinimizeMode {
438 Cmin,
440 Tmin,
442}
443
444#[derive(Clone, Debug)]
447pub struct FuzzMinimizeConfig {
448 pub input: Arc<[BasicTxDetails]>,
450 pub mode: FuzzMinimizeMode,
452 pub evm_edge_indices: FuzzMinimizeEdgeIndices,
455 pub observations: Arc<Mutex<Vec<FuzzMinimizeObservation>>>,
457}
458
459#[derive(Clone, Debug)]
461pub struct FuzzMinimizeObservation {
462 pub target: String,
464 pub observation: ReplayObservation,
466}
467
468#[derive(Clone, Debug)]
469pub struct SymbolicArtifactReplayConfig {
470 pub artifact: SymbolicCounterexampleArtifact,
472 pub path: PathBuf,
474}
475
476#[derive(Clone, Debug)]
480pub struct TestRunnerConfig<FEN: FoundryEvmNetwork> {
481 pub config: Arc<Config>,
483 pub inline_config: Arc<InlineConfig>,
485
486 pub evm_opts: EvmOpts,
488 pub evm_env: EvmEnvFor<FEN>,
490 pub tx_env: TxEnvFor<FEN>,
492 pub spec_id: SpecFor<FEN>,
494 pub sender: Address,
496
497 pub line_coverage: bool,
499 pub debug: bool,
501 pub decode_internal: InternalTraceMode,
503 pub record_all_steps: bool,
505 pub isolation: bool,
507 pub early_exit: EarlyExit,
509
510 pub multi_network: MultiNetworkConfig,
512
513 pub showmap: Option<ShowmapConfig>,
516 pub fuzz_minimize: Option<FuzzMinimizeConfig>,
518 pub fuzz_only: bool,
520 pub fuzz_failure_replay: bool,
522
523 pub symbolic_artifact_replay: Option<SymbolicArtifactReplayConfig>,
525}
526
527impl<FEN: FoundryEvmNetwork> TestRunnerConfig<FEN> {
528 pub fn reconfigure_with(&mut self, config: Arc<Config>) {
531 debug_assert!(!Arc::ptr_eq(&self.config, &config));
532
533 self.spec_id = config.evm_spec_id();
534 self.sender = config.sender;
535 self.evm_opts.networks = config.networks;
536 self.isolation = config.isolate;
537
538 self.evm_opts.always_use_create_2_factory = config.always_use_create_2_factory;
546
547 self.config = config;
550 }
551
552 pub fn configure_executor(&self, executor: &mut Executor<FEN>) {
554 let inspector = executor.inspector_mut();
557 if let Some(cheatcodes) = inspector.cheatcodes.as_mut() {
559 let mut config = cheatcodes.config.clone_with(&self.config, self.evm_opts.clone());
560 config.isolate = self.isolation;
561 cheatcodes.config = Arc::new(config);
562 }
563 inspector.tracing_requirements(self.trace_requirements());
564 inspector.collect_line_coverage(self.line_coverage);
565 inspector.enable_isolation(self.isolation);
566 inspector.networks(self.evm_opts.networks);
567 executor.set_spec_id(self.spec_id);
571 executor.set_legacy_assertions(self.config.legacy_assertions);
573 }
574
575 pub fn executor(
577 &self,
578 known_contracts: ContractsByArtifact,
579 analysis: Arc<solar::sema::Compiler>,
580 artifact_id: &ArtifactId,
581 db: Backend<FEN>,
582 ) -> Executor<FEN> {
583 let mut cheats_config = CheatsConfig::new(
584 &self.config,
585 self.evm_opts.clone(),
586 Some(known_contracts),
587 Some(artifact_id.clone()),
588 None,
589 false,
590 );
591 cheats_config.isolate = self.isolation;
592 let cheats_config = Arc::new(cheats_config);
593 ExecutorBuilder::default()
594 .inspectors(|stack| {
595 stack
596 .logs(self.config.live_logs)
597 .cheatcodes(cheats_config)
598 .trace_requirements(self.trace_requirements())
599 .line_coverage(self.line_coverage)
600 .enable_isolation(self.isolation)
601 .networks(self.evm_opts.networks)
602 .create2_deployer(self.evm_opts.create2_deployer)
603 .set_analysis(analysis)
604 })
605 .spec_id(self.spec_id)
606 .gas_limit(self.evm_opts.gas_limit())
607 .legacy_assertions(self.config.legacy_assertions)
608 .build(self.evm_env.clone(), self.tx_env.clone(), db)
609 }
610
611 fn trace_requirements(&self) -> TraceRequirements {
612 TraceRequirements::none()
613 .with_debug(self.debug)
614 .with_decode_internal(self.decode_internal)
615 .with_all_steps(self.record_all_steps)
616 .with_verbosity(self.config.tracing.verbosity.max(self.evm_opts.verbosity))
617 }
618}
619
620#[derive(Clone)]
622#[must_use = "builders do nothing unless you call `build` on them"]
623pub struct MultiContractRunnerBuilder {
624 pub sender: Option<Address>,
627 pub initial_balance: U256,
629 pub fork: Option<CreateFork>,
631 pub config: Arc<Config>,
633 pub inline_config: Arc<InlineConfig>,
635 pub line_coverage: bool,
637 pub debug: bool,
639 pub decode_internal: InternalTraceMode,
641 pub record_all_steps: bool,
643 pub isolation: bool,
645 pub fail_fast: bool,
647 pub multi_network: MultiNetworkConfig,
649 pub showmap: Option<ShowmapConfig>,
651 pub fuzz_minimize: Option<FuzzMinimizeConfig>,
653 pub fuzz_only: bool,
655 pub fuzz_failure_replay: bool,
657 pub symbolic_artifact_replay: Option<SymbolicArtifactReplayConfig>,
659 pub create2_deployer_available: Option<bool>,
661}
662
663impl MultiContractRunnerBuilder {
664 fn create2_deployer_available(&self, evm_opts: &EvmOpts) -> bool {
665 self.create2_deployer_available.unwrap_or_else(|| {
666 self.fork.is_none()
667 && evm_opts.fork_url.is_none()
668 && evm_opts.create2_deployer == foundry_evm::constants::DEFAULT_CREATE2_DEPLOYER
669 })
670 }
671
672 pub fn new(config: Arc<Config>, inline_config: Arc<InlineConfig>) -> Self {
673 Self {
674 config,
675 inline_config,
676 sender: Default::default(),
677 initial_balance: Default::default(),
678 fork: Default::default(),
679 line_coverage: Default::default(),
680 debug: Default::default(),
681 isolation: Default::default(),
682 decode_internal: Default::default(),
683 record_all_steps: Default::default(),
684 fail_fast: false,
685 multi_network: Default::default(),
686 showmap: None,
687 fuzz_minimize: None,
688 fuzz_only: false,
689 fuzz_failure_replay: false,
690 symbolic_artifact_replay: None,
691 create2_deployer_available: None,
692 }
693 }
694
695 pub const fn with_create2_deployer_available(mut self, available: bool) -> Self {
696 self.create2_deployer_available = Some(available);
697 self
698 }
699
700 pub fn with_showmap(mut self, showmap: Option<ShowmapConfig>) -> Self {
701 self.showmap = showmap;
702 self
703 }
704
705 pub fn with_fuzz_minimize(mut self, fuzz_minimize: Option<FuzzMinimizeConfig>) -> Self {
706 self.fuzz_minimize = fuzz_minimize;
707 self
708 }
709
710 pub const fn with_fuzz_only(mut self, fuzz_only: bool) -> Self {
711 self.fuzz_only = fuzz_only;
712 self
713 }
714
715 pub const fn with_fuzz_failure_replay(mut self, fuzz_failure_replay: bool) -> Self {
716 self.fuzz_failure_replay = fuzz_failure_replay;
717 self
718 }
719
720 pub fn with_symbolic_artifact_replay(
721 mut self,
722 replay: Option<SymbolicArtifactReplayConfig>,
723 ) -> Self {
724 self.symbolic_artifact_replay = replay;
725 self
726 }
727
728 pub const fn sender(mut self, sender: Address) -> Self {
729 self.sender = Some(sender);
730 self
731 }
732
733 pub const fn initial_balance(mut self, initial_balance: U256) -> Self {
734 self.initial_balance = initial_balance;
735 self
736 }
737
738 pub fn with_fork(mut self, fork: Option<CreateFork>) -> Self {
739 self.fork = fork;
740 self
741 }
742
743 pub const fn set_coverage(mut self, enable: bool) -> Self {
744 self.line_coverage = enable;
745 self
746 }
747
748 pub const fn set_debug(mut self, enable: bool) -> Self {
749 self.debug = enable;
750 self
751 }
752
753 pub const fn set_decode_internal(mut self, mode: InternalTraceMode) -> Self {
754 self.decode_internal = mode;
755 self
756 }
757
758 pub const fn set_record_all_steps(mut self, enable: bool) -> Self {
759 self.record_all_steps = enable;
760 self
761 }
762
763 pub fn with_multi_network(mut self, multi_network: MultiNetworkConfig) -> Self {
764 self.multi_network = multi_network;
765 self
766 }
767
768 pub const fn fail_fast(mut self, fail_fast: bool) -> Self {
769 self.fail_fast = fail_fast;
770 self
771 }
772
773 pub const fn enable_isolation(mut self, enable: bool) -> Self {
774 self.isolation = enable;
775 self
776 }
777
778 pub fn build<FEN: FoundryEvmNetwork, C: Compiler<CompilerContract = Contract>>(
781 self,
782 output: &ProjectCompileOutput,
783 evm_env: EvmEnvFor<FEN>,
784 tx_env: TxEnvFor<FEN>,
785 evm_opts: EvmOpts,
786 ) -> Result<MultiContractRunner<FEN>> {
787 let root = &self.config.root;
788 let contracts = output
789 .artifact_ids()
790 .map(|(id, v)| (id.with_stripped_file_prefixes(root), v))
791 .collect();
792 let linker = Linker::new(root, contracts);
793
794 let abis = linker
796 .contracts
797 .values()
798 .filter_map(|contract| contract.abi.as_ref().map(|abi| abi.borrow()));
799 let revert_decoder = RevertDecoder::new().with_abis(abis);
800
801 let configured_libraries = self.config.libraries_with_remappings()?;
802 let create2_deployer_available = self.create2_deployer_available(&evm_opts);
803 let create2 = if create2_deployer_available {
804 match linker.link_with_create2_detailed(
805 configured_libraries.clone(),
806 evm_opts.create2_deployer,
807 self.config.create2_library_salt,
808 linker.contracts.keys(),
809 ) {
810 Ok(output) => Some(output),
811 Err(LinkerError::CyclicDependency) => None,
812 Err(err) => return Err(err.into()),
813 }
814 } else {
815 None
816 };
817 let (
818 DetailedLinkOutput {
819 output: LinkOutput { libraries, library_addresses, libs_to_deploy },
820 artifact_libraries,
821 ..
822 },
823 library_deployment,
824 ) = if let Some(output) = create2 {
825 let deployment = if output.output.libs_to_deploy.is_empty() {
826 LibraryDeployment::Nonce
827 } else {
828 LibraryDeployment::Create2 {
829 deployer: evm_opts.create2_deployer,
830 salt: self.config.create2_library_salt,
831 }
832 };
833 (output, deployment)
834 } else {
835 (
836 linker.link_with_nonce_or_address_detailed(
837 configured_libraries,
838 LIBRARY_DEPLOYER,
839 0,
840 linker.contracts.keys(),
841 )?,
842 LibraryDeployment::Nonce,
843 )
844 };
845
846 let linked_contracts = linker
847 .get_linked_artifacts_cow_with_artifact_libraries(&libraries, &artifact_libraries)?;
848 let inline_config = self.inline_config;
849
850 let mut deployable_contracts = DeployableContracts::default();
852 let test_matcher = TestFunctionMatcher::new(
853 &self.config,
854 &inline_config,
855 self.symbolic_artifact_replay.as_ref(),
856 );
857 let empty_filter = EmptyTestFilter::default();
858 let resolver = Resolver::new(&linker);
859
860 for (id, contract) in linked_contracts.iter() {
861 let Some(abi) = contract.abi.as_ref() else { continue };
862
863 if abi.constructor.as_ref().map(|c| c.inputs.is_empty()).unwrap_or(true)
865 && test_matcher.matches_contract(&empty_filter, id, abi.borrow())
866 {
867 linker.ensure_linked(contract, id)?;
868
869 let Some(bytecode) =
870 contract.get_bytecode_bytes().map(|b| b.into_owned()).filter(|b| !b.is_empty())
871 else {
872 continue;
873 };
874
875 let artifact_libraries = artifact_libraries.get(id).unwrap_or(&libraries);
876 let library_addresses =
877 resolver.linked_library_addresses(id, artifact_libraries)?;
878
879 deployable_contracts.insert(
880 id.clone(),
881 TestContract { abi: abi.clone().into_owned(), bytecode, library_addresses },
882 );
883 }
884 }
885
886 let known_contracts =
888 ContractsByArtifactBuilder::new(linked_contracts).with_output(output, root).build();
889
890 let mut analysis = solar::sema::Compiler::new(
892 solar::interface::Session::builder().with_stderr_emitter().build(),
893 );
894 let dcx = analysis.dcx_mut();
895 dcx.set_emitter(Box::new(
896 solar::interface::diagnostics::HumanEmitter::stderr(Default::default())
897 .source_map(Some(dcx.source_map().unwrap())),
898 ));
899 dcx.set_flags_mut(|f| f.track_diagnostics = false);
900
901 let files: Vec<_> = output.output().sources.as_ref().keys().cloned().collect();
903
904 analysis.enter_mut(|compiler| -> Result<()> {
905 let mut pcx = compiler.parse();
906 configure_pcx_from_compile_output(
907 &mut pcx,
908 &self.config,
909 output,
910 if files.is_empty() { None } else { Some(&files) },
911 )?;
912 pcx.parse();
913 let _ = compiler.lower_asts();
914 Ok(())
915 })?;
916
917 let analysis = Arc::new(analysis);
918 let enum_bounds = EnumBounds::collect(&analysis);
920 let fuzz_max_literals = self.config.fuzz.dictionary.max_fuzz_dictionary_literals;
921 let invariant_max_literals = self.config.invariant.dictionary.max_fuzz_dictionary_literals;
922 let fuzz_literals = LiteralsDictionary::new(
923 Some(analysis.clone()),
924 Some(self.config.project_paths()),
925 fuzz_max_literals,
926 );
927 let invariant_literals = if invariant_max_literals == fuzz_max_literals {
928 fuzz_literals.clone()
929 } else {
930 LiteralsDictionary::new(
931 Some(analysis.clone()),
932 Some(self.config.project_paths()),
933 invariant_max_literals,
934 )
935 };
936
937 Ok(MultiContractRunner {
938 contracts: deployable_contracts,
939 revert_decoder,
940 known_contracts,
941 libs_to_deploy,
942 library_addresses,
943 library_deployment,
944 libraries,
945 analysis,
946 fuzz_literals,
947 invariant_literals,
948 enum_bounds,
949
950 tcfg: TestRunnerConfig {
951 evm_opts,
952 evm_env,
953 tx_env,
954 spec_id: self.config.evm_spec_id(),
955 sender: self.sender.unwrap_or(self.config.sender),
956 line_coverage: self.line_coverage,
957 debug: self.debug,
958 decode_internal: self.decode_internal,
959 record_all_steps: self.record_all_steps,
960 inline_config,
961 isolation: self.isolation,
962 early_exit: EarlyExit::new(self.fail_fast),
963 multi_network: self.multi_network,
964 showmap: self.showmap,
965 fuzz_minimize: self.fuzz_minimize,
966 fuzz_only: self.fuzz_only,
967 fuzz_failure_replay: self.fuzz_failure_replay,
968 symbolic_artifact_replay: self.symbolic_artifact_replay,
969 config: self.config,
970 },
971
972 fork: self.fork,
973 })
974 }
975}
976
977#[derive(Clone, Copy)]
978pub(crate) struct TestFunctionMatcher<'a> {
979 config: &'a Config,
980 inline_config: &'a InlineConfig,
981 symbolic_artifact_replay: Option<&'a SymbolicArtifactReplayConfig>,
982}
983
984impl<'a> TestFunctionMatcher<'a> {
985 pub(crate) const fn new(
986 config: &'a Config,
987 inline_config: &'a InlineConfig,
988 symbolic_artifact_replay: Option<&'a SymbolicArtifactReplayConfig>,
989 ) -> Self {
990 Self { config, inline_config, symbolic_artifact_replay }
991 }
992
993 fn symbolic_tests_enabled(&self, contract_id: &str) -> bool {
994 self.symbolic_artifact_replay.is_some_and(|artifact| {
995 artifact.artifact.kind == SymbolicCounterexampleArtifactKind::SingleCall
996 }) || self.inline_config.contract_symbolic_enabled(
997 &self.config.profile,
998 contract_id,
999 self.config.symbolic.enabled,
1000 )
1001 }
1002
1003 pub(crate) fn test_function_kind(
1004 &self,
1005 contract_id: &str,
1006 func: &Function,
1007 generated_symbolic_regression: bool,
1008 ) -> TestFunctionKind {
1009 if generated_symbolic_regression && !func.name.starts_with("test_regression_") {
1010 return TestFunctionKind::Unknown;
1011 }
1012
1013 TestFunctionKind::classify(
1014 func.name.as_str(),
1015 !func.inputs.is_empty(),
1016 self.symbolic_tests_enabled(contract_id),
1017 )
1018 }
1019
1020 pub(crate) fn matches_test_function(
1021 &self,
1022 filter: &dyn TestFilter,
1023 contract_id: &str,
1024 func: &Function,
1025 generated_symbolic_regression: bool,
1026 ) -> bool {
1027 filter.matches_test_function_kind_in_contract(
1028 contract_id,
1029 func,
1030 self.test_function_kind(contract_id, func, generated_symbolic_regression),
1031 )
1032 }
1033
1034 pub(crate) fn matches_contract(
1035 &self,
1036 filter: &dyn TestFilter,
1037 id: &ArtifactId,
1038 abi: &JsonAbi,
1039 ) -> bool {
1040 let identifier = id.identifier();
1041 let generated_symbolic_regression = is_generated_symbolic_regression_contract(abi);
1042 matches_contract(filter, &id.source, &id.name, &identifier, abi.functions(), |func| {
1043 self.test_function_kind(&identifier, func, generated_symbolic_regression)
1044 })
1045 }
1046}
1047
1048pub(crate) fn is_generated_symbolic_regression_contract(abi: &JsonAbi) -> bool {
1049 abi.functions().any(|func| func.name == SYMBOLIC_REGRESSION_MARKER && func.inputs.is_empty())
1050}
1051
1052pub(crate) fn matches_contract(
1053 filter: &dyn TestFilter,
1054 path: &Path,
1055 contract_name: &str,
1056 contract_id: &str,
1057 functions: impl IntoIterator<Item = impl std::borrow::Borrow<Function>>,
1058 test_function_kind: impl Fn(&Function) -> TestFunctionKind,
1059) -> bool {
1060 (filter.matches_path(path) && filter.matches_contract(contract_name))
1061 && functions.into_iter().any(|func| {
1062 let func = func.borrow();
1063 filter.matches_test_function_kind_in_contract(
1064 contract_id,
1065 func,
1066 test_function_kind(func),
1067 )
1068 })
1069}
1070
1071#[cfg(test)]
1072mod tests {
1073 use super::*;
1074 use foundry_common::TestFunctionExt;
1075
1076 fn abi_with_functions(functions: &[&str]) -> JsonAbi {
1077 let mut abi = JsonAbi::new();
1078 for function in functions {
1079 let function = Function::parse(function).unwrap();
1080 abi.functions.entry(function.name.clone()).or_default().push(function);
1081 }
1082 abi
1083 }
1084
1085 #[test]
1086 fn matches_contract_uses_provided_function_kind() {
1087 let filter = EmptyTestFilter::default();
1088 let path = Path::new("test/Symbolic.t.sol");
1089 let func = Function::parse("checkFilteredCompile(uint256)").unwrap();
1090
1091 assert!(matches_contract(&filter, path, "Symbolic", "Symbolic", [func.clone()], |_| {
1092 TestFunctionKind::SymbolicTest
1093 },));
1094 assert!(!matches_contract(&filter, path, "Symbolic", "Symbolic", [func], |func| {
1095 func.test_function_kind()
1096 }));
1097 }
1098
1099 #[test]
1100 fn generated_symbolic_regression_detection_uses_marker() {
1101 let user_suffix_abi = abi_with_functions(&["test_fails()"]);
1102 assert!(!is_generated_symbolic_regression_contract(&user_suffix_abi));
1103
1104 let generated_abi =
1105 abi_with_functions(&[&format!("{SYMBOLIC_REGRESSION_MARKER}()"), "test_fails()"]);
1106 assert!(is_generated_symbolic_regression_contract(&generated_abi));
1107 }
1108
1109 #[test]
1110 fn create2_deployer_availability_default_is_conservative() {
1111 let config = Arc::new(Config::default());
1112 let mut builder = MultiContractRunnerBuilder::new(config, Arc::new(InlineConfig::new()));
1113 let mut evm_opts = EvmOpts::default();
1114 assert!(builder.create2_deployer_available(&evm_opts));
1115
1116 builder.fork = Some(CreateFork {
1117 enable_caching: false,
1118 url: "http://localhost:8545".into(),
1119 evm_opts: evm_opts.clone(),
1120 });
1121 assert!(!builder.create2_deployer_available(&evm_opts));
1122 builder.fork = None;
1123
1124 evm_opts.fork_url = Some("http://localhost:8545".into());
1125 assert!(!builder.create2_deployer_available(&evm_opts));
1126 evm_opts.fork_url = None;
1127 evm_opts.create2_deployer = Address::ZERO;
1128 assert!(!builder.create2_deployer_available(&evm_opts));
1129 assert!(
1130 builder.with_create2_deployer_available(true).create2_deployer_available(&evm_opts)
1131 );
1132 }
1133}