DatingDapp

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

likeUser() never credits userBalances, so every like payment is locked in LikeRegistry forever and matches receive empty multisigs

Root + Impact

The whole economic loop of the protocol is broken by one missing state update. likeUser() collects at least 1 ETH per like, and matchRewards() is supposed to pool "all their previous like payments (minus a 10% fee)" into the matched couple's multisig. But likeUser never adds the payment to userBalances. Nothing in the contract ever writes a nonzero value into that mapping; matchRewards only reads the two balances (always 0) and zeroes them again. As a result every match computes totalRewards of 0, takes a fee of 0, and funds the couple's brand-new multisig with 0 ETH, while 100% of the ETH users actually paid accumulates in LikeRegistry with no way out: it is not the users' (they were never credited), and it is not the owner's either, because withdrawFees() can only pay out totalFees, which also stays 0 forever.

Description

function likeUser(address liked) external payable {
require(msg.value >= 1 ether, "Must send at least 1 ETH");
...
likes[msg.sender][liked] = true;
emit Liked(msg.sender, liked);
// @> // userBalances[msg.sender] += msg.value; <-- this line does not exist anywhere
if (likes[liked][msg.sender]) {
...
matchRewards(liked, msg.sender);
}
}
​
function matchRewards(address from, address to) internal {
// @> uint256 matchUserOne = userBalances[from]; // always 0
// @> uint256 matchUserTwo = userBalances[to]; // always 0
userBalances[from] = 0;
userBalances[to] = 0;
uint256 totalRewards = matchUserOne + matchUserTwo; // always 0
...
(bool success,) = payable(address(multiSigWallet)).call{value: rewards}(""); // sends 0
}

A grep of the codebase confirms userBalances is written nowhere except the two zeroing assignments in matchRewards.

Risk

Likelihood: High - it happens on the very first like and on every match; no special conditions, no attacker needed. The protocol simply never works as documented.
Impact: High - direct, permanent loss of all user funds sent to the protocol: every like costs at least 1 ETH (any overpayment above 1 ETH is equally uncredited), all of it stays locked in the contract, matched couples receive an empty wallet instead of their pooled payments, and even the contract owner cannot recover the stuck ETH because the only withdrawal function is gated on a fee counter that never accrues.

Proof of Concept

Verified with Foundry (test passes): Alice likes Bob for 1 ETH (her userBalances stays 0), Bob likes Alice for 1 ETH, which triggers the match. Afterwards the registry still holds the full 2 ETH, the match was registered, and the owner's withdrawFees() reverts with "No fees to withdraw", proving the funds are locked with no exit.

function test_LikePaymentsAreLockedForever() public {
vm.prank(alice);
registry.likeUser{value: 1 ether}(bob);
assertEq(registry.userBalances(alice), 0); // paid 1 ETH, credited nothing
vm.prank(bob);
registry.likeUser{value: 1 ether}(alice); // mutual like, match fires
assertEq(address(registry).balance, 2 ether); // both payments still stuck inside
vm.prank(owner);
vm.expectRevert("No fees to withdraw"); // owner cannot rescue them either
registry.withdrawFees();
}

Recommended Mitigation

Credit the payer inside likeUser before the mutual-like check:

likes[msg.sender][liked] = true;
+ userBalances[msg.sender] += msg.value;
emit Liked(msg.sender, liked);
Updates

Lead Judging Commences

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