2️⃣Tutorial 66: Vulnerability Arising from NFTs Supporting Both ERC721 and ERC1155 Standards

Introduction

In the world of NFTs, tokens are primarily based on two Ethereum token standards: ERC721 and ERC1155. ERC721 tokens are unique, while ERC1155 tokens can represent both unique and fungible tokens. However, some projects implement tokens that support both ERC721 and ERC1155 standards, which can cause issues in contracts that do not properly handle such cases. One common vulnerability is when a contract, such as an exchange platform, attempts to transfer NFTs but fails to distinguish between ERC721 and ERC1155 tokens correctly. This tutorial will explore how this vulnerability can manifest and how developers can avoid it.

Understanding the Standards

  • ERC721: A standard for non-fungible tokens (NFTs), where each token has a unique ID. The safeTransferFrom function is used to transfer tokens between addresses, ensuring that the recipient can handle the token.

  • ERC1155: A multi-token standard that supports both fungible and non-fungible tokens. It allows transferring multiple tokens in a single operation via safeTransferFrom and safeBatchTransferFrom.

Some real-world NFTs, such as those from The Sandbox Game, implement both the ERC721 and ERC1155 interfaces. This is where the vulnerability can arise, as smart contracts need to handle tokens that support both standards properly.

Vulnerability Explanation

The problem occurs when an NFT collection supports both ERC721 and ERC1155 standards. If a contract assumes a token is either ERC721 or ERC1155, it may handle the token transfer incorrectly. For instance, an exchange contract might attempt to transfer tokens assuming they are ERC721, but because the token also supports ERC1155, it may transfer fewer tokens than expected.

Here's an example of vulnerable code that does not correctly handle NFTs supporting both standards:

solidityCopy codefunction _transferNFTs(
  address from,
  address to,
  OrderTypes.OrderItem calldata item
) internal {
  if (IERC165(item.collection).supportsInterface(0x80ac58cd)) { // ERC721
    _transferERC721s(from, to, item);
  } else if (IERC165(item.collection).supportsInterface(0xd9b67a26)) { // ERC1155
    _transferERC1155s(from, to, item);
  }
}

In this example:

  • The contract checks if the token supports the ERC721 interface (0x80ac58cd).

  • If it does, it calls the function to transfer ERC721 tokens.

  • If it doesn’t, it checks for ERC1155 support (0xd9b67a26) and handles the token accordingly.

However, if a token supports both interfaces, the contract will prioritize the ERC721 transfer, potentially leading to incorrect behavior. Specifically, if a user tries to transfer multiple tokens, but the contract treats them as ERC721, only one token might be transferred, leading to a loss of tokens or an incorrect transaction.

Real-World Example

Let’s consider the example of The Sandbox Game’s asset tokens. These tokens support both ERC721 and ERC1155 interfaces. If a user wants to transfer two tokens with the same ID using a contract that prioritizes ERC721 transfers, the following will happen:

  • The contract will detect ERC721 support and attempt to transfer the tokens using the ERC721 transfer function (safeTransferFrom).

  • However, because the token also supports ERC1155, the transfer may be treated as a single ERC1155 transfer, resulting in only one token being transferred.

  • This results in the user paying for two tokens but receiving only one.

This vulnerability is especially critical in NFT marketplaces or exchange platforms where users expect fair transactions.

Impact

The primary impact of this vulnerability is under-transferring tokens, meaning that fewer tokens are transferred than expected. This can lead to:

  • User dissatisfaction: Users may pay for more tokens than they receive.

  • Token freezing: Some tokens may become stuck in the contract due to incorrect transfer logic.

  • Financial loss: Users or the platform may suffer financial losses due to incorrect token handling.

Mitigation Steps

To avoid this vulnerability, contracts must handle NFTs that support both ERC721 and ERC1155 properly. One way to do this is to prioritize the ERC1155 interface check before the ERC721 check, as ERC1155 supports bulk transfers.

Here’s the corrected code:

function _transferNFTs(
  address from,
  address to,
  OrderTypes.OrderItem calldata item
) internal {
  if (IERC165(item.collection).supportsInterface(0xd9b67a26)) { // ERC1155
    _transferERC1155s(from, to, item);
  } else if (IERC165(item.collection).supportsInterface(0x80ac58cd)) { // ERC721
    _transferERC721s(from, to, item);
  }
}

By checking for ERC1155 support first:

  • The contract can properly handle multi-token transfers.

  • It reduces the risk of transferring fewer tokens than expected, especially when dealing with tokens that support both standards.

Additionally, developers should always review the tokens they are dealing with and consider implementing more specific checks to ensure that the correct interface is being used for each transaction. In some cases, you may also want to log the details of token transfers to ensure that the intended number of tokens are transferred.

Conclusion

When developing contracts that interact with NFTs, especially in marketplaces or exchanges, it's crucial to handle tokens that may support multiple standards (such as ERC721 and ERC1155) properly. Failing to do so can result in incorrect token transfers, financial losses, and poor user experiences.

The key takeaway is to always prioritize the ERC1155 interface check before the ERC721 check when dealing with NFTs that might support both standards. By doing so, you can ensure that token transfers occur as expected and that users receive the correct number of tokens.

Last updated