0
0
BlockchainDebug / FixIntermediate · 4 min read

How to Prevent Reentrancy Attack in Solidity Smart Contracts

To prevent a reentrancy attack in Solidity, use the checks-effects-interactions pattern by updating state before calling external contracts. Additionally, use a reentrancy guard modifier like OpenZeppelin's nonReentrant to block recursive calls.
🔍

Why This Happens

A reentrancy attack happens when a contract calls an external contract before updating its own state. The external contract can then call back into the original contract repeatedly, causing unexpected behavior like draining funds.

solidity
pragma solidity ^0.8.0;

contract Vulnerable {
    mapping(address => uint) public balances;

    function deposit() external payable {
        balances[msg.sender] += msg.value;
    }

    function withdraw(uint _amount) external {
        require(balances[msg.sender] >= _amount, "Insufficient balance");
        (bool success, ) = msg.sender.call{value: _amount}("");
        require(success, "Transfer failed");
        balances[msg.sender] -= _amount;
    }
}
Output
The contract allows repeated withdrawals before balances[msg.sender] is updated, enabling attackers to drain funds.
🔧

The Fix

Update the user's balance before sending funds to prevent reentrancy. Alternatively, use a nonReentrant modifier to block recursive calls.

solidity
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

contract Safe is ReentrancyGuard {
    mapping(address => uint) public balances;

    function deposit() external payable {
        balances[msg.sender] += msg.value;
    }

    function withdraw(uint _amount) external nonReentrant {
        require(balances[msg.sender] >= _amount, "Insufficient balance");
        balances[msg.sender] -= _amount; // state updated first
        (bool success, ) = msg.sender.call{value: _amount}("");
        require(success, "Transfer failed");
    }
}
Output
Withdrawals succeed safely without allowing reentrant calls to drain funds.
🛡️

Prevention

Always follow these best practices to avoid reentrancy attacks:

  • Use the checks-effects-interactions pattern: check conditions, update state, then interact with external contracts.
  • Use nonReentrant modifiers from libraries like OpenZeppelin.
  • Prefer transfer or send for sending Ether when possible, as they limit gas and reduce risk.
  • Use static analysis tools and linters to detect reentrancy risks.
⚠️

Related Errors

Other common Solidity security issues include:

  • Integer overflow/underflow: Use Solidity 0.8+ which has built-in checks.
  • Unchecked external calls: Always check return values of external calls.
  • Access control flaws: Use modifiers like onlyOwner to restrict sensitive functions.

Key Takeaways

Always update contract state before calling external contracts to prevent reentrancy.
Use OpenZeppelin's nonReentrant modifier to block recursive calls safely.
Follow the checks-effects-interactions pattern consistently.
Use static analysis tools to catch reentrancy vulnerabilities early.
Be aware of related security issues like integer overflows and access control.