# Duplicate NFTs Can Be Minted if payableToken Has a Callback Attached to it

Joyn CoreCollection.mintToken pays payableToken before updating totalSupply, allowing reentrancy to bypass maxSupply and mint duplicate tokenIds.

- Payout: Audit contest
- Severity: high
- Program: Joyn
- Found by: leastwood
- Disclosed: 2022-07-25
- Write-up quality: 7/10
- Original: https://github.com/code-423n4/2022-03-joyn-findings/issues/121
- Library page: https://bugbountylibrary.com/r/c4%3A2022-03-joyn%3AH-07
- Tags: domain:web3 impact:integrity kind:contest platform:code4rena sev:high tech:evm tech:solidity technique:source-review vuln:accounting-error vuln:reentrancy

**Target:** ERC721 CoreCollection mintToken / ERC721Payable
**Root cause:** mintToken transfers the payableToken fee before incrementing totalSupply, so a callback-capable token can reenter while totalSupply is stale, bypassing the maxSupply check and wrapping tokenId generation.
**Steps:**
1. Set startingIndex=0, maxSupply=100; tokenId = ((startingIndex + totalSupply()) % maxSupply) + 1.
2. Mint until totalSupply()=99 (last token).
3. Use a payableToken with a callback (e.g. ERC777-style hook) triggered during the fee transfer in mintToken.
4. Reenter mintToken before totalSupply is updated; totalSupply()%maxSupply=0 so tokenId computes to 1, duplicating an existing token.
**How found:** Code reading of mintToken ordering: fee paid before totalSupply update, combined with tokenId formula using modulo maxSupply, revealing wrap-around to duplicate ids on reentry.
**Exploitation:** Attacker mints the final token with a callback-bearing payableToken; the transfer hook reenters mintToken while totalSupply is still 99, bypassing totalSupply()+amount<=maxSupply and minting a duplicate tokenId.
**Insight:** When tokenId is derived from totalSupply modulo maxSupply, any reentrancy before the supply increment can wrap ids back to already-minted values; check whether the payment token can callback and whether supply is updated after the external transfer.
