When two users mutually like each other, the protocol is expected to pool their paid like deposits, take a 10% protocol fee, and send the remaining ETH to a newly deployed MultiSigWallet for the matched pair.
The issue is that LikeRegistry.likeUser() accepts ETH but never credits the payment to userBalances[msg.sender]. As a result, when a mutual match occurs, matchRewards() reads zero balances for both users, deploys a MultiSigWallet, and sends 0 ETH to it. The actual ETH deposits remain stuck in LikeRegistry and cannot be withdrawn as fees because totalFees is also calculated from the zero internal balances.
Likelihood: High
This occurs for every successful mutual match because likeUser() never records the ETH sent by either user.
No special attacker behavior is required. Normal protocol usage triggers the issue when two valid profile owners like each other.
Impact: High
Matched users lose access to the ETH that should have funded their shared multisig wallet.
The deployed MultiSigWallet receives 0 ETH, while the paid deposits remain stuck in LikeRegistry.
The owner cannot recover the stranded ETH through withdrawFees() because totalFees is never increased from the uncredited deposits.
The following test shows Alice and Bob each paying 1 ETH to like each other. A mutual match is recorded, but the newly deployed multisig receives 0 ETH. The 2 ETH paid by users remains stuck in LikeRegistry, and withdrawFees() cannot recover it.
Credit each incoming like payment to the sender’s internal balance before checking for a mutual match. Then matchRewards() can correctly calculate the total reward, fees, and multisig funding amount.
With this change, when Alice and Bob mutually match after each paying 1 ETH, matchRewards() sees 2 ETH in total user balances, records the 10% fee, and transfers the remaining 1.8 ETH to the deployed MultiSigWallet.
Optionally, the protocol should also decide how to handle overpayment above 1 ETH: either reject msg.value > 1 ether, refund the excess, or intentionally include the full amount in the user’s match balance.
## 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.