Skip to content

Commit 3b175cb

Browse files
authored
refactor: deploy all libraries when running tests (foundry-rs#8034)
* refactor: deploy all libraries when running tests * fix test * fix tests * fix traces test * review fixes * fix * review fixes
1 parent 9eebb37 commit 3b175cb

File tree

11 files changed

+98
-122
lines changed

11 files changed

+98
-122
lines changed

crates/evm/core/src/backend/mod.rs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -536,11 +536,6 @@ impl Backend {
536536
/// This will also grant cheatcode access to the test account
537537
pub fn set_test_contract(&mut self, acc: Address) -> &mut Self {
538538
trace!(?acc, "setting test account");
539-
// toggle the previous sender
540-
if let Some(current) = self.inner.test_contract_address.take() {
541-
self.remove_persistent_account(&current);
542-
self.revoke_cheatcode_access(&acc);
543-
}
544539

545540
self.add_persistent_account(acc);
546541
self.allow_cheatcode_access(acc);

crates/forge/bin/cmd/coverage.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,8 @@ impl CoverageArgs {
246246
.set_coverage(true)
247247
.build(&root, output, env, evm_opts)?;
248248

249+
let known_contracts = runner.known_contracts.clone();
250+
249251
let outcome = self
250252
.test
251253
.run_tests(runner, config.clone(), verbosity, &self.test.filter(&config))
@@ -257,12 +259,10 @@ impl CoverageArgs {
257259
for result in suite.test_results.values() {
258260
let Some(hit_maps) = result.coverage.as_ref() else { continue };
259261
for map in hit_maps.0.values() {
260-
if let Some((id, _)) =
261-
suite.known_contracts.find_by_deployed_code(&map.bytecode)
262-
{
262+
if let Some((id, _)) = known_contracts.find_by_deployed_code(&map.bytecode) {
263263
hits.push((id, map, true));
264264
} else if let Some((id, _)) =
265-
suite.known_contracts.find_by_creation_code(&map.bytecode)
265+
known_contracts.find_by_creation_code(&map.bytecode)
266266
{
267267
hits.push((id, map, false));
268268
}

crates/forge/bin/cmd/test/mod.rs

Lines changed: 30 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -312,11 +312,12 @@ impl TestArgs {
312312
*test_pattern = Some(debug_test_pattern.clone());
313313
}
314314

315+
let libraries = runner.libraries.clone();
315316
let outcome = self.run_tests(runner, config, verbosity, &filter).await?;
316317

317318
if should_debug {
318319
// Get first non-empty suite result. We will have only one such entry
319-
let Some((suite_result, test_result)) = outcome
320+
let Some((_, test_result)) = outcome
320321
.results
321322
.iter()
322323
.find(|(_, r)| !r.test_results.is_empty())
@@ -328,7 +329,7 @@ impl TestArgs {
328329
let sources = ContractSources::from_project_output(
329330
output_clone.as_ref().unwrap(),
330331
project.root(),
331-
&suite_result.libraries,
332+
&libraries,
332333
)?;
333334

334335
// Run the debugger.
@@ -376,6 +377,7 @@ impl TestArgs {
376377
}
377378

378379
let remote_chain_id = runner.evm_opts.get_remote_chain_id().await;
380+
let known_contracts = runner.known_contracts.clone();
379381

380382
// Run tests.
381383
let (tx, rx) = channel::<(String, SuiteResult)>();
@@ -385,38 +387,40 @@ impl TestArgs {
385387
move || runner.test(&filter, tx)
386388
});
387389

390+
// Set up trace identifiers.
391+
let mut identifier = TraceIdentifiers::new().with_local(&known_contracts);
392+
393+
// Avoid using etherscan for gas report as we decode more traces and this will be
394+
// expensive.
395+
if !self.gas_report {
396+
identifier = identifier.with_etherscan(&config, remote_chain_id)?;
397+
}
398+
399+
// Build the trace decoder.
400+
let mut builder = CallTraceDecoderBuilder::new()
401+
.with_known_contracts(&known_contracts)
402+
.with_verbosity(verbosity);
403+
// Signatures are of no value for gas reports.
404+
if !self.gas_report {
405+
builder = builder.with_signature_identifier(SignaturesIdentifier::new(
406+
Config::foundry_cache_dir(),
407+
config.offline,
408+
)?);
409+
}
410+
let mut decoder = builder.build();
411+
388412
let mut gas_report = self
389413
.gas_report
390414
.then(|| GasReport::new(config.gas_reports.clone(), config.gas_reports_ignore.clone()));
391415

392416
let mut outcome = TestOutcome::empty(self.allow_failure);
393417

394418
let mut any_test_failed = false;
395-
for (contract_name, mut suite_result) in rx {
419+
for (contract_name, suite_result) in rx {
396420
let tests = &suite_result.test_results;
397421

398-
// Set up trace identifiers.
399-
let known_contracts = &suite_result.known_contracts;
400-
let mut identifier = TraceIdentifiers::new().with_local(known_contracts);
401-
402-
// Avoid using etherscan for gas report as we decode more traces and this will be
403-
// expensive.
404-
if !self.gas_report {
405-
identifier = identifier.with_etherscan(&config, remote_chain_id)?;
406-
}
407-
408-
// Build the trace decoder.
409-
let mut builder = CallTraceDecoderBuilder::new()
410-
.with_known_contracts(known_contracts)
411-
.with_verbosity(verbosity);
412-
// Signatures are of no value for gas reports.
413-
if !self.gas_report {
414-
builder = builder.with_signature_identifier(SignaturesIdentifier::new(
415-
Config::foundry_cache_dir(),
416-
config.offline,
417-
)?);
418-
}
419-
let mut decoder = builder.build();
422+
// Clear the addresses and labels from previous test.
423+
decoder.clear_addresses();
420424

421425
// We identify addresses if we're going to print *any* trace or gas report.
422426
let identify_addresses = verbosity >= 3 || self.gas_report || self.debug.is_some();
@@ -520,18 +524,15 @@ impl TestArgs {
520524
// Print suite summary.
521525
shell::println(suite_result.summary())?;
522526

523-
// Free memory if it's not needed.
524-
suite_result.clear_unneeded();
525-
526527
// Add the suite result to the outcome.
527528
outcome.results.insert(contract_name, suite_result);
528-
outcome.last_run_decoder = Some(decoder);
529529

530530
// Stop processing the remaining suites if any test failed and `fail_fast` is set.
531531
if self.fail_fast && any_test_failed {
532532
break;
533533
}
534534
}
535+
outcome.last_run_decoder = Some(decoder);
535536
let duration = timer.elapsed();
536537

537538
trace!(target: "forge::test", len=outcome.results.len(), %any_test_failed, "done with results");

crates/forge/src/multi_runner.rs

Lines changed: 32 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
//! Forge test runner for multiple contracts.
22
3-
use crate::{result::SuiteResult, ContractRunner, TestFilter, TestOptions};
3+
use crate::{
4+
result::SuiteResult, runner::LIBRARY_DEPLOYER, ContractRunner, TestFilter, TestOptions,
5+
};
46
use alloy_json_abi::{Function, JsonAbi};
57
use alloy_primitives::{Address, Bytes, U256};
68
use eyre::Result;
79
use foundry_common::{get_contract_name, ContractsByArtifact, TestFunctionExt};
8-
use foundry_compilers::{artifacts::Libraries, Artifact, ArtifactId, ProjectCompileOutput, Solc};
10+
use foundry_compilers::{artifacts::Libraries, Artifact, ArtifactId, ProjectCompileOutput};
911
use foundry_config::Config;
1012
use foundry_evm::{
1113
backend::Backend, decode::RevertDecoder, executors::ExecutorBuilder, fork::CreateFork,
@@ -27,8 +29,6 @@ use std::{
2729
pub struct TestContract {
2830
pub abi: JsonAbi,
2931
pub bytecode: Bytes,
30-
pub libs_to_deploy: Vec<Bytes>,
31-
pub libraries: Libraries,
3232
}
3333

3434
pub type DeployableContracts = BTreeMap<ArtifactId, TestContract>;
@@ -61,8 +61,12 @@ pub struct MultiContractRunner {
6161
pub test_options: TestOptions,
6262
/// Whether to enable call isolation
6363
pub isolation: bool,
64-
/// Output of the project compilation
65-
pub output: ProjectCompileOutput,
64+
/// Known contracts linked with computed library addresses.
65+
pub known_contracts: ContractsByArtifact,
66+
/// Libraries to deploy.
67+
pub libs_to_deploy: Vec<Bytes>,
68+
/// Library addresses used to link contracts.
69+
pub libraries: Libraries,
6670
}
6771

6872
impl MultiContractRunner {
@@ -181,17 +185,10 @@ impl MultiContractRunner {
181185
let identifier = artifact_id.identifier();
182186
let mut span_name = identifier.as_str();
183187

184-
let linker = Linker::new(
185-
self.config.project_paths::<Solc>().root,
186-
self.output.artifact_ids().collect(),
187-
);
188-
let linked_contracts = linker.get_linked_artifacts(&contract.libraries).unwrap_or_default();
189-
let known_contracts = ContractsByArtifact::new(linked_contracts);
190-
191188
let cheats_config = CheatsConfig::new(
192189
&self.config,
193190
self.evm_opts.clone(),
194-
Some(known_contracts.clone()),
191+
Some(self.known_contracts.clone()),
195192
None,
196193
Some(artifact_id.version.clone()),
197194
);
@@ -220,12 +217,13 @@ impl MultiContractRunner {
220217
&identifier,
221218
executor,
222219
contract,
220+
&self.libs_to_deploy,
223221
self.evm_opts.initial_balance,
224222
self.sender,
225223
&self.revert_decoder,
226224
self.debug,
227225
);
228-
let r = runner.run_tests(filter, &self.test_options, known_contracts, handle);
226+
let r = runner.run_tests(filter, &self.test_options, self.known_contracts.clone(), handle);
229227

230228
debug!(duration=?r.duration, "executed all tests in contract");
231229

@@ -332,10 +330,19 @@ impl MultiContractRunnerBuilder {
332330
.filter_map(|(_, contract)| contract.abi.as_ref().map(|abi| abi.borrow()));
333331
let revert_decoder = RevertDecoder::new().with_abis(abis);
334332

333+
let LinkOutput { libraries, libs_to_deploy } = linker.link_with_nonce_or_address(
334+
Default::default(),
335+
LIBRARY_DEPLOYER,
336+
0,
337+
linker.contracts.keys(),
338+
)?;
339+
340+
let linked_contracts = linker.get_linked_artifacts(&libraries)?;
341+
335342
// Create a mapping of name => (abi, deployment code, Vec<library deployment code>)
336343
let mut deployable_contracts = DeployableContracts::default();
337344

338-
for (id, contract) in linker.contracts.iter() {
345+
for (id, contract) in linked_contracts.iter() {
339346
let Some(abi) = contract.abi.as_ref() else {
340347
continue;
341348
};
@@ -344,35 +351,19 @@ impl MultiContractRunnerBuilder {
344351
if abi.constructor.as_ref().map(|c| c.inputs.is_empty()).unwrap_or(true) &&
345352
abi.functions().any(|func| func.name.is_test() || func.name.is_invariant_test())
346353
{
347-
let LinkOutput { libs_to_deploy, libraries } = linker.link_with_nonce_or_address(
348-
Default::default(),
349-
evm_opts.sender,
350-
1,
351-
id,
352-
)?;
353-
354-
let linked_contract = linker.link(id, &libraries)?;
355-
356-
let Some(bytecode) = linked_contract
357-
.get_bytecode_bytes()
358-
.map(|b| b.into_owned())
359-
.filter(|b| !b.is_empty())
354+
let Some(bytecode) =
355+
contract.get_bytecode_bytes().map(|b| b.into_owned()).filter(|b| !b.is_empty())
360356
else {
361357
continue;
362358
};
363359

364-
deployable_contracts.insert(
365-
id.clone(),
366-
TestContract {
367-
abi: abi.clone().into_owned(),
368-
bytecode,
369-
libs_to_deploy,
370-
libraries,
371-
},
372-
);
360+
deployable_contracts
361+
.insert(id.clone(), TestContract { abi: abi.clone(), bytecode });
373362
}
374363
}
375364

365+
let known_contracts = ContractsByArtifact::new(linked_contracts);
366+
376367
Ok(MultiContractRunner {
377368
contracts: deployable_contracts,
378369
evm_opts,
@@ -386,7 +377,9 @@ impl MultiContractRunnerBuilder {
386377
debug: self.debug,
387378
test_options: self.test_options.unwrap_or_default(),
388379
isolation: self.isolation,
389-
output,
380+
known_contracts,
381+
libs_to_deploy,
382+
libraries,
390383
})
391384
}
392385
}

crates/forge/src/result.rs

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@
22
33
use crate::gas_report::GasReport;
44
use alloy_primitives::{Address, Log};
5-
use foundry_common::{
6-
evm::Breakpoints, get_contract_name, get_file_name, shell, ContractsByArtifact,
7-
};
8-
use foundry_compilers::artifacts::Libraries;
5+
use foundry_common::{evm::Breakpoints, get_contract_name, get_file_name, shell};
96
use foundry_evm::{
107
coverage::HitMaps,
118
debug::DebugArena,
@@ -196,31 +193,15 @@ pub struct SuiteResult {
196193
pub test_results: BTreeMap<String, TestResult>,
197194
/// Generated warnings.
198195
pub warnings: Vec<String>,
199-
/// Libraries used to link test contract.
200-
pub libraries: Libraries,
201-
/// Contracts linked with correct libraries.
202-
///
203-
/// This is cleared at the end of the test run if coverage is not enabled.
204-
#[serde(skip)]
205-
pub known_contracts: ContractsByArtifact,
206196
}
207197

208198
impl SuiteResult {
209199
pub fn new(
210200
duration: Duration,
211201
test_results: BTreeMap<String, TestResult>,
212202
warnings: Vec<String>,
213-
libraries: Libraries,
214-
known_contracts: ContractsByArtifact,
215203
) -> Self {
216-
Self { duration, test_results, warnings, libraries, known_contracts }
217-
}
218-
219-
/// Frees memory that is not used for the final output.
220-
pub fn clear_unneeded(&mut self) {
221-
if !self.test_results.values().any(|r| r.coverage.is_some()) {
222-
ContractsByArtifact::clear(&mut self.known_contracts);
223-
}
204+
Self { duration, test_results, warnings }
224205
}
225206

226207
/// Returns an iterator over all individual succeeding tests and their names.

0 commit comments

Comments
 (0)