Skip to content
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

Add remaining fuzzing tests and remove pseudo-random fuzzing #5095

Draft
wants to merge 11 commits into
base: master
Choose a base branch
from

Conversation

cairoeth
Copy link
Contributor

@cairoeth cairoeth commented Jun 20, 2024

Fixes #4894 by removing unit tests that are already equivalently running with fuzzing via Foundry and adding remaining fuzzing unit tests. WIP.

PR Checklist

  • Checkpoints
  • CircularBuffer
  • AccessControlEnumerable
  • GovernorVotesQuorumFraction
  • GovernorCountingFractional
  • AuthorityUtils
  • CREATE2
  • ECDSA
  • Strings
  • ERC20Votes
  • ERC20Snapshot
  • ERC721Votes

  • Tests
  • Documentation
  • Changeset entry (run npx changeset add)

Removing Hardhat test as it's tested and fuzzed with Foundry via `test_transfer`
Remove `average` and `abs` unit tests which attempt to do fuzzing in favor of Foundry native-fuzzing, already found in `SignedMath.t.sol`
Remove in favor of fuzzed unit test with Foundry
Copy link

changeset-bot bot commented Jun 20, 2024

⚠️ No Changeset found

Latest commit: b607f30

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to check that this doesn't reduce coverage. It may be necessary to keep at least one test.
(foundry tests are not registered when doing coverage)

Copy link
Collaborator

@Amxx Amxx left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Our guideline so far was:

  • unit test first, with as close to 100% coverage as possible
  • fuzzing and formal verification where that is relevant, as a complement to unit tests (not as a replacement).

I still think this applies.

A direct consequence is that even when there is some fuzzing, we still want to have unit test that cover the code. In particular the corner cases whe have identified (and that we are not 100% sure fuzzing will explore).

The idea being the issue to avoid unit tests that loop over (large amount of) randomly generated values. However, that doesn't mean all loops in the tests should be removed. Following the swiss chesse model, fuzzing should be an extra layer, but we should not add holes in the unit test layers with the hope that fuzzing covers them.

My initial impression reading this PR is that the added foundry tests are great ... but I wouldn't necessarily remove (all) the corresponding unit tests. For example, there may to too many different input for testing SignedMath.average, but I still think we should test the combinaison of MinInt256 and MaxInt256 with each other, and with some smaller values.

Comment on lines +9 to +17
function testRecoverWithValidSignature(string calldata seed, string calldata message) public {
(address signer, uint256 key) = makeAddrAndKey(seed);

bytes32 hash = keccak256(abi.encode(message));
(uint8 v, bytes32 r, bytes32 s) = vm.sign(key, hash);

bytes memory signature = abi.encodePacked(r, s, v);
assertEq(signer, ECDSA.recover(hash, signature));
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
function testRecoverWithValidSignature(string calldata seed, string calldata message) public {
(address signer, uint256 key) = makeAddrAndKey(seed);
bytes32 hash = keccak256(abi.encode(message));
(uint8 v, bytes32 r, bytes32 s) = vm.sign(key, hash);
bytes memory signature = abi.encodePacked(r, s, v);
assertEq(signer, ECDSA.recover(hash, signature));
}
function testRecoverWithValidSignature(string calldata seed, bytes32 digest) public {
(address signer, uint256 key) = makeAddrAndKey(seed);
(uint8 v, bytes32 r, bytes32 s) = vm.sign(key, digest);
bytes memory signature = abi.encodePacked(r, s, v);
assertEq(signer, ECDSA.recover(digest, signature));
}

Comment on lines +19 to +27
function testRecoverWithWrongMessage(string calldata seed, string calldata message, bytes32 digest) public {
(address signer, uint256 key) = makeAddrAndKey(seed);

bytes32 validDigest = keccak256(abi.encode(message));
(uint8 v, bytes32 r, bytes32 s) = vm.sign(key, validDigest);

bytes memory signature = abi.encodePacked(r, s, v);
assertTrue(signer != ECDSA.recover(digest, signature));
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
function testRecoverWithWrongMessage(string calldata seed, string calldata message, bytes32 digest) public {
(address signer, uint256 key) = makeAddrAndKey(seed);
bytes32 validDigest = keccak256(abi.encode(message));
(uint8 v, bytes32 r, bytes32 s) = vm.sign(key, validDigest);
bytes memory signature = abi.encodePacked(r, s, v);
assertTrue(signer != ECDSA.recover(digest, signature));
}
function testRecoverWithWrongMessage(string calldata seed, bytes32 digest, bytes32 otherDigest) public {
vm.assume(digest != otherDigest);
(address signer, uint256 key) = makeAddrAndKey(seed);
(uint8 v, bytes32 r, bytes32 s) = vm.sign(key, digest);
bytes memory signature = abi.encodePacked(r, s, v);
assertFalse(signer == ECDSA.recover(otherDigest, signature));
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to check that this doesn't reduce coverage. It may be necessary to keep at least one test.
(foundry tests are not registered when doing coverage)

Note: I like that unit test cover the EIP-55 references specifically

Comment on lines -145 to -150
it('core takes over ownership on transfer', async function () {
await this.token.connect(this.alice).transferFrom(this.alice, this.receiver, tokenId);

expect(await this.token.ownerOf(tokenId)).to.equal(this.receiver);
});

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this test removed ?

Comment on lines -67 to -84
describe('when authority replies with a delay', function () {
beforeEach(async function () {
this.authority = this.authorityDelayMock;
});

for (const immediate of [true, false]) {
for (const delay of [0n, 42n]) {
it(`returns (immediate=${immediate}, delay=${delay})`, async function () {
await this.authority._setImmediate(immediate);
await this.authority._setDelay(delay);
const result = await this.mock.$canCallWithDelay(this.authority, this.user, this.other, '0x12345678');
expect(result.immediate).to.equal(immediate);
expect(result.delay).to.equal(delay);
});
}
}
});

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is not so much fuzzing as it is testing 4 clearly different cases. Not saying fuzzing would not cover them, but we don't really need 5000 fuzzing runs to cover the same code.

Also, I'm not sure how that change would affect coverage.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Remove pseudo-fuzzing tests in hardhat in favor of foundry fuzzing.
2 participants