Pieces Protocol

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

NFT-to-ERC20 mapping gets overwritten for NFTs from same collection, causing permanent token lock

Summary:

There is a potential issue with the association of ERC20 tokens and their original NFTs when creating a new ERC20 token for an NFT from the same collection.

Vulnerability Details:

When a new ERC20 token is created for an NFT from the same collection, it uses a new ERC20 contract address as the second key in the nftToErc20Info mapping. This can cause issues in tracking which ERC20 corresponds to which NFT, as the nftToErc20Info mapping will be overwritten for NFTs from the same collection. Although the balances mapping will track balances for both ERC20 tokens, the loss of proper association between ERC20 tokens and their original NFTs due to the nftToErc20Info mapping issue can lead to inconsistencies.

Impact:

The impact of this issue is that the contract may not be able to properly associate ERC20 tokens with their original NFTs, which can lead to confusion and unexpected behavior when interacting with the contract.

Tools Used:

Solidity, Foundry

Recommendations:

To avoid this issue, ensure that the nftToErc20Info mapping is properly maintained and updated when creating new ERC20 tokens for NFTs from the same collection. This can be achieved by using a more sophisticated data structure for storing the association between NFTs and ERC20 tokens, such as a mapping with a composite key (e.g., mapping(address => mapping(uint256 => ERC20Info))). This will allow for proper tracking of ERC20 tokens and their original NFTs, even when multiple ERC20 tokens are created for NFTs from the same collection.

Test proof:

function testNftToErc20MappingConfusion() public {
erc721Mock.mint(USER);
erc721Mock.mint(USER);
vm.startPrank(USER);
erc721Mock.approve(address(tokenDivider), 0);
erc721Mock.approve(address(tokenDivider), 1);
tokenDivider.divideNft(address(erc721Mock), 0, AMOUNT);
address firstErc20 = tokenDivider.getErc20InfoFromNft(address(erc721Mock), 0).erc20Address;
tokenDivider.divideNft(address(erc721Mock), 1, AMOUNT);
address secondErc20 = tokenDivider.getErc20InfoFromNft(address(erc721Mock), 1).erc20Address;
ERC20Mock(firstErc20).approve(address(tokenDivider), AMOUNT);
vm.expectRevert();
tokenDivider.claimNft(address(erc721Mock), 0);
vm.stopPrank();
assertEq(tokenDivider.getErc20InfoFromNft(address(erc721Mock), 0).erc20Address, firstErc20, "Mapping points to first ERC20");
assertNotEq(tokenDivider.getErc20InfoFromNft(address(erc721Mock), 0).erc20Address, secondErc20, "Mapping does not lost first ERC20");
assertEq(erc721Mock.ownerOf(0), address(tokenDivider), "First NFT not stuck in contract");
}
Updates

Lead Judging Commences

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

Wrong nft collection handling

Support

FAQs

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