Skip to content
Closed
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
104 changes: 104 additions & 0 deletions crates/config/src/inline/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,24 @@ const INLINE_CONFIG_PREFIX: &str = "forge-config:";

type DataMap = Map<Profile, Dict>;

/// A compiler-agnostic inline configuration entry.
///
/// This type mirrors `foundry_compilers::InlineConfigEntry` and serves as the
/// bridge between compiler-provided config overrides and Foundry's internal
/// NatSpec-based `InlineConfig`.
#[derive(Clone, Debug)]
pub struct InlineConfigEntry {
/// The contract identifier, in the form `path:ContractName`.
pub contract: String,
/// The function name, if this is a function-level override.
pub function: Option<String>,
/// The location in source for error reporting, e.g. `"10:5"`.
pub line: String,
/// Raw configuration lines in the same key format as `foundry.toml`,
/// e.g. `"default.fuzz.runs = 1024"`.
pub config_values: Vec<String>,
}

/// Errors returned when parsing inline config.
#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
pub enum InlineConfigErrorKind {
Expand Down Expand Up @@ -68,6 +86,45 @@ impl InlineConfig {
Ok(inline)
}

/// Creates a new [`InlineConfig`] from pre-parsed [`NatSpec`] entries.
///
/// This allows alternative compilers to provide inline config without going
/// through solc/solar AST parsing.
pub fn from_natspecs(natspecs: &[NatSpec], profiles: &[Profile]) -> eyre::Result<Self> {
let mut inline = Self::new();
for natspec in natspecs {
inline.insert(natspec)?;
natspec.validate_profiles(profiles)?;
}
Ok(inline)
}

/// Creates a new [`InlineConfig`] from [`InlineConfigEntry`] items.
///
/// This bridges the compiler-agnostic [`InlineConfigEntry`] type to
/// Foundry's internal [`NatSpec`]-based inline config, enabling non-Solidity
/// compilers to provide per-test configuration overrides.
pub fn from_entries(
entries: impl IntoIterator<Item = InlineConfigEntry>,
profiles: &[Profile],
) -> eyre::Result<Self> {
let natspecs: Vec<NatSpec> = entries
.into_iter()
.map(|entry| NatSpec {
contract: entry.contract,
function: entry.function,
line: entry.line,
docs: entry
.config_values
.iter()
.map(|v| format!("{INLINE_CONFIG_PREFIX} {v}"))
.collect::<Vec<_>>()
.join("\n"),
})
.collect();
Self::from_natspecs(&natspecs, profiles)
}

/// Inserts a new [`NatSpec`] into the [`InlineConfig`].
pub fn insert(&mut self, natspec: &NatSpec) -> Result<(), InlineConfigError> {
let map = if let Some(function) = &natspec.function {
Expand Down Expand Up @@ -184,3 +241,50 @@ fn extend_value(value: &mut Value, new: &Value) {
(value, new) => *value = new.clone(),
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_from_entries_empty() {
let config = InlineConfig::from_entries(vec![], &[Profile::Default]).unwrap();
assert!(!config.contains_contract("Foo"));
}

#[test]
fn test_from_entries_function_level() {
let entry = InlineConfigEntry {
contract: "src/Test.sol:TestContract".to_string(),
function: Some("testFoo".to_string()),
line: "10:5".to_string(),
config_values: vec!["default.fuzz.runs = 512".to_string()],
};
let config = InlineConfig::from_entries(vec![entry], &[Profile::Default]).unwrap();
assert!(config.contains_function("src/Test.sol:TestContract", "testFoo"));
}

#[test]
fn test_from_entries_contract_level() {
let entry = InlineConfigEntry {
contract: "src/Test.sol:TestContract".to_string(),
function: None,
line: "5:1".to_string(),
config_values: vec!["default.fuzz.runs = 256".to_string()],
};
let config = InlineConfig::from_entries(vec![entry], &[Profile::Default]).unwrap();
assert!(config.contains_contract("src/Test.sol:TestContract"));
}

#[test]
fn test_from_entries_invalid_profile() {
let entry = InlineConfigEntry {
contract: "src/Test.sol:TestContract".to_string(),
function: Some("testBar".to_string()),
line: "10:5".to_string(),
config_values: vec!["nonexistent.fuzz.runs = 100".to_string()],
};
let result = InlineConfig::from_entries(vec![entry], &[Profile::Default]);
assert!(result.is_err(), "Expected error for invalid profile");
}
}
2 changes: 1 addition & 1 deletion crates/config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ mod invariant;
pub use invariant::InvariantConfig;

mod inline;
pub use inline::{InlineConfig, InlineConfigError, NatSpec};
pub use inline::{InlineConfig, InlineConfigEntry, InlineConfigError, NatSpec};

pub mod soldeer;
use soldeer::{SoldeerConfig, SoldeerDependencyConfig};
Expand Down
2 changes: 1 addition & 1 deletion crates/evm/fuzz/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ pub use error::FuzzError;

pub mod invariant;
pub mod strategies;
pub use strategies::LiteralMaps;
pub use strategies::{FuzzLiteral, LiteralMaps};

mod inspector;
pub use inspector::Fuzzer;
Expand Down
Loading
Loading