The whole economic loop of the protocol is broken by one missing state update. likeUser() collects at least 1 ETH per like, and matchRewards() is supposed to pool "all their previous like payments (minus a 10% fee)" into the matched couple's multisig. But likeUser never adds the payment to userBalances. Nothing in the contract ever writes a nonzero value into that mapping; matchRewards only reads the two balances (always 0) and zeroes them again. As a result every match computes totalRewards of 0, takes a fee of 0, and funds the couple's brand-new multisig with 0 ETH, while 100% of the ETH users actually paid accumulates in LikeRegistry with no way out: it is not the users' (they were never credited), and it is not the owner's either, because withdrawFees() can only pay out totalFees, which also stays 0 forever.
A grep of the codebase confirms userBalances is written nowhere except the two zeroing assignments in matchRewards.
Likelihood: High - it happens on the very first like and on every match; no special conditions, no attacker needed. The protocol simply never works as documented.
Impact: High - direct, permanent loss of all user funds sent to the protocol: every like costs at least 1 ETH (any overpayment above 1 ETH is equally uncredited), all of it stays locked in the contract, matched couples receive an empty wallet instead of their pooled payments, and even the contract owner cannot recover the stuck ETH because the only withdrawal function is gated on a fee counter that never accrues.
Verified with Foundry (test passes): Alice likes Bob for 1 ETH (her userBalances stays 0), Bob likes Alice for 1 ETH, which triggers the match. Afterwards the registry still holds the full 2 ETH, the match was registered, and the owner's withdrawFees() reverts with "No fees to withdraw", proving the funds are locked with no exit.
Credit the payer inside likeUser before the mutual-like check:
## 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.