likeUser() enforces a payment of at least 1 ETH per like:
However, the paid value is never credited to either payer accounting or reward accounting.
The reward path (matchRewards) relies exclusively on userBalances:
But userBalances is never increased anywhere in the contract. In practice, this means users still pay >= 1 ETH for each like, while mutual matches compute totalRewards == 0 and send no meaningful value to the match multisig. The net effect is a direct contradiction of the documented economic model in README.md: paid likes do not translate into match rewards.
The issue is deterministic and always present in current logic: every like payment follows this path, and no alternative code path credits userBalances.
The protocol’s primary economic mechanism is broken. Users pay substantial value (>=1 ETH) but mutual matches do not receive pooled rewards as described. This can lead to severe user loss of confidence and economic failure of the product.
Static flow proof in src/LikeRegistry.sol:
likeUser() requires payment (msg.value >= 1 ether).
likeUser() does not write to userBalances.
matchRewards() computes payout only from userBalances[from] + userBalances[to].
Since both are never funded by protocol logic, totalRewards remains zero.
Consequence: users pay ETH for likes, but match payout logic has no funded inputs.
Track each payer’s like deposits and consume those tracked balances when a match is formed.
Illustrative patch:
Also consider documenting whether users can accumulate multiple deposits before matching and how partial refunds/withdrawals should work.
## 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.