Skip to free scan
critical

Reentrancy Attacks: How They Work, Detection Evidence, and Prevention

March 31, 2026Updated July 12, 2026
Chainsethereumarbitrumbaseoptimismpolygonbnb-chainavalanche
Detected byslithermythrilechidnahound-ai

Reentrancy occurs when an external call can enter contract logic again while important state is still inconsistent. The DAO's recursive-call vulnerability is the canonical example. Firepan can inspect a submitted repository and commit for suspicious call ordering and use best-effort Slither output as corroboration in compatible deep audits; that scoped evidence is not blanket monitoring of deployed contracts.

What Is Reentrancy Attack?

Reentrancy occurs when a contract makes an external call (typically sending ETH or tokens) before updating its own state variables. If the external contract is malicious, it can call back into the original contract's functions, exploiting the stale state to extract funds repeatedly in a single transaction. This violates the atomicity assumption most developers rely on.

The vulnerability breaks down into three categories:

  • Single-function reentrancy: Attacker recursively calls the same withdrawal function
  • Cross-function reentrancy: Attacker calls different functions that share vulnerable state
  • Read-only reentrancy: Attacker exploits view functions during state inconsistency

The core issue stems from violating the Checks-Effects-Interactions (CEI) pattern: state updates must happen before external calls, not after.

How Reentrancy Attack Works

The attack sequence follows a predictable pattern:

  1. Attacker calls a withdrawal or transfer function in the target contract
  2. Target contract sends ETH/tokens to attacker's malicious contract
  3. The fallback function in the malicious contract calls back into the target contract's withdrawal function
  4. Target contract checks balance (which hasn't been updated yet) and allows another withdrawal
  5. This recursion continues until the target contract's funds are exhausted
// VULNERABLE — example only
// Demonstrates: Reentrancy Attack
// Do NOT use in production

pragma solidity ^0.8.0;

contract VulnerableBank {
    mapping(address => uint256) public balances;

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

    // VULNERABLE: State update happens AFTER external call
    function withdraw(uint256 amount) public {
        require(balances[msg.sender] >= amount, "Insufficient balance");

        // External call before state update — reentrancy window opens
        (bool success, ) = msg.sender.call{value: amount}("");
        require(success, "Transfer failed");

        // State update happens too late
        balances[msg.sender] -= amount;
    }

    function getBalance() public view returns (uint256) {
        return address(this).balance;
    }
}

contract Attacker {
    VulnerableBank public target;
    uint256 public attackCount = 0;

    constructor(address _target) {
        target = VulnerableBank(_target);
    }

    function attack() public payable {
        target.deposit{value: msg.value}();
        target.withdraw(msg.value);
    }

    // Fallback function — called when receiving ETH
    receive() external payable {
        attackCount++;
        if (attackCount < 10) {
            // Recursively drain the contract
            target.withdraw(msg.value);
        }
    }
}

Real-World Reference

The Ethereum Foundation described The DAO incident as a recursive-calling vulnerability: the attacker repeatedly entered the split function within the same transaction. That primary account is useful because it identifies the execution pattern without turning a changing asset valuation into a permanent headline statistic.

How to Detect Reentrancy Attack

Manual detection requires analyzing call sequences:

  • Flow analysis: Identify all external calls (.call{}, .send(), .transfer(), interface calls)
  • State checks: Verify state updates occur before external calls, not after
  • Balance patterns: Flag contracts that read balance, then send, then decrement
  • Fallback functions: Review fallback/receive functions for calls back into the contract
  • Loop invariants: Check whether loop variables can be modified during iteration via reentrancy

Look for these red flags:

  • State variables updated after external calls
  • User-controlled amounts combined with external calls
  • Contracts accepting ETH with fallback/receive functions
  • Nested or chained external calls within a single transaction

How Firepan Analyzes Reentrancy

Firepan's repository analysis can combine several evidence sources:

  1. Control-flow review: Maps relevant external calls and state updates in the scoped code
  2. CEI pattern analysis: Flags violations of Checks-Effects-Interactions ordering
  3. Repository-context analysis: Traces state mutations and call boundaries across the scoped code
  4. Hypothesis testing: Investigates whether a suspicious call sequence appears reachable and materially exploitable
  5. Evidence curation: Records deterministic and Slither corroboration without treating tool silence as proof of safety

No automated result proves the absence of single-function, cross-function, or read-only reentrancy. Reviewers should pair repository analysis with invariant tests, targeted adversarial tests, and operational monitoring appropriate to the deployed protocol.

Prevention Best Practices

1. Follow Checks-Effects-Interactions (CEI) Pattern

Update all state variables before making external calls:

// SECURE
function withdraw(uint256 amount) public {
    require(balances[msg.sender] >= amount);
    balances[msg.sender] -= amount;  // Update state first
    (bool success, ) = msg.sender.call{value: amount}("");
    require(success);
}

2. Use Reentrancy Guards

Implement a mutex pattern with OpenZeppelin's ReentrancyGuard:

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

contract SecureBank is ReentrancyGuard {
    function withdraw(uint256 amount) public nonReentrant {
        // Function can only be called once per transaction
    }
}

3. Pull Over Push Pattern

Let users withdraw funds themselves rather than pushing to them:

// SECURE: Users pull funds
function withdraw() public {
    uint256 amount = balances[msg.sender];
    balances[msg.sender] = 0;
    (bool success, ) = msg.sender.call{value: amount}("");
    require(success);
}

4. Use Safe Transfer Functions

For ERC20/ERC721, use OpenZeppelin's safe wrappers that handle reentrancy:

IERC20(token).safeTransfer(recipient, amount);

5. Beware of ERC777 and Custom Callbacks

ERC777 tokens invoke a hook during transfer—treat like external calls. Never hold tokens across external calls without guards.

Frequently Asked Questions

Q: What is reentrancy attack in smart contracts?

A: Reentrancy occurs when a contract calls an external contract before updating its own state, allowing the external contract to recursively call back and exploit the stale state. The classic example is a withdrawal function that sends ETH before decrementing the user's balance.


Q: What is the best-known real reentrancy incident?

A: The DAO incident is the canonical case. The Ethereum Foundation described an attacker recursively entering the split function and collecting ether multiple times in one transaction.


Q: How does Firepan detect reentrancy attack?

A: Firepan uses deterministic patterns and HOUND AI repository analysis to identify suspicious external-call and state-update ordering. Deep audits can use best-effort Slither findings as corroboration and investigate exploitability in context. Teams should still maintain their own invariant tests and deployed monitoring appropriate to the protocol.


Q: Can reentrancy attack be exploited after deployment?

A: Yes. Reentrancy is exploitable post-deployment if the code violates CEI ordering. Attackers watch the mempool for vulnerable function calls and deploy malicious contracts to intercept and exploit them in the same transaction.


Q: How do I prevent reentrancy attack?

A: Follow Checks-Effects-Interactions ordering: update state before external calls. Use a suitable reentrancy guard, consider pull-based payments, and test invariants across callbacks and related entry points. Analysis can find evidence; it does not prevent every issue by itself.

Conclusion

Reentrancy risk is broader than one familiar withdrawal pattern: callbacks can cross functions and can expose inconsistent read-only state. Use CEI where it fits, apply guards carefully, test protocol invariants, and treat automated repository findings as evidence rather than a security guarantee.

Start securing your smart contracts at https://app.firepan.com/

Sources

Firepan

Scan Your Contracts Now

Run a free surface scan — results in minutes, no credit card required.

Run Free Scan →