The cancelParticipation() function allows users to withdraw their deposited funds before the event begins. However, the contract does not validate whether the caller has already cancelled or previously withdrawn their stake. Because there is no guard preventing repeated calls, the same user can invoke cancelParticipation() multiple times before the event start date.
While stakedAsset[msg.sender] is reset to 0 after the first call, the function continues to execute successfully in subsequent calls — performing a _burn() operation (even if balanceOf(msg.sender) is already zero) and emitting no error. This behavior leads to unnecessary gas consumption, inconsistent accounting, and potential logical drift between share supply and participant tracking.
If the _burn() is called multiple times on an address with no remaining shares, it may desynchronize total share supply, impacting later calculations in functions like setWinner() and withdraw().
Likelihood:
The issue can be triggered both unintentionally (user double-clicks cancel in UI) or intentionally (malicious scripts repeatedly calling before eventStartDate).
No state-based restriction prevents redundant executions.
Impact:
Causes incorrect total share supply and potential inconsistencies in later settlement calculations.
May open paths for griefing or denial-of-service if mass redundant cancellations occur, bloating storage and wasting gas.
Observed Output
The test demonstrates that cancelParticipation() can be called multiple times without reverting, even after the user’s stake and shares have been fully reset.
While the second call doesn’t transfer additional funds (since stakedAsset is 0), it still executes _burn() and safeTransfer() logic, silently allowing redundant execution.
This proves the function lacks a state validation guard, creating potential for gas griefing, accounting drift, and inconsistent event participation logic.
Added a guard that reverts when stakedAsset[msg.sender] == 0, blocking repeated cancelParticipation() calls after the first cancellation. Also burn shares only when balanceOf(msg.sender) > 0 to avoid redundant _burn() calls. These minimal edits prevent redundant refunds, avoid repeated/meaningless burns, and keep vault accounting consistent with the intended single-cancel flow.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.