Description: After the protocol owner blocks a user, the protocol does not store a list of blocked users. Therefore, the user can create a new profile and continue operating. Additionally, for users who have already liked that profile, there is no way to withdraw the deposited funds.
Impact: The protocol has no way to permanently block a user. It also lacks a mechanism to withdraw the deposited funds for a blocked user, causing this ETH to be permanently locked in the smart contract.
Proof of Concept:
function testBlockedProfileUnlockYourself() public {
vm.prank(user);
soulboundNFT.mintProfile("Alice", 25, "ipfs://profileImage");
uint256 tokenId = soulboundNFT.profileToToken(user);
assertEq(tokenId, 1, "Token should exist before blocking");
vm.prank(owner);
soulboundNFT.blockProfile(user);
uint256 newTokenId = soulboundNFT.profileToToken(user);
assertEq(newTokenId, 0, "Token should be removed after blocking");
vm.prank(user);
soulboundNFT.mintProfile("Alice", 25, "ipfs://profileImage");
tokenId = soulboundNFT.profileToToken(user);
assert(tokenId != 0);
}
Recommended Mitigation:
mapping(address user => bool isBlock) private _isBlocked;
function mintProfile(string memory name, uint8 age, string memory profileImage) external {
require(profileToToken[msg.sender] == 0, "Profile already exists");
+ require(!_isBlocked[msg.sender], "Profile already blocked");
...
}
function blockProfile(address blockAddress) external onlyOwner { // @audit Blocked users should not be able to unblock themselves
uint256 tokenId = profileToToken[blockAddress];
require(tokenId != 0, "No profile found");
_burn(tokenId);
delete profileToToken[blockAddress];
delete _profiles[tokenId];
+ _isBlocked[blockAddress] = true;
emit ProfileBurned(blockAddress, tokenId);
}