DatingDapp

AI First Flight #6
Beginner FriendlyFoundrySolidityNFT
EXP
View results
Submission Details
Impact: high
Likelihood: high
Invalid

Missing funds from liking users

Description

  • For a "Like" mechanism, this usually means that when a user pays a fee to interact with another profile, those funds are either instantly transferred to the recipient, held until a mutual match occurs, or collected by a treasury as a service fee.

  • The specific issue with "likeUser" function is that it permanently locks the user's funds within the contract without any logic to transfer them out. While the function successfully updates the likes and matches state, it effectively acts as a "black hole" for ETH because there is no "address(liked).call{value: msg.value}("")" or similar withdrawal mechanism, meaning the 1 ETH is trapped in the contract balance forever. Also, the funds are not received by the intended recipient because the contract lacks a transfer or withdrawal mechanism.

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

Risk

Likelihood:

  • Every user attempting to engage with the core "matching" feature will inevitably trigger this vulnerability upon a successful transaction.

  • The absence of a centralized withdraw function or a userBalances mapping prevents any manual recovery of the trapped ETH, ensuring the funds remain locked in the contract's address indefinitely.

Impact:

  • Financial loss for users is absolute; participants who spend ETH to "like" others receive the social "state change" (the like record) but the economic value is permanently removed from circulation, leading to a total loss of principal for the sender and zero gain for the recipient.

  • The protocol's reputation and utility are severely compromised, as the accumulation of "stuck" funds creates a "honeypot" of unrecoverable liquidity that serves no functional purpose within the dating ecosystem, effectively acting as a "black hole" for user capital.

Proof of Concept

The Proof of Concept demonstrates that while the transaction successfully completes and the sender’s balance is deducted, the intended recipient's balance remains unchanged. This test case validates the "black hole" logic vulnerability by programmatically asserting that the financial state of the recipient does not reflect the economic intent of the "like" interaction.

function testMissingFunfsFromLike() public {
vm.prank(user);
soulboundNFT.mintProfile("Alice", 25, "ipfs://profileImage");
vm.prank(user2);
soulboundNFT.mintProfile("Bob", 30, "ipfs://profileImage2");
vm.deal(user, 2 ether);
vm.deal(user2, 2 ether);
uint256 initialBalance = user2.balance;
//vm.expectRevert("Must send at least 1 ETH");
vm.startPrank(user2);
uint256 likeValue = 1 ether;
likeRegistry.likeUser{value: likeValue}(user); // Should revert due to missing funds
console2.log("User2 balance after like attempt:", user2.balance);
console2.log("User balance after like attempt:", user.balance);
assertNotEq(user.balance, initialBalance, "User should receive rewards from the like");
vm.stopPrank();
}

Recommended Mitigation

Instead of letting the ETH sit "anonymously" in the contract's main balance, assign the msg.value to a specific user's record. This allows the funds to be "received" by the user in a ledger-based system, which they can later withdraw using a separate function.

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;
// FIX: Assign the sent funds to the 'liked' user's balance
+ userBalances[liked] += 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 2 hours ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!