1use crate::{
4 ContractRunner, TestFilter,
5 progress::TestsProgress,
6 result::{SuiteResult, SymbolicCounterexampleArtifact, SymbolicCounterexampleArtifactKind},
7 runner::{
8 ContractRunnerContext, InvariantCampaignScope, count_runnable_invariant_campaign_anchors,
9 function_matches_network_pass,
10 },
11 symbolic_regression::SYMBOLIC_REGRESSION_MARKER,
12};
13use alloy_json_abi::{Function, JsonAbi};
14use alloy_primitives::{Address, Bytes, ChainId, U256};
15use eyre::Result;
16use foundry_cli::opts::configure_pcx_from_compile_output;
17use foundry_common::{
18 ContractsByArtifact, ContractsByArtifactBuilder, EmptyTestFilter, LIBRARY_DEPLOYER,
19 TestFunctionKind, external_compiler::external_artifact_is_test_eligible, get_contract_name,
20};
21use foundry_compilers::{
22 Artifact, ArtifactId, Compiler, ProjectCompileOutput,
23 artifacts::{Contract, Libraries},
24};
25use foundry_config::{Config, FoundryHardfork, InlineConfig};
26use foundry_evm::{
27 backend::Backend,
28 core::evm::{EvmEnvFor, FoundryEvmNetwork, SpecFor, TxEnvFor},
29 decode::RevertDecoder,
30 executors::{EarlyExit, Executor, ExecutorBuilder, ReplayObservation, ShowmapDomain},
31 fuzz::{
32 BaseCounterExample, BasicTxDetails,
33 strategies::{EnumBounds, LiteralsDictionary},
34 },
35 inspectors::{CheatsConfig, EdgeIndexMap},
36 opts::{EvmOpts, ExecutionSpecContext, resolve_execution_spec},
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::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 backend: Backend<FEN>,
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 pub(crate) 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)
142 .flat_map(move |(id, c)| matcher.matching_test_functions(filter, id, &c.abi))
143 }
144
145 pub fn all_test_functions<'a: 'b, 'b>(
147 &'a self,
148 filter: &'b dyn TestFilter,
149 ) -> impl Iterator<Item = &'a Function> + 'b {
150 let matcher = self.test_function_matcher();
151 self.contracts
152 .iter()
153 .filter(|(id, _)| filter.matches_path(&id.source) && filter.matches_contract(&id.name))
154 .flat_map(move |(id, c)| {
155 matcher.test_functions(id.identifier(), &c.abi, |_, _, kind| kind.is_any_test())
156 })
157 }
158
159 pub fn list(&self, filter: &dyn TestFilter) -> BTreeMap<String, BTreeMap<String, Vec<String>>> {
161 self.list_with(filter, |func| func.name.clone())
162 }
163
164 pub(crate) fn list_signatures(
165 &self,
166 filter: &dyn TestFilter,
167 ) -> BTreeMap<String, BTreeMap<String, Vec<String>>> {
168 self.list_with(filter, |func| func.signature())
169 }
170
171 fn list_with(
172 &self,
173 filter: &dyn TestFilter,
174 format_test: impl Fn(&Function) -> String,
175 ) -> BTreeMap<String, BTreeMap<String, Vec<String>>> {
176 let matcher = self.test_function_matcher();
177 let fuzz_only = self.tcfg.fuzz_only;
178 let mut out = BTreeMap::<_, BTreeMap<_, _>>::new();
179 for (id, c) in self.matching_contracts(filter) {
180 let tests = matcher
181 .test_functions(id.identifier(), &c.abi, |contract_id, func, kind| {
182 (!fuzz_only
183 || matches!(
184 kind,
185 TestFunctionKind::FuzzTest { .. } | TestFunctionKind::InvariantTest
186 ))
187 && filter.matches_test_function_kind_in_contract(contract_id, func, kind)
188 })
189 .map(&format_test)
190 .collect::<Vec<_>>();
191 if !tests.is_empty() {
192 out.entry(id.source.display().to_string())
193 .or_default()
194 .insert(id.name.clone(), tests);
195 }
196 }
197 out
198 }
199
200 pub fn test_collect(
206 &mut self,
207 filter: &dyn TestFilter,
208 ) -> Result<BTreeMap<String, SuiteResult>> {
209 let (tx, rx) = mpsc::channel();
210 self.test(filter, tx, false)?;
211 Ok(rx.into_iter().collect())
212 }
213
214 pub fn test(
221 &mut self,
222 filter: &dyn TestFilter,
223 tx: mpsc::Sender<(String, SuiteResult)>,
224 show_progress: bool,
225 ) -> Result<()> {
226 let tokio_handle = tokio::runtime::Handle::current();
227 trace!("running all tests");
228
229 let db = self.backend.clone();
231
232 let find_timer = Instant::now();
233 let contracts = self.matching_contracts(filter).collect::<Vec<_>>();
234 debug!(
235 "Found {} test contracts out of {} in {:?}",
236 contracts.len(),
237 self.contracts.len(),
238 find_timer.elapsed(),
239 );
240 let num_invariant_campaign_anchors = contracts
241 .iter()
242 .map(|(id, contract)| {
243 count_runnable_invariant_campaign_anchors(
244 &contract.abi,
245 filter,
246 InvariantCampaignScope {
247 config: &self.tcfg.config,
248 inline_config: &self.tcfg.inline_config,
249 contract_name: &id.identifier(),
250 all_override_networks: &self.tcfg.multi_network.all_override_networks,
251 pass_network: self.tcfg.multi_network.pass_network.as_ref(),
252 },
253 )
254 })
255 .sum();
256
257 let progress = show_progress
258 .then(|| TestsProgress::new(contracts.len(), rayon::current_num_threads()));
259 let run_suite = |&(id, contract): &(&ArtifactId, &TestContract)| {
260 let _guard = tokio_handle.enter();
261 let identifier = id.identifier();
262 if let Some(progress) = &progress {
263 progress.inner.lock().start_suite_progress(&identifier);
264 }
265 let result = self.run_test_suite(
266 id,
267 contract,
268 &db,
269 filter,
270 ContractRunnerContext {
271 progress: progress.as_ref(),
272 tokio_handle: tokio_handle.clone(),
273 num_invariant_campaign_anchors,
274 },
275 );
276 if let Some(progress) = &progress {
277 progress.inner.lock().end_suite_progress(&identifier, result.summary());
278 }
279 (identifier, result)
280 };
281
282 if let Some(progress) = &progress {
283 let results = contracts.par_iter().map(run_suite).collect::<Vec<_>>();
286 progress.inner.lock().clear();
287 for result in results {
288 let _ = tx.send(result);
289 }
290 } else {
291 contracts.par_iter().for_each(|contract| {
292 let _ = tx.send(run_suite(contract));
293 });
294 }
295
296 Ok(())
297 }
298
299 fn run_test_suite(
300 &self,
301 artifact_id: &ArtifactId,
302 contract: &TestContract,
303 db: &Backend<FEN>,
304 filter: &dyn TestFilter,
305 context: ContractRunnerContext<'_>,
306 ) -> SuiteResult {
307 let identifier = artifact_id.identifier();
308 let span_name = if enabled!(tracing::Level::TRACE) {
309 identifier.as_str()
310 } else {
311 get_contract_name(&identifier)
312 };
313 let span = debug_span!("suite", name = %span_name);
314 let _guard = span.clone().entered();
315
316 debug!("start executing all tests in contract");
317
318 let executor = self.tcfg.executor(
319 self.known_contracts.clone(),
320 self.analysis.clone(),
321 artifact_id,
322 db.clone(),
323 );
324 let runner = ContractRunner::new(&identifier, contract, executor, span, self, context);
325 let r = runner.run_tests(filter);
326
327 debug!(duration=?r.duration, "executed all tests in contract");
328
329 r
330 }
331}
332
333#[derive(Clone, Debug, Default)]
341pub struct MultiNetworkConfig {
342 pub all_override_networks: Vec<NetworkVariant>,
345 pub pass_network: Option<NetworkVariant>,
350}
351
352#[derive(Clone, Debug)]
355pub struct ShowmapConfig {
356 pub out_dir: PathBuf,
358 pub approach: String,
360 pub trial: String,
362 pub per_input: bool,
364 pub domain: ShowmapDomain,
366 pub corpus_dir: Option<PathBuf>,
369 pub emit_files: bool,
371}
372
373pub type FuzzMinimizeEdgeIndices = Arc<Mutex<BTreeMap<String, Arc<Mutex<EdgeIndexMap>>>>>;
374
375#[derive(Clone, Copy, Debug, PartialEq, Eq)]
377pub enum FuzzMinimizeMode {
378 Cmin,
380 Tmin,
382}
383
384#[derive(Clone, Debug)]
387pub struct FuzzMinimizeConfig {
388 pub input: Arc<[BasicTxDetails]>,
390 pub mode: FuzzMinimizeMode,
392 pub evm_edge_indices: FuzzMinimizeEdgeIndices,
395 pub observations: Arc<Mutex<Vec<FuzzMinimizeObservation>>>,
397}
398
399#[derive(Clone, Debug)]
401pub struct FuzzMinimizeObservation {
402 pub target: String,
404 pub observation: ReplayObservation,
406}
407
408#[derive(Clone, Debug)]
409pub struct SymbolicArtifactReplayConfig {
410 pub artifact: SymbolicCounterexampleArtifact,
412 pub path: PathBuf,
414}
415
416#[derive(Clone, Debug)]
418pub struct FuzzFailureReplayConfig {
419 pub failure: Arc<BaseCounterExample>,
421 pub contract: String,
423 pub test: String,
425}
426
427#[derive(Clone, Debug)]
431pub struct TestRunnerConfig<FEN: FoundryEvmNetwork> {
432 pub config: Arc<Config>,
434 pub inline_config: Arc<InlineConfig>,
436
437 pub evm_opts: EvmOpts,
439 pub executor_builder: ExecutorBuilder<FEN>,
441 pub evm_env: EvmEnvFor<FEN>,
443 pub tx_env: TxEnvFor<FEN>,
445 pub spec_id: SpecFor<FEN>,
447 pub hardfork: Option<FoundryHardfork>,
449 pub fork_chain_id: Option<ChainId>,
451 pub fork_hardfork: Option<FoundryHardfork>,
453 pub sender: Address,
455
456 pub line_coverage: bool,
458 pub debug: bool,
460 pub decode_internal: InternalTraceMode,
462 pub record_all_steps: bool,
464 pub isolation: bool,
466 pub early_exit: EarlyExit,
468
469 pub multi_network: MultiNetworkConfig,
471
472 pub showmap: Option<ShowmapConfig>,
475 pub fuzz_minimize: Option<FuzzMinimizeConfig>,
477 pub fuzz_only: bool,
479 pub fuzz_failure_replay: bool,
481 pub fuzz_input: Option<FuzzFailureReplayConfig>,
483
484 pub symbolic_artifact_replay: Option<SymbolicArtifactReplayConfig>,
486}
487
488impl<FEN: FoundryEvmNetwork> TestRunnerConfig<FEN> {
489 pub fn reconfigure_with(&mut self, config: Arc<Config>) {
492 debug_assert!(!Arc::ptr_eq(&self.config, &config));
493
494 self.sender = config.sender;
495 self.evm_opts.networks = config.networks;
496 self.hardfork = resolve_execution_spec(
497 config.evm_version,
498 config.hardfork,
499 &mut self.evm_env,
500 ExecutionSpecContext::local_or_fork(self.fork_chain_id, self.fork_hardfork),
501 None,
502 );
503 self.spec_id = self.evm_env.cfg_env.spec;
504 self.isolation = config.isolate;
505 self.evm_opts.always_use_create_2_factory = config.always_use_create_2_factory;
509 self.evm_opts.no_fork_bal = config.no_fork_bal;
510 self.evm_opts.fork_state_by_number = config.fork_state_by_number;
511 self.config = config;
512 }
513
514 pub fn configure_executor(&self, executor: &mut Executor<FEN>) {
516 debug_assert!(
517 executor.backend().networks().has_same_execution_profile(&self.evm_opts.networks)
518 );
519 debug_assert!(
520 executor.inspector().networks.has_same_execution_profile(&self.evm_opts.networks)
521 );
522 let inspector = executor.inspector_mut();
523 if let Some(cheatcodes) = inspector.cheatcodes.as_mut() {
524 let mut config = cheatcodes.config.clone_with(&self.config, self.evm_opts.clone());
525 config.isolate = self.isolation;
526 cheatcodes.config = Arc::new(config);
527 }
528 inspector.tracing_requirements(self.trace_requirements());
529 inspector.collect_line_coverage(self.line_coverage);
530 inspector.enable_isolation(self.isolation);
531 executor.set_spec_id(self.spec_id);
532 executor.set_legacy_assertions(self.config.legacy_assertions);
533 }
534
535 pub fn executor(
537 &self,
538 known_contracts: ContractsByArtifact,
539 analysis: Arc<solar::sema::Compiler>,
540 artifact_id: &ArtifactId,
541 db: Backend<FEN>,
542 ) -> Executor<FEN> {
543 let mut cheats_config = CheatsConfig::new(
544 &self.config,
545 self.evm_opts.clone(),
546 Some(known_contracts),
547 Some(artifact_id.clone()),
548 false,
549 );
550 cheats_config.isolate = self.isolation;
551 let cheats_config = Arc::new(cheats_config);
552 self.executor_builder
553 .clone()
554 .inspectors(|stack| {
555 stack
556 .logs(self.config.live_logs)
557 .cheatcodes(cheats_config)
558 .trace_requirements(self.trace_requirements())
559 .line_coverage(self.line_coverage)
560 .enable_isolation(self.isolation)
561 .create2_deployer(self.evm_opts.create2_deployer)
562 .set_analysis(analysis)
563 })
564 .spec_id(self.spec_id)
565 .gas_limit(self.evm_opts.gas_limit())
566 .legacy_assertions(self.config.legacy_assertions)
567 .build(self.evm_env.clone(), self.tx_env.clone(), db, self.evm_opts.networks)
568 }
569
570 fn trace_requirements(&self) -> TraceRequirements {
571 TraceRequirements::none()
572 .with_debug(self.debug)
573 .with_decode_internal(self.decode_internal)
574 .with_all_steps(self.record_all_steps)
575 .with_verbosity(self.config.tracing.verbosity.max(self.evm_opts.verbosity))
576 }
577}
578
579#[derive(Clone)]
581#[must_use = "builders do nothing unless you call `build` on them"]
582pub struct MultiContractRunnerBuilder {
583 pub sender: Option<Address>,
586 pub initial_balance: U256,
588 pub config: Arc<Config>,
590 pub inline_config: Arc<InlineConfig>,
592 pub line_coverage: bool,
594 pub debug: bool,
596 pub decode_internal: InternalTraceMode,
598 pub record_all_steps: bool,
600 pub isolation: bool,
602 pub fail_fast: bool,
604 pub multi_network: MultiNetworkConfig,
606 pub showmap: Option<ShowmapConfig>,
608 pub fuzz_only: bool,
610 pub fuzz_failure_replay: bool,
612 pub fuzz_input: Option<FuzzFailureReplayConfig>,
614 pub symbolic_artifact_replay: Option<SymbolicArtifactReplayConfig>,
616 pub create2_deployer_available: Option<bool>,
618}
619
620impl MultiContractRunnerBuilder {
621 fn create2_deployer_available<FEN: FoundryEvmNetwork>(
622 &self,
623 evm_opts: &EvmOpts,
624 backend: &Backend<FEN>,
625 ) -> bool {
626 self.create2_deployer_available.unwrap_or_else(|| {
627 !backend.is_in_forking_mode()
628 && evm_opts.fork_url.is_none()
629 && evm_opts.create2_deployer == foundry_evm::constants::DEFAULT_CREATE2_DEPLOYER
630 })
631 }
632
633 pub fn new(config: Arc<Config>, inline_config: Arc<InlineConfig>) -> Self {
634 Self {
635 config,
636 inline_config,
637 sender: None,
638 initial_balance: U256::ZERO,
639 line_coverage: false,
640 debug: false,
641 isolation: false,
642 decode_internal: Default::default(),
643 record_all_steps: false,
644 fail_fast: false,
645 multi_network: Default::default(),
646 showmap: None,
647 fuzz_only: false,
648 fuzz_failure_replay: false,
649 fuzz_input: None,
650 symbolic_artifact_replay: None,
651 create2_deployer_available: None,
652 }
653 }
654
655 pub const fn with_create2_deployer_available(mut self, available: bool) -> Self {
656 self.create2_deployer_available = Some(available);
657 self
658 }
659
660 pub fn with_showmap(mut self, showmap: Option<ShowmapConfig>) -> Self {
661 self.showmap = showmap;
662 self
663 }
664
665 pub const fn with_fuzz_only(mut self, fuzz_only: bool) -> Self {
666 self.fuzz_only = fuzz_only;
667 self
668 }
669
670 pub const fn with_fuzz_failure_replay(mut self, fuzz_failure_replay: bool) -> Self {
671 self.fuzz_failure_replay = fuzz_failure_replay;
672 self
673 }
674
675 pub fn with_fuzz_input(mut self, fuzz_input: Option<FuzzFailureReplayConfig>) -> Self {
676 self.fuzz_input = fuzz_input;
677 self
678 }
679
680 pub fn with_symbolic_artifact_replay(
681 mut self,
682 replay: Option<SymbolicArtifactReplayConfig>,
683 ) -> Self {
684 self.symbolic_artifact_replay = replay;
685 self
686 }
687
688 pub const fn sender(mut self, sender: Address) -> Self {
689 self.sender = Some(sender);
690 self
691 }
692
693 pub const fn initial_balance(mut self, initial_balance: U256) -> Self {
694 self.initial_balance = initial_balance;
695 self
696 }
697
698 pub const fn set_coverage(mut self, enable: bool) -> Self {
699 self.line_coverage = enable;
700 self
701 }
702
703 pub const fn set_debug(mut self, enable: bool) -> Self {
704 self.debug = enable;
705 self
706 }
707
708 pub const fn set_decode_internal(mut self, mode: InternalTraceMode) -> Self {
709 self.decode_internal = mode;
710 self
711 }
712
713 pub const fn set_record_all_steps(mut self, enable: bool) -> Self {
714 self.record_all_steps = enable;
715 self
716 }
717
718 pub fn with_multi_network(mut self, multi_network: MultiNetworkConfig) -> Self {
719 self.multi_network = multi_network;
720 self
721 }
722
723 pub const fn fail_fast(mut self, fail_fast: bool) -> Self {
724 self.fail_fast = fail_fast;
725 self
726 }
727
728 pub const fn enable_isolation(mut self, enable: bool) -> Self {
729 self.isolation = enable;
730 self
731 }
732
733 pub fn build<FEN: FoundryEvmNetwork, C: Compiler<CompilerContract = Contract>>(
736 self,
737 output: &ProjectCompileOutput,
738 mut evm_env: EvmEnvFor<FEN>,
739 tx_env: TxEnvFor<FEN>,
740 evm_opts: EvmOpts,
741 backend: Backend<FEN>,
742 executor_builder: ExecutorBuilder<FEN>,
743 ) -> Result<MultiContractRunner<FEN>> {
744 let root = &self.config.root;
745 let coverage_artifacts = self
746 .line_coverage
747 .then(|| self.config.coverage_cache_path())
748 .flatten()
749 .map(|path| path.join("artifacts"));
750 let artifact_id = |mut id: ArtifactId| {
751 if let Some(coverage_artifacts) = &coverage_artifacts
754 && let Ok(path) = id.path.strip_prefix(coverage_artifacts)
755 {
756 id.path = self.config.out.join(path);
757 }
758 id.with_stripped_file_prefixes(root).with_slashed_paths()
759 };
760 let contracts = output.artifact_ids().map(|(id, v)| (artifact_id(id), v)).collect();
761 let linker = Linker::new(root, contracts);
762
763 let abis = linker
765 .contracts
766 .values()
767 .filter_map(|contract| contract.abi.as_ref().map(|abi| abi.borrow()));
768 let revert_decoder = RevertDecoder::new().with_abis(abis);
769
770 let configured_libraries = self.config.libraries_with_remappings()?;
771 let create2 = if self.create2_deployer_available(&evm_opts, &backend) {
772 match linker.link_with_create2_detailed(
773 configured_libraries.clone(),
774 evm_opts.create2_deployer,
775 self.config.create2_library_salt,
776 linker.contracts.keys(),
777 ) {
778 Ok(output) => Some(output),
779 Err(LinkerError::CyclicDependency) => None,
780 Err(err) => return Err(err.into()),
781 }
782 } else {
783 None
784 };
785 let (
786 DetailedLinkOutput {
787 output: LinkOutput { libraries, library_addresses, libs_to_deploy },
788 artifact_libraries,
789 ..
790 },
791 library_deployment,
792 ) = match create2 {
793 Some(output) => {
794 let deployment = if output.output.libs_to_deploy.is_empty() {
795 LibraryDeployment::Nonce
796 } else {
797 LibraryDeployment::Create2 {
798 deployer: evm_opts.create2_deployer,
799 salt: self.config.create2_library_salt,
800 }
801 };
802 (output, deployment)
803 }
804 None => (
805 linker.link_with_nonce_or_address_detailed(
806 configured_libraries,
807 LIBRARY_DEPLOYER,
808 0,
809 linker.contracts.keys(),
810 )?,
811 LibraryDeployment::Nonce,
812 ),
813 };
814
815 let linked_contracts = linker
816 .get_linked_artifacts_cow_with_artifact_libraries(&libraries, &artifact_libraries)?;
817 let inline_config = self.inline_config;
818
819 let mut deployable_contracts = DeployableContracts::default();
821 let test_matcher = TestFunctionMatcher::new(
822 &self.config,
823 &inline_config,
824 self.symbolic_artifact_replay.as_ref(),
825 );
826 let empty_filter = EmptyTestFilter::default();
827 let resolver = Resolver::new(&linker);
828 for (id, contract) in linked_contracts.iter() {
829 let Some(abi) = contract.abi.as_ref() else { continue };
830 if abi.constructor.as_ref().is_some_and(|c| !c.inputs.is_empty())
831 || !test_matcher.matches_contract(&empty_filter, id, abi)
832 {
833 continue;
834 }
835 linker.ensure_linked(contract, id)?;
836 let Some(bytecode) =
837 contract.get_bytecode_bytes().map(|b| b.into_owned()).filter(|b| !b.is_empty())
838 else {
839 continue;
840 };
841 let artifact_libraries = artifact_libraries.get(id).unwrap_or(&libraries);
842 let library_addresses = resolver.linked_library_addresses(id, artifact_libraries)?;
843 deployable_contracts.insert(
844 id.clone(),
845 TestContract { abi: abi.clone().into_owned(), bytecode, library_addresses },
846 );
847 }
848
849 let known_contracts = ContractsByArtifactBuilder::new(linked_contracts)
851 .with_storage_layouts(output.artifact_ids().filter_map(|(id, artifact)| {
852 artifact.storage_layout.as_ref().map(|layout| (artifact_id(id), layout.clone()))
853 }))
854 .build();
855
856 let mut analysis = solar::sema::Compiler::new(
858 solar::interface::Session::builder().with_stderr_emitter().build(),
859 );
860 let dcx = analysis.dcx_mut();
861 dcx.set_emitter(Box::new(
862 solar::interface::diagnostics::HumanEmitter::stderr(Default::default())
863 .source_map(Some(dcx.source_map().unwrap())),
864 ));
865 dcx.set_flags_mut(|f| f.track_diagnostics = false);
866
867 let files: Vec<_> = output.output().sources.as_ref().keys().cloned().collect();
869 analysis.enter_mut(|compiler| -> Result<()> {
870 let mut pcx = compiler.parse();
871 configure_pcx_from_compile_output(
872 &mut pcx,
873 &self.config,
874 output,
875 (!self.line_coverage && !files.is_empty()).then_some(&files),
876 )?;
877 pcx.parse();
878 let _ = compiler.lower_asts();
879 Ok(())
880 })?;
881 let analysis = Arc::new(analysis);
882
883 let enum_bounds = EnumBounds::collect(&analysis);
885 let literals = |max_literals| {
886 LiteralsDictionary::new(
887 Some(analysis.clone()),
888 Some(self.config.project_paths()),
889 max_literals,
890 )
891 };
892 let fuzz_max_literals = self.config.fuzz.dictionary.max_fuzz_dictionary_literals;
893 let invariant_max_literals = self.config.invariant.dictionary.max_fuzz_dictionary_literals;
894 let fuzz_literals = literals(fuzz_max_literals);
895 let invariant_literals = if invariant_max_literals == fuzz_max_literals {
896 fuzz_literals.clone()
897 } else {
898 literals(invariant_max_literals)
899 };
900
901 let fork_context = backend.fork()?.as_ref().map(|fork| fork.context());
902 let fork_chain_id = fork_context.map(|context| context.source_chain_id);
903 let fork_hardfork = fork_context.and_then(|context| context.hardfork);
904 let hardfork = resolve_execution_spec(
905 self.config.evm_version,
906 self.config.hardfork,
907 &mut evm_env,
908 ExecutionSpecContext::local_or_fork(fork_chain_id, fork_hardfork),
909 None,
910 );
911 let spec_id = evm_env.cfg_env.spec;
912
913 Ok(MultiContractRunner {
914 contracts: deployable_contracts,
915 revert_decoder,
916 known_contracts,
917 libs_to_deploy,
918 library_addresses,
919 library_deployment,
920 libraries,
921 analysis,
922 fuzz_literals,
923 invariant_literals,
924 enum_bounds,
925
926 tcfg: TestRunnerConfig {
927 evm_opts,
928 executor_builder,
929 evm_env,
930 tx_env,
931 spec_id,
932 hardfork,
933 fork_chain_id,
934 fork_hardfork,
935 sender: self.sender.unwrap_or(self.config.sender),
936 line_coverage: self.line_coverage,
937 debug: self.debug,
938 decode_internal: self.decode_internal,
939 record_all_steps: self.record_all_steps,
940 inline_config,
941 isolation: self.isolation,
942 early_exit: EarlyExit::new(self.fail_fast),
943 multi_network: self.multi_network,
944 showmap: self.showmap,
945 fuzz_minimize: None,
946 fuzz_only: self.fuzz_only,
947 fuzz_failure_replay: self.fuzz_failure_replay,
948 fuzz_input: self.fuzz_input,
949 symbolic_artifact_replay: self.symbolic_artifact_replay,
950 config: self.config,
951 },
952
953 backend,
954 })
955 }
956}
957
958#[derive(Clone, Copy)]
959pub(crate) struct TestFunctionMatcher<'a> {
960 config: &'a Config,
961 inline_config: &'a InlineConfig,
962 symbolic_artifact_replay: Option<&'a SymbolicArtifactReplayConfig>,
963}
964
965impl<'a> TestFunctionMatcher<'a> {
966 pub(crate) const fn new(
967 config: &'a Config,
968 inline_config: &'a InlineConfig,
969 symbolic_artifact_replay: Option<&'a SymbolicArtifactReplayConfig>,
970 ) -> Self {
971 Self { config, inline_config, symbolic_artifact_replay }
972 }
973
974 fn symbolic_tests_enabled(&self, contract_id: &str) -> bool {
975 self.symbolic_artifact_replay.is_some_and(|artifact| {
976 artifact.artifact.kind == SymbolicCounterexampleArtifactKind::SingleCall
977 }) || self.inline_config.contract_symbolic_enabled(
978 &self.config.profile,
979 contract_id,
980 self.config.symbolic.enabled,
981 )
982 }
983
984 pub(crate) fn test_function_kind(
985 &self,
986 contract_id: &str,
987 func: &Function,
988 generated_symbolic_regression: bool,
989 ) -> TestFunctionKind {
990 if generated_symbolic_regression && !func.name.starts_with("test_regression_") {
991 return TestFunctionKind::Unknown;
992 }
993
994 TestFunctionKind::classify(
995 func.name.as_str(),
996 !func.inputs.is_empty(),
997 self.symbolic_tests_enabled(contract_id),
998 )
999 }
1000
1001 pub(crate) fn test_functions(
1004 self,
1005 contract_id: String,
1006 abi: &JsonAbi,
1007 mut keep: impl FnMut(&str, &Function, TestFunctionKind) -> bool,
1008 ) -> impl Iterator<Item = &Function> {
1009 let generated_symbolic_regression = is_generated_symbolic_regression_contract(abi);
1010 abi.functions().filter(move |func| {
1011 let kind = self.test_function_kind(&contract_id, func, generated_symbolic_regression);
1012 keep(&contract_id, func, kind)
1013 })
1014 }
1015
1016 fn matching_test_functions<'b>(
1018 self,
1019 filter: &dyn TestFilter,
1020 id: &ArtifactId,
1021 abi: &'b JsonAbi,
1022 ) -> impl Iterator<Item = &'b Function> {
1023 self.test_functions(id.identifier(), abi, move |contract_id, func, kind| {
1024 filter.matches_test_function_kind_in_contract(contract_id, func, kind)
1025 })
1026 }
1027
1028 pub(crate) fn count_fuzz_engine_targets(
1031 &self,
1032 filter: &dyn TestFilter,
1033 id: &ArtifactId,
1034 abi: &JsonAbi,
1035 multi_network: &MultiNetworkConfig,
1036 ) -> (usize, usize) {
1037 let contract_name = id.identifier();
1038 let matches_network_pass = |func: &Function| {
1039 function_matches_network_pass(
1040 &multi_network.all_override_networks,
1041 multi_network.pass_network.as_ref(),
1042 self.inline_config.network_for(&self.config.profile, &contract_name, &func.name),
1043 )
1044 };
1045 let fuzz = self
1046 .test_functions(contract_name.clone(), abi, |contract_id, func, kind| {
1047 kind.is_fuzz_test()
1048 && filter.matches_test_function_kind_in_contract(contract_id, func, kind)
1049 && matches_network_pass(func)
1050 })
1051 .count();
1052 let invariant = count_runnable_invariant_campaign_anchors(
1053 abi,
1054 filter,
1055 InvariantCampaignScope {
1056 config: self.config,
1057 inline_config: self.inline_config,
1058 contract_name: &contract_name,
1059 all_override_networks: &multi_network.all_override_networks,
1060 pass_network: multi_network.pass_network.as_ref(),
1061 },
1062 );
1063 (fuzz, invariant)
1064 }
1065
1066 pub(crate) fn matches_contract(
1067 &self,
1068 filter: &dyn TestFilter,
1069 id: &ArtifactId,
1070 abi: &JsonAbi,
1071 ) -> bool {
1072 external_artifact_is_test_eligible(&id.build_id)
1073 && filter.matches_path(&id.source)
1074 && filter.matches_contract(&id.name)
1075 && self.matching_test_functions(filter, id, abi).next().is_some()
1076 }
1077}
1078
1079pub(crate) fn is_generated_symbolic_regression_contract(abi: &JsonAbi) -> bool {
1080 abi.functions().any(|func| func.name == SYMBOLIC_REGRESSION_MARKER && func.inputs.is_empty())
1081}
1082
1083#[cfg(test)]
1084mod tests {
1085 use super::*;
1086 use foundry_evm::core::evm::EthEvmNetwork;
1087
1088 fn abi_with_functions(functions: &[&str]) -> JsonAbi {
1089 let mut abi = JsonAbi::new();
1090 for function in functions {
1091 let function = Function::parse(function).unwrap();
1092 abi.functions.entry(function.name.clone()).or_default().push(function);
1093 }
1094 abi
1095 }
1096
1097 #[test]
1098 fn generated_symbolic_regression_detection_uses_marker() {
1099 let user_suffix_abi = abi_with_functions(&["test_fails()"]);
1100 assert!(!is_generated_symbolic_regression_contract(&user_suffix_abi));
1101
1102 let generated_abi =
1103 abi_with_functions(&[&format!("{SYMBOLIC_REGRESSION_MARKER}()"), "test_fails()"]);
1104 assert!(is_generated_symbolic_regression_contract(&generated_abi));
1105 }
1106
1107 #[test]
1108 fn create2_deployer_availability_default_is_conservative() {
1109 let config = Arc::new(Config::default());
1110 let builder = MultiContractRunnerBuilder::new(config, Arc::new(InlineConfig::new()));
1111 let mut evm_opts = EvmOpts::default();
1112 let backend = Backend::<EthEvmNetwork>::spawn(None).unwrap();
1113 assert!(builder.create2_deployer_available(&evm_opts, &backend));
1114
1115 evm_opts.fork_url = Some("http://localhost:8545".into());
1116 assert!(!builder.create2_deployer_available(&evm_opts, &backend));
1117 evm_opts.fork_url = None;
1118 evm_opts.create2_deployer = Address::ZERO;
1119 assert!(!builder.create2_deployer_available(&evm_opts, &backend));
1120 assert!(
1121 builder
1122 .with_create2_deployer_available(true)
1123 .create2_deployer_available(&evm_opts, &backend)
1124 );
1125 }
1126}