closePot() distributes rewards to all entries in the claimants array by iterating the array and calling _transferReward for each. claimCut() correctly zeroes the player mapping before transferring, following checks-effects-interactions.
closePot() has no reentrancy guard and does not zero remainingRewards or clear claimants before beginning distribution. If the token implements ERC777 _afterTokenTransfer hooks, a claimant contract can re-enter closePot from within its hook during the distribution loop, restarting the loop while the original state is still intact and paying all claimants again from the remaining balance.
Likelihood:
The pot token is supplied at construction time and not constrained to a specific implementation. Any ERC777 token or ERC20 with _afterTokenTransfer hooks (OpenZeppelin ERC20 extensions) is a valid trigger.
The contest creator or an attacker who convinces the manager to use such a token enables the attack.
Impact:
Every claimant in the distribution loop is paid multiple times until the pot balance is exhausted. Tokens are drained beyond the designed payout, with excess taken from the shares of other claimants or from the contract balance entirely.
The attack is deterministic once the token type is known — any claimant who is a contract can execute it reliably.
Static analysis is sufficient for this finding. closePot() loops over claimants calling claimCut() for each, but remainingRewards is not zeroed and claimants is not cleared before the loop begins. An ERC777 token or any token with a transfer hook could re-enter closePot() or claimCut() during the distribution loop, receiving payment multiple times before state is updated.
closePot() at line 49 iterates claimants (line 14, populated at line 45) with remainingRewards still non-zero (checked at line 53, never zeroed before the loop) and the claimants array fully populated — no state is cleared before the external calls begin at line 59.
Inherit ReentrancyGuard in Pot and apply the nonReentrant modifier to claimCut(), or move the state-update (playersToRewards[player] = 0) before the _transferReward call to follow the checks-effects-interactions pattern.
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.