Learn how attackers can exploit reentrancy vulnerabilities in ERC-1155 implementations to drain vault contracts. This practical example demonstrates a real-world attack scenario.
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
createfunction - These NFTs can be freely transferred between users
- Users unlock ETH using the
payEthfunction - NFT holders can then
withdrawETH 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:
- Initial creation
- Attacker calls
create(1000, 1 wei) - Vault mints 1000 NFTs with ID =
k(getNextId())
- Attacker calls
- 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
- During
- 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:

Preventing the attack
Two key approaches can prevent this vulnerability:
- Checks-effects-interactions pattern
- Update state variables before making external calls
- This is the recommended approach
- 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: