foundry-rs / foundry

Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.
https://getfoundry.sh
Apache License 2.0
7.94k stars 1.61k forks source link

feat(forge): native support for simulating the passage of time when running invariants #4994

Closed PaulRBerg closed 2 months ago

PaulRBerg commented 1 year ago

Component

Forge

Describe the feature you would like

Because most smart contract systems expect time to pass, it is a common need to simulate the passage of time (with vm.warp) when running invariant test campaigns with Foundry.

But this is difficult to do today because state is not preserved during invariants runs. Just look at Maple Finance's solution - they have to apply the useCurrentTimestamp modifier to all invariant tests, as well as all functions in the handler contracts.

Now, regardless of the state-preserving limitation and if and when it will be lifted, I posit that it would be helpful to offer native support for time warps in invariants.

Here's what I have in mind :

[profile.default.invariant]
  time_jump_min = 50
  time_jump_max = 1000

Nevermind the names - we can think about what would fit best later. What matters is the idea - to introduce two new config options which would be used for bounding a time jump that gets applied after every invariant test run, i.e. have Foundry do something like this:

modifier jump() {
    uint256 timeWarp = block.timestamp;
    timeWarp = _bound(timeWarp, 50 seconds, 1000 seconds);
    timestampStore.increaseCurrentTimestamp(timeWarp);
    vm.warp(timestampStore.currentTimestamp());
    _;
}

This way, an invariant test campaign would more accurately simulate the real world, where time passes between function calls.

Additional context

Related discussions:

simplyoptimistic commented 1 year ago

I would also like something like this, but I would add that I think it makes sense to move forward blocks by some amount as well (e.g. perhaps by allowing the user to set the average frequency a new block is expected, to allow for variations across chains).

mds1 commented 1 year ago

I like the idea here. I'd suggest also adding a time_jump_frequency config value which is [0,100] which specifies the probability that you add a time jump before (and/or after?) the next function call to have a little more control. Since testing txs in the same block is also useful, and it'd be nice to have both scenarios with a single config.

Like @simplyoptimistic I think we should couple this with block number increases to keep those in sync to make sure things are realistic and minimize footguns.

Partly related to https://github.com/foundry-rs/foundry/issues/749#issuecomment-1445181592 where we discuss the idea of codegen'ing handler contracts that have properties like this defined

viraj124 commented 1 year ago

hey guys, just wondering if there's an eta as to when this will be addressed as my invariant tests too have some dependency on block.timestamp but right it get's reset to the default value after 2-3 runs

PaulRBerg commented 1 year ago

@viraj124 not sure about any ETA but in the meantime you can look at how we tackled this issue in our project sablier-labs/v2-core:

https://github.com/sablier-labs/v2-core/blob/f25aad3a3bff37b7b095aa77bfdc8a91add09382/test/invariant/stores/TimestampStore.sol

viraj124 commented 1 year ago

Thanks a lot @PaulRBerg this looks a elegant workaround was working on enabling something like this