President Elector

First Flight #24
Beginner FriendlyFoundry
100 EXP
View results
Submission Details
Severity: medium
Valid

Voter Can Accidentally Overwrite Previous Vote, Potentially Resulting in Unintended Candidate Changes

Summary

Although each voter is intended to cast only one vote, the lack of checks to enforce this can lead to accidental overwriting of their previous choice. This unintended overwrite may affect the voting outcome by altering the recorded vote in ways not intended, potentially causing inaccuracies in the final results.

Vulnerability Details

The RankedChoice::rankCandidates and RankedChoice::rankCandidatesBySig function does not verify if a voter has already cast a vote. As a result, a voter could accidentally vote again, potentially altering their vote to different candidates than their original choice.

...
function rankCandidates(address[] memory orderedCandidates) external {
// No check implemented to verify whether a voter has already voted
_rankCandidates(orderedCandidates, msg.sender);
}
function rankCandidatesBySig(address[] memory orderedCandidates, bytes memory signature)
external
{
bytes32 structHash = keccak256(abi.encode(TYPEHASH, orderedCandidates));
bytes32 hash = _hashTypedDataV4(structHash);
address signer = ECDSA.recover(hash, signature);
// No check implemented to verify whether a voter has already voted
_rankCandidates(orderedCandidates, signer);
}
...

Impact

Voters can accidentally overwrite their previous vote, leading to potential discrepancies in the voting results and inaccuracies in the final tally.

PoC

In the RankedChoiceTest.t.sol file, add the following code:

...
// storage variable
// alice is not a candidate
address alice = makeAddr("alice");
...
function testVoteOverwrite() public {
orderedCandidates = [candidates[0], candidates[1], candidates[2]];
vm.prank(voters[0]);
rankedChoice.rankCandidates(orderedCandidates);
// overwriting vote with different candidates
// vote again
orderedCandidates = [candidates[0], candidates[1], alice];
vm.prank(voters[0]);
rankedChoice.rankCandidates(orderedCandidates);
assertEq(rankedChoice.getUserCurrentVote(voters[0]), orderedCandidates);
}

Tools Used

VS Code, Manual Review

Recommendations

Add a check to ensure that each voter can only vote once.

contract RankedChoice is EIP712{
...
// add a new mapping
+ mapping(address => bool) private hasVoted;
...
function rankCandidates(address[] memory orderedCandidates) external {
// check if address has voted
+ require(!hasVoted[msg.sender], "Address already voted");
_rankCandidates(orderedCandidates, msg.sender);
}
function rankCandidatesBySig( // @note looks OK
address[] memory orderedCandidates, bytes memory signature)
external
{
bytes32 structHash = keccak256(abi.encode(TYPEHASH, orderedCandidates));
bytes32 hash = _hashTypedDataV4(structHash);
address signer = ECDSA.recover(hash, signature);
+ require(!hasVoted[signer], "Address already voted");
_rankCandidates(orderedCandidates, signer);
}
...
}
Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

Voters can change their vote

Support

FAQs

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