A user's pending like funds are tied to their address in LikeRegistry.userBalances, but neither voluntary profile burning nor owner blocking refunds that balance. Once the profile is removed, the address fails the profile checks required to participate in a future mutual match, and the registry exposes no refund or cancellation function.
SoulboundProfileNFT.burnProfile and blockProfile at lines 43-66 burn the NFT and delete profileToToken without coordinating with LikeRegistry. LikeRegistry.likeUser at lines 35-36 requires both parties to still have profiles, while the contract has no function that lets a user withdraw an unmatched balance.
After the intended missing credit in likeUser is fixed, a user can deposit ETH in one or more pending likes and then burn their profile, or be blocked by the owner. Their balance remains in the registry, but no new mutual match can settle it.
Legitimate users can permanently lose all ETH committed to pending likes through a normal supported action or an administrative block.
Add an explicit cancellation/refund path in LikeRegistry using checks-effects-interactions: read the refundable unmatched balance, set it to zero, then transfer it to the user. Coordinate burning/blocking with the registry so pending funds are returned before profile state is deleted. Keep already-settled multisig funds out of this path.
Credit a user's pending balance, burn or block the profile, execute the refund flow, and assert the user receives the full unmatched amount and the registry balance entry becomes zero. Also assert a second refund reverts.
## 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.