President Elector

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

_selectPresidentRecursive does not check ties

Summary

The _selectPresidentRecursive() function in the RankedChoice contract is responsible for eliminating the lowest-voted candidate in each round of voting. However, the function does not account for ties in the vote count. In case of a tie between multiple candidates with the lowest votes, the function may arbitrarily eliminate one of the candidates, which could lead to unfair outcomes in the election.

Vulnerability Details

The vulnerability lies in how the _selectPresidentRecursive() function handles the elimination process. Currently, the function selects the candidate with the fewest votes for removal without checking if multiple candidates share the same lowest vote count. If multiple candidates are tied for the lowest votes, the contract removes one of them without any tie-breaking mechanism. This could result in an unfair advantage or disadvantage for the tied candidates.

Here’s how the issue occurs:

  1. The function iterates through the list of candidates and compares their vote counts.

  2. It identifies the candidate with the fewest votes and selects them for elimination.

  3. If two or more candidates have the same (lowest) vote count, the function still only eliminates one of them, potentially arbitrarily, without resolving the tie.

Since there is no logic to handle ties, this creates a fairness issue where tied candidates are not treated equally in the elimination process.

Impact

The absence of tie-breaking logic can lead to the following impacts:

  • Unfair Candidate Elimination: Tied candidates may not have equal chances of advancing to the next round. One candidate could be eliminated without a fair reason.

  • Election Manipulation: This vulnerability could be exploited to influence election results by strategically creating ties, leading to arbitrary eliminations that favor certain candidates.

  • Integrity Risk: The lack of a clear tie-breaking mechanism can undermine the transparency and fairness of the voting process, potentially damaging trust in the election system.

Tools Used

Recursive Functions: The issue lies in the recursive structure of the function that eliminates candidates without checking for ties.

Recommendations

Add Tie-Breaking Logic: Modify the _selectPresidentRecursive() function to handle ties explicitly. If two or more candidates have the same number of votes, the contract should implement a fair method for deciding which candidate to eliminate. For example, you could:

  • Implement a random selection mechanism in case of a tie.

  • Use additional criteria, such as voter ranking in earlier rounds, to break ties.

  • Example of Handling Ties:

    function _selectPresidentRecursive(
    address[] memory candidateList,
    uint256 roundNumber
    ) internal returns (address[] memory) {
    if (candidateList.length == 1) {
    return candidateList;
    }
    // Track the lowest vote count and tie candidates
    address fewestVotesCandidate = candidateList[0];
    uint256 fewestVotes = s_candidateVotesByRound[fewestVotesCandidate][s_voteNumber][roundNumber];
    address[] memory tiedCandidates;
    uint256 tieCount = 0;
    // Find candidates with the fewest votes
    for (uint256 i = 1; i < candidateList.length; i++) {
    uint256 votes = s_candidateVotesByRound[candidateList[i]][s_voteNumber][roundNumber];
    if (votes < fewestVotes) {
    fewestVotes = votes;
    fewestVotesCandidate = candidateList[i];
    tiedCandidates = new address ;
    tiedCandidates[0] = candidateList[i];
    tieCount = 1;
    } else if (votes == fewestVotes) {
    tieCount++;
    address[] memory temp = new address[]();
    for (uint256 j = 0; j < tieCount - 1; j++) {
    temp[j] = tiedCandidates[j];
    }
    temp[tieCount - 1] = candidateList[i];
    tiedCandidates = temp;
    }
    }
    // If there's a tie, use tie-breaking logic (e.g., random elimination or additional criteria)
    if (tieCount > 1) {
    // Implement fair tie-breaking logic here
    fewestVotesCandidate = _breakTie(tiedCandidates);
    }
    // Remove the fewestVotesCandidate from the list and proceed
    address[] memory newCandidateList = new address[]();
    bool passedCandidate = false;
    for (uint256 i; i < candidateList.length; i++) {
    if (passedCandidate) {
    newCandidateList[i - 1] = candidateList[i];
    } else if (candidateList[i] == fewestVotesCandidate) {
    passedCandidate = true;
    } else {
    newCandidateList[i] = candidateList[i];
    }
    }
    return _selectPresidentRecursive(newCandidateList, roundNumber + 1);
    // Example tie-breaking function (could use randomness or anything you want)
    function _breakTie(address[] memory tiedCandidates) internal returns (address) {
    // Implement tie-breaking logic, e.g., pseudo-random selection
    uint256 randomIndex = uint256(keccak256(abi.encodePacked(block.timestamp, block.difficulty))) % tiedCandidates.length;
    return tiedCandidates[randomIndex];
    }
Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Known issue

Support

FAQs

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