Beginner FriendlyFoundryGameFi
100 EXP
View results
Submission Details
Severity: medium
Valid

`MartenitsaEvent::stopEvent` does not properly reset participant status after an event

Summary

MartenitsaEvent::stopEvent does not properly reset participant status after an event concludes, leading to a situation where users who participated in a previous event are unable to join future events.

Vulnerability Details

The MartenitsaEvent contract manages event participation through an array that includes all participants (participants and a mapping that facilitates the lookup of participant status (_participants). Within this contract MartenitsaEvent::stopEvent is supposed to stop the event and clear all variables that reflect user statuses that are temporary during the event. Although the participants and _participants mapping are exactly such variables, MartenitsaEvent::stopEvent does not adequately reset/clear them. At the same time, importantly, MartenitsaEvent::stopEvent does revoke participants's producer status:

function stopEvent() external onlyOwner {
require(block.timestamp >= eventEndTime, "Event is not ended");
for (uint256 i = 0; i < participants.length; i++) {
@> // Current participant cleanup logic is missing
@> isProducer[participants[i]] = false;
}
}

The persistent participant status prevents users from re-engaging in subsequent events, effectively barring them from participation after their initial event. This issue arises because the _participants mapping, which tracks whether an address has joined an event, is not cleared after an event concludes. At the same time

This is demonstrated in the following piece of code:

Proof of Code
function testParticipantArrayNotUpdatedAfterEventCannotParticipateInMoreEvents() public {
address user = makeAddr("user");
// jack gives 3 NFTs to user as a gift
vm.startPrank(jack);
martenitsaToken.createMartenitsa("bracelet");
martenitsaToken.createMartenitsa("bracelet");
martenitsaToken.createMartenitsa("bracelet");
martenitsaToken.approve(address(marketplace), 0);
martenitsaToken.approve(address(marketplace), 1);
martenitsaToken.approve(address(marketplace), 2);
marketplace.makePresent(user, 0);
marketplace.makePresent(user, 1);
marketplace.makePresent(user, 2);
vm.stopPrank();
// user collects HT reward after 3 NFTs
vm.prank(user);
marketplace.collectReward();
assert(healthToken.balanceOf(user) == 1 ether);
// protocol owner starts event
martenitsaEvent.startEvent(1 days);
// user enters the event as participant
vm.startPrank(user);
healthToken.approve(address(martenitsaEvent), 1 ether);
martenitsaEvent.joinEvent();
vm.stopPrank();
// protocol owner ends event
vm.warp(block.timestamp + 1 days + 1 seconds);
martenitsaEvent.stopEvent();
// user is still listed as participant
assert(martenitsaEvent.getParticipant(user) == true);
// protocol owner starts new event
martenitsaEvent.startEvent(1 days);
// user tries to join new event but cant
vm.prank(user);
vm.expectRevert("You have already joined the event");
martenitsaEvent.joinEvent();
}

Impact

  • Participant exclusion: Once a user joins an event, they are permanently marked as having participated and cannot join any subsequent events.

  • Confusion about statuses: users who have the participant status are supposed to have also the producer status, but after an event Martenitsa::stopEvent revokes/clears only the latter, not the former, leading to a status inconsistent with the protocol's intentions.

  • Extra computational overhead: the participants will keep growing from event to event. The piece of code in MartenitsaEvent::stopEvent that loops through this array will become increasingly computationally expensive to run:

function stopEvent() external onlyOwner {
require(block.timestamp >= eventEndTime, "Event is not ended");
@> for (uint256 i = 0; i < participants.length; i++) {
isProducer[participants[i]] = false;
}
}

Tools Used

Manual review, Foundry.

Recommendations

Ensure that the _participants mapping and the participants array are cleared in MartenitsaEvent::stopEvent:

function stopEvent() external onlyOwner {
require(block.timestamp >= eventEndTime, "Event is not ended");
for (uint256 i = 0; i < participants.length; i++) {
isProducer[participants[i]] = false;
+ _participants[participants[i]] = false; // Reset participant status
}
+ delete participants;
}
Updates

Lead Judging Commences

bube Lead Judge over 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

_participants is not updated

paprikrumplikas Submitter
over 1 year ago
bube Lead Judge
over 1 year ago
bube Lead Judge over 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

_participants is not updated

Support

FAQs

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