# Reentrancy Attack in ERC-721

> In this article, we'll examine how attackers can manipulate the _safeMint function's external call to bypass minting limits and drain NFT collections.

- Canonical: https://ack3.ai/research/reentrancy-attack-in-erc-721/
- Published: August 4, 2025
- Authors: Naoki Yoshida
- Category: Technical guide
- Topics: education, ethereum, exploit, hack, reentrancy-attack, solidity, tutorial, wake

ERC721 tokens have become the backbone of the NFT ecosystem, however their implementation contains subtle security risks that developers often overlook. The ERC721 standard includes a safety mechanism called the `ERC721Receiver` hook, designed to prevent tokens from being lost when sent to contracts. However, this same mechanism introduces an external call that can be exploited through reentrancy attacks.

In this article, we’ll examine how attackers can manipulate the `_safeMint` function’s external call to bypass minting limits and drain NFT collections, even when developers believe they’ve followed secure coding practices.

## Example Contract: Expected Usage

The `Masks` contract extends ERC721 and manages NFT minting with the following constraints:

  * Users can call the `mintNFT` function to mint NFTs
  * Maximum of 20 NFTs per transaction
  * Total supply limited by `MAX_NFT_SUPPLY`



## Vulnerable Contract

The vulnerability arises from the external call `IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, data)` within the `_safeMint` function.

The call flow follows this pattern:

  * `mintNFT` calls `_safeMint`
  * `_safeMint` calls `_safeMint` to set arguments
  * `_safeMint` calls `_checkOnERC721Received`
  * `_checkOnERC721Received` calls `IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, data)`



The Masks contract checks the number of NFTs being minted at the function’s beginning. `totalSupply()` uses `_tokenOwners.length()`.

This value is updated by `_tokenOwners.set(tokenId, to);` in the `_mint` function, and after that `_checkOnERC721Received` is called. So it initially seems that reentrancy does not occur.

However, the condition in `mintNFT` compares the current `totalSupply` and `numberOfNft` with `MAX_NFT_SUPPLY`.

If an attacker re-enters the for loop, the comparison uses an outdated `totalSupply` value, allowing excess NFT minting.

## Attack Example

The attack proceeds through these steps:

  * Attacker calls `mintNFT(20)`
  * Let’s say the value of `totalSupply()` is `N`
  * The `_mint()` function updates `_tokenOwners`, so now `totalSupply()` is `N+1`
  * The function `_checkOnERC721Received` calls `onERC721Received()` in the attacker contract:
    * Attacker calls `mintNFT(20)` again via reentrancy
    * At this moment `totalSupply()` is `N+1` **not`N+20` – this is the main point**
    * So we can generate 18 additional NFTs from `totalSupply()` of `N+1`
    * It will check whether `N+1+18` is less than `MAX_NFT_SUPPLY`, but it should be checked with `N+20+18`, which should revert
    * Repeat the process similarly



This is an attacker contract:

And this is the exploit:

The attacker successfully mints 110 NFTs, which exceeds both the 20 NFT limit and the maximum mintable number in one transaction.

## Prevention

Implement a reentrancy guard to prevent this vulnerability.

## Conclusion

The vulnerability demonstrated here highlights a critical lesson for [smart contract](/research/glossary/smart-contract/) developers: even when attempting to follow established security patterns like checks-effects-interactions, the introduction of loops with external calls can create unexpected attack vectors. The ERC721 standard’s safety features, while well-intentioned, can become security liabilities without proper safeguards.

This case underscores why comprehensive security audits and reentrancy guards are essential for any contract handling valuable assets. As the DeFi and NFT ecosystems continue to evolve, developers must remain vigilant about these subtle but devastating vulnerabilities that can bypass seemingly robust validation logic.

We maintain a [Reentrancy Examples Github Repository](https://github.com/Ackee-Blockchain/reentrancy-examples) that covers other types of reentrancy attacks and protocol-specific reentrancies.

We have also written about type-specific reentrancy attacks:

  * [Single Function Reentrancy Attack](/research/single-function-reentrancy-attack/)
  * [Cross Function Reentrancy Attack](/research/cross-function-reentrancy-attack/)
  * [Cross Contract Reentrancy Attack](/research/cross-contract-reentrancy-attack/)
  * [Read Only Reentrancy Attack](/research/read-only-reentrancy-attack/)



### Resource

<https://samczsun.com/the-dangers-of-surprising-code/>
