DatingDapp

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

User Payments Are Never Credited, Causing Permanent Loss of Funds

Root + Impact

Description

  • Normal behavior:
    When a user calls likeUser and sends ETH, the payment should be attributed to that user so that, upon a mutual match, both users’ contributed funds (minus protocol fees) are pooled and transferred to a newly deployed multisig wallet.

  • Issue:
    likeUser accepts ETH but never credits msg.value to userBalances[msg.sender].
    As a result, all user payments are silently accumulated in the contract balance without being associated with any user, making them unusable for matches.

function likeUser(address liked) external payable {
require(msg.value >= 1 ether, "Must send at least 1 ETH");
...
// @> msg.value is never added to userBalances[msg.sender]
}

When a match occurs, rewards are calculated exclusively from userBalances, which are always zero.

uint256 matchUserOne = userBalances[from]; // always 0
uint256 matchUserTwo = userBalances[to]; // always 0

Risk

Likelihood:

  • Reason 1: Any call to likeUser with ETH triggers this behavior.

  • Reason 2: No special permissions or edge conditions are required.

Impact:

  • Impact 1: Permanent loss of user funds: ETH sent during likeUser is never recoverable by users.

  • Impact 2: Broken core functionality: matched users receive 0 ETH in their multisig wallet, defeating the main purpose of the protocol.

  • Impact 3: Protocol trust failure: users pay but receive no economic outcome, even on successful matches.

Proof of Concept

Explanation

Because msg.value is not recorded, the accounting system is permanently out of sync with the actual ETH balance of the contract.

// User A likes User B, sending 1 ETH
likeUser{value: 1 ether}(userB);
// userBalances[userA] == 0
// User B likes User A, sending 1 ETH
likeUser{value: 1 ether}(userA);
// Mutual match occurs
// matchRewards() uses userBalances (both zero)
// Result:
// - Multisig is created
// - 0 ETH is transferred
// - 2 ETH remains stuck in LikeRegistry

The stuck ETH cannot be withdrawn by users and is not assigned to fees.

Recommended Mitigation

Credit incoming ETH to the sender’s balance when liking a profile.

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

Optionally, add a user withdrawal or refund mechanism for unmatched likes to further protect users.

Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge 1 day 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!