According to the MyCut protocol specification, users should be able to "claim their cut of a Pot" within 90 days, after which the manager takes a cut and remaining funds are distributed to those who successfully claimed in time
The claimCut() function fails to check the return value of the ERC20 transfer() call, allowing critical state updates even when the token transfer fails. This breaks the core business requirement that users can claim their rewards.
Many ERC20 tokens return false on failed transfers instead of reverting (e.g., ZRX, EURS). The function updates all critical state variables (playersToRewards, remainingRewards, claimants) before the transfer occurs, and never validates the transfer succeeded.
Likelihood:
High - This vulnerability affects any ERC20 token that returns false on failure rather than reverting. The issue is triggered on every claimCut() call when:
The Pot contract has insufficient token balance
The token implementation returns false for any reason
Transfer restrictions or blacklists prevent the transfer
Impact:
High - Multiple severe consequences occur:
Permanent Fund Loss: Users lose their rewards permanently as their playersToRewards[player] is set to 0, preventing re-claims
Accounting Mismatch: remainingRewards decrements despite failed transfers, causing contract state to diverge from actual token balance
Unfair Distribution: Failed claimants are added to claimants[] array, making them eligible for bonus distribution in closePot() despite never receiving their initial reward
Manager Receives Inflated Cut: When closePot() executes, the manager's cut is calculated from inflated remainingRewards that doesn't match actual tokens held
The test testClaimCutWithFailedTokenTransfer_Does_Not_Revert demonstrates this vulnerability:
Attack Scenario:
Pot is created with insufficient funding or uses a token that can return false
User calls claimCut()
Transfer returns false but function doesn't revert
User's reward is zeroed out and they're added to claimants
User permanently loses their reward
After 90 days, closePot() distributes remaining funds to claimants including the failed claimer
User who never received initial reward gets bonus distribution while actual funds remain locked
Use SafeERC20 (Recommended)
Replace direct transfer() calls with OpenZeppelin's SafeERC20 library which handles both reverting and non-reverting tokens:
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.