The PoC and test for H-1 assume that the L-1 mitigation (MatchInfo struct, updated matches mapping, and updated getMatches return type) has already been applied.
Please review and apply L-1 first to avoid compilation errors and to correctly reproduce this issue.
The likeUser function requires users to send at least 1 ETH when liking another user, but it never records the sent ETH into the userBalances mapping. As a result, when a mutual like occurs and matchRewards is executed, both users’ balances are read as zero.
Because matchRewards relies entirely on userBalances[from] and userBalances[to] to calculate the reward amount, the deployed multisig wallet receives 0 ETH even though both users paid ETH during the like process. This breaks the core reward mechanism of the protocol and leads to permanent loss of user funds locked in the contract.
Impact: High
The multisig wallet to receive 0 ETH even though both users paid at least 1 ETH.
User funds to become stuck permanently inside the LikeRegistry contract.
The core business logic (rewarding matched users) to completely fail.
Likelihood: High
This bug occurs on every successful match, not just in edge cases.
Any pair of users who mutually like each other will trigger matchRewards.
Since userBalances is never updated, the failure happens deterministically.
Copy the code below to testSoulboundProfileNFT.t.sol.
Run command forge test --mt testLikeAndMatch -vvvv.
Put this following mitigation in conntract LikeRegistry:
Introduce an enum LikeStatus { None, Liked, Matched } and repurpose the existing Like struct to track both the cumulative ETH sent and the current like status, since the original Like struct is unused.
Replace the likes mapping from mapping(address => mapping(address => bool)) to mapping(address => mapping(address => Like)) in order to persist both the like status and the total amount sent per user pair.
Introduce a constant MIN_LIKE_AMOUNT to replace the hardcoded minimum ETH requirement and reduce gas usage.
Update the likeUser function to:
Enforce the minimum amount using the constant.
Prevent duplicate likes or re-matches by checking the enum status.
Accumulate the sent ETH in the Like struct.
Update the matchRewards function to:
Read balances directly from the Like struct instead of userBalances.
Validate that both users have contributed the minimum amount.
Reset stored balances after rewards are processed.
Persist and emit the deployed multisig wallet address together with match metadata.
## 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.