Beginner FriendlyFoundryGameFi
100 EXP
View results
Submission Details
Severity: high
Invalid

DoS in `MartenitsaVoting::voteForMartenitsa` function, causing the users to be unable to vote.

Description

In the MartenitsaVoting::voteForMartenitsa function, the hasVoted mapping is used to track whether a user has already voted in a given poll. If a user votes, the hasVoted mapping will be set to true for each vote, preventing the user from voting again. This can lead to a denial-of-service (DoS) as the hasVoted mapping is not reseted after a voting is ended and the MartenitsaVoting::announceWinner function is called to announce the winner of the voting, causing the users to be unable to vote in future polls.

function voteForMartenitsa(uint256 tokenId) external {
require(!hasVoted[msg.sender], "You have already voted");
require(block.timestamp < startVoteTime + duration, "The voting is no longer active");
list = _martenitsaMarketplace.getListing(tokenId);
require(list.forSale, "You are unable to vote for this martenitsa");
@> hasVoted[msg.sender] = true;
voteCounts[tokenId] += 1;
_tokenIds.push(tokenId);
}

Impact

The denial-of-service (DoS) vulnerability in the MartenitsaVoting::voteForMartenitsa function can prevent users from voting in future polls. This can lead to a loss of user engagement and participation in the voting process, causing no one to be able to vote in future polls.

Proof of Concept

  1. A user votes for a martenitsaToken in the voting contract.

  2. The user's hasVoted mapping is set to true, preventing them from voting again.

  3. Voting is ended and the MartenitsaVoting::announceWinner function is called to announce the winner of the voting.

  4. New voting is started and the user tries to vote again, but the hasVoted mapping is set to true causing the user to be unable to vote.

Proof Of Code:

function test_VoteForMartenitsaCausesDoS() public {
// Round 1 start voting
vm.warp(block.timestamp);
voting.startVoting();
// create users
address[] memory users1 = new address[](5);
for (uint256 i = 0; i < users1.length; i++) {
string memory addr1 = string(abi.encodePacked(i));
users1[i] = makeAddr(addr1);
}
// create martenitsa and list it for sale
vm.startPrank(chasy);
for (uint256 i = 0; i < users1.length; i++) {
martenitsaToken.createMartenitsa("bracelet");
marketplace.listMartenitsaForSale(i, 1 wei);
}
vm.stopPrank();
// voting
for (uint256 i = 0; i < users1.length; i++) {
vm.startPrank(users1[i]);
voting.voteForMartenitsa(i);
vm.stopPrank();
}
// Announce winner for round 1
vm.warp(block.timestamp + 1 days + 1);
vm.recordLogs();
voting.announceWinner();
Vm.Log[] memory entries1 = vm.getRecordedLogs();
address winner1 = address(uint160(uint256(entries1[0].topics[2])));
console.log("The winner of round 1: ", winner1);
// Round 2 start voting
vm.warp(block.timestamp);
voting.startVoting();
// create martenitsa and list it for sale
vm.startPrank(chasy);
for (uint256 i = 5; i < users1.length; i++) {
martenitsaToken.createMartenitsa("bracelet");
marketplace.listMartenitsaForSale(i, 1 wei);
}
vm.stopPrank();
// voting
for (uint256 i = 0; i < users1.length; i++) {
vm.startPrank(users1[i]);
// As the isVoted mapping is not reset, the users cannot vote
vm.expectRevert();
voting.voteForMartenitsa(i);
vm.stopPrank();
}
}
Foundry Output
Logs:
The winner of round 1: 0x4f19A0AcB98b7aA30b0138D26e5E5F9634F32F5A
Traces:
[1527913] BugTest::test_VoteForMartenitsaCausesDoS()
├─ [0] VM::warp(1)
│ └─ ← [Return]
├─ [28094] MartenitsaVoting::startVoting()
│ ├─ emit Voting(startTime: 1, duration: 86400 [8.64e4])
│ └─ ← [Stop]
├─ [0] VM::addr(<pk>) [staticcall]
│ └─ ← [Return] : [0xa433f323541CF82f97395076B5F83a7A06F1646c]
├─ [0] VM::label(: [0xa433f323541CF82f97395076B5F83a7A06F1646c], "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0")
│ └─ ← [Return]
├─ [0] VM::addr(<pk>) [staticcall]
│ └─ ← [Return] : [0xF80999DCaF1cd7eE68F8b72C4e9451581A92bb56]
├─ [0] VM::label(: [0xF80999DCaF1cd7eE68F8b72C4e9451581A92bb56], "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\u{1}")
│ └─ ← [Return]
├─ [0] VM::addr(<pk>) [staticcall]
│ └─ ← [Return] : [0x217Dc6A3130a6ab45942219DC4258d680BA5e160]
├─ [0] VM::label(: [0x217Dc6A3130a6ab45942219DC4258d680BA5e160], "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\u{2}")
│ └─ ← [Return]
├─ [0] VM::addr(<pk>) [staticcall]
│ └─ ← [Return] : [0xC9C673b3B2857C3dc44DA8dbeb4FdAD7908edcd3]
├─ [0] VM::label(: [0xC9C673b3B2857C3dc44DA8dbeb4FdAD7908edcd3], "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\u{3}")
│ └─ ← [Return]
├─ [0] VM::addr(<pk>) [staticcall]
│ └─ ← [Return] : [0xEC9261B092BD9Cb33687c6ffeEffDD90aA9e27Ef]
├─ [0] VM::label(: [0xEC9261B092BD9Cb33687c6ffeEffDD90aA9e27Ef], "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\u{4}")
│ └─ ← [Return]
├─ [0] VM::startPrank(chasy: [0x4f19A0AcB98b7aA30b0138D26e5E5F9634F32F5A])
│ └─ ← [Return]
├─ [119271] MartenitsaToken::createMartenitsa("bracelet")
│ ├─ emit Created(owner: chasy: [0x4f19A0AcB98b7aA30b0138D26e5E5F9634F32F5A], tokenId: 0, design: 0xc1cbd2c9dfe820e277e3455cc1eae64e59aa0f2ea37665d303cd84c1c94670fd)
│ ├─ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: chasy: [0x4f19A0AcB98b7aA30b0138D26e5E5F9634F32F5A], tokenId: 0)
│ └─ ← [Stop]
├─ [100437] MartenitsaMarketplace::listMartenitsaForSale(0, 1)
│ ├─ [598] MartenitsaToken::ownerOf(0) [staticcall]
│ │ └─ ← [Return] chasy: [0x4f19A0AcB98b7aA30b0138D26e5E5F9634F32F5A]
│ ├─ [630] MartenitsaToken::isProducer(chasy: [0x4f19A0AcB98b7aA30b0138D26e5E5F9634F32F5A]) [staticcall]
│ │ └─ ← [Return] true
│ ├─ [1555] MartenitsaToken::getDesign(0) [staticcall]
│ │ └─ ← [Return] "bracelet"
│ ├─ emit MartenitsaListed(tokenId: 0, seller: chasy: [0x4f19A0AcB98b7aA30b0138D26e5E5F9634F32F5A], price: 1)
│ └─ ← [Stop]
├─ [51571] MartenitsaToken::createMartenitsa("bracelet")
│ ├─ emit Created(owner: chasy: [0x4f19A0AcB98b7aA30b0138D26e5E5F9634F32F5A], tokenId: 1, design: 0xc1cbd2c9dfe820e277e3455cc1eae64e59aa0f2ea37665d303cd84c1c94670fd)
│ ├─ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: chasy: [0x4f19A0AcB98b7aA30b0138D26e5E5F9634F32F5A], tokenId: 1)
│ └─ ← [Stop]
├─ [118337] MartenitsaMarketplace::listMartenitsaForSale(1, 1)
│ ├─ [598] MartenitsaToken::ownerOf(1) [staticcall]
│ │ └─ ← [Return] chasy: [0x4f19A0AcB98b7aA30b0138D26e5E5F9634F32F5A]
│ ├─ [630] MartenitsaToken::isProducer(chasy: [0x4f19A0AcB98b7aA30b0138D26e5E5F9634F32F5A]) [staticcall]
│ │ └─ ← [Return] true
│ ├─ [1555] MartenitsaToken::getDesign(1) [staticcall]
│ │ └─ ← [Return] "bracelet"
│ ├─ emit MartenitsaListed(tokenId: 1, seller: chasy: [0x4f19A0AcB98b7aA30b0138D26e5E5F9634F32F5A], price: 1)
│ └─ ← [Stop]
├─ [51571] MartenitsaToken::createMartenitsa("bracelet")
│ ├─ emit Created(owner: chasy: [0x4f19A0AcB98b7aA30b0138D26e5E5F9634F32F5A], tokenId: 2, design: 0xc1cbd2c9dfe820e277e3455cc1eae64e59aa0f2ea37665d303cd84c1c94670fd)
│ ├─ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: chasy: [0x4f19A0AcB98b7aA30b0138D26e5E5F9634F32F5A], tokenId: 2)
│ └─ ← [Stop]
├─ [118337] MartenitsaMarketplace::listMartenitsaForSale(2, 1)
│ ├─ [598] MartenitsaToken::ownerOf(2) [staticcall]
│ │ └─ ← [Return] chasy: [0x4f19A0AcB98b7aA30b0138D26e5E5F9634F32F5A]
│ ├─ [630] MartenitsaToken::isProducer(chasy: [0x4f19A0AcB98b7aA30b0138D26e5E5F9634F32F5A]) [staticcall]
│ │ └─ ← [Return] true
│ ├─ [1555] MartenitsaToken::getDesign(2) [staticcall]
│ │ └─ ← [Return] "bracelet"
│ ├─ emit MartenitsaListed(tokenId: 2, seller: chasy: [0x4f19A0AcB98b7aA30b0138D26e5E5F9634F32F5A], price: 1)
│ └─ ← [Stop]
├─ [51571] MartenitsaToken::createMartenitsa("bracelet")
│ ├─ emit Created(owner: chasy: [0x4f19A0AcB98b7aA30b0138D26e5E5F9634F32F5A], tokenId: 3, design: 0xc1cbd2c9dfe820e277e3455cc1eae64e59aa0f2ea37665d303cd84c1c94670fd)
│ ├─ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: chasy: [0x4f19A0AcB98b7aA30b0138D26e5E5F9634F32F5A], tokenId: 3)
│ └─ ← [Stop]
├─ [118337] MartenitsaMarketplace::listMartenitsaForSale(3, 1)
│ ├─ [598] MartenitsaToken::ownerOf(3) [staticcall]
│ │ └─ ← [Return] chasy: [0x4f19A0AcB98b7aA30b0138D26e5E5F9634F32F5A]
│ ├─ [630] MartenitsaToken::isProducer(chasy: [0x4f19A0AcB98b7aA30b0138D26e5E5F9634F32F5A]) [staticcall]
│ │ └─ ← [Return] true
│ ├─ [1555] MartenitsaToken::getDesign(3) [staticcall]
│ │ └─ ← [Return] "bracelet"
│ ├─ emit MartenitsaListed(tokenId: 3, seller: chasy: [0x4f19A0AcB98b7aA30b0138D26e5E5F9634F32F5A], price: 1)
│ └─ ← [Stop]
├─ [51571] MartenitsaToken::createMartenitsa("bracelet")
│ ├─ emit Created(owner: chasy: [0x4f19A0AcB98b7aA30b0138D26e5E5F9634F32F5A], tokenId: 4, design: 0xc1cbd2c9dfe820e277e3455cc1eae64e59aa0f2ea37665d303cd84c1c94670fd)
│ ├─ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: chasy: [0x4f19A0AcB98b7aA30b0138D26e5E5F9634F32F5A], tokenId: 4)
│ └─ ← [Stop]
├─ [118337] MartenitsaMarketplace::listMartenitsaForSale(4, 1)
│ ├─ [598] MartenitsaToken::ownerOf(4) [staticcall]
│ │ └─ ← [Return] chasy: [0x4f19A0AcB98b7aA30b0138D26e5E5F9634F32F5A]
│ ├─ [630] MartenitsaToken::isProducer(chasy: [0x4f19A0AcB98b7aA30b0138D26e5E5F9634F32F5A]) [staticcall]
│ │ └─ ← [Return] true
│ ├─ [1555] MartenitsaToken::getDesign(4) [staticcall]
│ │ └─ ← [Return] "bracelet"
│ ├─ emit MartenitsaListed(tokenId: 4, seller: chasy: [0x4f19A0AcB98b7aA30b0138D26e5E5F9634F32F5A], price: 1)
│ └─ ← [Stop]
├─ [0] VM::stopPrank()
│ └─ ← [Return]
├─ [0] VM::startPrank(: [0xa433f323541CF82f97395076B5F83a7A06F1646c])
│ └─ ← [Return]
├─ [168004] MartenitsaVoting::voteForMartenitsa(0)
│ ├─ [3740] MartenitsaMarketplace::getListing(0) [staticcall]
│ │ └─ ← [Return] Listing({ tokenId: 0, seller: 0x4f19A0AcB98b7aA30b0138D26e5E5F9634F32F5A, price: 1, design: "bracelet", forSale: true })
│ └─ ← [Stop]
├─ [0] VM::stopPrank()
│ └─ ← [Return]
├─ [0] VM::startPrank(: [0xF80999DCaF1cd7eE68F8b72C4e9451581A92bb56])
│ └─ ← [Return]
├─ [94104] MartenitsaVoting::voteForMartenitsa(1)
│ ├─ [3740] MartenitsaMarketplace::getListing(1) [staticcall]
│ │ └─ ← [Return] Listing({ tokenId: 1, seller: 0x4f19A0AcB98b7aA30b0138D26e5E5F9634F32F5A, price: 1, design: "bracelet", forSale: true })
│ └─ ← [Stop]
├─ [0] VM::stopPrank()
│ └─ ← [Return]
├─ [0] VM::startPrank(: [0x217Dc6A3130a6ab45942219DC4258d680BA5e160])
│ └─ ← [Return]
├─ [74204] MartenitsaVoting::voteForMartenitsa(2)
│ ├─ [3740] MartenitsaMarketplace::getListing(2) [staticcall]
│ │ └─ ← [Return] Listing({ tokenId: 2, seller: 0x4f19A0AcB98b7aA30b0138D26e5E5F9634F32F5A, price: 1, design: "bracelet", forSale: true })
│ └─ ← [Stop]
├─ [0] VM::stopPrank()
│ └─ ← [Return]
├─ [0] VM::startPrank(: [0xC9C673b3B2857C3dc44DA8dbeb4FdAD7908edcd3])
│ └─ ← [Return]
├─ [74204] MartenitsaVoting::voteForMartenitsa(3)
│ ├─ [3740] MartenitsaMarketplace::getListing(3) [staticcall]
│ │ └─ ← [Return] Listing({ tokenId: 3, seller: 0x4f19A0AcB98b7aA30b0138D26e5E5F9634F32F5A, price: 1, design: "bracelet", forSale: true })
│ └─ ← [Stop]
├─ [0] VM::stopPrank()
│ └─ ← [Return]
├─ [0] VM::startPrank(: [0xEC9261B092BD9Cb33687c6ffeEffDD90aA9e27Ef])
│ └─ ← [Return]
├─ [74204] MartenitsaVoting::voteForMartenitsa(4)
│ ├─ [3740] MartenitsaMarketplace::getListing(4) [staticcall]
│ │ └─ ← [Return] Listing({ tokenId: 4, seller: 0x4f19A0AcB98b7aA30b0138D26e5E5F9634F32F5A, price: 1, design: "bracelet", forSale: true })
│ └─ ← [Stop]
├─ [0] VM::stopPrank()
│ └─ ← [Return]
├─ [0] VM::warp(86402 [8.64e4])
│ └─ ← [Return]
├─ [0] VM::recordLogs()
│ └─ ← [Return]
├─ [69207] MartenitsaVoting::announceWinner()
│ ├─ [3740] MartenitsaMarketplace::getListing(0) [staticcall]
│ │ └─ ← [Return] Listing({ tokenId: 0, seller: 0x4f19A0AcB98b7aA30b0138D26e5E5F9634F32F5A, price: 1, design: "bracelet", forSale: true })
│ ├─ [51219] HealthToken::distributeHealthToken(chasy: [0x4f19A0AcB98b7aA30b0138D26e5E5F9634F32F5A], 1)
│ │ ├─ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: chasy: [0x4f19A0AcB98b7aA30b0138D26e5E5F9634F32F5A], value: 1000000000000000000 [1e18])
│ │ └─ ← [Stop]
│ ├─ emit WinnerAnnounced(winnerTokenId: 0, winner: chasy: [0x4f19A0AcB98b7aA30b0138D26e5E5F9634F32F5A])
│ └─ ← [Stop]
├─ [0] VM::getRecordedLogs()
│ └─ ← [Return] [([0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000004f19a0acb98b7aa30b0138d26e5e5f9634f32f5a], 0x0000000000000000000000000000000000000000000000000de0b6b3a7640000, 0x2e234DAe75C793f67A35089C9d99245E1C58470b), ([0x3f03f99bfb71558f9ba7a427ae6718175a5e8fe6d6d77555965ac9418c990b38, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000004f19a0acb98b7aa30b0138d26e5e5f9634f32f5a], 0x, 0x5991A2dF15A8F6A256D3Ec51E99254Cd3fb576A9)]
├─ [0] console::log("The winner of round 1: ", chasy: [0x4f19A0AcB98b7aA30b0138D26e5E5F9634F32F5A]) [staticcall]
│ └─ ← [Stop]
├─ [0] VM::warp(86402 [8.64e4])
│ └─ ← [Return]
├─ [2094] MartenitsaVoting::startVoting()
│ ├─ emit Voting(startTime: 86402 [8.64e4], duration: 86400 [8.64e4])
│ └─ ← [Stop]
├─ [0] VM::startPrank(chasy: [0x4f19A0AcB98b7aA30b0138D26e5E5F9634F32F5A])
│ └─ ← [Return]
├─ [0] VM::stopPrank()
│ └─ ← [Return]
├─ [0] VM::startPrank(: [0xa433f323541CF82f97395076B5F83a7A06F1646c])
│ └─ ← [Return]
├─ [0] VM::expectRevert(custom error f4844814:)
│ └─ ← [Return]
├─ [572] MartenitsaVoting::voteForMartenitsa(0)
│ └─ ← [Revert] revert: You have already voted
├─ [0] VM::stopPrank()
│ └─ ← [Return]
├─ [0] VM::startPrank(: [0xF80999DCaF1cd7eE68F8b72C4e9451581A92bb56])
│ └─ ← [Return]
├─ [0] VM::expectRevert(custom error f4844814:)
│ └─ ← [Return]
├─ [572] MartenitsaVoting::voteForMartenitsa(1)
│ └─ ← [Revert] revert: You have already voted
├─ [0] VM::stopPrank()
│ └─ ← [Return]
├─ [0] VM::startPrank(: [0x217Dc6A3130a6ab45942219DC4258d680BA5e160])
│ └─ ← [Return]
├─ [0] VM::expectRevert(custom error f4844814:)
│ └─ ← [Return]
├─ [572] MartenitsaVoting::voteForMartenitsa(2)
│ └─ ← [Revert] revert: You have already voted
├─ [0] VM::stopPrank()
│ └─ ← [Return]
├─ [0] VM::startPrank(: [0xC9C673b3B2857C3dc44DA8dbeb4FdAD7908edcd3])
│ └─ ← [Return]
├─ [0] VM::expectRevert(custom error f4844814:)
│ └─ ← [Return]
├─ [572] MartenitsaVoting::voteForMartenitsa(3)
│ └─ ← [Revert] revert: You have already voted
├─ [0] VM::stopPrank()
│ └─ ← [Return]
├─ [0] VM::startPrank(: [0xEC9261B092BD9Cb33687c6ffeEffDD90aA9e27Ef])
│ └─ ← [Return]
├─ [0] VM::expectRevert(custom error f4844814:)
│ └─ ← [Return]
├─ [572] MartenitsaVoting::voteForMartenitsa(4)
│ └─ ← [Revert] revert: You have already voted
├─ [0] VM::stopPrank()
│ └─ ← [Return]
└─ ← [Stop]

Recommended Mitigation

  1. Ensure that the hasVoted mapping is reset before starting a new round of voting to prevent users from being unable to vote.

function announceWinner() external onlyOwner {
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);
+ delete hasVoted;
emit WinnerAnnounced(winnerTokenId, list.seller);
}
Updates

Lead Judging Commences

bube Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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