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, 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 fork::CreateFork,
32 fuzz::{
33 BaseCounterExample, BasicTxDetails,
34 strategies::{EnumBounds, LiteralsDictionary},
35 },
36 inspectors::{CheatsConfig, EdgeIndexMap},
37 opts::{EvmOpts, ExecutionSpecContext, resolve_execution_spec},
38 traces::{InternalTraceMode, TraceRequirements},
39};
40use foundry_evm_networks::NetworkVariant;
41
42use foundry_linking::{DetailedLinkOutput, LinkOutput, Linker, LinkerError, Resolver};
43use rayon::prelude::*;
44use std::{
45 borrow::Borrow,
46 collections::{BTreeMap, BTreeSet},
47 ops::{Deref, DerefMut},
48 path::PathBuf,
49 sync::{Arc, Mutex, mpsc},
50 time::Instant,
51};
52
53#[derive(Debug, Clone)]
54pub struct TestContract {
55 pub abi: JsonAbi,
56 pub bytecode: Bytes,
57 pub library_addresses: BTreeSet<Address>,
58}
59
60pub type DeployableContracts = BTreeMap<ArtifactId, TestContract>;
61
62#[derive(Clone, Debug)]
65pub struct MultiContractRunner<FEN: FoundryEvmNetwork> {
66 pub contracts: DeployableContracts,
69 pub known_contracts: ContractsByArtifact,
71 pub revert_decoder: RevertDecoder,
73 pub libs_to_deploy: Vec<Bytes>,
75 pub library_addresses: Vec<Address>,
77 pub library_deployment: LibraryDeployment,
79 pub libraries: Libraries,
81 pub analysis: Arc<solar::sema::Compiler>,
83 pub fuzz_literals: LiteralsDictionary,
85 pub invariant_literals: LiteralsDictionary,
87 pub enum_bounds: EnumBounds,
89
90 pub fork: Option<CreateFork>,
92
93 pub tcfg: TestRunnerConfig<FEN>,
95}
96
97#[derive(Clone, Copy, Debug)]
99pub enum LibraryDeployment {
100 Nonce,
101 Create2 { deployer: Address, salt: alloy_primitives::B256 },
102}
103
104impl<FEN: FoundryEvmNetwork> Deref for MultiContractRunner<FEN> {
105 type Target = TestRunnerConfig<FEN>;
106
107 fn deref(&self) -> &Self::Target {
108 &self.tcfg
109 }
110}
111
112impl<FEN: FoundryEvmNetwork> DerefMut for MultiContractRunner<FEN> {
113 fn deref_mut(&mut self) -> &mut Self::Target {
114 &mut self.tcfg
115 }
116}
117
118impl<FEN: FoundryEvmNetwork> MultiContractRunner<FEN> {
119 pub(crate) fn test_function_matcher(&self) -> TestFunctionMatcher<'_> {
120 TestFunctionMatcher::new(
121 &self.config,
122 &self.inline_config,
123 self.tcfg.symbolic_artifact_replay.as_ref(),
124 )
125 }
126
127 pub fn matching_contracts<'a: 'b, 'b>(
129 &'a self,
130 filter: &'b dyn TestFilter,
131 ) -> impl Iterator<Item = (&'a ArtifactId, &'a TestContract)> + 'b {
132 let matcher = self.test_function_matcher();
133 self.contracts.iter().filter(move |&(id, c)| matcher.matches_contract(filter, id, &c.abi))
134 }
135
136 pub fn matching_test_functions<'a: 'b, 'b>(
138 &'a self,
139 filter: &'b dyn TestFilter,
140 ) -> impl Iterator<Item = &'a Function> + 'b {
141 let matcher = self.test_function_matcher();
142 self.matching_contracts(filter)
143 .flat_map(move |(id, c)| matcher.matching_test_functions(filter, id, &c.abi))
144 }
145
146 pub fn all_test_functions<'a: 'b, 'b>(
148 &'a self,
149 filter: &'b dyn TestFilter,
150 ) -> impl Iterator<Item = &'a Function> + 'b {
151 let matcher = self.test_function_matcher();
152 self.contracts
153 .iter()
154 .filter(|(id, _)| filter.matches_path(&id.source) && filter.matches_contract(&id.name))
155 .flat_map(move |(id, c)| {
156 matcher.test_functions(id.identifier(), &c.abi, |_, _, kind| kind.is_any_test())
157 })
158 }
159
160 pub fn list(&self, filter: &dyn TestFilter) -> BTreeMap<String, BTreeMap<String, Vec<String>>> {
162 self.list_with(filter, |func| func.name.clone())
163 }
164
165 pub(crate) fn list_signatures(
166 &self,
167 filter: &dyn TestFilter,
168 ) -> BTreeMap<String, BTreeMap<String, Vec<String>>> {
169 self.list_with(filter, |func| func.signature())
170 }
171
172 fn list_with(
173 &self,
174 filter: &dyn TestFilter,
175 format_test: impl Fn(&Function) -> String,
176 ) -> BTreeMap<String, BTreeMap<String, Vec<String>>> {
177 let matcher = self.test_function_matcher();
178 let fuzz_only = self.tcfg.fuzz_only;
179 let mut out = BTreeMap::<_, BTreeMap<_, _>>::new();
180 for (id, c) in self.matching_contracts(filter) {
181 let tests = matcher
182 .test_functions(id.identifier(), &c.abi, |contract_id, func, kind| {
183 (!fuzz_only
184 || matches!(
185 kind,
186 TestFunctionKind::FuzzTest { .. } | TestFunctionKind::InvariantTest
187 ))
188 && filter.matches_test_function_kind_in_contract(contract_id, func, kind)
189 })
190 .map(&format_test)
191 .collect::<Vec<_>>();
192 if !tests.is_empty() {
193 out.entry(id.source.display().to_string())
194 .or_default()
195 .insert(id.name.clone(), tests);
196 }
197 }
198 out
199 }
200
201 pub fn test_collect(
207 &mut self,
208 filter: &dyn TestFilter,
209 ) -> Result<BTreeMap<String, SuiteResult>> {
210 let (tx, rx) = mpsc::channel();
211 self.test(filter, tx, false)?;
212 Ok(rx.into_iter().collect())
213 }
214
215 pub fn test(
222 &mut self,
223 filter: &dyn TestFilter,
224 tx: mpsc::Sender<(String, SuiteResult)>,
225 show_progress: bool,
226 ) -> Result<()> {
227 let tokio_handle = tokio::runtime::Handle::current();
228 trace!("running all tests");
229
230 let db = Backend::spawn(self.fork.take())?;
232
233 let find_timer = Instant::now();
234 let contracts = self.matching_contracts(filter).collect::<Vec<_>>();
235 debug!(
236 "Found {} test contracts out of {} in {:?}",
237 contracts.len(),
238 self.contracts.len(),
239 find_timer.elapsed(),
240 );
241 let num_invariant_campaign_anchors = contracts
242 .iter()
243 .map(|(id, contract)| {
244 count_runnable_invariant_campaign_anchors(
245 &contract.abi,
246 filter,
247 InvariantCampaignScope {
248 config: &self.tcfg.config,
249 inline_config: &self.tcfg.inline_config,
250 contract_name: &id.identifier(),
251 all_override_networks: &self.tcfg.multi_network.all_override_networks,
252 pass_network: self.tcfg.multi_network.pass_network.as_ref(),
253 },
254 )
255 })
256 .sum();
257
258 let progress = show_progress
259 .then(|| TestsProgress::new(contracts.len(), rayon::current_num_threads()));
260 let run_suite = |&(id, contract): &(&ArtifactId, &TestContract)| {
261 let _guard = tokio_handle.enter();
262 let identifier = id.identifier();
263 if let Some(progress) = &progress {
264 progress.inner.lock().start_suite_progress(&identifier);
265 }
266 let result = self.run_test_suite(
267 id,
268 contract,
269 &db,
270 filter,
271 ContractRunnerContext {
272 progress: progress.as_ref(),
273 tokio_handle: tokio_handle.clone(),
274 num_invariant_campaign_anchors,
275 },
276 );
277 if let Some(progress) = &progress {
278 progress.inner.lock().end_suite_progress(&identifier, result.summary());
279 }
280 (identifier, result)
281 };
282
283 if let Some(progress) = &progress {
284 let results = contracts.par_iter().map(run_suite).collect::<Vec<_>>();
287 progress.inner.lock().clear();
288 for result in results {
289 let _ = tx.send(result);
290 }
291 } else {
292 contracts.par_iter().for_each(|contract| {
293 let _ = tx.send(run_suite(contract));
294 });
295 }
296
297 Ok(())
298 }
299
300 fn run_test_suite(
301 &self,
302 artifact_id: &ArtifactId,
303 contract: &TestContract,
304 db: &Backend<FEN>,
305 filter: &dyn TestFilter,
306 context: ContractRunnerContext<'_>,
307 ) -> SuiteResult {
308 let identifier = artifact_id.identifier();
309 let span_name = if enabled!(tracing::Level::TRACE) {
310 identifier.as_str()
311 } else {
312 get_contract_name(&identifier)
313 };
314 let span = debug_span!("suite", name = %span_name);
315 let _guard = span.clone().entered();
316
317 debug!("start executing all tests in contract");
318
319 let executor = self.tcfg.executor(
320 self.known_contracts.clone(),
321 self.analysis.clone(),
322 artifact_id,
323 db.clone(),
324 );
325 let runner = ContractRunner::new(&identifier, contract, executor, span, self, context);
326 let r = runner.run_tests(filter);
327
328 debug!(duration=?r.duration, "executed all tests in contract");
329
330 r
331 }
332}
333
334#[derive(Clone, Debug, Default)]
342pub struct MultiNetworkConfig {
343 pub all_override_networks: Vec<NetworkVariant>,
346 pub pass_network: Option<NetworkVariant>,
351}
352
353#[derive(Clone, Debug)]
356pub struct ShowmapConfig {
357 pub out_dir: PathBuf,
359 pub approach: String,
361 pub trial: String,
363 pub per_input: bool,
365 pub domain: ShowmapDomain,
367 pub corpus_dir: Option<PathBuf>,
370 pub emit_files: bool,
372}
373
374pub type FuzzMinimizeEdgeIndices = Arc<Mutex<BTreeMap<String, Arc<Mutex<EdgeIndexMap>>>>>;
375
376#[derive(Clone, Copy, Debug, PartialEq, Eq)]
378pub enum FuzzMinimizeMode {
379 Cmin,
381 Tmin,
383}
384
385#[derive(Clone, Debug)]
388pub struct FuzzMinimizeConfig {
389 pub input: Arc<[BasicTxDetails]>,
391 pub mode: FuzzMinimizeMode,
393 pub evm_edge_indices: FuzzMinimizeEdgeIndices,
396 pub observations: Arc<Mutex<Vec<FuzzMinimizeObservation>>>,
398}
399
400#[derive(Clone, Debug)]
402pub struct FuzzMinimizeObservation {
403 pub target: String,
405 pub observation: ReplayObservation,
407}
408
409#[derive(Clone, Debug)]
410pub struct SymbolicArtifactReplayConfig {
411 pub artifact: SymbolicCounterexampleArtifact,
413 pub path: PathBuf,
415}
416
417#[derive(Clone, Debug)]
419pub struct FuzzFailureReplayConfig {
420 pub failure: Arc<BaseCounterExample>,
422 pub contract: String,
424 pub test: String,
426}
427
428#[derive(Clone, Debug)]
432pub struct TestRunnerConfig<FEN: FoundryEvmNetwork> {
433 pub config: Arc<Config>,
435 pub inline_config: Arc<InlineConfig>,
437
438 pub evm_opts: EvmOpts,
440 pub executor_builder: ExecutorBuilder<FEN>,
442 pub evm_env: EvmEnvFor<FEN>,
444 pub tx_env: TxEnvFor<FEN>,
446 pub spec_id: SpecFor<FEN>,
448 pub hardfork: Option<FoundryHardfork>,
450 pub fork_chain_id: Option<ChainId>,
452 pub fork_hardfork: Option<FoundryHardfork>,
454 pub sender: Address,
456
457 pub line_coverage: bool,
459 pub debug: bool,
461 pub decode_internal: InternalTraceMode,
463 pub record_all_steps: bool,
465 pub isolation: bool,
467 pub early_exit: EarlyExit,
469
470 pub multi_network: MultiNetworkConfig,
472
473 pub showmap: Option<ShowmapConfig>,
476 pub fuzz_minimize: Option<FuzzMinimizeConfig>,
478 pub fuzz_only: bool,
480 pub fuzz_failure_replay: bool,
482 pub fuzz_input: Option<FuzzFailureReplayConfig>,
484
485 pub symbolic_artifact_replay: Option<SymbolicArtifactReplayConfig>,
487}
488
489impl<FEN: FoundryEvmNetwork> TestRunnerConfig<FEN> {
490 pub fn reconfigure_with(&mut self, config: Arc<Config>) {
493 debug_assert!(!Arc::ptr_eq(&self.config, &config));
494
495 self.sender = config.sender;
496 self.evm_opts.networks = config.networks;
497 self.hardfork = resolve_execution_spec(
498 config.evm_version,
499 config.hardfork,
500 &mut self.evm_env,
501 ExecutionSpecContext::local_or_fork(self.fork_chain_id, self.fork_hardfork),
502 None,
503 );
504 self.spec_id = self.evm_env.cfg_env.spec;
505 self.isolation = config.isolate;
506 self.evm_opts.always_use_create_2_factory = config.always_use_create_2_factory;
510 self.config = config;
511 }
512
513 pub fn configure_executor(&self, executor: &mut Executor<FEN>) {
515 debug_assert!(
516 executor.backend().networks().has_same_execution_profile(&self.evm_opts.networks)
517 );
518 debug_assert!(
519 executor.inspector().networks.has_same_execution_profile(&self.evm_opts.networks)
520 );
521 let inspector = executor.inspector_mut();
522 if let Some(cheatcodes) = inspector.cheatcodes.as_mut() {
523 let mut config = cheatcodes.config.clone_with(&self.config, self.evm_opts.clone());
524 config.isolate = self.isolation;
525 cheatcodes.config = Arc::new(config);
526 }
527 inspector.tracing_requirements(self.trace_requirements());
528 inspector.collect_line_coverage(self.line_coverage);
529 inspector.enable_isolation(self.isolation);
530 executor.set_spec_id(self.spec_id);
531 executor.set_legacy_assertions(self.config.legacy_assertions);
532 }
533
534 pub fn executor(
536 &self,
537 known_contracts: ContractsByArtifact,
538 analysis: Arc<solar::sema::Compiler>,
539 artifact_id: &ArtifactId,
540 db: Backend<FEN>,
541 ) -> Executor<FEN> {
542 let mut cheats_config = CheatsConfig::new(
543 &self.config,
544 self.evm_opts.clone(),
545 Some(known_contracts),
546 Some(artifact_id.clone()),
547 false,
548 );
549 cheats_config.isolate = self.isolation;
550 let cheats_config = Arc::new(cheats_config);
551 self.executor_builder
552 .clone()
553 .inspectors(|stack| {
554 stack
555 .logs(self.config.live_logs)
556 .cheatcodes(cheats_config)
557 .trace_requirements(self.trace_requirements())
558 .line_coverage(self.line_coverage)
559 .enable_isolation(self.isolation)
560 .create2_deployer(self.evm_opts.create2_deployer)
561 .set_analysis(analysis)
562 })
563 .spec_id(self.spec_id)
564 .gas_limit(self.evm_opts.gas_limit())
565 .legacy_assertions(self.config.legacy_assertions)
566 .build(self.evm_env.clone(), self.tx_env.clone(), db, self.evm_opts.networks)
567 }
568
569 fn trace_requirements(&self) -> TraceRequirements {
570 TraceRequirements::none()
571 .with_debug(self.debug)
572 .with_decode_internal(self.decode_internal)
573 .with_all_steps(self.record_all_steps)
574 .with_verbosity(self.config.tracing.verbosity.max(self.evm_opts.verbosity))
575 }
576}
577
578#[derive(Clone)]
580#[must_use = "builders do nothing unless you call `build` on them"]
581pub struct MultiContractRunnerBuilder {
582 pub sender: Option<Address>,
585 pub initial_balance: U256,
587 pub fork: Option<CreateFork>,
589 pub fork_chain_id: Option<ChainId>,
591 pub fork_hardfork: Option<FoundryHardfork>,
593 pub config: Arc<Config>,
595 pub inline_config: Arc<InlineConfig>,
597 pub line_coverage: bool,
599 pub debug: bool,
601 pub decode_internal: InternalTraceMode,
603 pub record_all_steps: bool,
605 pub isolation: bool,
607 pub fail_fast: bool,
609 pub multi_network: MultiNetworkConfig,
611 pub showmap: Option<ShowmapConfig>,
613 pub fuzz_only: bool,
615 pub fuzz_failure_replay: bool,
617 pub fuzz_input: Option<FuzzFailureReplayConfig>,
619 pub symbolic_artifact_replay: Option<SymbolicArtifactReplayConfig>,
621 pub create2_deployer_available: Option<bool>,
623}
624
625impl MultiContractRunnerBuilder {
626 fn create2_deployer_available(&self, evm_opts: &EvmOpts) -> bool {
627 self.create2_deployer_available.unwrap_or_else(|| {
628 self.fork.is_none()
629 && evm_opts.fork_url.is_none()
630 && evm_opts.create2_deployer == foundry_evm::constants::DEFAULT_CREATE2_DEPLOYER
631 })
632 }
633
634 pub fn new(config: Arc<Config>, inline_config: Arc<InlineConfig>) -> Self {
635 Self {
636 config,
637 inline_config,
638 sender: None,
639 initial_balance: U256::ZERO,
640 fork: None,
641 fork_chain_id: None,
642 fork_hardfork: None,
643 line_coverage: false,
644 debug: false,
645 isolation: false,
646 decode_internal: Default::default(),
647 record_all_steps: false,
648 fail_fast: false,
649 multi_network: Default::default(),
650 showmap: None,
651 fuzz_only: false,
652 fuzz_failure_replay: false,
653 fuzz_input: None,
654 symbolic_artifact_replay: None,
655 create2_deployer_available: None,
656 }
657 }
658
659 pub const fn with_create2_deployer_available(mut self, available: bool) -> Self {
660 self.create2_deployer_available = Some(available);
661 self
662 }
663
664 pub fn with_showmap(mut self, showmap: Option<ShowmapConfig>) -> Self {
665 self.showmap = showmap;
666 self
667 }
668
669 pub const fn with_fuzz_only(mut self, fuzz_only: bool) -> Self {
670 self.fuzz_only = fuzz_only;
671 self
672 }
673
674 pub const fn with_fuzz_failure_replay(mut self, fuzz_failure_replay: bool) -> Self {
675 self.fuzz_failure_replay = fuzz_failure_replay;
676 self
677 }
678
679 pub fn with_fuzz_input(mut self, fuzz_input: Option<FuzzFailureReplayConfig>) -> Self {
680 self.fuzz_input = fuzz_input;
681 self
682 }
683
684 pub fn with_symbolic_artifact_replay(
685 mut self,
686 replay: Option<SymbolicArtifactReplayConfig>,
687 ) -> Self {
688 self.symbolic_artifact_replay = replay;
689 self
690 }
691
692 pub const fn sender(mut self, sender: Address) -> Self {
693 self.sender = Some(sender);
694 self
695 }
696
697 pub const fn initial_balance(mut self, initial_balance: U256) -> Self {
698 self.initial_balance = initial_balance;
699 self
700 }
701
702 pub fn with_fork(mut self, fork: Option<CreateFork>) -> Self {
703 self.fork = fork;
704 self
705 }
706
707 pub const fn with_fork_chain_id(mut self, chain_id: Option<ChainId>) -> Self {
708 self.fork_chain_id = chain_id;
709 self
710 }
711
712 pub const fn with_fork_hardfork(mut self, hardfork: Option<FoundryHardfork>) -> Self {
713 self.fork_hardfork = hardfork;
714 self
715 }
716
717 pub const fn set_coverage(mut self, enable: bool) -> Self {
718 self.line_coverage = enable;
719 self
720 }
721
722 pub const fn set_debug(mut self, enable: bool) -> Self {
723 self.debug = enable;
724 self
725 }
726
727 pub const fn set_decode_internal(mut self, mode: InternalTraceMode) -> Self {
728 self.decode_internal = mode;
729 self
730 }
731
732 pub const fn set_record_all_steps(mut self, enable: bool) -> Self {
733 self.record_all_steps = enable;
734 self
735 }
736
737 pub fn with_multi_network(mut self, multi_network: MultiNetworkConfig) -> Self {
738 self.multi_network = multi_network;
739 self
740 }
741
742 pub const fn fail_fast(mut self, fail_fast: bool) -> Self {
743 self.fail_fast = fail_fast;
744 self
745 }
746
747 pub const fn enable_isolation(mut self, enable: bool) -> Self {
748 self.isolation = enable;
749 self
750 }
751
752 pub fn build<FEN: FoundryEvmNetwork, C: Compiler<CompilerContract = Contract>>(
755 self,
756 output: &ProjectCompileOutput,
757 mut evm_env: EvmEnvFor<FEN>,
758 tx_env: TxEnvFor<FEN>,
759 evm_opts: EvmOpts,
760 executor_builder: ExecutorBuilder<FEN>,
761 ) -> Result<MultiContractRunner<FEN>> {
762 let root = &self.config.root;
763 let contracts = output
764 .artifact_ids()
765 .map(|(id, v)| (id.with_stripped_file_prefixes(root), v))
766 .collect();
767 let linker = Linker::new(root, contracts);
768
769 let abis = linker
771 .contracts
772 .values()
773 .filter_map(|contract| contract.abi.as_ref().map(|abi| abi.borrow()));
774 let revert_decoder = RevertDecoder::new().with_abis(abis);
775
776 let configured_libraries = self.config.libraries_with_remappings()?;
777 let create2 = if self.create2_deployer_available(&evm_opts) {
778 match linker.link_with_create2_detailed(
779 configured_libraries.clone(),
780 evm_opts.create2_deployer,
781 self.config.create2_library_salt,
782 linker.contracts.keys(),
783 ) {
784 Ok(output) => Some(output),
785 Err(LinkerError::CyclicDependency) => None,
786 Err(err) => return Err(err.into()),
787 }
788 } else {
789 None
790 };
791 let (
792 DetailedLinkOutput {
793 output: LinkOutput { libraries, library_addresses, libs_to_deploy },
794 artifact_libraries,
795 ..
796 },
797 library_deployment,
798 ) = match create2 {
799 Some(output) => {
800 let deployment = if output.output.libs_to_deploy.is_empty() {
801 LibraryDeployment::Nonce
802 } else {
803 LibraryDeployment::Create2 {
804 deployer: evm_opts.create2_deployer,
805 salt: self.config.create2_library_salt,
806 }
807 };
808 (output, deployment)
809 }
810 None => (
811 linker.link_with_nonce_or_address_detailed(
812 configured_libraries,
813 LIBRARY_DEPLOYER,
814 0,
815 linker.contracts.keys(),
816 )?,
817 LibraryDeployment::Nonce,
818 ),
819 };
820
821 let linked_contracts = linker
822 .get_linked_artifacts_cow_with_artifact_libraries(&libraries, &artifact_libraries)?;
823 let inline_config = self.inline_config;
824
825 let mut deployable_contracts = DeployableContracts::default();
827 let test_matcher = TestFunctionMatcher::new(
828 &self.config,
829 &inline_config,
830 self.symbolic_artifact_replay.as_ref(),
831 );
832 let empty_filter = EmptyTestFilter::default();
833 let resolver = Resolver::new(&linker);
834 for (id, contract) in linked_contracts.iter() {
835 let Some(abi) = contract.abi.as_ref() else { continue };
836 if abi.constructor.as_ref().is_some_and(|c| !c.inputs.is_empty())
837 || !test_matcher.matches_contract(&empty_filter, id, abi)
838 {
839 continue;
840 }
841 linker.ensure_linked(contract, id)?;
842 let Some(bytecode) =
843 contract.get_bytecode_bytes().map(|b| b.into_owned()).filter(|b| !b.is_empty())
844 else {
845 continue;
846 };
847 let artifact_libraries = artifact_libraries.get(id).unwrap_or(&libraries);
848 let library_addresses = resolver.linked_library_addresses(id, artifact_libraries)?;
849 deployable_contracts.insert(
850 id.clone(),
851 TestContract { abi: abi.clone().into_owned(), bytecode, library_addresses },
852 );
853 }
854
855 let known_contracts =
857 ContractsByArtifactBuilder::new(linked_contracts).with_output(output, root).build();
858
859 let mut analysis = solar::sema::Compiler::new(
861 solar::interface::Session::builder().with_stderr_emitter().build(),
862 );
863 let dcx = analysis.dcx_mut();
864 dcx.set_emitter(Box::new(
865 solar::interface::diagnostics::HumanEmitter::stderr(Default::default())
866 .source_map(Some(dcx.source_map().unwrap())),
867 ));
868 dcx.set_flags_mut(|f| f.track_diagnostics = false);
869
870 let files: Vec<_> = output.output().sources.as_ref().keys().cloned().collect();
872 analysis.enter_mut(|compiler| -> Result<()> {
873 let mut pcx = compiler.parse();
874 configure_pcx_from_compile_output(
875 &mut pcx,
876 &self.config,
877 output,
878 (!files.is_empty()).then_some(&files),
879 )?;
880 pcx.parse();
881 let _ = compiler.lower_asts();
882 Ok(())
883 })?;
884 let analysis = Arc::new(analysis);
885
886 let enum_bounds = EnumBounds::collect(&analysis);
888 let literals = |max_literals| {
889 LiteralsDictionary::new(
890 Some(analysis.clone()),
891 Some(self.config.project_paths()),
892 max_literals,
893 )
894 };
895 let fuzz_max_literals = self.config.fuzz.dictionary.max_fuzz_dictionary_literals;
896 let invariant_max_literals = self.config.invariant.dictionary.max_fuzz_dictionary_literals;
897 let fuzz_literals = literals(fuzz_max_literals);
898 let invariant_literals = if invariant_max_literals == fuzz_max_literals {
899 fuzz_literals.clone()
900 } else {
901 literals(invariant_max_literals)
902 };
903
904 let fork_chain_id = self.fork_chain_id.or_else(|| {
905 (self.fork.is_some() || evm_opts.fork_url.is_some()).then_some(evm_env.cfg_env.chain_id)
906 });
907 let hardfork = resolve_execution_spec(
908 self.config.evm_version,
909 self.config.hardfork,
910 &mut evm_env,
911 ExecutionSpecContext::local_or_fork(fork_chain_id, self.fork_hardfork),
912 None,
913 );
914 let spec_id = evm_env.cfg_env.spec;
915
916 Ok(MultiContractRunner {
917 contracts: deployable_contracts,
918 revert_decoder,
919 known_contracts,
920 libs_to_deploy,
921 library_addresses,
922 library_deployment,
923 libraries,
924 analysis,
925 fuzz_literals,
926 invariant_literals,
927 enum_bounds,
928
929 tcfg: TestRunnerConfig {
930 evm_opts,
931 executor_builder,
932 evm_env,
933 tx_env,
934 spec_id,
935 hardfork,
936 fork_chain_id,
937 fork_hardfork: self.fork_hardfork,
938 sender: self.sender.unwrap_or(self.config.sender),
939 line_coverage: self.line_coverage,
940 debug: self.debug,
941 decode_internal: self.decode_internal,
942 record_all_steps: self.record_all_steps,
943 inline_config,
944 isolation: self.isolation,
945 early_exit: EarlyExit::new(self.fail_fast),
946 multi_network: self.multi_network,
947 showmap: self.showmap,
948 fuzz_minimize: None,
949 fuzz_only: self.fuzz_only,
950 fuzz_failure_replay: self.fuzz_failure_replay,
951 fuzz_input: self.fuzz_input,
952 symbolic_artifact_replay: self.symbolic_artifact_replay,
953 config: self.config,
954 },
955
956 fork: self.fork,
957 })
958 }
959}
960
961#[derive(Clone, Copy)]
962pub(crate) struct TestFunctionMatcher<'a> {
963 config: &'a Config,
964 inline_config: &'a InlineConfig,
965 symbolic_artifact_replay: Option<&'a SymbolicArtifactReplayConfig>,
966}
967
968impl<'a> TestFunctionMatcher<'a> {
969 pub(crate) const fn new(
970 config: &'a Config,
971 inline_config: &'a InlineConfig,
972 symbolic_artifact_replay: Option<&'a SymbolicArtifactReplayConfig>,
973 ) -> Self {
974 Self { config, inline_config, symbolic_artifact_replay }
975 }
976
977 fn symbolic_tests_enabled(&self, contract_id: &str) -> bool {
978 self.symbolic_artifact_replay.is_some_and(|artifact| {
979 artifact.artifact.kind == SymbolicCounterexampleArtifactKind::SingleCall
980 }) || self.inline_config.contract_symbolic_enabled(
981 &self.config.profile,
982 contract_id,
983 self.config.symbolic.enabled,
984 )
985 }
986
987 pub(crate) fn test_function_kind(
988 &self,
989 contract_id: &str,
990 func: &Function,
991 generated_symbolic_regression: bool,
992 ) -> TestFunctionKind {
993 if generated_symbolic_regression && !func.name.starts_with("test_regression_") {
994 return TestFunctionKind::Unknown;
995 }
996
997 TestFunctionKind::classify(
998 func.name.as_str(),
999 !func.inputs.is_empty(),
1000 self.symbolic_tests_enabled(contract_id),
1001 )
1002 }
1003
1004 pub(crate) fn test_functions(
1007 self,
1008 contract_id: String,
1009 abi: &JsonAbi,
1010 mut keep: impl FnMut(&str, &Function, TestFunctionKind) -> bool,
1011 ) -> impl Iterator<Item = &Function> {
1012 let generated_symbolic_regression = is_generated_symbolic_regression_contract(abi);
1013 abi.functions().filter(move |func| {
1014 let kind = self.test_function_kind(&contract_id, func, generated_symbolic_regression);
1015 keep(&contract_id, func, kind)
1016 })
1017 }
1018
1019 fn matching_test_functions<'b>(
1021 self,
1022 filter: &dyn TestFilter,
1023 id: &ArtifactId,
1024 abi: &'b JsonAbi,
1025 ) -> impl Iterator<Item = &'b Function> {
1026 self.test_functions(id.identifier(), abi, move |contract_id, func, kind| {
1027 filter.matches_test_function_kind_in_contract(contract_id, func, kind)
1028 })
1029 }
1030
1031 pub(crate) fn count_fuzz_engine_targets(
1034 &self,
1035 filter: &dyn TestFilter,
1036 id: &ArtifactId,
1037 abi: &JsonAbi,
1038 multi_network: &MultiNetworkConfig,
1039 ) -> (usize, usize) {
1040 let contract_name = id.identifier();
1041 let matches_network_pass = |func: &Function| {
1042 function_matches_network_pass(
1043 &multi_network.all_override_networks,
1044 multi_network.pass_network.as_ref(),
1045 self.inline_config.network_for(&self.config.profile, &contract_name, &func.name),
1046 )
1047 };
1048 let fuzz = self
1049 .test_functions(contract_name.clone(), abi, |contract_id, func, kind| {
1050 matches!(kind, TestFunctionKind::FuzzTest { .. })
1051 && filter.matches_test_function_kind_in_contract(contract_id, func, kind)
1052 && matches_network_pass(func)
1053 })
1054 .count();
1055 let invariant = count_runnable_invariant_campaign_anchors(
1056 abi,
1057 filter,
1058 InvariantCampaignScope {
1059 config: self.config,
1060 inline_config: self.inline_config,
1061 contract_name: &contract_name,
1062 all_override_networks: &multi_network.all_override_networks,
1063 pass_network: multi_network.pass_network.as_ref(),
1064 },
1065 );
1066 (fuzz, invariant)
1067 }
1068
1069 pub(crate) fn matches_contract(
1070 &self,
1071 filter: &dyn TestFilter,
1072 id: &ArtifactId,
1073 abi: &JsonAbi,
1074 ) -> bool {
1075 filter.matches_path(&id.source)
1076 && filter.matches_contract(&id.name)
1077 && self.matching_test_functions(filter, id, abi).next().is_some()
1078 }
1079}
1080
1081pub(crate) fn is_generated_symbolic_regression_contract(abi: &JsonAbi) -> bool {
1082 abi.functions().any(|func| func.name == SYMBOLIC_REGRESSION_MARKER && func.inputs.is_empty())
1083}
1084
1085#[cfg(test)]
1086mod tests {
1087 use super::*;
1088
1089 fn abi_with_functions(functions: &[&str]) -> JsonAbi {
1090 let mut abi = JsonAbi::new();
1091 for function in functions {
1092 let function = Function::parse(function).unwrap();
1093 abi.functions.entry(function.name.clone()).or_default().push(function);
1094 }
1095 abi
1096 }
1097
1098 #[test]
1099 fn generated_symbolic_regression_detection_uses_marker() {
1100 let user_suffix_abi = abi_with_functions(&["test_fails()"]);
1101 assert!(!is_generated_symbolic_regression_contract(&user_suffix_abi));
1102
1103 let generated_abi =
1104 abi_with_functions(&[&format!("{SYMBOLIC_REGRESSION_MARKER}()"), "test_fails()"]);
1105 assert!(is_generated_symbolic_regression_contract(&generated_abi));
1106 }
1107
1108 #[test]
1109 fn create2_deployer_availability_default_is_conservative() {
1110 let config = Arc::new(Config::default());
1111 let mut builder = MultiContractRunnerBuilder::new(config, Arc::new(InlineConfig::new()));
1112 let mut evm_opts = EvmOpts::default();
1113 assert!(builder.create2_deployer_available(&evm_opts));
1114
1115 builder.fork = Some(CreateFork {
1116 enable_caching: false,
1117 url: "http://localhost:8545".into(),
1118 evm_opts: evm_opts.clone(),
1119 resolved: None,
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}