Santa's List

AI First Flight #3
Beginner FriendlyFoundry
EXP
View results
Submission Details
Impact: high
Likelihood: medium
Invalid

No refund mechanism for unmatched likes -- ETH permanently locked

Description

  • The protocol expects users to deposit ETH when liking others. If the like is mutual, a match triggers and rewards are distributed. If not, the user's deposit should remain available.

  • There is no function allowing users to withdraw their userBalances for unmatched likes. The only ETH exit paths are matchRewards() (requires mutual match) and withdrawFees() (owner-only, fees only). If a user likes someone who never reciprocates, the deposited ETH is irrecoverable -- there is no unlike(), refund(), or withdrawBalance() function.

contract LikeRegistry is Ownable {
mapping(address => uint256) public userBalances;
// @> userBalances is read in matchRewards and reset to 0
// @> withdrawFees only withdraws totalFees, not userBalances
// @> No function exists to withdraw userBalances
// @> ETH from unmatched likes is permanently locked
}

Risk

Likelihood:

  • This occurs whenever a user likes someone who does not reciprocate -- a common scenario in a dating application

  • The more users interact with the protocol, the more unmatched ETH accumulates with no exit path

Impact:

  • Users lose >= 1 ETH for every unreciprocated like with no way to recover it

  • This creates a significant disincentive to use the protocol, as every like is a gamble with no refund option

Proof of Concept

function testNoRefundForUnmatchedLikes() public {
vm.prank(alice);
profileNFT.mintProfile("Alice", 25, "img");
vm.prank(bob);
profileNFT.mintProfile("Bob", 26, "img");
// Alice likes Bob with 1 ETH (assuming userBalances fix applied)
vm.prank(alice);
likeRegistry.likeUser{value: 1 ether}(bob);
// Bob never likes Alice back
// Alice has no function to call to recover her 1 ETH
assertEq(likeRegistry.userBalances(alice), 1 ether); // ETH tracked but not withdrawable
// No withdrawBalance() or refund() function exists
}

Recommended Mitigation

+ function withdrawBalance() external {
+ uint256 balance = userBalances[msg.sender];
+ require(balance > 0, "No balance to withdraw");
+ userBalances[msg.sender] = 0;
+ (bool success,) = payable(msg.sender).call{value: balance}("");
+ require(success, "Transfer failed");
+ }
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!