Summary
A voter can rank the same person multiple times.
Vulnerability Details
In the function _rankCandidates the orderedCandidates array can consists of the same candidate.That means that a voter can call rankCandidates with the 10 rankings(MAX_CANDIDATES) being the same person.There is not a requirement or check to see if the candidates are the same candidate so it passes and does not revert anything. Here is a test:
function testIdiaVotes() public {
console.log("difthinsi 0", address(candidates[0]));
console.log("difthinsi 1", address(candidates[1]));
console.log("difthinsi 2", address(candidates[2]));
console.log("difthinsi 3", address(candidates[3]));
orderedCandidates = [candidates[0], candidates[0], candidates[0]];
uint256 startingIndex = 0;
uint256 endIndex = 10;
uint256 votes1 = 0;
for (uint256 i = startingIndex; i < endIndex; i++) {
vm.prank(voters[i]);
choice.rankCandidates(orderedCandidates);
votes1++;
}
uint256 startingIndex1 = endIndex + 1;
uint256 endIndex1 = 16;
uint256 votes2 = 0;
orderedCandidates = [candidates[3], candidates[2], candidates[1]];
for (uint256 j = startingIndex1; j < endIndex1; j++) {
vm.prank(voters[j]);
choice.rankCandidates(orderedCandidates);
votes2++;
}
uint256 startingIndex2 = endIndex1 + 1;
uint256 endIndex2 = 22;
uint256 votes3 = 0;
orderedCandidates = [candidates[3], candidates[2], candidates[5]];
for (uint256 j = startingIndex2; j < endIndex2; j++) {
vm.prank(voters[j]);
choice.rankCandidates(orderedCandidates);
votes3++;
}
console.log("Number of votes", votes1);
console.log("Number of votes2", votes2);
console.log("Number of votes3", votes3);
vm.warp(block.timestamp + 1460 days);
choice.selectPresident();
choice.getCurrentPresident();
}
Foundry Test results:
RankedChoice::getCurrentPresident() [staticcall]:br│ └─ ← [Return] 0x1efF47bc3a10a45D4B230B5d10E37751FE6AA718:br└─ ← [Stop]
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 100.85ms (44.23ms CPU time)
Ran 1 test suite in 806.26ms (100.85ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)
Impact
This vulnerability can impact the voting logic of the contract giving false voting results in the end.It also imppacts trust in the voting system.
Tools Used
Manually/Foudnry
Recommendations
I would recommend changing the _rankCandidates function to include a check to ensure all the candidates are unique. For example you could add this:
\
function _rankCandidates(
address[] memory orderedCandidates,
address voter
) internal {
if (orderedCandidates.length > MAX_CANDIDATES) {
revert RankedChoice__InvalidInput();
}
if (!_isInArray(VOTERS, voter)) {
revert RankedChoice__InvalidVoter();
}
+for (uint256 i = 0; i < orderedCandidates.length; i++) {
+ for (uint256 j = i + 1; j < orderedCandidates.length; j++) {
+ if (orderedCandidates[i] == orderedCandidates[j]) {
+ revert("Duplicate candidate");
+ }
+}
+ }
s_rankings[voter][s_voteNumber] = orderedCandidates;
}
}