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
30 changes: 30 additions & 0 deletions test/access/manager/AuthorityUtils.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import {Test} from "forge-std/Test.sol";

import {AuthorityUtils} from "@openzeppelin/contracts/access/manager/AuthorityUtils.sol";
import {AuthorityDelayMock} from "@openzeppelin/contracts/mocks/AuthorityMock.sol";

contract AuthorityUtilsTest is Test {
AuthorityDelayMock internal _authority;

function setUp() public {
_authority = new AuthorityDelayMock();
}

function testAuthorityReturnDelay(uint32 delay, bool immediate) public {
_authority._setImmediate(immediate);
_authority._setDelay(delay);

(bool _immediate, uint32 _delay) = AuthorityUtils.canCallWithDelay(
address(_authority),
address(this),
address(this),
hex"12345678"
);
assertEq(_immediate, immediate);
assertEq(_delay, delay);
}
}
18 changes: 0 additions & 18 deletions test/access/manager/AuthorityUtils.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,24 +64,6 @@ describe('AuthorityUtils', function () {
});
});

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);
});
}
}
});

Comment on lines -67 to -84
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.

describe('when authority replies with empty data', function () {
beforeEach(async function () {
this.authority = this.authorityNoResponse;
Expand Down
167 changes: 167 additions & 0 deletions test/governance/extensions/GovernorCountingFractional.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import {Test} from "forge-std/Test.sol";

import {GovernorFractionalMock} from "@openzeppelin/contracts/mocks/governance/GovernorFractionalMock.sol";
import {ERC20VotesTimestampMock} from "@openzeppelin/contracts/mocks/token/ERC20VotesTimestampMock.sol";

import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import {Governor} from "@openzeppelin/contracts/governance/Governor.sol";
import {GovernorSettings} from "@openzeppelin/contracts/governance/extensions/GovernorSettings.sol";
import {GovernorCountingFractional} from "@openzeppelin/contracts/governance/extensions/GovernorCountingFractional.sol";
import {GovernorVotesQuorumFraction} from "@openzeppelin/contracts/governance/extensions/GovernorVotesQuorumFraction.sol";
import {GovernorVotes} from "@openzeppelin/contracts/governance/extensions/GovernorVotes.sol";

import {IVotes} from "@openzeppelin/contracts/governance/utils/IVotes.sol";

contract GovernorMock is GovernorFractionalMock {
constructor(
string memory name,
uint48 votingDelay,
uint32 votingPeriod,
uint256 proposalThreshold,
address tokenAddress,
uint256 quorumNumeratorValue
)
GovernorVotesQuorumFraction(quorumNumeratorValue)
GovernorVotes(IVotes(tokenAddress))
GovernorSettings(votingDelay, votingPeriod, proposalThreshold)
Governor(name)
{}
}

contract TokenMock is ERC20VotesTimestampMock {
constructor() ERC20("TokenMock", "TKN") EIP712("TokenMock", "1") {}

function mint(address account, uint256 amount) public {
_mint(account, amount);
}
}

contract GovernorCountingFractionalTest is Test {
GovernorFractionalMock internal _governor;
TokenMock internal _token;

uint256 internal _proposerPrivateKey;
uint256 internal _voterPrivateKey;

address internal _proposer;
address internal _voter;

function setUp() public {
_token = new TokenMock();
_governor = new GovernorMock("OZ-Governor", 0, 99999, 0, address(_token), 10);

_proposerPrivateKey = 0xA11CE;
_voterPrivateKey = 0xB0B;

_proposer = vm.addr(_proposerPrivateKey);
_voter = vm.addr(_voterPrivateKey);
}

function createProposal() internal returns (uint256 proposalId) {
address[] memory targets = new address[](1);
targets[0] = address(this);

uint256[] memory values = new uint256[](1);
values[0] = 0;

bytes[] memory calldatas = new bytes[](1);
calldatas[0] = "";

vm.prank(_proposer);
proposalId = _governor.propose(targets, values, calldatas, "proposal description");

vm.warp(block.timestamp + 1);
}

function testFractionalVotingTwice(uint32[3] memory votes) public {
uint256 sumVotes = uint256(votes[0]) + uint256(votes[1]) + uint256(votes[2]);
vm.assume(sumVotes > 0);

_token.mint(_voter, sumVotes * 2);

vm.prank(_voter);
_token.delegate(_voter);

vm.warp(block.timestamp + 1);

uint256 proposalId = createProposal();

(uint256 againstVotes, uint256 forVotes, uint256 abstainVotes) = _governor.proposalVotes(proposalId);

assertTrue(againstVotes == 0 && forVotes == 0 && abstainVotes == 0);
assertEq(_governor.hasVoted(proposalId, _voter), false);
assertEq(_governor.usedVotes(proposalId, _voter), 0);

vm.startPrank(_voter);
// TODO: check emitted events
_governor.castVoteWithReasonAndParams(
proposalId,
255,
"no particular reason",
abi.encodePacked(uint128(votes[0]), uint128(votes[1]), uint128(votes[2]))
);
_governor.castVoteWithReasonAndParams(
proposalId,
255,
"no particular reason",
abi.encodePacked(uint128(votes[0]), uint128(votes[1]), uint128(votes[2]))
);

(againstVotes, forVotes, abstainVotes) = _governor.proposalVotes(proposalId);

assertTrue(
againstVotes == uint256(votes[0]) * 2 &&
forVotes == uint256(votes[1]) * 2 &&
abstainVotes == uint256(votes[2]) * 2
);
assertEq(_governor.hasVoted(proposalId, _voter), true);
assertEq(_governor.usedVotes(proposalId, _voter), sumVotes * 2);
}

function testFractionalThenNominal(uint32[3] memory fractional, uint160 nominal) public {
uint256 sumVotes = uint256(fractional[0]) + uint256(fractional[1]) + uint256(fractional[2]);
vm.assume(sumVotes > 0);
vm.assume(nominal > 0);

_token.mint(_voter, sumVotes + nominal);

vm.prank(_voter);
_token.delegate(_voter);

vm.warp(block.timestamp + 1);

uint256 proposalId = createProposal();

(uint256 againstVotes, uint256 forVotes, uint256 abstainVotes) = _governor.proposalVotes(proposalId);

assertTrue(againstVotes == 0 && forVotes == 0 && abstainVotes == 0);
assertEq(_governor.hasVoted(proposalId, _voter), false);
assertEq(_governor.usedVotes(proposalId, _voter), 0);

vm.startPrank(_voter);
// TODO: check emitted events
_governor.castVoteWithReasonAndParams(
proposalId,
255,
"no particular reason",
abi.encodePacked(uint128(fractional[0]), uint128(fractional[1]), uint128(fractional[2]))
);

_governor.castVoteWithReason(proposalId, 0, "no particular reason");

(againstVotes, forVotes, abstainVotes) = _governor.proposalVotes(proposalId);

assertTrue(
againstVotes == uint256(fractional[0]) + nominal &&
forVotes == uint256(fractional[1]) &&
abstainVotes == uint256(fractional[2])
);
assertEq(_governor.hasVoted(proposalId, _voter), true);
assertEq(_governor.usedVotes(proposalId, _voter), sumVotes + nominal);
}
}
80 changes: 0 additions & 80 deletions test/governance/extensions/GovernorCountingFractional.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');

const { GovernorHelper } = require('../../helpers/governance');
const { VoteType } = require('../../helpers/enums');
const { zip } = require('../../helpers/iterate');
const { sum } = require('../../helpers/math');

const TOKENS = [
Expand Down Expand Up @@ -94,85 +93,6 @@ describe('GovernorCountingFractional', function () {
});

describe('voting with a fraction of the weight', function () {
it('twice', async function () {
await this.helper.connect(this.proposer).propose();
await this.helper.waitForSnapshot();

expect(await this.mock.proposalVotes(this.proposal.id)).to.deep.equal([0n, 0n, 0n]);
expect(await this.mock.hasVoted(this.proposal.id, this.voter2)).to.equal(false);
expect(await this.mock.usedVotes(this.proposal.id, this.voter2)).to.equal(0n);

const steps = [
['0', '2', '1'],
['1', '0', '1'],
].map(votes => votes.map(vote => ethers.parseEther(vote)));

for (const votes of steps) {
const params = ethers.solidityPacked(['uint128', 'uint128', 'uint128'], votes);
await expect(
this.helper.connect(this.voter2).vote({
support: VoteType.Parameters,
reason: 'no particular reason',
params,
}),
)
.to.emit(this.mock, 'VoteCastWithParams')
.withArgs(
this.voter2,
this.proposal.id,
VoteType.Parameters,
sum(...votes),
'no particular reason',
params,
);
}

expect(await this.mock.proposalVotes(this.proposal.id)).to.deep.equal(zip(...steps).map(v => sum(...v)));
expect(await this.mock.hasVoted(this.proposal.id, this.voter2)).to.equal(true);
expect(await this.mock.usedVotes(this.proposal.id, this.voter2)).to.equal(sum(...[].concat(...steps)));
});

it('fractional then nominal', async function () {
await this.helper.connect(this.proposer).propose();
await this.helper.waitForSnapshot();

expect(await this.mock.proposalVotes(this.proposal.id)).to.deep.equal([0n, 0n, 0n]);
expect(await this.mock.hasVoted(this.proposal.id, this.voter2)).to.equal(false);
expect(await this.mock.usedVotes(this.proposal.id, this.voter2)).to.equal(0n);

const weight = ethers.parseEther('7');
const fractional = ['1', '2', '1'].map(ethers.parseEther);

const params = ethers.solidityPacked(['uint128', 'uint128', 'uint128'], fractional);
await expect(
this.helper.connect(this.voter2).vote({
support: VoteType.Parameters,
reason: 'no particular reason',
params,
}),
)
.to.emit(this.mock, 'VoteCastWithParams')
.withArgs(
this.voter2,
this.proposal.id,
VoteType.Parameters,
sum(...fractional),
'no particular reason',
params,
);

await expect(this.helper.connect(this.voter2).vote({ support: VoteType.Against }))
.to.emit(this.mock, 'VoteCast')
.withArgs(this.voter2, this.proposal.id, VoteType.Against, weight - sum(...fractional), '');

expect(await this.mock.proposalVotes(this.proposal.id)).to.deep.equal([
weight - sum(...fractional.slice(1)),
...fractional.slice(1),
]);
expect(await this.mock.hasVoted(this.proposal.id, this.voter2)).to.equal(true);
expect(await this.mock.usedVotes(this.proposal.id, this.voter2)).to.equal(weight);
});

it('revert if params spend more than available', async function () {
await this.helper.connect(this.proposer).propose();
await this.helper.waitForSnapshot();
Expand Down
6 changes: 0 additions & 6 deletions test/token/ERC721/extensions/ERC721Consecutive.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,12 +142,6 @@ describe('ERC721Consecutive', function () {
describe('ERC721 behavior', function () {
const tokenId = offset + 1n;

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);
});

Comment on lines -145 to -150
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 ?

it('tokens can be burned and re-minted #1', async function () {
await expect(this.token.connect(this.alice).$_burn(tokenId))
.to.emit(this.token, 'Transfer')
Expand Down
6 changes: 6 additions & 0 deletions test/utils/Create2.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,10 @@ contract Create2Test is Test {
}
assertEq(spillage, bytes32(0));
}

function testCreate2(bytes32 salt, bytes32 bytecodeHash, address deployer) public {
address actual = Create2.computeAddress(salt, bytecodeHash, deployer);
address expected = vm.computeCreate2Address(salt, bytecodeHash, deployer);
assertEq(actual, expected);
}
}
35 changes: 35 additions & 0 deletions test/utils/Strings.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import {Test} from "forge-std/Test.sol";

import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";

contract StringsTest is Test {
function testStringUint256(uint256 value) public {
string memory actual = Strings.toString(value);
assertEq(actual, vm.toString(value));
}

function testStringInt256(int256 value) public {
string memory actual = Strings.toStringSigned(value);
assertEq(actual, vm.toString(value));
}

function testHexStringUint256(uint256 value) public {
string memory actual = vm.replace(Strings.toHexString(value), "0", "");
string memory expected = vm.replace(vm.toString(bytes32(value)), "0", "");
assertEq(actual, expected);
}

function testHexStringAddress(address value) public {
string memory actual = Strings.toHexString(value);
assertEq(actual, vm.toLowercase(vm.toString(value)));
}

function testChecksumHexStringAddress(address value) public {
string memory actual = Strings.toChecksumHexString(value);
assertEq(actual, vm.toString(value));
}
}
Loading
Loading