President Elector

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

Immediate Election Exploit Allows First Voter to Unilaterally Elect President and Lock Contract

Summary

The selectPresident function can be called immediately after contract deployment without any initial time lock. This allows the first voter to submit a single vote and immediately call selectPresident, effectively electing a president of their choice without participation from other voters. Consequently, the contract becomes locked for the entire presidential term, preventing a fair election process.

Vulnerability Details

  • Affected Function: selectPresident

  • Location in Code:

    function selectPresident() external {
    //@audit after launch just make one vote from a voter and declare president as there is no lock initially.
    // now the first voter can make president of his choice.
    if (
    block.timestamp - s_previousVoteEndTimeStamp <=
    i_presidentalDuration
    ) {
    revert RankedChoice__NotTimeToVote(); // Info Need more correct Error message like NotTimeToSelect
    }
    // ... rest of the function ...
    }
  • Issue Explanation:

    • No Initial Time Constraint: At deployment, s_previousVoteEndTimeStamp is uninitialized (defaulting to zero). This means the condition in the if statement:

      if (block.timestamp - s_previousVoteEndTimeStamp <= i_presidentalDuration)

      evaluates to block.timestamp - 0 <= i_presidentalDuration, which is always true for a long duration (e.g., 4 years).

    • Immediate Election Possible: Since the revert statement triggers when the condition is true, and given the initial state, the revert does not execute, allowing anyone to call selectPresident immediately after deployment.

    • First Voter Advantage: The first voter can submit their vote and call selectPresident without waiting for other voters, unilaterally deciding the president.

    • Contract Lock-In: After selectPresident is called, s_previousVoteEndTimeStamp is updated to the current block.timestamp, and s_voteNumber increments. However, due to the condition logic, subsequent calls to selectPresident will revert until i_presidentalDuration has passed, effectively locking the contract for that duration.

Impact

Severity: High

  • Unfair Election: The election process can be hijacked by a single voter, undermining the democratic intent of the voting system.

  • Contract Lockdown: The contract becomes unusable for the duration of the presidential term, preventing other voters from participating in future elections until the term expires.

  • Governance Manipulation: The first voter gains disproportionate control over the election outcome and the governance process.

Tools Used

  • Manual Code Review: Analyzed the initialization of s_previousVoteEndTimeStamp and the condition in selectPresident.

Recommendations

  • Initialize s_previousVoteEndTimeStamp to Current block.timestamp:

    Set s_previousVoteEndTimeStamp to block.timestamp in the constructor to ensure the initial time lock is enforced.

    constructor(address[] memory voters) EIP712("RankedChoice", "1") {
    VOTERS = voters;
    i_presidentalDuration = 1460 days;
    s_currentPresident = msg.sender;
    s_previousVoteEndTimeStamp = block.timestamp; // Initialize to current time
    s_voteNumber = 0;
    }
  • Correct the Conditional Logic:

    Ensure that the selectPresident function properly checks whether the presidential duration has passed before allowing a new election.

    function selectPresident() external {
    if (block.timestamp - s_previousVoteEndTimeStamp < i_presidentalDuration) {
    revert RankedChoice__NotTimeToSelect();
    }
    // ... rest of the function ...
    }
  • Improve Error Messaging:

    Update the error message to accurately reflect the issue.

    error RankedChoice__NotTimeToSelect();
  • Implement a Voting Period Mechanism:

    Introduce a defined voting period during which votes can be cast before selectPresident can be called.

    uint256 private i_votingDuration = 7 days;
    uint256 private s_votingStartTime;
    function startVotingPeriod() external onlyAuthorized {
    s_votingStartTime = block.timestamp;
    }
    function selectPresident() external {
    if (block.timestamp < s_votingStartTime + i_votingDuration) {
    revert RankedChoice__VotingPeriodOngoing();
    }
    if (block.timestamp - s_previousVoteEndTimeStamp < i_presidentalDuration) {
    revert RankedChoice__NotTimeToSelect();
    }
    // ... rest of the function ...
    }
  • Restrict selectPresident Access:

    Limit the ability to call selectPresident to authorized entities or through a consensus mechanism to prevent unilateral action.

  • Require Minimum Participation:

    Enforce a minimum number of votes before the president can be selected.

    uint256 private constant MIN_VOTES_REQUIRED = 5;
    function selectPresident() external {
    if (getTotalVotes() < MIN_VOTES_REQUIRED) {
    revert RankedChoice__NotEnoughVotes();
    }
    // ... rest of the function ...
    }
    function getTotalVotes() internal view returns (uint256) {
    uint256 totalVotes = 0;
    for (uint256 i = 0; i < VOTERS.length; i++) {
    if (s_rankings[VOTERS[i]][s_voteNumber].length > 0) {
    totalVotes++;
    }
    }
    return totalVotes;
    }
  • Prevent Contract Locking:

    Allow for new elections if the previous one was invalid or did not meet certain criteria (e.g., participation threshold).

Updates

Lead Judging Commences

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

`s_previousVoteEndTimeStamp` variable not being initialized correctly

Support

FAQs

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