Description
As rankCandidates does not check if the orderedCandidates array contains duplicate addresses, this allows for voters to vote for the same candidate up to 10 times.
Impact
It is possible that this is acceptable to the protocol, as a voter may only have 1 candidate that they would like to vote for as president, so they put in this address for each of their available ranked votes.
Proof of Concept
function testCanVoteForAddressMultipleTimes() public {
orderedCandidates = [candidates[0], candidates[1], candidates[0]];
vm.prank(voters[0]);
rankedChoice.rankCandidates(orderedCandidates);
assertEq(rankedChoice.getUserCurrentVote(voters[0]), orderedCandidates);
}
Output:
[427488] MyTest::testCanVoteForAddressMultipleTimes()
├─ [0] VM::prank(0x0000000000000000000000000000000000000064)
│ └─ ← [Return]
├─ [312448] RankedChoice::rankCandidates([0x00000000000000000000000000000000000000C8, 0x00000000000000000000000000000000000000C9, 0x00000000000000000000000000000000000000C8])
│ └─ ← [Stop]
├─ [1918] RankedChoice::getUserCurrentVote(0x0000000000000000000000000000000000000064) [staticcall]
│ └─ ← [Return] [0x00000000000000000000000000000000000000C8, 0x00000000000000000000000000000000000000C9, 0x00000000000000000000000000000000000000C8]
├─ [0] VM::assertEq([0x00000000000000000000000000000000000000C8, 0x00000000000000000000000000000000000000C9, 0x00000000000000000000000000000000000000C8], [0x00000000000000000000000000000000000000C8, 0x00000000000000000000000000000000000000C9, 0x00000000000000000000000000000000000000C8]) [staticcall]
│ └─ ← [Return]
└─ ← [Stop]
Recommendations
Update documentation/known issues if this is acceptable behavior within the protocol, else you can add a check for duplicate addresses within the orderedCandidates array passed to _rankCandidates; also you will need to create a new error like RankedChoice__DuplicateCandidate:
+ error RankedChoice__DuplicateCandidate();
// in `_rankCandidates`
if (orderedCandidates.length > MAX_CANDIDATES) {
revert RankedChoice__InvalidInput();
}
if (!_isInArray(VOTERS, voter)) {
revert RankedChoice__InvalidVoter();
}
// Check for 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__DuplicateCandidate();
+ }
+ }
+ }
// Internal Effects
s_rankings[voter][s_voteNumber] = orderedCandidates;