Miscalculation of rewards causes funds to be permanently stuck in the contract.
Documents state that “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.”. However in the contract, when a vote passes, rewards are calculated with this integer uint256 rewardPerVoter = totalRewards / totalVotes;
(Line #192). totalVotes
is calculated with this integer uint256 totalVotes = totalVotesFor + totalVotesAgainst;
(Line #172). This means that the rewards are not shared according to total number of ‘for’ voters.
uint256 rewardPerVoter = totalRewards / totalVotes;
and rewardPerVoter = Math.mulDiv(totalRewards, 1, totalVotes, Math.Rounding.Ceil);
lines of code causes funds to get permanently stuck in the contract if at least one voter votes ‘against’.
For example when vote is passed with 2 ‘for’ voters and 1 ‘against’ voter 1 ETH reward would be split split into 3 due to use dividing with totalVotes
this causes ‘for’ voters to get 0.3333… ETH each while rest of the ETH is stuck in the contract.
Add import "forge-std/console.sol";
into imports in VotingBoothTest.t.sol
Add the following test in VotingBoothTest.t.sol
Run forge test --match-test testVotePassesAndMoneyIsStuckInTheContract -vvvv
in foundry.
Console prints the following lines and proving that after voting ends and rewards are sent to ‘for’ voters, 0.33333 ETH is still stuck in the contract with no way of recovering it.
Every voting that passes that has ‘against’ votes, rewards that ‘for’ voters receive will be less than the total amount that was funded into the contract. With no other way of recovering funds, rest of the rewards will be permanently stuck in the contract.
Foundry and manual review.
I recommend two possible fixes for this vulnerability.
If all the funds are meant to be split equally between ‘for’ voters update the following lines in _distributeRewards()
as shown below:
foundry prints the following lines:
Test proves that with this fix, rewards are split between ‘for’ voters and funds do not get stuck in the contract.
If the intended implementation is that rewards of ‘for’ voters should be divided based on the total amount of votes, update the following lines in _distributeRewards()
as shown below:
_sendEth(s_creator, address(this).balance);
line that is added will make sure that funds that are left after rewards are distributed will be sent back to the creator.
Running this test prints the following lines:
This proves that rewards are split between ‘for’ voters and remaining funds are sent to the creator.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.