DatingDapp

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

C-1: Like payments are never credited — all ETH permanently trapped, rewards and fees always zero

Root + Impact

likeUser demands msg.value >= 1 ether but never records it in userBalances[msg.sender]. matchRewards reads those always-zero balances, so the pooled reward is always 0 and totalFees never grows. withdrawFees is therefore permanently unreachable, and no function can recover the ETH that accumulates in the contract. Every like payment is lost forever and the protocol's advertised matching economy never functions.

Description

// src/LikeRegistry.sol
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;
@> // BUG: msg.value is never added to userBalances[msg.sender]
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);
}
}
​
function matchRewards(address from, address to) internal {
@> uint256 matchUserOne = userBalances[from]; // always 0 (never credited)
@> uint256 matchUserTwo = userBalances[to]; // always 0 (never credited)
userBalances[from] = 0;
userBalances[to] = 0;
uint256 totalRewards = matchUserOne + matchUserTwo; // always 0
uint256 matchingFees = (totalRewards * FIXEDFEE) / 100; // always 0
uint256 rewards = totalRewards - matchingFees; // always 0
totalFees += matchingFees;
MultiSigWallet multiSigWallet = new MultiSigWallet(from, to);
(bool success,) = payable(address(multiSigWallet)).call{value: rewards}(""); // sends 0
require(success, "Transfer failed");
}
​
function withdrawFees() external onlyOwner {
require(totalFees > 0, "No fees to withdraw"); // always reverts
@> // totalFees can never become > 0, so this path is dead
}

Risk

Likelihood: Certain — triggered by every single like, no special conditions or attacker needed.

Impact: Complete and permanent loss of all user deposits. Matched users receive an empty wallet; the protocol owner can never collect fees; the contract becomes an irreversible ETH sink.

Proof of Concept

function test_LikePaymentsTrapped() public {
_mint(alice);
_mint(bob);
vm.deal(alice, 10 ether);
vm.deal(bob, 10 ether);
​
vm.prank(alice);
registry.likeUser{value: 1 ether}(bob);
assertEq(address(registry).balance, 1 ether); // alice's payment held
​
vm.prank(bob);
registry.likeUser{value: 1 ether}(alice); // mutual -> matchRewards runs
assertEq(address(registry).balance, 2 ether); // both payments stranded
assertEq(registry.userBalances(alice), 0); // never credited
assertEq(registry.userBalances(bob), 0); // never credited
​
vm.expectRevert(bytes("No fees to withdraw")); // only exit is dead
registry.withdrawFees();
assertEq(address(registry).balance, 2 ether); // no recovery path
}

Recommended Mitigation

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

Additionally, reconcile the fee/payout split against the real contract balance and add a guarded rescue function for unattributed ETH.


Updates

Lead Judging Commences

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