DatingDapp

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

likeUser() never credits userBalances — all ETH paid is locked in the registry and matches pay 0 rewards

Root + Impact

Description

likeUser() is payable and requires msg.value >= 1 ether, but it never credits the payment to the liker's balance:

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

There is no userBalances[msg.sender] += msg.value anywhere in the contract. The userBalances mapping is only ever read and zeroed inside 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;
@>
@> MultiSigWallet multiSigWallet = new MultiSigWallet(from, to);
@> (bool success,) = payable(address(multiSigWallet)).call{value: rewards}("");
@> require(success, "Transfer failed");
@> }

Because both balances are always 0, every match computes totalRewards = 0, takes a 10% fee of 0, and sends 0 ETH to the matched users' freshly deployed MultiSig wallet. The matched users receive nothing, the owner collects no fees (totalFees stays 0, so withdrawFees() always reverts with "No fees to withdraw"), and the ETH both users paid stays locked in LikeRegistry forever — there is no function that can return it.

Risk

Likelihood: High — the bug triggers on every single match; it is not an edge case but the normal flow of the protocol.

Impact: High — every user who likes someone loses their 1+ ETH permanently (it is locked in the contract with no withdrawal path), matched users receive zero rewards despite paying, and the owner's fee mechanism is entirely broken.

Proof of Concept

Alice and Bob each pay 1 ETH and match. Both userBalances read 0, the registry still holds the full 2 ETH, and withdrawFees() reverts.

function test_H1_likePaymentsNeverCredited_MatchPaysZero() public {
vm.prank(alice);
profileNFT.mintProfile("Alice", 25, "ipfs://alice");
vm.prank(bob);
profileNFT.mintProfile("Bob", 28, "ipfs://bob");
​
// Alice likes Bob, paying 1 ETH.
vm.prank(alice);
likeRegistry.likeUser{value: 1 ether}(bob);
assertEq(likeRegistry.userBalances(alice), 0, "alice paid 1 ETH but her balance is 0");
​
// Bob likes Alice, paying 1 ETH -> mutual match -> matchRewards.
vm.prank(bob);
likeRegistry.likeUser{value: 1 ether}(alice);
​
// Both paid 1 ETH each, but their balances were never credited.
assertEq(likeRegistry.userBalances(alice), 0);
assertEq(likeRegistry.userBalances(bob), 0);
​
// The 2 ETH is stuck in LikeRegistry: nothing was sent to the
// matched users' MultiSig wallet (rewards were 0).
assertEq(address(likeRegistry).balance, 2 ether, "both payments stuck in the registry");
​
// The owner cannot withdraw anything either: totalFees is 0.
vm.expectRevert("No fees to withdraw");
likeRegistry.withdrawFees();
}

Recommended Mitigation

Credit the payment in likeUser() (and consider refunding any amount above 1 ether).

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");
+
+ userBalances[msg.sender] += msg.value;
likes[msg.sender][liked] = true;
emit Liked(msg.sender, 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!