DatingDapp

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

[M-01] Like state persists after profile burn, enabling stale matches with reminted profiles

Description

When a user burns their profile via burnProfile(), like mappings in LikeRegistry are never cleared. A user who burns and remints carries forward stale likes, triggering unintended matches.

Vulnerability Details

burnProfile() deletes profileToToken and _profiles but has no way to clear the likes mapping in LikeRegistry:

// src/SoulboundProfileNFT.sol, line 44-54
function burnProfile() external {
uint256 tokenId = profileToToken[msg.sender];
require(tokenId != 0, "No profile found");
require(ownerOf(tokenId) == msg.sender, "Not profile owner");
_burn(tokenId);
delete profileToToken[msg.sender];
delete _profiles[tokenId];
// @> likes[msg.sender][...] in LikeRegistry NOT cleared
}

After reminting, likes[oldAddress][otherUser] is still true. When otherUser likes the reminted profile, the mutual check finds the stale like and triggers a match:

// src/LikeRegistry.sol, line 42
if (likes[liked][msg.sender]) { // @> stale like from old profile still returns true

The match is based on a like from the user's previous identity. The user never explicitly liked anyone with their new profile.

Risk

Likelihood:

  • Any user who burns and remints their profile retains all previous likes. Profile burn/remint is a normal user action (changing profile details), not an attack. The stale likes trigger matches the moment someone new likes them.

Impact:

  • Matches form without genuine mutual consent. ETH from the new liker is pooled with ETH from a like the other user made under a different identity. The stale like count lets someone accumulate likes across multiple profile iterations, gaming the matching system.

PoC

The test shows Alice liking Bob, then burning and reminting her profile. After the remint, likes[alice][bob] is still true from the old profile. When Bob then likes Alice, the stale mutual like triggers a match even though Alice never liked anyone with her new profile.

function testStaleMatchAfterBurn() public {
// Alice likes Bob with her first profile
vm.prank(alice);
likeRegistry.likeUser{value: 1 ether}(bob);
assertTrue(likeRegistry.likes(alice, bob));
// Alice burns her profile
vm.prank(alice);
profileNFT.burnProfile();
// Alice remints with a new identity
vm.prank(alice);
profileNFT.mintProfile("New Alice", 30, "ipfs://new");
// likes[alice][bob] is STILL true from old profile
assertTrue(likeRegistry.likes(alice, bob));
// Bob likes Alice -> stale mutual like triggers match
vm.prank(bob);
likeRegistry.likeUser{value: 1 ether}(alice);
// Match formed without Alice ever liking Bob with her new profile
}

Recommendations

Track likes by tokenId rather than address. When a profile is burned, the old tokenId becomes invalid, so all associated likes are automatically invalidated. New profiles get new tokenIds, starting with a clean like slate.

- mapping(address => mapping(address => bool)) public likes;
+ mapping(uint256 => mapping(uint256 => bool)) public likes; // tokenId => tokenId
function likeUser(address liked) external payable {
+ uint256 likerToken = profileNFT.profileToToken(msg.sender);
+ uint256 likedToken = profileNFT.profileToToken(liked);
+ require(likerToken != 0, "Must have a profile NFT");
+ require(likedToken != 0, "Liked user must have a profile NFT");
+ require(!likes[likerToken][likedToken], "Already liked");
+ likes[likerToken][likedToken] = true;
+ if (likes[likedToken][likerToken]) {
// match
}
}
Updates

Lead Judging Commences

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