Learn how attackers can exploit reentrancy vulnerabilities in ERC-1155 implementations to drain vault contracts. This practical example demonstrates a real-world attack scenario.

ERC-1155 is a multi-token standard for managing multiple token types in a single contract. While powerful, this flexibility creates potential attack vectors through external calls.

Understanding the vulnerable contract

We’ll examine a simplified vault contract that demonstrates the reentrancy vulnerability. Here’s how it should work:

  • Users create ETH-locked NFTs via the create function
  • These NFTs can be freely transferred between users
  • Users unlock ETH using the payEth function
  • NFT holders can then withdraw ETH by burning their NFTs

Below is the vulnerable Vault contract implementation:

Exploiting the vulnerability

The vulnerability lies in the mint function’s external call to IERC1155Receiver(to).onERC1155Received(). This call occurs before updating the fnftsCreated counter, creating a reentrancy opportunity.

The attack vector

The attacker exploits two key contract features:

  • The id_to_required_eth[nft_id] mapping controls the ETH lock amount
  • The nft_price[nft_id] sets the price per individual NFT

Attack steps

1. Call create with a large nftAmount but small value
2. During the mint callback, reenter with a small nftAmount but large value
3. This sets a high nft_price[nft_id] for all NFTs
4. Withdraw to receive: total_nfts * high_price in ETH

Detailed attack flow

Let’s break down the attack step by step:

  1. Initial creation
    • Attacker calls create(1000, 1 wei)
    • Vault mints 1000 NFTs with ID = k (getNextId())
  2. Reentrancy attack
    • During onERC1155Received() callback:
    • Attacker calls create(1, 1 ether)
    • Same nft_id (k) is used (counter not updated)
    • Sets nft_price[k] = 1 ether
  3. Profit extraction
    • Attacker unlocks with 1 ETH
    • Withdraws all 1001 NFTs
    • Receives 1001 ETH (1001 NFTs * 1 ETH price)

Attacker contract

Proof of concept

Below is the complete attack implementation with Wake testing framework:

Running this exploit successfully drains the vault:

Wake test output showing successful vault drain

Preventing the attack

Two key approaches can prevent this vulnerability:

  1. Checks-effects-interactions pattern
    • Update state variables before making external calls
    • This is the recommended approach
  2. Reentrancy guard
    • Use OpenZeppelin’s ReentrancyGuard
    • Adds a modifier to prevent reentrant calls

Here’s the fixed implementation:

Key takeaways

  • ERC standards’ external calls can create unexpected reentrancy vectors
  • State variables shared across contracts need careful handling
  • Always update state before external calls
  • Consider using reentrancy guards as an additional safety measure

Further reading

Explore our Reentrancy Examples Repository for more attack vectors: