Every call to likeUser requires at least 1 ETH. If the liked user never reciprocates, the sender's ETH has no exit path. matchRewards is only called on a mutual match, so single-sided likes leave ETH stranded in the contract indefinitely. There is no cancelLike, refund, or user-facing withdraw function. withdrawFees is owner-only and restricted to totalFees — it cannot touch individual user payments:
On a dating platform, the vast majority of likes will never be returned. Every unreturned like permanently destroys the sender's ETH.
Any user who likes someone who never likes back loses their full payment with zero recourse. This is the expected outcome for most interactions on the platform. Even the contract owner cannot recover individual user funds. Combined with H-02, the situation is compounded: matched ETH is also stuck, meaning there is no scenario under the current implementation where a user's ETH is returned or forwarded correctly.
Test: testPoC_UnmatchedLikeETHPermanentlyLocked in test/testLikeRegistry.t.sol
Run with: forge test --match-test testPoC_UnmatchedLikeETHPermanentlyLocked -vvv
Retain userBalances[msg.sender] += msg.value (from the H-02 fix) and expose a withdrawal function that allows users to reclaim their unmatched balance. Once a match fires, zero out the balance atomically so it cannot be double-withdrawn:
## Description User A calls `likeUser` and sends `value > 1` ETH. According to the design of DatingDapp, the amount for user A should be accumulated by `userBalances`. Otherwise, in the subsequent calculations, the balance for each user will be 0. ## Vulnerability Details When User A calls `likeUser`, the accumulation of `userBalances` is not performed. ```solidity function likeUser( address liked ) external payable { require(msg.value >= 1 ether, "Must send at least 1 ETH"); require(!likes[msg.sender][liked], "Already liked"); require(msg.sender != liked, "Cannot like yourself"); require(profileNFT.profileToToken(msg.sender) != 0, "Must have a profile NFT"); require(profileNFT.profileToToken(liked) != 0, "Liked user must have a profile NFT"); likes[msg.sender][liked] = true; emit Liked(msg.sender, liked); // Check if mutual like if (likes[liked][msg.sender]) { matches[msg.sender].push(liked); matches[liked].push(msg.sender); emit Matched(msg.sender, liked); matchRewards(liked, msg.sender); } } ``` This will result in `totalRewards` always being 0, affecting all subsequent calculations: ```solidity uint256 totalRewards = matchUserOne + matchUserTwo; uint256 matchingFees = (totalRewards * FIXEDFEE ) / 100; uint256 rewards = totalRewards - matchingFees; totalFees += matchingFees; ``` ## POC ```solidity function testUserBalanceshouldIncreaseAfterLike() public { vm.prank(user1); likeRegistry.likeUser{value: 20 ether}(user2); assertEq(likeRegistry.userBalances(user1), 20 ether, "User1 balance should be 20 ether"); } ``` Then we will get an error: ```shell [FAIL: User1 balance should be 20 ether: 0 != 20000000000000000000] ``` ## Impact - Users will be unable to receive rewards. - The contract owner will also be unable to withdraw ETH from the contract. ## Recommendations Add processing for `userBalances` in the `likeUser` function: ```diff function likeUser( address liked ) external payable { require(msg.value >= 1 ether, "Must send at least 1 ETH"); require(!likes[msg.sender][liked], "Already liked"); require(msg.sender != liked, "Cannot like yourself"); require(profileNFT.profileToToken(msg.sender) != 0, "Must have a profile NFT"); require(profileNFT.profileToToken(liked) != 0, "Liked user must have a profile NFT"); likes[msg.sender][liked] = true; + userBalances[msg.sender] += msg.value; emit Liked(msg.sender, liked); [...] } ```
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.