President Elector

First Flight #24
Beginner FriendlyFoundry
100 EXP
View results
Submission Details
Severity: high
Invalid

Lack of Validation in rankCandidates() Allows Duplicate Candidates and Empty Votes

Summary

The rankCandidates() function in the RankedChoice contract does not include necessary validation to prevent duplicate candidates in a voter's ranked list or the submission of empty votes. These two vulnerabilities can result in inaccurate vote tallies and potential manipulation of the voting process, undermining the integrity of the election.

Vulnerability Details

  1. Duplicate Candidates: The rankCandidates() function allows voters to submit duplicate addresses in the list of ranked candidates. This can lead to vote inflation for certain candidates, as a voter could rank the same candidate multiple times, giving them an unfair advantage. The lack of a mechanism to detect and prevent duplicates compromises the fairness of the ranked-choice voting system.

  2. Empty Vote: The protocol currently allows a voter to submit an empty list of candidates, meaning that a vote can be submitted with no candidates ranked. This does not contribute to the election process and can create discrepancies in the vote counting mechanism, potentially leading to an incorrect outcome or wasted gas.

Example Scenarios

  1. Duplicate Candidates Scenario:

    • Initial Setup: A voter submits the following ranked list: [Candidate1, Candidate2, Candidate2].

    • Execution: The contract allows the vote to be cast without reverting or validating that each candidate in the list is unique.

    • Outcome: Candidate2 receives an unfair advantage by being ranked twice in the same vote, distorting the election results.

  2. Empty Vote Scenario:

    • Initial Setup: A voter submits an empty list [].

    • Execution: The contract allows the empty vote to be submitted, which provides no valid input for the election process.

    • Outcome: The vote is recorded without contributing to the election, potentially wasting resources and gas fees.

Impact

  • Duplicate Candidates:

    • Allows voters to unfairly rank the same candidate multiple times, resulting in an inflated number of votes for that candidate.

    • Compromises the integrity of the ranked-choice voting system by allowing vote manipulation.

  • Empty Vote:

    • Allows submission of meaningless votes that do not contribute to the election process.

    • May cause unnecessary gas consumption and confusion in the vote tallying process, resulting in wasted resources.

PoC:

function invariant_testDublicateVote() public {
// Setup ordered candidates with a duplicate (for this test, we take first 3 candidates)
orderedCandidates = new address[](3);
orderedCandidates[0] = candidates[0];
orderedCandidates[1] = candidates[1];
orderedCandidates[2] = candidates[1]; // duplicate candidate
// Simulate a valid voter trying to rank duplicate candidates
address validVoter = voters[0];
vm.prank(validVoter);
// Currently, we allow duplicate candidates since there is no revert for duplicates in the contract
rankedChoice.rankCandidates(orderedCandidates);
console.log("Valid voter ranked candidates with duplicates");
// For completeness, let's ensure all valid voters can rank candidates
(even with duplicates)
for (uint256 i = 0; i < voters.length; i++) {
// Simulate call from each valid voter
vm.prank(voters[i]);
rankedChoice.rankCandidates(orderedCandidates); // Should not revert as duplicate checking isn't implemented yet
console.log("Voter ", i, " successfully ranked candidates,
even with duplicates.");
}
}
function testEmptyVote() public {
// print orderedCandidates array length
console.log("Length of orderedCandidates before voting: ",
orderedCandidates.length);
// Prank voter 0 to attempt to rank an empty candidate list
vm.prank(voters[0]);
rankedChoice.rankCandidates(orderedCandidates);
// print orderedCandidates array length
console.log("Length of orderedCandidates after voting: ",
orderedCandidates.length);
}

Tools Used

  • Manual review

  • Testing with unit tests and stateful fuzzing.

Recommendations

Add Validation for Duplicate Candidates:
The contract should enforce uniqueness in the orderedCandidates array to ensure that no candidate is ranked more than once. Implement a validation check in _rankCandidates that reverts the transaction if duplicates are detected.

// New validation to prevent duplicates
for (uint256 i = 0; i < orderedCandidates.length; i++) {
for (uint256 j = i + 1; j < orderedCandidates.length; j++) {
if (orderedCandidates[i] == orderedCandidates[j]) {
revert RankedChoice__InvalidInput("Duplicate candidate detected"); // Duplicates detected
}
}
}

Add Validation for Empty Votes:
The contract should reject votes that contain an empty candidate list by reverting the transaction.

// Validation to prevent empty votes
if (orderedCandidates.length == 0) {
revert RankedChoice__InvalidInput("Empty vote detected"); // Empty vote detected
}

Updates

Lead Judging Commences

inallhonesty Lead Judge 9 months ago
Submission Judgement Published
Validated
Assigned finding tags:

rankCandidates() allows duplicate votes inside the `orderedCandidates` array

Appeal created

inallhonesty Lead Judge 9 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
Assigned finding tags:

rankCandidates() allows duplicate votes inside the `orderedCandidates` array

Support

FAQs

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