# Attackers can bypass `tokensPerMint` and mint lots of tokens in a transaction

NFTCollection's per-tx tokensPerMint cap is bypassed via ERC721A _safeMint onERC721Received callback reentering mint(), letting an attacker mint unlimited tokens in one transaction.

- Payout: Contest reward
- Severity: medium
- Program: Nftport
- Credited: 0xSmartContract
- Disclosed: 2022-10-18
- Write-up quality: 6/10
- Original: https://github.com/sherlock-audit/2022-10-nftport-judging/issues/85
- Library page: https://bugbountylibrary.com/r/sherlock%3A2022-10-nftport%3AM-4
- Tags: domain:web3 impact:integrity kind:contest platform:sherlock sev:medium tech:evm tech:solidity technique:source-review vuln:business-logic vuln:reentrancy

**Target:** NFTCollection.sol mint()/presaleMint() ERC721A template
**Root cause:** mint() checks amount <= tokensPerMint then calls _safeMint(), whose onERC721Received callback to a contract recipient reenters mint() before state accounting completes, re-passing the same per-call check.
**Steps:**
1. Deploy AttackerContract implementing onERC721Received.
2. AttackerContract calls NFTCollection.mint(5) where tokensPerMint=5.
3. L318 check amount<=tokensPerMint passes; _safeMint() invoked.
4. ERC721A calls onERC721Received on AttackerContract.
5. Inside callback, call mint(5) again; check passes again.
6. Repeat recursion to mint 5+5+... tokens in a single transaction.
**How found:** Code review of mint() flow: noticed _safeMint() triggers an external callback to contract recipients and that tokensPerMint is enforced per-call rather than per-transaction, hypothesizing reentrancy re-passes the check.
**Exploitation:** Attacker contract recursively reenters mint() from the ERC721A receive callback, each call satisfying the per-call tokensPerMint check, accumulating far more than the intended cap per transaction; noted as practically limited to free mints since msg.value is 0 in the reentrant call and paymentProvided would revert paid mints.
**Insight:** When a per-transaction limit is checked inside a function that later makes an external callback (_safeMint/_safeTransferFrom), the limit is per-call, not per-tx; test contract recipients to reenter and re-satisfy the check.
