DatingDapp

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

Funds Not Tracked Due to Missing `userBalances` Update in `LikeRegistry` contract

Summary

In the LikeRegistry contract, the likeUser() function requires users to send at least 1 ETH, but it does not update the userBalances mapping. As a result, when a match occurs, matchRewards() retrieves a balance of 0 for both users, leading to no funds being pooled into the multisig wallet. This makes the payment mechanism ineffective and prevents users from accessing their intended match funds.

Vulnerability Details

Affected Code in likeUser()

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

Affected Code in matchRewards()

function matchRewards(address from, address to) internal {
uint256 matchUserOne = userBalances[from];
uint256 matchUserTwo = userBalances[to];
userBalances[from] = 0;
userBalances[to] = 0;
uint256 totalRewards = matchUserOne + matchUserTwo;
uint256 matchingFees = (totalRewards * FIXEDFEE) / 100;
uint256 rewards = totalRewards - matchingFees;
totalFees += matchingFees;
// Deploy a MultiSig contract for the matched users
MultiSigWallet multiSigWallet = new MultiSigWallet(from, to);
// Send ETH to the deployed multisig wallet
(bool success,) = payable(address(multiSigWallet)).call{value: rewards}("");
require(success, "Transfer failed");
}

Root Cause:

  • userBalances[msg.sender] is never updated when sending ETH in likeUser().

  • When matchRewards() runs, it pulls the user’s balance, which is always 0, preventing fund distribution.

Impact

  • High Severity: This completely breaks the core payment logic of the DatingDapp.

  • Users pay 1 ETH per like, but their funds are never recorded, so the match rewards system never transfers ETH to the multisig wallet.

  • Funds remain stuck in the contract, meaning users are effectively paying ETH for nothing.

Tools Used

  • Manual Code Review

  • Foundry Testing (console.log() used to verify balances)

Foundry POC

// SPDX-License-Identifier: SEE LICENSE IN LICENSE
pragma solidity ^0.8.19;
import "../src/LikeRegistry.sol";
import {Test} from "forge-std/Test.sol";
import {console} from "forge-std/console.sol";
import "../src/SoulboundProfileNFT.sol";
contract LikeRegistryTest is Test {
LikeRegistry target;
SoulboundProfileNFT soulboundNFT;
address LikeRegistryAddress;
address User1 = address(0x1);
address User2 = address(0x2);
function setUp() public {
soulboundNFT = new SoulboundProfileNFT();
target = new LikeRegistry(address(soulboundNFT));
vm.deal(User1, 5 ether);
vm.deal(User2, 5 ether);
vm.startPrank(User1);
soulboundNFT.mintProfile("x", 20, "png://test.png");
vm.stopPrank();
vm.startPrank(User2);
soulboundNFT.mintProfile("godwin", 20, "png://test.png");
vm.stopPrank();
}
function testLikeUser() public {
uint256 amount = 1 ether;
vm.startPrank(User1);
target.likeUser{value: amount}(User2);
uint256 balance1 = target.userBalances(User1);
assertEq(balance1, amount, "User1 balance1 should be updated to 1 ether");
vm.stopPrank();
vm.startPrank(User2);
target.likeUser{value: amount}(User1);
uint256 balance2 = target.userBalances(User2);
assertEq(balance2, amount, "User2 balance2 should be updated to 1 ether");
}
}

The Test code fails in the section when we expect user balance to update after succesfully calling the likeUser function, but it does not.

Recommendations

Fix: Properly Update userBalances in likeUser()
Modify the function to store the ETH sent by the user:

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");
// ✅ Fix: 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);
}
}

Additional Fix: Ensure totalRewards is Greater Than 0 Before Transfer

function matchRewards(address from, address to) internal {
uint256 matchUserOne = userBalances[from];
uint256 matchUserTwo = userBalances[to];
uint256 totalRewards = matchUserOne + matchUserTwo;
require(totalRewards > 0, "No funds to distribute");
uint256 matchingFees = (totalRewards * FIXEDFEE) / 100;
uint256 rewards = totalRewards - matchingFees;
totalFees += matchingFees;
userBalances[from] = 0;
userBalances[to] = 0;
MultiSigWallet multiSigWallet = new MultiSigWallet(from, to);
(bool success,) = payable(address(multiSigWallet)).call{value: rewards}("");
require(success, "Transfer failed");
}
Updates

Appeal created

n0kto Lead Judge 7 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.