President Elector

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

Recursive through `s_candidateList` array to select the next president in `RankedChoice::selectPresident` is a potential Denial of Service (DoS) attack, incrementing gas costs for voting ending.

Summary

The RankedChoice::selectPresident function use recursion to loop through the s_candidateList array to select the new president. However, the longer is s_candidateList array is, the more checks a new candidate will have to make. This means the gas costs for select new president when the voting end will be dramatically quite high. Every ordered candidate list of voters will have to make.

Vulnerability Details

Proof of Concept:

  1. We have minus 44 voters and each of them has 10 difference ordered candidates.

  2. After the voting period, anyone want to select new president that will not be able to call the selectPresident because OutOfGas error.

Proof of Code:

function selectPresident() external {
...
for (uint256 i = 0; i < VOTERS.length; i++) {
address[] memory orderedCandidates = s_rankings[VOTERS[i]][
s_voteNumber
];
for (uint256 j = 0; j < orderedCandidates.length; j++) {
if (!_isInArray(s_candidateList, orderedCandidates[j])) {
@> s_candidateList.push(orderedCandidates[j]);
}
}
}
@> address[] memory winnerList = _selectPresidentRecursive(
@> s_candidateList,
0
);
...
}
function _selectPresidentRecursive(
address[] memory candidateList,
uint256 roundNumber
) internal returns (address[] memory) {
if (candidateList.length == 1) {
return candidateList;
}
// Tally up the picks
for (uint256 i = 0; i < VOTERS.length; i++) {
for (
uint256 j = 0;
j < s_rankings[VOTERS[i]][s_voteNumber].length;
j++
) {
address candidate = s_rankings[VOTERS[i]][s_voteNumber][j];
@> if (_isInArray(candidateList, candidate)) {
s_candidateVotesByRound[candidate][s_voteNumber][
roundNumber
] += 1;
break;
} else {
continue;
}
}
}
// Remove the lowest candidate or break
address fewestVotesCandidate = candidateList[0];
uint256 fewestVotes = s_candidateVotesByRound[fewestVotesCandidate][
s_voteNumber
][roundNumber];
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];
}
}
address[] memory newCandidateList = new address[](
candidateList.length - 1
);
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);
}

Testing function:

function test_BreakVotingSystemByOutOfGas(int numberOfVoters) public {
numberOfVoters = int(bound(uint256(numberOfVoters), 0, MAX_VOTERS));
for (uint256 i = 0; i < uint256(numberOfVoters); i++) {
address voter = address(uint160(i + VOTERS_ADDRESS_MODIFIER));
vm.prank(voter);
orderedCandidates = new address[](0);
for (uint256 j = 0; j < MAX_CANDIDATES; j++) {
orderedCandidates.push(
address(uint160(j + CANDIDATES_ADDRESS_MODIFIER * i))
);
}
rankedChoice.rankCandidates(orderedCandidates);
}
vm.warp(block.timestamp + rankedChoice.getDuration());
uint256 gasStart = gasleft();
rankedChoice.selectPresident();
uint256 gasEnd = gasleft();
uint256 gasUsed = (gasStart - gasEnd) * tx.gasprice;
console.log("Gas cost : ", gasUsed);
console.log(rankedChoice.getCurrentPresident());
}

Impact

The RankedChoice contract will be broken and the new president can't be selected forever.

Tools Used

Foundry

Recommendation

There are many solutions to solve this problems. However, i suggest an easiest practice that make the list of potential president for voter selecting.

Updates

Lead Judging Commences

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

A high number of candidates could cause an OOG

Support

FAQs

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