The likeUser function is designed to collect ETH from users to be used as a reward for a potential match.
However, the contract fails to record the incoming msg.value into the userBalances mapping.
When a mutual like occurs, matchRewards calculates the payout based on userBalances. Since these are never updated, the reward is calculated as 0, leaving the original ETH trapped in the LikeRegistry contract.
Likelihood:
This issue occurs every time a match is made.
There is no manual withdraw function, so any ETH sent to the contract is immediately at risk.
Impact:
High: 100% loss of user funds for every matched pair.
High: The MultiSigWallet intended for the couple remains empty, breaking the core utility of the protocol.
Step-by-step reproduction:
Setup a LikeRegistry and SoulboundProfileNFT.
Two users (Alice and Bob) mint their profiles.
Alice likes Bob with 1 ETH.
Bob likes Alice with 1 ETH.
The contract correctly identifies a match and deploys a MultiSigWallet.
The Bug: Check the balance of the newly created MultiSigWallet. It will be 0 ETH instead of the expected 1.8 ETH (2 ETH minus 10% fee).
The full 2 ETH remains stuck in the LikeRegistry contract with no way to retrieve it.
To run the test: forge test --match-path test/testLikeRegistry.t.sol -vvvv
Explanation: The vulnerability stems from the contract's failure to update internal accounting. By adding userBalances[msg.sender] += msg.value;, we ensure the contract tracks how much each user has deposited. This allows the matchRewards function to pull the correct values when calculating the total payout for the match.
## 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.