Beginner FriendlyFoundry
100 EXP
View results
Submission Details
Severity: high
Invalid

Vote will end early with a wrong result when there is an even number of votes if `s_totalAllowedVoters` is 3 or 7, and remaining voters who can change the result of the vote can't vote

Summary

Vote will end early with a wrong result when there is an even number of votes if s_totalAllowedVoters is 3 or 7, and remaining voters who can change the result of the vote can't vote

Vulnerability Details

An allowList with a length of 3, 5, 7 and 9 is allowed

// perform some sanity checks. NOTE: checks for duplicate inputs
// are performed by entity creating the proposal who is
// supplying the eth and is trusted, so the contract intentionally
// does not re-check for duplicate inputs. Findings related to
// not checking for duplicate inputs are invalid.
require(allowListLength >= MIN_VOTERS, "DP: Minimum 3 voters required");
require(allowListLength <= MAX_VOTERS, "DP: Maximum 9 voters allowed");
// odd number of voters required to simplify quorum check
require(allowListLength % 2 != 0, "DP: Odd number of voters required");

When the allowList has a length of 3 or 7, it will reach the MIN_QUORUM of 51% when there is an even number of votes :

» uint(1) * 100 / 3
33
» uint(2) * 100 / 3
66
» uint(3) * 100 / 7
42
» uint(4) * 100 / 7
57

For example, s_totalAllowedVoters is 3, and voter1 wants to vote against, and voter2 and voter3 wants to vote for, so there is 2 for votes and 1 against vote, the for voters should win the vote and voting reward should be distributed to the for voters

However, it reaches the MIN_QUORUM of 51% when there is 2 votes, so voter1 voted against and voter2 voted for, then voter3 who want to vote for can't vote and will revert as the vote has ended, but voter3 can change the result of the vote

After voter2 has voted, it reaches MIN_QUORUM and the vote will be ended, and there is 1 for vote and 1 against vote which is equal

if (totalVotesAgainst >= totalVotesFor) {
// proposal creator is trusted to create a proposal from an address
// that can receive ETH. See comment before declaration of `s_creator`
_sendEth(s_creator, totalRewards);
}
// otherwise the proposal passed so distribute rewards to the `For` voters
else {

If the for votes and against votes are equal when it end the vote, it will send the reward to the creator, but there are more voters that want to vote for that the proposal should pass and the reward should be distributed to the for voters instead

PoC

function setUp() public virtual {
// deal this contract the proposal reward
deal(address(this), ETH_REWARD);
// setup the allowed list of voters
voters.push(address(0x1));
voters.push(address(0x2));
voters.push(address(0x3));
// setup contract to be tested
booth = new VotingBooth{value: ETH_REWARD}(voters);
// verify setup
//
// proposal has rewards
assert(address(booth).balance == ETH_REWARD);
// proposal is active
assert(booth.isActive());
// proposal has correct number of allowed voters
assert(booth.getTotalAllowedVoters() == voters.length);
// this contract is the creator
assert(booth.getCreator() == address(this));
}
// required to receive refund if proposal fails
receive() external payable {}
function testThirdVoterCantVote() public {
uint256 startingAmount = address(0x2).balance;
vm.prank(address(0x1));
booth.vote(false);
vm.prank(address(0x2));
booth.vote(true);
vm.prank(address(0x3));
try booth.vote(true) {} catch {
console.log("3rd voter can't vote");
}
assert(!booth.isActive());
assert(address(0x2).balance > startingAmount);
}

Impact

The vote can have a wrong result and proposal supposed to pass can be defeated

Tools Used

Manual review

Recommendations

Change the vote() function so it won't end early when there is an even number of votes

Updates

Lead Judging Commences

0xnevi Lead Judge over 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

VotingBooth.vote: In certain scenarios, proposal can pass when for and against votes are equal

0xnevi Lead Judge
over 1 year ago
0xnevi Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.