Summary
TokenDivider::divideNft Allows Multiple ERC20 Token Deployments for Same NFT Leading to State Inconsistencies
Vulnerability Details
In the TokenDivider::divideNft
function, a new ERC20 token contract is deployed each time the function is called, even for the same NFT. This can lead to a serious issue where newer token deployments overwrite the previous mappings in nftToErc20Info
and erc20ToNft
, potentially causing loss of user funds and state inconsistencies.
Impact
This vulnerability can result in:
Loss of track of previously minted ERC20 tokens
Users unable to locate or claim their tokens from previous fractionalization
State inconsistencies in the protocol
Potential permanent loss of user funds locked in old ERC20 contracts
Proof of Concept: Add the following test to demonstrate the vulnerability:
function testMultipleErc20DeploymentsForSameNft() public {
vm.startPrank(USER);
ERC721Mock(erc721Mock).approve(address(tokenDivider), 0);
tokenDivider.divideNft(address(erc721Mock), 0, AMOUNT);
address firstErc20 = tokenDivider.getErc20InfoFromNft(address(erc721Mock)).erc20Address;
vm.startPrank(address(tokenDivider));
ERC721Mock(erc721Mock).safeTransferFrom(address(tokenDivider), USER, 0);
vm.stopPrank();
vm.startPrank(USER);
ERC721Mock(erc721Mock).approve(address(tokenDivider), 0);
tokenDivider.divideNft(address(erc721Mock), 0, AMOUNT);
address secondErc20 = tokenDivider.getErc20InfoFromNft(address(erc721Mock)).erc20Address;
assertFalse(firstErc20 == secondErc20);
ERC20Mock firstErc20Mock = ERC20Mock(firstErc20);
uint256 lostTokens = firstErc20Mock.balanceOf(USER);
assertEq(lostTokens, AMOUNT);
firstErc20Mock.approve(address(tokenDivider), AMOUNT);
vm.expectRevert();
tokenDivider.claimNft(address(erc721Mock));
vm.stopPrank();
}
Tools Used
Foundry
Recommendations
function divideNft(address nftAddress, uint256 tokenId, uint256 amount) onlyNftOwner(nftAddress, tokenId) external {
if(nftAddress == address(0)) { revert TokenDivider__NftAddressIsZero(); }
if(amount == 0) { revert TokenDivider__AmountCantBeZero(); }
+ if(nftToErc20Info[nftAddress].erc20Address != address(0)) {
+ revert TokenDivider__NftAlreadyFractionalized();
+ }
ERC20ToGenerateNftFraccion erc20Contract = new ERC20ToGenerateNftFraccion(
string(abi.encodePacked(ERC721(nftAddress).name(), "Fraccion")),
string(abi.encodePacked("F", ERC721(nftAddress).symbol())));
// Rest of the function remains the same
}