Skip to content

targetContract(address(this)) includes setUp, test_ and invariant_ functions as target functions #10469

Closed
@0xkarmacoma

Description

@0xkarmacoma

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

No one assigned

    Labels

    Type

    No type

    Projects

    Status

    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions