closePot() performs the final settlement but never updates the accounting it reads from.
remainingRewards is only ever reduced by claimCut(). The settlement pays the manager and every claimant, but leaves the variable untouched, so the guard if (remainingRewards > 0) still passes on the next call and the whole payout runs again against whatever balance is left.
The result is a settlement that is not idempotent. Each additional call pays the manager another remainingRewards / 10 and each claimant another (remainingRewards - managerCut) / i_players.length, draining the pot in repeated rounds long after the single settlement the protocol describes.
Likelihood: Medium. The function is restricted to the owner, but the payout repeats on every call while the balance covers it, so a retry after a failed batch, a duplicated operations script or a re-run for a second batch of claimants silently pays everyone a second time. Nothing in the contract prevents it.
Impact: High. Funds reserved for the contest are paid out more than once: the manager receives a repeated cut and each claimant receives repeated settlement shares, while the recorded remainingRewards still reports the original figure. The pot is drained below what the accounting says it should hold.
Four players, each entitled to 1000, pool of 4000. One player claims, the contest is closed twice, and the same claimant is paid twice while the accounting never moves.
Logged values: first close pays 675, second close pays another 675, pot balance goes 4000 -> 2025 -> 1050, remainingRewards stays at 3000.
Make the settlement single-shot: zero the accounting before transferring and record that the pot is closed.
The contest is live. Earn rewards by submitting a finding.
Submissions are being reviewed by our AI judge. Results will be available in a few minutes.
View all submissionsThe contest is complete and the rewards are being distributed.