Description
Component
Forge
Have you ensured that all of these are up to date?
- Foundry
- Foundryup
What version of Foundry are you on?
forge Version: 1.1.0-stable Commit SHA: d484a00 Build Timestamp: 2025-04-30T13:51:59.442211000Z (1746021119) Build Profile: maxperf
What version of Foundryup are you on?
foundryup: 1.0.1
What command(s) is the bug in?
forge test
Operating System
macOS (Intel)
Describe the bug
When using targetContract(address(this))
in a test contract, we need to consider what state-changing functions are considered valid targets.
TLDR: as of #10274, setUp()
, test_
and invariant_
functions are all considered targets functions, which I found a little surprising.
My initial assumption was that all these are special functions that should be excluded for target functions by default. The case seems particularly clear for excluding setUp()
, but @GalloDaSballo thinks that invariant_
functions could legitimately be considered valid state-changing targets. My bias would be to exclude all special functions by default (setUp()
, test_
and invariant_
), I don't think they belong in call sequences.
Repro
I use the following test contract to evaluate the behavior and find out which functions are targets:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import "forge-std/Test.sol";
contract InvariantTest is Test {
bool fooCalled;
bool testSanityCalled;
uint256 invariantCalledNum;
uint256 setUpCalledNum;
function setUp() public {
targetContract(address(this));
}
function foo() public {
fooCalled = true;
}
function test_sanity() public {
testSanityCalled = true;
}
function invariant_foo_called() public view {
assert(!fooCalled);
}
function invariant_testSanity_considered_target() public {
assertFalse(testSanityCalled);
}
function invariant_setUp_considered_target() public {
setUpCalledNum++;
assertLt(setUpCalledNum, 3);
}
function invariant_considered_target() public {
// this is evaluated for invariant violations, but also considered a target state-changing function
invariantCalledNum++;
assertLt(invariantCalledNum, 3);
}
}
Expected Behavior
I think it would be fair for this test to have output like this:
[FAIL] invariant_foo_called
[PASS] invariant_testSanity_considered_target
[PASS] invariant_setUp_considered_target
[PASS] invariant_considered_target
meaning that foo()
would be recognized as the only valid state-changing target function in this contract
Actual Behavior
Full output below, showing all target functions called:
ft --mc InvariantTest
[⠒] Compiling...
[⠃] Compiling 1 files with Solc 0.8.28
[⠊] Solc 0.8.28 finished in 690.33ms
Compiler run successful with warnings:
Warning (2018): Function state mutability can be restricted to view
--> test/Invariant.t.sol:28:5:
|
28 | function invariant_testSanity_considered_target() public {
| ^ (Relevant source part starts here and spans across multiple lines).
Ran 5 tests for test/Invariant.t.sol:InvariantTest
[FAIL: assertion failed: 3 >= 3]
[Sequence] (original: 7, shrunk: 2)
sender=0xDD2073ceDaF7259a8c4C7cAEdba084a2EbbC92bd addr=[test/Invariant.t.sol:InvariantTest]0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496 calldata=invariant_considered_target() args=[]
sender=0x0000000000000000000000000000000000000D08 addr=[test/Invariant.t.sol:InvariantTest]0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496 calldata=invariant_considered_target() args=[]
invariant_considered_target() (runs: 0, calls: 0, reverts: 2)
╭---------------+----------------------------------------+-------+---------+----------╮
| Contract | Selector | Calls | Reverts | Discards |
+=====================================================================================+
| InvariantTest | invariant_considered_target | 2 | 0 | 0 |
|---------------+----------------------------------------+-------+---------+----------|
| InvariantTest | invariant_testSanity_considered_target | 2 | 2 | 0 |
|---------------+----------------------------------------+-------+---------+----------|
| InvariantTest | setUp | 4 | 0 | 0 |
|---------------+----------------------------------------+-------+---------+----------|
| InvariantTest | test_sanity | 1 | 0 | 0 |
╰---------------+----------------------------------------+-------+---------+----------╯
[FAIL: panic: assertion failed (0x01)]
[Sequence] (original: 5, shrunk: 1)
sender=0x0000000000000000000000000000000000001134 addr=[test/Invariant.t.sol:InvariantTest]0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496 calldata=foo() args=[]
invariant_foo_called() (runs: 0, calls: 0, reverts: 0)
╭---------------+-----------------------------------+-------+---------+----------╮
| Contract | Selector | Calls | Reverts | Discards |
+================================================================================+
| InvariantTest | foo | 1 | 0 | 0 |
|---------------+-----------------------------------+-------+---------+----------|
| InvariantTest | invariant_considered_target | 1 | 0 | 0 |
|---------------+-----------------------------------+-------+---------+----------|
| InvariantTest | invariant_setUp_considered_target | 2 | 0 | 0 |
|---------------+-----------------------------------+-------+---------+----------|
| InvariantTest | setUp | 1 | 0 | 0 |
╰---------------+-----------------------------------+-------+---------+----------╯
[FAIL: assertion failed: 3 >= 3]
[Sequence] (original: 10, shrunk: 2)
sender=0x0000000000000000000000000000000000000639 addr=[test/Invariant.t.sol:InvariantTest]0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496 calldata=invariant_setUp_considered_target() args=[]
sender=0x0000000000000000000000000000000000001108 addr=[test/Invariant.t.sol:InvariantTest]0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496 calldata=invariant_setUp_considered_target() args=[]
invariant_setUp_considered_target() (runs: 0, calls: 0, reverts: 2)
╭---------------+-----------------------------------+-------+---------+----------╮
| Contract | Selector | Calls | Reverts | Discards |
+================================================================================+
| InvariantTest | foo | 3 | 0 | 0 |
|---------------+-----------------------------------+-------+---------+----------|
| InvariantTest | invariant_considered_target | 4 | 2 | 0 |
|---------------+-----------------------------------+-------+---------+----------|
| InvariantTest | invariant_setUp_considered_target | 2 | 0 | 0 |
|---------------+-----------------------------------+-------+---------+----------|
| InvariantTest | setUp | 1 | 0 | 0 |
|---------------+-----------------------------------+-------+---------+----------|
| InvariantTest | test_sanity | 2 | 0 | 0 |
╰---------------+-----------------------------------+-------+---------+----------╯
[FAIL: assertion failed]
[Sequence] (original: 15, shrunk: 1)
sender=0x0000000000000000000000000000000000001856 addr=[test/Invariant.t.sol:InvariantTest]0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496 calldata=test_sanity() args=[]
invariant_testSanity_considered_target() (runs: 0, calls: 0, reverts: 4)
╭---------------+----------------------------------------+-------+---------+----------╮
| Contract | Selector | Calls | Reverts | Discards |
+=====================================================================================+
| InvariantTest | foo | 3 | 0 | 0 |
|---------------+----------------------------------------+-------+---------+----------|
| InvariantTest | invariant_considered_target | 4 | 2 | 0 |
|---------------+----------------------------------------+-------+---------+----------|
| InvariantTest | invariant_setUp_considered_target | 4 | 2 | 0 |
|---------------+----------------------------------------+-------+---------+----------|
| InvariantTest | invariant_testSanity_considered_target | 1 | 0 | 0 |
|---------------+----------------------------------------+-------+---------+----------|
| InvariantTest | setUp | 6 | 0 | 0 |
|---------------+----------------------------------------+-------+---------+----------|
| InvariantTest | test_sanity | 1 | 0 | 0 |
╰---------------+----------------------------------------+-------+---------+----------╯
[PASS] test_sanity() (gas: 5304)
Suite result: FAILED. 1 passed; 4 failed; 0 skipped; finished in 22.00ms (37.32ms CPU time)
Ran 1 test suite in 406.19ms (22.00ms CPU time): 1 tests passed, 4 failed, 0 skipped (5 total tests)
Failing tests:
Encountered 4 failing tests in test/Invariant.t.sol:InvariantTest
[FAIL: assertion failed: 3 >= 3]
[Sequence] (original: 7, shrunk: 2)
sender=0xDD2073ceDaF7259a8c4C7cAEdba084a2EbbC92bd addr=[test/Invariant.t.sol:InvariantTest]0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496 calldata=invariant_considered_target() args=[]
sender=0x0000000000000000000000000000000000000D08 addr=[test/Invariant.t.sol:InvariantTest]0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496 calldata=invariant_considered_target() args=[]
invariant_considered_target() (runs: 0, calls: 0, reverts: 2)
[FAIL: panic: assertion failed (0x01)]
[Sequence] (original: 5, shrunk: 1)
sender=0x0000000000000000000000000000000000001134 addr=[test/Invariant.t.sol:InvariantTest]0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496 calldata=foo() args=[]
invariant_foo_called() (runs: 0, calls: 0, reverts: 0)
[FAIL: assertion failed: 3 >= 3]
[Sequence] (original: 10, shrunk: 2)
sender=0x0000000000000000000000000000000000000639 addr=[test/Invariant.t.sol:InvariantTest]0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496 calldata=invariant_setUp_considered_target() args=[]
sender=0x0000000000000000000000000000000000001108 addr=[test/Invariant.t.sol:InvariantTest]0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496 calldata=invariant_setUp_considered_target() args=[]
invariant_setUp_considered_target() (runs: 0, calls: 0, reverts: 2)
[FAIL: assertion failed]
[Sequence] (original: 15, shrunk: 1)
sender=0x0000000000000000000000000000000000001856 addr=[test/Invariant.t.sol:InvariantTest]0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496 calldata=test_sanity() args=[]
invariant_testSanity_considered_target() (runs: 0, calls: 0, reverts: 4)
Encountered a total of 4 failing tests, 1 tests succeeded
Background
Before #10274, selectors had to be explicitly configured for address(this)
, which was verbose but explicit and understandable with no surprises:
targetContract(address(this));
selectors = new bytes4[](2);
selectors[0] = this.foo.selector;
selectors[1] = this.bar.selector;
targetSelector(FuzzSelector({
addr: address(this),
selectors: selectors
}));
We are trying to implement a similar feature and are trying to be compatible with foundry when possible: a16z/halmos#506
Metadata
Metadata
Assignees
Labels
Type
Projects
Status