Pieces Protocol

First Flight #32
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Severity: high
Invalid

Reentrancy in the claimNft function in TokenDivider.sol

Summary

The claimNft() function in the TokenDivider contract is vulnerable to reentrancy attacks due to improper state management. The vulnerability comes from the use of safeTransferFrom(), which can trigger callback mechanisms in recipient contracts, allowing malicious actors to manipulate the contract's state before the transaction completes.

Vulnerability Details

The safeTransferFrom() function of the ERC721 standard allows external contracts to execute code via the onERC721Received() callback. If the recipient is a malicious contract, this callback can be used to recursively call the vulnerable claimNft() function. Since the state updates occur before the transfer, the attacker can exploit the function to claim multiple NFTs or drain the contract's NFT holdings.

function claimNft(address nftAddress) external {
// Vulnerable sequence of operations
ERC20Info storage tokenInfo = nftToErc20Info[nftAddress];
// Token burning and state zeroing BEFORE transfer
ERC20ToGenerateNftFraccion(tokenInfo.erc20Address).burnFrom(msg.sender, erc20ToMintedAmount[tokenInfo.erc20Address]);
balances[msg.sender][tokenInfo.erc20Address] = 0;
erc20ToMintedAmount[tokenInfo.erc20Address] = 0;
// External transfer can trigger recursive callback
IERC721(nftAddress).safeTransferFrom(address(this), msg.sender, tokenInfo.tokenId);
}

Example Exploit Scenario

  1. An attacker creates a malicious contract implementing onERC721Received().

  2. The attacker calls claimNft() with a prepared NFT address.

  3. During the execution of safeTransferFrom(), the onERC721Received() callback is triggered. Within the callback, the attacker recursively calls claimNft() while the internal state (e.g., balances and erc20ToMintedAmount) has already been reset.

  4. The function's state has already been modified (balances zeroed), allowing multiple unauthorized NFT claims.

Impact

  • Unauthorized multiple NFT withdrawals

  • Potential complete drainage of contract's NFT holdings

  • Manipulation of token burning and transfer mechanisms

  • Significant financial risk to the protocol and its users

Recommendation

The function should be written this way

function claimNft(address nftAddress) external nonReentrant {
// Validate input
if(nftAddress == address(0)) {
revert TokenDivider__NftAddressIsZero();
}
ERC20Info storage tokenInfo = nftToErc20Info[nftAddress];
uint256 requiredAmount = erc20ToMintedAmount[tokenInfo.erc20Address];
// Strict balance checks
if(balances[msg.sender][tokenInfo.erc20Address] < requiredAmount) {
revert TokenDivider__NotEnoughErc20Balance();
}
// Update state BEFORE external interactions
balances[msg.sender][tokenInfo.erc20Address] -= requiredAmount;
erc20ToMintedAmount[tokenInfo.erc20Address] -= requiredAmount;
// Burn tokens
ERC20ToGenerateNftFraccion(tokenInfo.erc20Address).burnFrom(msg.sender, requiredAmount);
// Transfer NFT as final action
IERC721(nftAddress).safeTransferFrom(address(this), msg.sender, tokenInfo.tokenId);
// Emit event for successful claim
emit NftClaimed(nftAddress, msg.sender);
}
}
Updates

Lead Judging Commences

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

Reentrancy

Appeal created

fishy Lead Judge 5 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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