DatingDapp

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

`userBalances` is never incremented in `likeUser()` — all deposited ETH permanently locked, match rewards always zero

Description

  • Normal: When a user pays ETH to like another profile, that payment should be tracked so it can be pooled into the match reward when a mutual like occurs. The protocol's economic model depends on user balances accumulating and being redistributed on match.

  • Bug: likeUser() accepts msg.value >= 1 ether but never increments userBalances[msg.sender]. When a mutual match triggers matchRewards(), both users' balances read as 0. The match reward is always 0 ETH, the deployed MultiSig wallet receives nothing, and all user deposits are permanently locked in the contract with no recovery mechanism.

// LikeRegistry.sol:31-48
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);
//@> BUG: userBalances[msg.sender] += msg.value; // THIS LINE IS MISSING
if (likes[liked][msg.sender]) {
matches[msg.sender].push(liked);
matches[liked].push(msg.sender);
emit Matched(msg.sender, liked);
matchRewards(liked, msg.sender);
}
}
function matchRewards(address from, address to) internal {
uint256 matchUserOne = userBalances[from]; //@> Always 0
uint256 matchUserTwo = userBalances[to]; //@> Always 0
// ... totalRewards = 0, matchingFees = 0, rewards = 0
// MultiSig deployed but receives 0 ETH
}

Complete chain of consequences:

  1. User pays 1+ ETH → contract balance increases, but userBalances unchanged (always 0)

  2. Mutual match → matchRewards computes totalRewards = 0 + 0 = 0

  3. matchingFees = 0 * 10 / 100 = 0totalFees never grows

  4. rewards = 0 → MultiSig wallet deployed with 0 ETH

  5. withdrawFees() always reverts with "No fees to withdraw" — even the owner cannot access funds

  6. All user ETH permanently locked — no withdrawal function, no recovery path

Risk

  • Likelihood: Certain — every single likeUser() call triggers this. The bug is in the function's normal execution path with no preconditions to avoid it.

  • Impact: CRITICAL — All user funds are permanently locked with zero recovery. The core feature (match rewards funding a shared MultiSig) is completely non-functional. The protocol's entire economic model collapses.

Proof of Concept

function testH01_UserBalancesNeverUpdated() public {
// Alice likes Bob with 1 ETH
vm.prank(alice);
likeRegistry.likeUser{value: 1 ether}(bob);
// BUG: userBalances[alice] is still 0 despite sending 1 ETH
assertEq(likeRegistry.userBalances(alice), 0);
assertEq(address(likeRegistry).balance, 1 ether);
// Bob likes Alice → mutual match triggers matchRewards
vm.prank(bob);
likeRegistry.likeUser{value: 1 ether}(alice);
// After match, balances still 0
assertEq(likeRegistry.userBalances(alice), 0);
assertEq(likeRegistry.userBalances(bob), 0);
// 2 ETH locked in LikeRegistry
assertEq(address(likeRegistry).balance, 2 ether);
// Owner cannot withdraw fees (totalFees always 0)
vm.prank(address(this));
vm.expectRevert("No fees to withdraw");
likeRegistry.withdrawFees();
}
function testH01_MatchRewardsReceivesZeroETH() public {
uint256 nonceBefore = vm.getNonce(address(likeRegistry));
vm.prank(alice);
likeRegistry.likeUser{value: 1 ether}(bob);
vm.prank(bob);
likeRegistry.likeUser{value: 1 ether}(alice);
// MultiSig was deployed but received 0 ETH
address multiSig = vm.computeCreateAddress(address(likeRegistry), nonceBefore);
assertEq(multiSig.balance, 0); // <-- BUG: core feature broken
assertEq(address(likeRegistry).balance, 2 ether); // All ETH locked
}

Run with:

forge test --match-contract DatingDappPoC -vvv

Recommended Mitigation

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

Lead Judging Commences

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