LikeRegistry.likeUser{value: 1 ether}(addr) holds the sent ETH in the contract. ETH only leaves via matchRewards (on a mutual match) or withdrawFees (onlyOwner, fees only). There is no user-callable withdraw/refund/cancelLike. So any ETH a user sends for a like that never becomes a mutual match is permanently locked. One-sided interest is the normal case for a dating app, so this is the dominant flow. (Distinct from the missing userBalances credit bug — even with correct accounting there is still no user exit.)
Likelihood: Medium — unmatched likes are expected behavior; every such like strands funds.
Impact: Medium — user principal (1 ETH per unmatched like) is irrecoverable.
The test below shows the trap: Alice likes Bob with 1 ETH, Bob never likes back, so the ETH sits in the registry. We then assert that Alice has no way to retrieve it — withdrawFees() reverts for her (it is onlyOwner), no other recovery function exists, and her balance stays down 1 ETH permanently. Add to a Foundry test file:
The two final assertions are the proof: Alice's balance never returns to 5 ETH and the registry still holds her 1 ETH, with no code path to release it to her.
Track refundable principal and add a user-callable withdraw; zero it on a match.
Why this fixes it: principal is recorded on every like; matched funds are zeroed and moved out by matchRewards, while unmatched funds stay reclaimable via withdraw() (CEI-ordered to prevent reentrancy). Unmatched likers now have a guaranteed exit the contract currently lacks.
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.