The contract accepts ETH through paid likes (likeUser()) and through direct transfers (receive()).
However, the only withdrawal path is withdrawFees(), and that function transfers only totalFees:
There is no function that withdraws address(this).balance, and there is no complete accounting path that maps all incoming ETH to totalFees. In the current logic, ETH paid in likeUser() is not added to totalFees, and ETH received through receive() is not added either. As a consequence, contract ETH can accumulate outside the withdrawable fee bucket and become permanently unreachable.
The issue follows normal protocol behavior (users paying for likes) and does not require edge conditions or privileged access.
User ETH and externally sent ETH can become permanently locked, causing irreversible value loss and significant operational and reputational damage.
Deterministic scenario:
User calls likeUser() and pays 1 ETH.
Contract balance increases by 1 ETH.
totalFees does not increase from that payment path.
Owner calls withdrawFees() and can withdraw only the totalFees amount.
Remaining ETH (from likes/direct sends) has no dedicated withdrawal path and can remain stuck indefinitely.
Define one coherent accounting model and align all inflows and outflows to that model.
Minimum safe approach:
Account like payments either to user escrow (userBalances) or to fee pool (totalFees) according to intended economics.
Restrict or remove raw receive() if unsolicited ETH is not intended.
Add an explicit recovery path for unexpected ETH with strict access control and event logging.
Illustrative addition for accidental ETH recovery:
If user funds are intended to be non-custodial and match-bound, replace owner recovery with explicit user-facing refund/claim mechanics.
## 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.