DatingDapp

AI First Flight #6
Beginner FriendlyFoundrySolidityNFT
EXP
View results
Submission Details
Severity: high
Valid

[H-01] `likeUser()` never updates `userBalances`, permanently locking all ETH in the contract

Description

LikeRegistry collects 1 ETH per like but never credits it to userBalances. When a match triggers, matchRewards() reads 0 for both users and deploys an empty MultiSig. All ETH is trapped.

Vulnerability Details

likeUser() accepts msg.value >= 1 ether but the function body never writes to userBalances[msg.sender]:

// src/LikeRegistry.sol, line 31-48
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;
// @> MISSING: userBalances[msg.sender] += msg.value;
emit Liked(msg.sender, liked);
if (likes[liked][msg.sender]) {
matches[msg.sender].push(liked);
matches[liked].push(msg.sender);
emit Matched(msg.sender, liked);
matchRewards(liked, msg.sender);
}
}

matchRewards() reads userBalances[from] and userBalances[to], both 0. The MultiSig wallet receives 0 ETH. withdrawFees() reverts because totalFees is also 0. No recovery function exists.

Risk

Likelihood:

  • Every call to likeUser() triggers this bug. There are no edge cases or special conditions — the balance is simply never updated. The bug is present from deployment and the first user to send ETH loses it.

Impact:

  • 100% of ETH sent to the contract is permanently locked. Matched users receive empty MultiSig wallets. The owner collects zero fees. There is no admin rescue function, so the ETH is irrecoverable without a contract upgrade or migration.

PoC

The test below shows Alice and Bob each sending 1 ETH via likeUser(). After the match triggers, the contract still holds all 2 ETH because userBalances was never credited. The owner's withdrawFees() also reverts because totalFees is 0.

function testExploit_ETH_Locked() public {
vm.prank(alice);
profileNFT.mintProfile("Alice", 25, "ipfs://alice");
vm.prank(bob);
profileNFT.mintProfile("Bob", 28, "ipfs://bob");
vm.prank(alice);
likeRegistry.likeUser{value: 1 ether}(bob);
assertEq(likeRegistry.userBalances(alice), 0); // never updated
vm.prank(bob);
likeRegistry.likeUser{value: 1 ether}(alice); // match triggers
assertEq(address(likeRegistry).balance, 2 ether); // all ETH trapped
// Owner can't withdraw fees either
vm.prank(owner);
vm.expectRevert("No fees to withdraw");
likeRegistry.withdrawFees();
}

Recommendations

Add the missing balance update in likeUser() so that each user's ETH is tracked and available for matchRewards() to distribute. This single line restores the entire reward flow — matches will deploy funded MultiSig wallets and the owner will collect fees.

likes[msg.sender][liked] = true;
+ userBalances[msg.sender] += msg.value;
emit Liked(msg.sender, liked);
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 16 hours ago
Submission Judgement Published
Validated
Assigned finding tags:

[H-01] After the user calls the `likeUser` function, the userBalance does not increase by the corresponding value.

## 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); [...] } ```

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!