-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Description
Component
Forge
Have you ensured that all of these are up to date?
- FoundryFoundryupTo pick up a draggable item, press the space bar. While dragging, use the arrow keys to move the item. Press space again to drop the item in its new position, or press escape to cancel.
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
Activity