The joinEvent() function captures the user's current share balance at the time they join the event and stores it in userSharesToCountry[msg.sender][countryId] for use in winner share calculations. Under normal behavior, a user's shares should remain consistent between join time and withdrawal time, ensuring that the shares used to calculate totalWinnerShares match the shares used during withdrawal.
However, users can deposit additional funds after joining the event, which increases their share balance. When joinEvent() is called on line 260, it captures the current share balance using balanceOf(msg.sender) and stores it in userSharesToCountry[msg.sender][countryId]. If the user deposits more after joining, their share balance increases, but the stored value in userSharesToCountry is not updated. When _getWinnerShares() calculates totalWinnerShares, it uses the old join-time share values from the mapping. However, when withdraw() is called on line 305, it uses the current balanceOf(msg.sender), which may be higher than the join-time shares. This creates an inconsistency where totalWinnerShares is calculated with lower share values, but withdrawals use higher share values, potentially allowing users to withdraw more than their proportional share of the prize pool.
Likelihood:
This vulnerability occurs when a user deposits additional funds after calling joinEvent(), as the function captures share balance at join time but does not update userSharesToCountry when subsequent deposits increase the user's share balance
The bug manifests during withdrawal when withdraw() uses the current balanceOf(msg.sender) while totalWinnerShares was calculated using the older join-time share values stored in userSharesToCountry, creating a mismatch in the payout calculation
Impact:
Users can withdraw more than their proportional share of the prize pool because totalWinnerShares is calculated with lower join-time shares while withdrawal uses higher current shares
The financial impact increases with the amount of additional deposits made after joining, as larger discrepancies between join-time and current shares result in larger over-withdrawals
In extreme cases, a user can drain the entire vault by depositing enough after joining, leaving nothing for other winners and causing complete loss of funds for legitimate participants
Explanation:
This proof of concept demonstrates the vulnerability by showing how a user can deposit additional funds after joining the event, causing their share balance to increase while the stored join-time shares remain unchanged. The test performs the following steps:
Setup: User deposits initial funds and joins the event
Additional Deposit: User deposits more funds after joining, increasing their share balance
Winner Set: Owner sets the winner, triggering _getWinnerShares() calculation using join-time shares
Verification: The test verifies that totalWinnerShares uses the lower join-time shares
Withdrawal: User withdraws using current (higher) shares, receiving more than their fair share
The test calculates the expected payout (if shares were consistent) versus the actual payout (with the inconsistency) to demonstrate the financial impact.
Explanation:
The recommended mitigation ensures consistency between join-time shares and withdrawal shares by using the stored join-time shares from userSharesToCountry during withdrawal instead of the current share balance. This ensures that the shares used to calculate totalWinnerShares match the shares used during withdrawal.
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.