-
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
feat(cheatcodes
): support additional cheatcodes to aid in symbolic testing
#4072
Comments
Another one we're adding is This can probably be a no-op on the Foundry side, as I doubt that it will exercise many cases where infinite gas is actually called for. But for symbolic reasoning it's needed. |
forge does have |
Well, it's different semantics really, infinite gas means you still get gas metering but it won't run out, so you can still tell how much gas something used. Maybe you also mean that? We'll take a look. But would also be nice to have the |
@gakonst , I had a conversation with @mds1 about this at EthDenver, and I tried to emphasize that When you are testing against contract state, Foundry is currently verifying that "against an initial deployment of the contract, the given property holds". Being able to fuzz against storage makes it instead that "against an arbitrary intermediate state, the given property holds", it is a massive generalization. For verification, it honestly does not even make much benefit to verify without this extra expressive power. Cheatcode
That way, every storage slot is being fuzzed over, and you can still do Note this is more general than just being able to say that some particular value is arbitrary, which can already be achieved by passing in a random |
Agreed, I do think these would be generically useful and they all seem pretty straightforward to implement, cc @mattsse. @ehildenb I think 1 or 2 examples of how usage would look in practice would be valuable. Would this be a good example demonstrating the use of contract ERC20Burn is Test {
ERC20 token;
function setUp() public {
token = new ERC20("Token", "TKN");
vm.setArbitraryStorage(address(token));
}
// QUESTION: Is this supposed to be structured as a fuzz test? Or maybe we need another
// way to specify how many runs to execute since it is different than a regular fuzz test
function test_BurnsTheUsersFullTokenBalance() {
// Owner should be able to burn all of the user's tokens. When the burn
// method checks the user's balance, the `setArbitraryStorage` tells forge
// to put a random value there since it has not yet been accessed.
vm.prank(owner);
token.burn(user);
assertEq(token.balanceOf(user), 0);
}
} |
That looks like an excellent test! If you had a token which just implemented the |
wen |
cheatcodes
): support additional cheatcodes to aid in symbolic testing
I think we can use |
@ehildenb for contract Counter {
uint256 public a;
address public b;
function setA(uint256 _a) public {
a = _a;
}
function setB(address _b) public {
b = _b;
}
}
contract CounterTest is Test {
Counter public counter;
Counter public counter1;
function setUp() public {
counter = new Counter();
counter.setA(1000);
counter.setB(address(27));
counter1 = new Counter();
counter1.setA(11);
counter1.setB(address(50));
}
function test_copy_storage() public {
assertEq(counter.a(), 1000);
assertEq(counter.b(), address(27));
assertEq(counter1.a(), 11);
assertEq(counter1.b(), address(50));
vm.copyStorage(address(counter), address(counter1));
assertEq(counter.a(), 1000);
assertEq(counter.b(), address(27));
assertEq(counter1.a(), 1000);
assertEq(counter1.b(), address(27));
}
} |
for keeping scope and progress relevant, remaining |
Component
Forge, Cast, Chisel
Describe the feature you would like
From a conversation with Runtime Verification highlighting additional cheatcodes that would be valuable for them as well as be generic enough to lay a foundation for symbolic testing: #15
freshStorage(address)
: makes the storage of the given address fully symbolic. Any subsequentSLOAD
reads a random value, and then the random value that was read is memorized (so if youSLOAD
the same slot again, you get the same result)copyStorage(address, address)
: copies the storage of one contract to another.freshAddress()
,freshBool()
: returns a symbolic address or bool value, respectively.freshBytes(uint256)
external returns (bytes memory b): returns a symbolic bytes array of a given size; inKontrol
, the returned bytes b have dynamic size but we add an assumption on b.length.freshUInt(uint8)
: returns a symbolic unsigned integer of a given size in bytes. We use this freshUInt cheatcode to define fixed-length variations for unsigned (freshUInt256(), freshUInt248(), etc.) and signed integers (freshSInt32(), freshSInt8(), etc.).mockFunction(address callee, address calledContract, bytes calldata data)
: whenever a call is made to callee with calldata data, this cheatcode instead calls calledContract with the same calldata. This functionality is similar to a delegate call made to calledContract from callee. We use it to substitute a call to a client's function with another implementation that captures the primary logic of the original function but is easier to reason about. It was inspired by Foundry’s mockCall, which helps substitute a call with specified return data but allows capturing more observable behaviors than just the return value.expect*Call
cheatcodes: similar to expectEmit, these let us specify the expected call with its callee and calldata. We use these less frequently, but I think they can be useful even for fuzzing tests.expectRegularCall
: Expect a call using theCALL
opcode and specified parametersexpectStaticCall
: Expect a call using theSTATICCALL
opcode and specified parametersexpectDelegateCall
: Expect a call using theDELEGATECALL
opcode and specified parametersexpectNoCall
: Expect no calls to be made after this cheat codeexpectCreate
: Expects the deployment of the specified bytecode by the specified address using theCREATE
opcodeexpectCreate2
: Expects the deployment of the specified bytecode by the specified address using theCREATE2
opcodeFor
expect
cheatcodes other thanexpectRegularCall
andexpectStaticCall
, we don't have tests showing their behavior, but they should be similar to the call tests. ForsymbolicStorage
we have this test.The
fresh*
cheatcodes should return a random value. ThesymbolicStorage
could be renamed tofreshStorage
, and basically what it should mean is described here:Context:
The
freshStorage
is important in terms of improving the expressive power of Foundry tests significantly, and also for Kontrol "official" integration, since we can't really claim full formal verification if it's only from the 0-initialized storage.Everything where we would return a symbolic value should basically return a random value in Foundry.
Currently, when we take our clients property tests, we have to generalize them with the above cheatcodes to make them meaningful symbolically. This has 2 downsides: (i) now their test is no longer runnable with Foundry directly, only wiht Kontrol, and (ii) the foundry tests they write aren't covering as much behavior as possible, because they can't generalize the storage or inputs as much.
Adding support to these cheatcodes directly to Foundry would solve both of these problems, users could write more general tests from the start (and fuzz them), and then switching to Kontrol for formal verification wouldn't stop them from using Foundry.
Additional context
No response
The text was updated successfully, but these errors were encountered: