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

# Wrong formula in `_distributeRewards()` function causes some amount to be left in the contract

Summary

If the 51% quorum is reached and the for voters are more than the against voters, the contract balance should be sent to the for voters.

Vulnerability Details

The vulnerability in the contract is because of the following calculation in the _distributedRewards() function:

uint256 rewardPerVoter = totalRewards / totalVotes;
....
_sendEth(s_votersFor[i], rewardPerVoter);

This formula calculates the rewardPerVoter based on the totalVotes. But in case the for voters win, the rewardPerVoter is sent only to the for voters. Remaining amount is left in the VotingBooth contract. This can happen if majority (>50%) of the voters vote for and 1 or more voters vote against.

Impact

In the above scenario, some of the balance remains in the contract and for voters receive less than the expected amount.

Below is the statefulFuzz test to prove the vulnerability:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;
import {VotingBooth} from "../src/VotingBooth.sol";
import {Test, console, console2} from "forge-std/Test.sol";
import {StdInvariant} from "forge-std/StdInvariant.sol";
contract Invariant is StdInvariant, Test {
uint256 constant ETH_REWARD = 10e18;
address[] public voters = [ address(0x1), address(0x2), address(0x3), address(0x4), address(0x5), address(0x6), address(0x7), address(0x8), address(0x9)];
VotingBooth votingBooth;
function setUp() public {
votingBooth = new VotingBooth{value: ETH_REWARD}(voters);
for (uint256 i = 0; i < voters.length; i++) {
targetSender(voters[i]);
}
targetContract(address(votingBooth));
}
function statefulFuzz_CheckIfVotingBalanceAlwaysZero() public view {
if (!votingBooth.isActive()) {
console.log("Voting is completed!");
console.log(address(votingBooth).balance);
assert((address(votingBooth).balance) == 0);
}
}
}

Foundry.toml:

[fuzz]
runs = 100
seed = "0x1"
[invariant]
runs = 20
depth = 10
fail_on_revert = false

As it can be seen in the output of the test, when there is one or more against votes there is some amount remaining in the contract balance:

$forge test --mt statefulFuzz_CheckIfVotingBalanceAlwaysZero -vv
[⠰] Compiling...
No files changed, compilation skipped
Running 1 test for test/Invariant.t.sol:Invariant
[FAIL. Reason: panic: assertion failed (0x01)]
[Sequence]
sender=0x0000000000000000000000000000000000000006 addr=[src/VotingBooth.sol:VotingBooth]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f calldata=vote(bool) args=[false]
sender=0x0000000000000000000000000000000000000002 addr=[src/VotingBooth.sol:VotingBooth]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f calldata=vote(bool) args=[true]
sender=0x0000000000000000000000000000000000000003 addr=[src/VotingBooth.sol:VotingBooth]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f calldata=vote(bool) args=[true]
sender=0x0000000000000000000000000000000000000004 addr=[src/VotingBooth.sol:VotingBooth]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f calldata=vote(bool) args=[true]
sender=0x0000000000000000000000000000000000000005 addr=[src/VotingBooth.sol:VotingBooth]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f calldata=vote(bool) args=[false]
statefulFuzz_CheckIfVotingBalanceAlwaysZero() (runs: 20, calls: 199, reverts: 100)
Logs:
Voting is completed!
4000000000000000000
Test result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 29.84ms
Ran 1 test suites: 0 tests passed, 1 failed, 0 skipped (1 total tests)
Failing tests:
Encountered 1 failing test in test/Invariant.t.sol:Invariant
[FAIL. Reason: panic: assertion failed (0x01)]
[Sequence]
sender=0x0000000000000000000000000000000000000006 addr=[src/VotingBooth.sol:VotingBooth]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f calldata=vote(bool) args=[false]
sender=0x0000000000000000000000000000000000000002 addr=[src/VotingBooth.sol:VotingBooth]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f calldata=vote(bool) args=[true]
sender=0x0000000000000000000000000000000000000003 addr=[src/VotingBooth.sol:VotingBooth]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f calldata=vote(bool) args=[true]
sender=0x0000000000000000000000000000000000000004 addr=[src/VotingBooth.sol:VotingBooth]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f calldata=vote(bool) args=[true]
sender=0x0000000000000000000000000000000000000005 addr=[src/VotingBooth.sol:VotingBooth]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f calldata=vote(bool) args=[false]
statefulFuzz_CheckIfVotingBalanceAlwaysZero() (runs: 20, calls: 199, reverts: 100)
Encountered a total of 1 failing tests, 0 tests succeeded

Tools Used

  • manual review

  • foundry

Recommendations

uint256 rewardPerVoter = totalRewards / totalVotes;
totalVotes can be replaced with totalVotesFor

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.