Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
## Pre 1.0

### Important note for users

Multiple breaking changes will occur so Semver can be followed as soon as Foundry 1.0 is released. They will be listed here, along with the updates needed for your projects.

If you need a stable Foundry version, we recommend using the latest pinned nightly of May 2nd, locally and on your CI.

To use the latest pinned nightly locally, use the following command:

```
foundryup --version nightly-e15e33a07c0920189fc336391f538c3dad53da73
````

To use the latest pinned nightly on your CI, modify your Foundry installation step to use an specific version:

```
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
version: nightly-e15e33a07c0920189fc336391f538c3dad53da73
```

### Breaking changes

- [expectEmit](https://github.com/foundry-rs/foundry/pull/4920) will now only work for the next call.
- expectCall will now only work if the call(s) are made exactly after the cheatcode is invoked.
- expectRevert will now work if the next call does revert, instead of expecting a revert during the whole test.
- [precompiles will not be compatible with all cheatcodes](https://github.com/foundry-rs/foundry/pull/4905).
- The difficulty and prevrandao cheatcodes now [fail if not used with the correct EVM version](https://github.com/foundry-rs/foundry/pull/4904).
- The default EVM version will be Shanghai. If you're using an EVM chain which is not compatible with [EIP-3855](https://eips.ethereum.org/EIPS/eip-3855) you need to change your EVM version. See [Matt Solomon's thread](https://twitter.com/msolomon44/status/1656411871635972096) for more information.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 17 additions & 6 deletions cli/src/cmd/forge/test/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ use forge::{
identifier::{EtherscanIdentifier, LocalTraceIdentifier, SignaturesIdentifier},
CallTraceDecoderBuilder, TraceKind,
},
MultiContractRunner, MultiContractRunnerBuilder, TestOptions,
MultiContractRunner, MultiContractRunnerBuilder, TestOptions, TestOptionsBuilder,
};
use foundry_common::{
compile::{self, ProjectCompiler},
evm::EvmArgs,
get_contract_name, get_file_name,
};
use foundry_config::{figment, Config};
use foundry_config::{figment, get_available_profiles, Config};
use regex::Regex;
use std::{collections::BTreeMap, path::PathBuf, sync::mpsc::channel, thread, time::Duration};
use tracing::trace;
Expand Down Expand Up @@ -123,8 +123,6 @@ impl TestArgs {
// Merge all configs
let (mut config, mut evm_opts) = self.load_config_and_evm_opts_emit_warnings()?;

let test_options = TestOptions { fuzz: config.fuzz, invariant: config.invariant };

let mut filter = self.filter(&config);

trace!(target: "forge::test", ?filter, "using filter");
Expand All @@ -150,6 +148,19 @@ impl TestArgs {
compiler.compile(&project)
}?;

// Create test options from general project settings
// and compiler output
let project_root = &project.paths.root;
let toml = config.get_config_path();
let profiles = get_available_profiles(toml)?;

let test_options: TestOptions = TestOptionsBuilder::default()
.fuzz(config.fuzz)
.invariant(config.invariant)
.compile_output(&output)
.profiles(profiles)
.build(project_root)?;

// Determine print verbosity and executor verbosity
let verbosity = evm_opts.verbosity;
if self.gas_report && evm_opts.verbosity < 3 {
Expand All @@ -167,8 +178,8 @@ impl TestArgs {
.sender(evm_opts.sender)
.with_fork(evm_opts.get_fork(&config, env.clone()))
.with_cheats_config(CheatsConfig::new(&config, &evm_opts))
.with_test_options(test_options)
.build(project.paths.root, output, env, evm_opts)?;
.with_test_options(test_options.clone())
.build(project_root, output, env, evm_opts)?;

if self.debug.is_some() {
filter.args_mut().test_pattern = self.debug;
Expand Down
2 changes: 1 addition & 1 deletion common/src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ impl TestFunctionExt for Function {

impl<'a> TestFunctionExt for &'a str {
fn is_invariant_test(&self) -> bool {
self.starts_with("invariant")
self.starts_with("invariant") || self.starts_with("statefulFuzz")
}

fn is_fuzz_test(&self) -> bool {
Expand Down
1 change: 1 addition & 0 deletions config/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ figment = { version = "0.10", features = ["toml", "env"] }
number_prefix = "0.4.0"
serde = { version = "1.0", features = ["derive"] }
serde_regex = "1.1.0"
serde_json = "1.0.95"
toml = { version = "0.7", features = ["preserve_order"] }
toml_edit = "0.19"

Expand Down
1 change: 1 addition & 0 deletions config/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ call_override = false
dictionary_weight = 80
include_storage = true
include_push_bytes = true
shrink_sequence = true

[fmt]
line_length = 100
Expand Down
6 changes: 6 additions & 0 deletions config/src/doc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ pub struct DocConfig {
pub title: String,
/// Path to user provided `book.toml`.
pub book: PathBuf,
/// Path to user provided welcome markdown.
///
/// If none is provided, it defaults to `README.md`.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub homepage: Option<PathBuf>,
/// The repository url.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub repository: Option<String>,
Expand All @@ -24,6 +29,7 @@ impl Default for DocConfig {
Self {
out: PathBuf::from("docs"),
book: PathBuf::from("book.toml"),
homepage: Some(PathBuf::from("README.md")),
title: String::default(),
repository: None,
ignore: Vec::default(),
Expand Down
86 changes: 86 additions & 0 deletions config/src/fuzz.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
use ethers_core::types::U256;
use serde::{Deserialize, Serialize};

use crate::inline::{
parse_config_u32, InlineConfigParser, InlineConfigParserError, INLINE_CONFIG_FUZZ_KEY,
};

/// Contains for fuzz testing
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct FuzzConfig {
Expand Down Expand Up @@ -35,6 +39,34 @@ impl Default for FuzzConfig {
}
}

impl InlineConfigParser for FuzzConfig {
fn config_key() -> String {
INLINE_CONFIG_FUZZ_KEY.into()
}

fn try_merge(&self, configs: &[String]) -> Result<Option<Self>, InlineConfigParserError> {
let overrides: Vec<(String, String)> = Self::get_config_overrides(configs);

if overrides.is_empty() {
return Ok(None)
}

// self is Copy. We clone it with dereference.
let mut conf_clone = *self;

for pair in overrides {
let key = pair.0;
let value = pair.1;
match key.as_str() {
"runs" => conf_clone.runs = parse_config_u32(key, value)?,
"max-test-rejects" => conf_clone.max_test_rejects = parse_config_u32(key, value)?,
_ => Err(InlineConfigParserError::InvalidConfigProperty(key))?,
}
}
Ok(Some(conf_clone))
}
}

/// Contains for fuzz testing
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct FuzzDictionaryConfig {
Expand Down Expand Up @@ -70,3 +102,57 @@ impl Default for FuzzDictionaryConfig {
}
}
}

#[cfg(test)]
mod tests {
use crate::{inline::InlineConfigParser, FuzzConfig};

#[test]
fn unrecognized_property() {
let configs = &["forge-config: default.fuzz.unknownprop = 200".to_string()];
let base_config = FuzzConfig::default();
if let Err(e) = base_config.try_merge(configs) {
assert_eq!(e.to_string(), "'unknownprop' is an invalid config property");
} else {
assert!(false)
}
}

#[test]
fn successful_merge() {
let configs = &["forge-config: default.fuzz.runs = 42424242".to_string()];
let base_config = FuzzConfig::default();
let merged: FuzzConfig = base_config.try_merge(configs).expect("No errors").unwrap();
assert_eq!(merged.runs, 42424242);
}

#[test]
fn merge_is_none() {
let empty_config = &[];
let base_config = FuzzConfig::default();
let merged = base_config.try_merge(empty_config).expect("No errors");
assert!(merged.is_none());
}

#[test]
fn merge_is_none_unrelated_property() {
let unrelated_configs = &["forge-config: default.invariant.runs = 2".to_string()];
let base_config = FuzzConfig::default();
let merged = base_config.try_merge(unrelated_configs).expect("No errors");
assert!(merged.is_none());
}

#[test]
fn override_detection() {
let configs = &[
"forge-config: default.fuzz.runs = 42424242".to_string(),
"forge-config: ci.fuzz.runs = 666666".to_string(),
"forge-config: default.invariant.runs = 2".to_string(),
];
let variables = FuzzConfig::get_config_overrides(configs);
assert_eq!(
variables,
vec![("runs".into(), "42424242".into()), ("runs".into(), "666666".into())]
);
}
}
Loading