DatingDapp

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

Ghost likes from burned profiles persist in `likes` mapping — stale likes trigger unexpected matches

Description

burnProfile and blockProfile both delete profileToToken and _profiles but never touch the likes mapping in LikeRegistry. A like is a permanent one-way record:

likes[msg.sender][liked] = true; // set in likeUser, never cleared

The mutual-like check reads this stale entry directly:

if (likes[liked][msg.sender]) { // true even if msg.sender burned their profile
matchRewards(liked, msg.sender);
}

This creates the following exploit path, enabled by M-01 (free re-mint after burn):

  1. User A likes User B and pays 1 ETH.

  2. User A burns their profile.

  3. User A re-mints a fresh profile (M-01).

  4. User B likes User A's new profile.

  5. likes[A][B] is still true from step 1 — a match fires immediately.

  6. User A matched without ever liking User B from their new identity.

Impact

The mutual-like invariant is broken. A user can engineer a match without genuinely expressing interest from their current profile — the ghost like from a discarded identity does the work. This undermines the core consent model of the matching system. Combined with M-01, the cost to an attacker is only a burn (free) and a re-mint (gas only), and they arrive in a match with whoever they liked before burning.

PoC

Test: testPoC_GhostLikePersistsAfterBurn in test/testLikeRegistry.t.sol

[PASS] testPoC_GhostLikePersistsAfterBurn() (gas: 1,230,796)
Flow:
alice likeUser(bob) {value: 1 ETH} -> likes[alice][bob] = true
alice burnProfile() -> profileToToken[alice] = 0
alice mintProfile("Alice_v2", ...) -> new profile, likes[alice][bob] still true
bob likeUser(alice) {value: 1 ETH} -> likes[bob][alice] = true
mutual check: likes[alice][bob] == true
match fires from ghost like
alice.getMatches()[0] = bob -> matched without re-liking

Run with: forge test --match-test testPoC_GhostLikePersistsAfterBurn -vvv

Recommended Mitigation

Clear the sender's outgoing likes in burnProfile and blockProfile. Because the likes mapping is keyed by address pairs, the contract cannot enumerate all addresses a user has liked — maintain a separate likedAddresses array per user to support cleanup:

mapping(address => address[]) private likedList;
// In likeUser:
likedList[msg.sender].push(liked);
// In burnProfile / blockProfile:
address[] storage liked = likedList[msg.sender];
for (uint256 i = 0; i < liked.length; i++) {
likeRegistry.clearLike(msg.sender, liked[i]);
}
delete likedList[msg.sender];

Alternatively, include a profileVersion nonce per address in the like key so that a re-mint automatically invalidates all prior likes without requiring iteration.

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!