# CouncilMember:burn renders the contract inoperable after the first execution

CouncilMember.burn pops the balances array while tokenIds stay fixed, misaligning them and bricking claim, burn, and mint.

- Payout: Contest reward
- Severity: high
- Program: Telcoin
- Credited: 0xAsen
- Disclosed: 2024-01-04
- Write-up quality: 6/10
- Original: https://github.com/sherlock-audit/2024-01-telcoin-judging/issues/199
- Library page: https://bugbountylibrary.com/r/sherlock%3A2024-01-telcoin%3AH-3
- Tags: domain:web3 impact:dos kind:contest platform:sherlock sev:high tech:evm tech:solidity technique:source-review vuln:business-logic

**Target:** CouncilMember ERC721 governance contract (Solidity)
**Root cause:** burn() calls balances.pop() but tokenIds are assigned from totalSupply() at mint, so after a burn the array index no longer matches existing tokenIds; the highest tokenId now exceeds balances.length.
**Steps:**
1. Mint a new member: `councilMember.mint(lastCouncilMember.address)` pushes balances and mints tokenId = totalSupply().
2. Burn tokenId 1: `councilMember.burn(1, support.address)` pops balances, shrinking length below the highest tokenId.
3. Holder of tokenId 3 calls `claim(3, 1)` -> reverts with Panic 0x32 (out-of-bounds array access).
4. `burn(3, ...)` also reverts Panic 0x32.
5. `mint(...)` reverts ERC721InvalidSender due to tokenId collision with existing supply.
**How found:** Code reading of burn() and mint() revealed balances.pop() versus tokenId = totalSupply(), exposing index/tokenId drift after the first burn.
**Exploitation:** Any single burn by an authorized burner permanently desynchronizes balances from tokenIds, blocking claims, further burns, and new mints for all members.
**Insight:** When an ERC721 uses totalSupply() as tokenId and a parallel array indexed by tokenId, any pop() on burn breaks the invariant; check array/tokenId coupling in NFT+balance designs.
