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

Reward definition is inconsistent, can lead to confusion

Summary

The definition of a reward for users of a passing vote or, alternatively, for the creator in case of a rejected vote, is inconsistent. The two rewards are represented by significantly different amounts, although definitions and comments in the contract might lead to think that they should represent the same sum. This leads to confusion on how rewarding it can be to participate in a winning vote.

Vulnerability Details

There are several occasions where the wording of description and comments leads to think that the "reward" is a specific sum, both for the creator in a rejected vote, and split among "for" voters in a passing vote.
First example:

// This contract allows the creator to invite a select group of people
// to vote on something and provides an eth reward to the `for` voters
// if the proposal passes, otherwise refunds the reward to the creator.

Second example:

// distributes rewards to the `for` voters if the proposal has
// passed or refunds the rewards back to the creator if the proposal
// failed

However, the reward is not the same amount.
The reward refunded to the creator is the balance of the contract, as clearly shown in this snapshot of the code:

uint256 totalRewards = address(this).balance;
// if the proposal was defeated refund reward back to the creator
// for the proposal to be successful it must have had more `For` votes
// than `Against` votes
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);
}

The reward divided between for voters, however, is less than that, as the computation divides it by the number of totalVotes, instead of just the number of passing votes, as shown here:

else {
uint256 rewardPerVoter = totalRewards / totalVotes;
for (uint256 i; i < totalVotesFor; ++i) {
if (i == totalVotesFor - 1) {
rewardPerVoter = Math.mulDiv(totalRewards, 1, totalVotes, Math.Rounding.Ceil);
}
_sendEth(s_votersFor[i], rewardPerVoter);
}

As a practical example, let's suppose a scenario with:

  • five allowed voters;

  • a total reward of 100 ETH.
    If the vote passes (thus, 2 "for" votes and one against, or three "for" votes), each rewardPerVoter will be 20 ETH (100/5). The amount sent out will be 20*2 or 20*3, for a total ranging between 40 and 60 ETH.
    However, if the vote is rejected, the reward sent out is the full 100 ETH.

Impact

There is a lack of clarity in the documentation regarding the behavior of this "reward" mechanism.

Tools Used

Manual review.

Recommendations

A better documentation can clearly set users expectations when taking part in the voting system. If documentation is correct, and the reward to send out should always be the same amount, then it is the code that needs fixing. In this case, dividing the total reward by the amount of "for" voters would compute the correct share to send out to each "for" voter.

Updates

Lead Judging Commences

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

VotingBooth._distributeRewards(): Incorrect computation of rewardPerVoter

Support

FAQs

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