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

Incorrect divisor and dust in the contract

Summary

There is a flawed implementation in rewardPerVoter that causes conflicts in many combinations, resulting in a poor distribution of rewards. This is exacerbated a little bit by the use of Math.mulDiv(totalRewards, 1, totalVotes, Math.Rounding.Ceil).

Vulnerability Details

The line 192 is much more severe as it divides the rewards by totalVotes, causing a significant dilution of rewards in most situations where distribution to users is required.

The line 207 is less critical, leaving at least one wei remaining in some combinations. Rounding to a single wei for the last user is sometimes insufficient, breaking the invariant test.

Impact

Funds locked in the contract may become unrecoverable, leading to significant losses.

POC

It may be necessary to make some VotingBooth.sol storage public again.

Handler:

// SPDX-Solidity-Identifier: MIT
pragma solidity 0.8.23;
import {Test, console} from "forge-std/Test.sol";
import {VotingBooth} from "../../src/VotingBooth.sol";
contract Handler is Test {
struct HandlerProps {
VotingBooth votingBooth;
address[] allowList;
}
HandlerProps state;
address[] forVoters;
constructor(HandlerProps memory props){
state.votingBooth = props.votingBooth;
for(uint i = 0; i < props.allowList.length; i++){
state.allowList.push(props.allowList[i]);
}
}
function vote(bool _vote, uint256 _voterSeed) external {
uint index = bound(_voterSeed, 0, state.allowList.length - 1);
address voter = state.allowList[index];
if(state.votingBooth.s_voters(voter) != 1) return;
if(!state.votingBooth.isActive()) return;
if(_vote){
forVoters.push(voter);
}
vm.startPrank(voter);
state.votingBooth.vote(_vote);
vm.stopPrank();
}
//////////////////// HELPERS ////////////////////
function getForVoters(uint256 index) external view returns (address){
return forVoters[index];
}
function getForVotersAmount() external view returns (uint256){
return forVoters.length;
}
}

Invariant:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;
import {Test, StdInvariant, console} from "forge-std/Test.sol";
import {Handler} from "./Handler.t.sol";
import {VotingBooth} from "../../src/VotingBooth.sol";
contract VotingBoothInvariant is StdInvariant, Test {
//-----------------------Strucs-----------------------//
struct SetUpConfig {
address OWNER;
uint256 totalAllowedVoters;
uint256 REWARD;
}
struct VotingBoothProps {
address[] allowList;
}
//-----------------------Props-----------------------//
SetUpConfig config;
VotingBoothProps votingBoothProps;
//-----------------------Handler-----------------------//
Handler handler;
VotingBooth votingBooth;
function setUp() external {
// SetUpConfig
config.OWNER = makeAddr("OWNER");
config.REWARD = 5 ether;
vm.deal(config.OWNER, config.REWARD);
config.totalAllowedVoters = 7;
// Create allowList Array
for(uint256 i = 0; i < config.totalAllowedVoters; i++){
votingBoothProps.allowList.push(address(uint160(i+1)));
}
// Create VotingBooth
vm.startPrank(config.OWNER);
votingBooth = new VotingBooth{value: config.REWARD}(votingBoothProps.allowList);
vm.stopPrank();
// Create Handler Props && TargetContract
Handler.HandlerProps memory handlerProps = Handler.HandlerProps({votingBooth: votingBooth, allowList: votingBoothProps.allowList});
handler = new Handler(handlerProps);
bytes4[] memory selectors = new bytes4[](1);
selectors[0] = handler.vote.selector;
targetSelector(FuzzSelector({ addr: address(handler), selectors: selectors }));
targetContract(address(handler));
}
function invariant_distributedVotingBoothBalance() external {
if(votingBooth.isActive()){
return;
}
assert(address(votingBooth).balance == 0);
uint256 totalForVoters = handler.getForVotersAmount();
if(((config.totalAllowedVoters / 2) + 1) / 2 < totalForVoters){
uint256 distributedReward = config.REWARD / totalForVoters;
for(uint256 i = 0; i < totalForVoters; i++){
address voter = handler.getForVoters(i);
assert(voter.balance >= distributedReward);
}
}
else{
assert(config.OWNER.balance == config.REWARD);
}
}
}

Tools Used

Foundry

Recommendations

- uint256 rewardPerVoter = totalRewards / totalVotes;
+ uint256 rewardPerVoter = totalRewards / totalVotesFor;
- rewardPerVoter = Math.mulDiv(totalRewards, 1, totalVotes, Math.Rounding.Ceil);
+ rewardPerVoter = address(this).balance;
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.