When a user calls likeUser, they are required to send at least MIN_LIKE_AMOUNT ETH, which is stored in the likes[msg.sender][liked].totalSent field and held by the contract until a mutual like occurs and matchRewards is executed.
However, there is no mechanism to refund or reclaim these funds if the liked user’s profile is removed before a match is formed. A profile can be removed in two ways:
The liked user voluntarily calls burnProfile.
The contract owner forcibly removes the profile using blockProfile.
In both cases, the liked user’s profile NFT is burned and their profile data is deleted, making it impossible for them to ever like back and complete a match. Despite this, the ETH previously sent by the liking user remains locked in the contract, with no function available to withdraw or refund it.
As a result, any user who likes a profile that is later burned or blocked permanently loses access to their deposited ETH, even though no match can ever be formed and no rewards will ever be distributed.
Impact: Medium
User funds sent during likeUser can become permanently locked inside the contract.
Users have no way to recover their deposited ETH if the counterparty profile is burned or blocked before a match is formed.
Funds are lost without any fault from the sending user, even though no rewards are ever distributed.
=> This results in direct financial loss and degrades user trust in the protocol.
Likelihood: Medium
This issue occurs whenever a liked profile is removed before forming a match.
Any user who likes a profile that is later burned by the user or blocked by the owner will be affected.
Profile burning and admin blocking are expected lifecycle operations, not exceptional edge cases.
No malicious behavior is required—normal contract usage is sufficient to trigger the issue.
Copy the code below to testSoulboundProfileNFT.t.sol.
Run command forge test --mt testLikedProfilesThenBurn -vvvv.
Put this following mitigation in conntract LikeRegistry:
Add mapping(address => address[]) public likedUsers; to track all users that each address has liked.
Add uint256 public constant MAX_LIKEDUSER_AMOUNT = 100; to cap the number of liked users per account and prevent gas limit issues
Add a refundUnmatchedLikesFromBurnedProfiles function to allow users to reclaim funds sent to profiles that have been burned or blocked before a match is formed.
## Description App owner can block users at will, causing users to have their funds locked. ## Vulnerability Details `SoulboundProfileNFT::blockProfile` can block any app's user at will. ```js /// @notice App owner can block users function blockProfile(address blockAddress) external onlyOwner { uint256 tokenId = profileToToken[blockAddress]; require(tokenId != 0, "No profile found"); _burn(tokenId); delete profileToToken[blockAddress]; delete _profiles[tokenId]; emit ProfileBurned(blockAddress, tokenId); } ``` ## Proof of Concept The following code demonstrates the scenario where the app owner blocks `bob` and he is no longer able to call `LikeRegistry::likeUser`. Since the contract gives no posibility of fund withdrawal, `bob`'s funds are now locked. Place `test_blockProfileAbuseCanCauseFundLoss` in `testSoulboundProfileNFT.t.sol`: ```js function test_blockProfileAbuseCanCauseFundLoss() public { vm.deal(bob, 10 ether); vm.deal(alice, 10 ether); // mint a profile NFT for bob vm.prank(bob); soulboundNFT.mintProfile("Bob", 25, "ipfs://profileImage"); // mint a profile NFT for Alice vm.prank(alice); soulboundNFT.mintProfile("Alice", 25, "ipfs://profileImage"); // alice <3 bob vm.prank(alice); likeRegistry.likeUser{value: 1 ether}(bob); vm.startPrank(owner); soulboundNFT.blockProfile(bob); assertEq(soulboundNFT.profileToToken(msg.sender), 0); vm.startPrank(bob); vm.expectRevert("Must have a profile NFT"); // bob is no longer able to like a user, as his profile NFT is deleted // his funds are effectively locked likeRegistry.likeUser{value: 1 ether}(alice); } ``` And run the test: ```bash $ forge test --mt test_blockProfileAbuseCanCauseFundLoss Ran 1 test for test/testSoulboundProfileNFT.t.sol:SoulboundProfileNFTTest [PASS] test_blockProfileAbuseCanCauseFundLoss() (gas: 326392) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.42ms (219.63µs CPU time) Ran 1 test suite in 140.90ms (1.42ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests) ``` ## Impact App users can have their funds locked, as well as miss out on potential dates. ## Recommendations Add a voting mechanism to prevent abuse and/or centralization of the feature.
The contest is live. Earn rewards by submitting a finding.
Submissions are being reviewed by our AI judge. Results will be available in a few minutes.
View all submissionsThe contest is complete and the rewards are being distributed.