♻️Preventing Reentrancy When Using SafeERC721

The SafeERC721 library from OpenZeppelin is often used to ensure the safe transfer of ERC-721 tokens by verifying that contracts correctly implement the onERC721Received interface. However, this does not automatically protect against reentrancy attacks. The use of onERC721Received creates an external call to the recipient contract, which opens a potential reentrancy attack vector if the recipient contract is malicious or incorrectly implemented.

This tutorial focuses on how reentrancy vulnerabilities can arise when using SafeERC721 and what precautions to take to ensure your contracts are secure.


Understanding SafeERC721 and Reentrancy

OpenZeppelin's SafeERC721 provides a safer way to transfer non-fungible tokens (NFTs) by ensuring that the recipient contract implements the IERC721Receiver interface. The key point of vulnerability lies in the onERC721Received hook. When transferring a token using safeTransferFrom, the transfer process checks whether the recipient is a contract. If it is, the contract must implement the onERC721Received function.

Here’s a simplified version of how safeTransferFrom works:

solidityCopy codefunction safeTransferFrom(
    address from,
    address to,
    uint256 tokenId,
    bytes memory data
) public {
    _transfer(from, to, tokenId);

    if (to.isContract()) {
        require(
            IERC721Receiver(to).onERC721Received(msg.sender, from, tokenId, data) ==
            IERC721Receiver.onERC721Received.selector,
            "ERC721: transfer to non ERC721Receiver implementer"
        );
    }
}

After the token is transferred, the contract calls onERC721Received on the recipient. This is where the risk of reentrancy comes into play.


The Risk of Reentrancy in onERC721Received

The onERC721Received hook is executed by the recipient contract after the token transfer is initiated. A malicious contract could use this function to perform reentrant calls, such as calling back into the original contract and executing additional operations before the original transaction has finished processing. This can result in the contract being in an inconsistent state and exploited by the attacker.

onERC721Received function is used to re-enter the original contract and perform an additional call before the first transfer completes. If your contract doesn’t guard against reentrancy, it could allow an attacker to transfer tokens multiple times or manipulate the contract’s state.


How to Prevent Reentrancy When Using SafeERC721

To protect against reentrancy attacks when using SafeERC721, you need to:

  1. Use OpenZeppelin's ReentrancyGuard modifier: This prevents functions from being called recursively during the execution of a transaction.

  2. Update state variables before making external calls: Always update the contract's state before calling safeTransferFrom or any other external function to avoid having an outdated state during a reentrancy attempt.

Last updated