Minting new CouncilMemeber NFTs is no longer possible after burning any but last of the NFTs
Summary
The CouncilMember::mint() method utilizes totalSupply() call to determine the new NFT index. Because in the process of burning NFT totalSupply is decreased, if any of the NFTs (excluding the last one) gets burned, no subsequent mints will be possible, as the contract will try to mint the already existing token ID.
Vulnerability Detail
When new CouncilMember token gets minted, total supply is used to determine which index should the new token get.
_mint(newMember, totalSupply());
The issue is that the totalSupply can be decreased by the CouncilMember::burn() method call. As such, if a token gets burned, subsequent CouncilMember::mint() will revert.
Please consider the following scenario:
Alice mints NFT ID 0. Total supply gets updated to 1.
Bob mints NFT ID 1. Total supply gets updated to 2.
Alice burns her NFT. Total supply gets updated to 1.
Charlie wants to mint a NFT. The CouncilMember::mint() will try to mint NFT ID 1 for him. This will revert, as this ID is already owned by Bob.
The POC for the issue is provided below. Please paste it into CouncilMember.test.ts test suite.
POC
describe("POC", () => {
it.only("Burning any NFT locks the last NFT owner from their balance", async () => {
/* 1. Mint 3 NFTs */
await expect(councilMember.mint(member.address)).to.not.reverted;
await expect(councilMember.mint(support.address)).to.not.reverted;
await expect(councilMember.mint(holder.address)).to.not.reverted;
/* 2. Burn one NFT */
await councilMember.burn(1, support.address);
//* 3. No new NFTs can be minted, as the contract attempts to mint ID 1 again */
await expect(councilMember.mint(member.address)).to.be.revertedWithCustomError(councilMember, "ERC721InvalidSender");
});
})
Please note that burning the NFT with the highest ID so far will not have the aforementioned consequences, as this ID will no longer be owned. The issue applies to burning any but last of the NFTs.
Impact
High, as the core functionality of the protocol will be DOSed forever.
sobieski
high
Minting new CouncilMemeber NFTs is no longer possible after burning any but last of the NFTs
Summary
The
CouncilMember::mint()
method utilizestotalSupply()
call to determine the new NFT index. Because in the process of burning NFTtotalSupply
is decreased, if any of the NFTs (excluding the last one) gets burned, no subsequent mints will be possible, as the contract will try to mint the already existing token ID.Vulnerability Detail
When new
CouncilMember
token gets minted, total supply is used to determine which index should the new token get.The issue is that the
totalSupply
can be decreased by theCouncilMember::burn()
method call. As such, if a token gets burned, subsequentCouncilMember::mint()
will revert.Please consider the following scenario:
CouncilMember::mint()
will try to mint NFT ID 1 for him. This will revert, as this ID is already owned by Bob.The POC for the issue is provided below. Please paste it into
CouncilMember.test.ts
test suite.POC
Please note that burning the NFT with the highest ID so far will not have the aforementioned consequences, as this ID will no longer be owned. The issue applies to burning any but last of the NFTs.
Impact
High, as the core functionality of the protocol will be DOSed forever.
Code Snippet
https://github.com/sherlock-audit/2024-01-telcoin/blob/0954297f4fefac82d45a79c73f3a4b8eb25f10e9/telcoin-audit/contracts/sablier/core/CouncilMember.sol#L181
Tool used
Manual Review
Recommendation
Keep track of the number of NFTs minted so far and use that number to determine the new index instead of
totalSupply
.Duplicate of #199