Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
9 changes: 9 additions & 0 deletions crates/common/src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,15 @@ pub trait TestFunctionExt {
self.test_function_kind().is_fixture()
}

/// Returns `true` if this function is test reserved function.
fn is_reserved(&self) -> bool {
self.is_any_test() ||
self.is_setup() ||
self.is_before_test_setup() ||
self.is_after_invariant() ||
self.is_fixture()
}

#[doc(hidden)]
fn tfe_as_str(&self) -> &str;
#[doc(hidden)]
Expand Down
23 changes: 23 additions & 0 deletions crates/evm/evm/src/executors/invariant/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ mod replay;
pub use replay::{replay_error, replay_run};

mod result;
use foundry_common::TestFunctionExt;
pub use result::InvariantFuzzTestResult;
use serde::{Deserialize, Serialize};

Expand Down Expand Up @@ -795,6 +796,28 @@ impl<'a> InvariantExecutor<'a> {
address: Address,
targeted_contracts: &mut TargetedContracts,
) -> Result<()> {
if let Some(target) = targeted_contracts.get(&address) {
// If test contract is a target, then include only state-changing functions
// that are not reserved.
let selectors: Vec<_> = target
.abi
.functions()
.filter_map(|func| {
if matches!(
func.state_mutability,
alloy_json_abi::StateMutability::Pure |
alloy_json_abi::StateMutability::View
) || func.is_reserved()
{
None
} else {
Some(func.selector())
}
})
.collect();
self.add_address_with_functions(address, &selectors, false, targeted_contracts)?;
}

for (address, (identifier, _)) in self.setup_contracts {
if let Some(selectors) = self.artifact_filters.targeted.get(identifier) {
self.add_address_with_functions(*address, selectors, false, targeted_contracts)?;
Expand Down
110 changes: 110 additions & 0 deletions crates/forge/tests/it/invariant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1340,3 +1340,113 @@ contract InvariantTest is Test {
"#]],
);
});

// Tests that reserved test functions are not fuzzed when test is set as target.
// <https://github.com/foundry-rs/foundry/issues/10469>
forgetest_init!(invariant_target_test_contract_selectors, |prj, cmd| {
prj.update_config(|config| {
config.invariant.runs = 10;
config.invariant.depth = 100;
});
prj.add_test(
"InvariantTargetTest.t.sol",
r#"
import {Test} from "forge-std/Test.sol";

contract InvariantTargetTest is Test {
bool fooCalled;
bool testSanityCalled;
bool testTableCalled;
uint256 invariantCalledNum;
uint256 setUpCalledNum;

function setUp() public {
targetContract(address(this));
}

function beforeTestSetup() public {
}

// Only this selector should be targeted.
function foo() public {
fooCalled = true;
}

function fixtureCalled() public returns (bool[] memory) {
}

function table_sanity(bool called) public {
testTableCalled = called;
}

function test_sanity() public {
testSanityCalled = true;
}

function afterInvariant() public {
}

function invariant_foo_called() public view {
}

function invariant_testSanity_considered_target() public {
}

function invariant_setUp_considered_target() public {
setUpCalledNum++;
}

function invariant_considered_target() public {
invariantCalledNum++;
}
}
"#,
)
.unwrap();

cmd.args(["test", "--mc", "InvariantTargetTest", "--mt", "invariant"])
.assert_success()
.stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

Ran 4 tests for test/InvariantTargetTest.t.sol:InvariantTargetTest
[PASS] invariant_considered_target() (runs: 10, calls: 1000, reverts: 0)

╭---------------------+----------+-------+---------+----------╮
| Contract | Selector | Calls | Reverts | Discards |
+=============================================================+
| InvariantTargetTest | foo | 1000 | 0 | 0 |
╰---------------------+----------+-------+---------+----------╯

[PASS] invariant_foo_called() (runs: 10, calls: 1000, reverts: 0)

╭---------------------+----------+-------+---------+----------╮
| Contract | Selector | Calls | Reverts | Discards |
+=============================================================+
| InvariantTargetTest | foo | 1000 | 0 | 0 |
╰---------------------+----------+-------+---------+----------╯

[PASS] invariant_setUp_considered_target() (runs: 10, calls: 1000, reverts: 0)

╭---------------------+----------+-------+---------+----------╮
| Contract | Selector | Calls | Reverts | Discards |
+=============================================================+
| InvariantTargetTest | foo | 1000 | 0 | 0 |
╰---------------------+----------+-------+---------+----------╯

[PASS] invariant_testSanity_considered_target() (runs: 10, calls: 1000, reverts: 0)

╭---------------------+----------+-------+---------+----------╮
| Contract | Selector | Calls | Reverts | Discards |
+=============================================================+
| InvariantTargetTest | foo | 1000 | 0 | 0 |
╰---------------------+----------+-------+---------+----------╯

Suite result: ok. 4 passed; 0 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 4 tests passed, 0 failed, 0 skipped (4 total tests)

"#]]);
});