-
Notifications
You must be signed in to change notification settings - Fork 1.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Invariant test does not revert though fail_on_revert is set to true #6694
Comments
I'm not positive but I think I have a similar issue. I have an assert that fails but the test still indicates that it passed. This is very concerning to me as I would not have realized things were broken if I hadn't been running with high verbosity for another issue. I can't seem to figure out what would cause it but I am somewhat new to using foundry for tests. I believe I have something pretty compact and reproducible running with the following settings and code.
foundry.toml [profile.default]
src = "src"
out = "out"
libs = ["lib"]
ffi = true
optimizer = true
evm_version = 'shanghai'
fs_permissions = [{ access = "read", path = "./" }]
[fuzz]
runs = 256
seed = '0x3'
[invariant]
runs = 64
depth = 32
fail_on_revert = true AssertTests.t.sol // SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.20;
import {Test, console2} from "forge-std/Test.sol";
import {StdInvariant} from "forge-std/StdInvariant.sol";
contract ContractTestSuite is StdInvariant, Test {
Mock mock;
MockHandler mockHandler;
uint256 id = 0;
function setUp() public {
mock = new Mock();
mockHandler = new MockHandler(address(mock));
targetContract(address(mockHandler));
bytes4[] memory selectors = new bytes4[](2);
selectors[0] = mockHandler.setTimeById.selector;
selectors[1] = mockHandler.hasRequiredTimePassed.selector;
targetSelector(FuzzSelector({addr: address(mockHandler), selectors: selectors}));
}
function statefulFuzz_testHasTimePassed() public {
assert(mock.hasRequiredTimePassed(id));
}
}
contract Mock {
uint256 public constant REQUIRED_TIME = 1 days;
mapping(uint256 id => uint256 timeStamp) public idsToTimestamps;
constructor() {}
function setTimeById(uint256 id) external {
idsToTimestamps[id] = block.timestamp;
}
function hasRequiredTimePassed(uint256 id) external view returns (bool) {
if (idsToTimestamps[id] <= block.timestamp - REQUIRED_TIME) {
return false;
}
return true;
}
}
contract MockHandler is Test {
address private originalContract;
uint256 counter = 0;
constructor(address _originalContract) {
originalContract = _originalContract;
vm.warp(623095235);
Mock(originalContract).setTimeById(0);
}
function setTimeById() external {
++counter;
uint256 rollForwardBy = counter % 2 == 0 ? 36 hours : 12 hours;
vm.warp(vm.getBlockTimestamp()+rollForwardBy);
vm.roll(vm.getBlockNumber()+1);
Mock(originalContract).setTimeById(0);
}
function hasRequiredTimePassed() external returns (bool) {
++counter;
uint256 rollForwardBy = counter % 2 == 0 ? 36 hours : 12 hours;
vm.warp(vm.getBlockTimestamp()+rollForwardBy);
vm.roll(vm.getBlockNumber()+1);
return Mock(originalContract).hasRequiredTimePassed(0);
}
} Output:
If I apply the following diff to the stateful fuzz test it appears as if the failed assertion is properly recognized: function statefulFuzz_testHasTimePassed() public {
- assert(mock.hasRequiredTimePassed(id));
+ assert(mockHandler.hasRequiredTimePassed());
} Output:
|
@keyneom Yes, I think we have the same issue.
Same here, from what I see @Evalir Really apologize for the tag, but not sure how else to draw attention to this issue and am a bit worried by the possibility of codebases relying on |
+1, would like to request this gets fixed too |
@0xMelkor I saw you have assigned #3411 for invariant fuzz test benchmarks. I just don't think benchmarks will be very meaningful if the tests aren't reporting failures correctly and I'd like to draw attention to this issue as it appears there are more and more people that are noticing its impact on them. I just want to make sure you all are aware of it since I know you've all got a ton of issues open right now. |
…e state (e.g. using cheatcodes like roll, warp), see foundry-rs#6694 - active only in conjunction with fail_on_revert true
this is something that at some point made us consider switching away from foundry, but glad we stayed with it :) What we ended up in our project was to save chain state in handler and reapply on each call, e.g. pragma solidity ^0.8.19;
import "forge-std/Test.sol";
import "forge-std/console2.sol";
contract DummyInvariantOkTest is Test {
uint256 curBlock;
bytes4[] internal selectors;
modifier roll(uint256 blocks) {
blocks = blocks % 10;
vm.roll(curBlock + blocks);
_;
curBlock = block.number;
}
function setUp() public {
vm.label({account: address(this), newLabel: "DummyHandler"});
selectors.push(this.thisFunctionReverts.selector);
targetSelector(
FuzzSelector({addr: address(this), selectors: selectors})
);
}
function invariant_DummyInvariant() public {
assertTrue(true);
}
function thisFunctionReverts(uint256 blocks) public roll(blocks) {
if (block.number < 20) {} else {
revert();
}
}
} Totally agree the behavior is unintuitive and bad ux so I am going to craft a PR with a new invariant var |
made a draft PR #7219 probably not the best solution for this issue @mattsse @Evalir @DaniPopes any thoughts on this really appreciated. thank you |
* - add preserve_state invariant config: useful for handlers that change state (e.g. using cheatcodes like roll, warp), see #6694 - active only in conjunction with fail_on_revert true * Add test from issue 6694
I believe this issue was closed by #7219, but can reopen if I misunderstood |
A bit late, but thank you for the fix @grandizzy ! I confirm it fixes the issue on my end. Just to summarize if anyone stumbles here with an invariant test not reverting, make sure your
|
Component
Forge
Have you ensured that all of these are up to date?
What version of Foundry are you on?
forge 0.2.0 (2bcb4a1 2024-01-02T00:17:30.395578000Z)
What command(s) is the bug in?
forge test
Describe the bug
I am running an invariant test through a handler and have a weird issue where
fail_on_revert
is set totrue
in my config (confirmed by runningforge config
), one of the calls revert (confirmed by looking at traces with high verbosity) but the test passes. I believefail_on_revert
is not respected when usingvm.warp
and/orvm.roll
. I do not use--via-ir
. It is problematic as it means some inner calls can fail silently without you noticing (which is what initially happened in my case).I managed to create a small self-contained repro:
Reproduction code
Foundry.toml
DummyInvariant.t.sol
DummyHandler.sol
In the above, the handler has two functions:
advanceTime
increments the block number and the timestampthisFunctionReverts
reverts ifblock.number >= 20
.It should be trivial to make the above revert with a stateful test. However, if I run the above with
forge test --match-contract DummyInvariantTest -vvvvv
, I can see that calls tothisFunctionReverts
do revert in the logs but the test passes successfully.Importantly, if I comment out
vm.warp
&vm.roll
in theadvanceTime
function and just callvm.roll
(to roll to block 21) directly insetUp
, the test reverts successfully. This points to the issue being when the handler callsvm.roll
orvm.warp
.The text was updated successfully, but these errors were encountered: