Summary
In the function announceWinner() external onlyOwner, the variable winnerTokenId is initialized to 0. So, when nobody votes, the producer that lists tokenId = 0 automatically gets the reward of 1 health token.
Vulnerability Details
When voting is open and at the end of the period, nobody votes, the reward is given to the producer that lists the NFT with tokenid = 0.
POC
Add this function in the smart contract MartenitsaVoting and run the code:
forge test --mt testNovoteAnnounceWinner
function testNovoteAnnounceWinner() public listMartenitsa {
vm.warp(block.timestamp + 1 days + 1);
vm.recordLogs();
voting.announceWinner();
Vm.Log[] memory entries = vm.getRecordedLogs();
address winner = address(uint160(uint256(entries[0].topics[2])));
assert(winner == chasy);
}
We get the output:
Ran 1 test for test/MartenitsaVoting.t.sol:MartenitsaVoting
[PASS] testNovoteAnnounceWinner() (gas: 410532)
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 7.02ms (422.92µs CPU time)
Impact
A producer that lists NFT TokenId = 0 can get a reward that he shouldn't.
Tools Used
Manual Review
Recommendations
The function announceWinner() should revert if there are no votes:
function announceWinner() external onlyOwner {
+ require(_tokenIds.length > 0 "There is no vote ");
require(block.timestamp >= startVoteTime + duration, "The voting is active");
uint256 winnerTokenId;
uint256 maxVotes = 0;
for (uint256 i = 0; i < _tokenIds.length; i++) {
if (voteCounts[_tokenIds[i]] > maxVotes) {
maxVotes = voteCounts[_tokenIds[i]];
winnerTokenId = _tokenIds[i];
}
}
list = _martenitsaMarketplace.getListing(winnerTokenId);
_healthToken.distributeHealthToken(list.seller, 1);
emit WinnerAnnounced(winnerTokenId, list.seller);
}