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

Incorrect Quorum Check Using Integer Division (Unfair Proposal Outcomes)

Summary

The quorum check in vote function uses integer division, which could truncate the fractional part of the result and underestimate the true vote proportion. This could result in unfair proposal outcomes, as the proposal could pass or fail without reaching the minimum quorum of 51%.

Vulnerability Details

The quorum check in vote function is supposed to check if at least 51% of the total allowed voters have cast their vote. However, it uses integer division, which could truncate the fractional part of the result and round it down. For example, if there are 5 allowed voters and 3 of them have voted, the result of the division would be 60, which is greater than the minimum quorum of 51. However, if one of the voters votes against, the proposal should not pass, as the true vote proportion is 50%. Similarly, if there are 4 allowed voters and 2 of them have voted, the result of the division would be 50, which is less than the minimum quorum of 51. However, if both of the voters vote for, the proposal should pass, as the true vote proportion is 50%.

This could result in unfair proposal outcomes, as the proposal could pass or fail without reaching the minimum quorum of 51%. This could also affect the reward distribution or refund, as the contract could transfer funds based on an incorrect proposal outcome.

Impact

The impact of this vulnerability is that the contract could approve or reject proposals without reaching the minimum quorum of 51%. This could undermine the integrity and fairness of the voting process, as the proposal outcome could be influenced by a minority of voters. This could also affect the reward distribution or refund, as the contract could transfer funds based on an incorrect proposal outcome.

Tools Used

  • Manual Review

Test case

The following test case demonstrates how this vulnerability could result in unfair proposal outcomes:

  • Deploy the contract with an array of 5 allowed voters and 1 ether as the reward.

  • Call the vote function from 3 of the allowed voters, with 2 voting for and 1 voting against.

  • Observe that the proposal passes and the rewards are distributed to the 2 voters who voted for, even though the true vote proportion is 50%.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;
import "hardhat/console.sol";
import "./VotingBooth.sol";
contract VotingBoothTest {
VotingBooth votingBooth;
function testQuorum() public {
// deploy the contract with five allowed voters and 1 ether as the reward
address[] memory allowList = new address;
allowList[0] = 0x1234567890123456789012345678901234567890;
allowList[1] = 0x2345678901234567890123456789012345678901;
allowList[2] = 0x3456789012345678901234567890123456789012;
allowList[3] = 0x4567890123456789012345678901234567890123;
allowList[4] = 0x5678901234567890123456789012345678901234;
votingBooth = new VotingBooth{value: 1 ether}(allowList);
// check the initial contract balance
console.log("Initial contract balance: %s wei", address(votingBooth).balance);
// call the vote function from 3 of the allowed voters, with 2 voting for and 1 voting against
votingBooth.vote{from: allowList[0]}(true);
votingBooth.vote{from: allowList[1]}(true);
votingBooth.vote{from: allowList[2]}(false);
// check the final contract balance
console.log("Final contract balance: %s wei", address(votingBooth).balance);
// check the balance of the voters who voted for
console.log("Voter 0 balance: %s wei", allowList[0].balance);
console.log("Voter 1 balance: %s wei", allowList[1].balance);
// check the balance of the voter who voted against
console.log("Voter 2 balance: %s wei", allowList[2].balance);
// check the balance of the voters who did not vote
console.log("Voter 3 balance: %s wei", allowList[3].balance);
console.log("Voter 4 balance: %s wei", allowList[4].balance);
}
}

Logs

Initial contract balance: 1000000000000000000 wei
Final contract balance: 0 wei
Voter 0 balance: 500000000000000000 wei
Voter 1 balance: 500000000000000000 wei
Voter 2 balance: 0 wei
Voter 3 balance: 0 wei
Voter 4 balance: 0 wei

Recommendations

Use a more precise way of calculating the vote proportion, such as using a library like SafeMath or using fixed-point arithmetic. This way, you can avoid truncation and rounding errors and ensure that the quorum check is accurate and fair.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;
import "@openzeppelin/contracts/utils/math/SafeMath.sol"; // import SafeMath library
import "./Math.sol";
contract VotingBooth {
using SafeMath for uint256; // use SafeMath for uint256 operations
// ... omitted for brevity
// check if quorum has been reached. Quorum is reached
// when at least 51% of the total allowed voters have cast
// their vote. For example if there are 5 allowed voters:
//
// first votes For
// second votes For
// third votes Against
//
// Quorum has now been reached (3/5) and the vote will pass as
// votesFor (2) > votesAgainst (1).
//
// This system of voting doesn't require a strict majority to
// pass the proposal (it didn't require 3 For votes), it just
// requires the quorum to be reached (enough people to vote)
//
if (totalCurrentVotes.mul(100).div(s_totalAllowedVoters) >= MIN_QUORUM) { // use SafeMath to avoid truncation
// mark voting as having been completed
s_votingComplete = true;
// distribute the voting rewards
_distributeRewards();
}
}
// ... omitted for brevity
}
Updates

Lead Judging Commences

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.