DatingDapp

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

`burnProfile()` leaves stale like state that blocks reminted profiles

Severity

Low

Likelihood

High

Root + Impact

burnProfile() deletes the NFT, but stale likes remain and block future interactions after reminting.

Description

  • The expected behavior is that burning a profile fully removes that profile's active dating state, so a user who later mints a new profile can interact as a fresh participant.

  • Instead, burnProfile() only clears NFT ownership and metadata in SoulboundProfileNFT, while LikeRegistry keeps likes[msg.sender][liked] = true. As a result, a reminted user inherits stale relationship state and can be prevented from liking the same user again.

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];
}
function likeUser(address liked) external payable {
require(msg.value >= 1 ether, "Must send at least 1 ETH");
@> require(!likes[msg.sender][liked], "Already liked");
...
@> likes[msg.sender][liked] = true;
}

Risk

Likelihood:

  • This occurs during the normal lifecycle where a user likes another profile, burns their profile, and later mints a new one.

  • The stale state persists automatically because burnProfile() does not trigger any cleanup in LikeRegistry.

Impact:

  • A reminted profile can be blocked from performing valid interactions because it inherits the previous profile's like state.

  • This makes profile deletion incomplete and leaves the protocol in an inconsistent cross-contract state.

Proof of Concept

The following test shows that after burning the original profile and minting a new one, the user is still treated as having already liked the same target because likes[msg.sender][liked] was never cleared in LikeRegistry.

function test_burnProfile_doesNotClearPreviousLikeState() public {
address wallet_A = makeAddr("Wallet_A");
address wallet_B = makeAddr("Wallet_B");
deal(wallet_A, 2 ether);
deal(wallet_B, 1 ether);
vm.prank(wallet_A);
soulboundNFT.mintProfile("Wallet_A", 25, "ipfs://Wallet_A");
vm.prank(wallet_B);
soulboundNFT.mintProfile("Wallet_B", 35, "ipfs://Wallet_B");
vm.prank(wallet_A);
registry.likeUser{value: 1 ether}(wallet_B);
// Burn the original profile.
vm.prank(wallet_A);
soulboundNFT.burnProfile();
assertEq(soulboundNFT.profileToToken(wallet_A), 0);
assertTrue(registry.likes(wallet_A, wallet_B));
// Mint a new profile for the same address.
vm.prank(wallet_A);
soulboundNFT.mintProfile("Wallet_A_v2", 26, "ipfs://Wallet_A_v2");
// The old like state still blocks the new profile.
vm.prank(wallet_A);
vm.expectRevert("Already liked");
registry.likeUser{value: 1 ether}(wallet_B);
}

Recommended Mitigation

Do not allow burnProfile() while the user still has active registry state, unless the protocol is redesigned to track and safely clear all dependent likes and matches.

+ interface ILikeRegistry {
+ function hasActiveState(address user) external view returns (bool);
+ }
contract SoulboundProfileNFT is ERC721, Ownable {
+ ILikeRegistry public likeRegistry;
+
+ function setLikeRegistry(address _likeRegistry) external onlyOwner {
+ likeRegistry = ILikeRegistry(_likeRegistry);
+ }
function burnProfile() external {
...
+ require(!likeRegistry.hasActiveState(msg.sender), "Active registry state exists");
_burn(tokenId);
...
emit ProfileBurned(msg.sender, tokenId);
}
}
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!