DatingDapp

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

No refund or withdraw path for likes that never match, so unmatched like payments are locked forever

Root + Impact

Description

  • A user pays 1 ETH or more for each like. If the like is never returned, or the liked profile is blocked by the owner or burned, the money should not be lost forever.

  • LikeRegistry has no function that returns ETH to users. Funds only leave through matchRewards (needs a mutual like) or withdrawFees (owner only, limited to totalFees). Likes to someone who never likes back, or who gets blocked / burns their profile (then likeUser towards the liker is impossible because the liked user has no profile), are stuck forever. This remains true after fixing F1 (userBalances would hold the value but nothing lets the user take it back).

contract LikeRegistry is Ownable {
mapping(address => uint256) public userBalances;
...
function likeUser(address liked) external payable { ... } // money in
function matchRewards(...) internal { ... } // out only on a mutual like
@> function withdrawFees() external onlyOwner { ... } // owner, only totalFees
@> // no refund / withdraw for users
}

Path:

  1. Alice likes Bob with 1 ETH.

  2. The owner blocks Bob (or Bob burns his profile, or simply never likes Alice).

  3. Bob can never like Alice back (Must have a profile NFT), so no match will ever happen.

  4. Alice has no way to get her 1 ETH back; the owner cannot withdraw it either (it is not part of totalFees).

Risk

Likelihood:

  • Most likes in a dating app are not returned; blocks and profile deletions are normal operations.

Impact:

  • Every unreturned like permanently locks at least 1 ETH of user funds; across the user base this is the majority of all deposits.

Proof of Concept

Test setup: owner deploys SoulboundProfileNFT and LikeRegistry(address(nft)); alice, bob, carol each get 10 ETH and mint a profile (token ids 1, 2, 3). Full file test/AuditPoC.t.sol, forge test --mc AuditPoC passes.

function test_F2_unmatchedLikeFundsLocked() public {
vm.prank(alice);
reg.likeUser{value: 1 ether}(bob);
vm.prank(owner);
nft.blockProfile(bob); // bob can never like back
assertEq(address(reg).balance, 1 ether);
// LikeRegistry exposes no function returning ETH to alice; only owner-only withdrawFees.
vm.prank(owner);
vm.expectRevert("No fees to withdraw");
reg.withdrawFees();
}

Recommended Mitigation

Let users withdraw unmatched like payments (or refund after a timeout), keeping the pooled amount accounted per user:

+ function withdrawUnmatched() external {
+ uint256 amount = userBalances[msg.sender];
+ require(amount > 0, "Nothing to withdraw");
+ userBalances[msg.sender] = 0;
+ (bool ok,) = payable(msg.sender).call{value: amount}("");
+ require(ok, "Transfer failed");
+ }

If the product requires funds to stay locked until a match, add an expiry after which the liker can reclaim.

Updates

Lead Judging Commences

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