DatingDapp

First Flight #33
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Severity: high
Valid

Untracked User Funds in `likeUser` in `LikeRegistry.sol` Contract

Description

Users' ETH payments in the LikeRegistry contract are not tracked, risking permanent loss of funds when users like profiles. The contract receives ETH but does not record individual user contributions, creating a critical vulnerability where sent funds become effectively trapped.
Here is the affected code:

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

Impact

  • User-sent ETH becomes untraceable

  • There is no mechanism to recover or withdraw individual user funds

  • Potential permanent loss of user investments

Proof of Code

Create a folder for testing for the LikeRegistry.sol contract
Paste this into the folder

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "forge-std/Test.sol";
import "../src/LikeRegistry.sol";
import "../src/SoulboundProfileNFT.sol";
contract LikeRegistryTest is Test {
LikeRegistry public likeRegistry;
SoulboundProfileNFT public profileNFT;
address alice = address(0x1);
address bob = address(0x2);
function setUp() public {
// Deploy the profile NFT contract
profileNFT = new SoulboundProfileNFT();
// Deploy the like registry
likeRegistry = new LikeRegistry(address(profileNFT));
// Give ETH to test users
vm.deal(alice, 10 ether);
vm.deal(bob, 10 ether);
// Create profiles for test users
vm.prank(alice);
profileNFT.mintProfile("Alice", 25, "ipfs://alice");
vm.prank(bob);
profileNFT.mintProfile("Bob", 28, "ipfs://bob");
}
function testLostFunds() public {
// Step 1: Alice likes Bob
vm.prank(alice);
likeRegistry.likeUser{value: 1 ether}(bob);
// Check Alice's tracked balance - Should be 1 ether but it's 0!
assertEq(likeRegistry.userBalances(alice), 0);
// Prove that the money is stuck in the contract
assertEq(address(likeRegistry).balance, 1 ether);
// Step 2: Bob likes Alice back
vm.prank(bob);
likeRegistry.likeUser{value: 1 ether}(alice);
// When they match, no funds are distributed because balances were never tracked
// The matchRewards function will send 0 ETH to their multisig
// Show that both ETH payments are now stuck
assertEq(address(likeRegistry).balance, 2 ether);
// There's no way to get these funds back!
// Even the owner's withdrawFees() can't access them because they weren't counted as fees
}
}

This test shows the vulnerability in a few simple steps:

  • Alice sends 1 ETH to like Bob

  • The contract accepts the ETH but doesn't track it in userBalances

  • Bob likes Alice back with 1 ETH

  • When they match, no funds get distributed because their balances show 0

  • Both users' ETH (2 ETH total) is now stuck in the contract forever

Tools Used

  • Manual Code Review

  • Solidity Static Analysis

Recommended Mitigation

Add userBalances[msg.sender] += msg.value; to track user contributions.

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");
// Track user balance
@> userBalances[msg.sender] += msg.value;
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);
}
}
Updates

Appeal created

n0kto Lead Judge 6 months ago
Submission Judgement Published
Validated
Assigned finding tags:

finding_likeUser_no_userBalances_updated

Likelihood: High, always. Impact: High, loss of funds

Support

FAQs

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