DatingDapp

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

[H-01] Missing `userBalances` Update in `likeUser()` — All User ETH Permanently

[H-01] Missing userBalances Update in likeUser() — All User ETH Permanently Locked

Scope

  • LikeRegistry.sol

Description

The likeUser() function is the core entry point for users to deposit ETH and express interest. It requires msg.value >= 1 ether and records the like. However, the function never updates the sender's balance mapping, causing all deposited ETH to become irrecoverable.

When a mutual match occurs, matchRewards() reads userBalances[from] and userBalances[to] — both are permanently zero. The reward calculation produces zero, and zero ETH is sent to the deployed MultiSig wallet.

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);

Risk

Likelihood: High

  • Every user who calls likeUser() is affected. The function is the only way to interact with the protocol. 100% of deposited ETH is lost.

Impact: High

  • Complete, permanent loss of all user funds. No withdrawal mechanism exists. ETH is trapped in the contract with no recovery path.

Severity: Critical

  • SWC: SWC-105 (Unprotected Ether Withdrawal — inverse: trapped ether)

  • CWE: CWE-682 (Incorrect Calculation)

  • Evidence Grade: A (exploit confirmed via PoC, full trace captured)

Proof of Concept

Alice sends 1 ETH to like Bob. Bob sends 1 ETH to like Alice. A mutual match triggers matchRewards(), which reads userBalances (both 0), calculates 0 rewards, and deploys a MultiSig that receives 0 ETH. The 2 ETH remains permanently locked in the LikeRegistry contract.

function test_P0_H1_ETH_permanently_locked() public {
vm.prank(alice);
registry.likeUser{value: 1 ether}(bob);
assertEq(address(registry).balance, 1 ether);
assertEq(registry.userBalances(alice), 0, "BUG: userBalances not updated");
vm.prank(bob);
registry.likeUser{value: 1 ether}(alice);
assertEq(address(registry).balance, 2 ether, "All 2 ETH locked in registry");
}

forge test --match-test test_P0_H1_ETH_permanently_locked -vvvvPASS

Recommended Mitigation

The userBalances mapping must be updated immediately after the msg.value check. This restores the invariant that deposited ETH is tracked and can be distributed via matchRewards().

function likeUser(address liked) external payable {
require(msg.value >= 1 ether, "Must send at least 1 ETH");
+ userBalances[msg.sender] += msg.value;
require(!likes[msg.sender][liked], "Already liked");
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 2 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!