# Reentrancy and Checks-Effects-Interactions — Blockchain & Smart Contracts (Solidity)

Source: https://www.geekswithgeeks.com/en/solidity/x-reentrancy

> Recognise reentrancy and prevent it with CEI and guards.

## The classic smart contract bug

**Reentrancy** happens when a contract makes an external call (for example, sending ether) **before** updating its own state, and the recipient's code calls back into the original function while the old state is still in place. The 2016 **DAO hack** drained millions of ether this way: the attacker's `receive` function called `withdraw` again and again before the balance was set to zero. The core defence is the **checks-effects-interactions (CEI)** pattern: first **check** conditions, then apply **effects** (update balances and state), and only then perform **interactions** (external calls and transfers). Add **`ReentrancyGuard`** (`nonReentrant` modifier) on functions that make external calls, especially when several functions share state, because **cross-function** and **read-only reentrancy** (a view function returning stale state during a callback, which another protocol trusts) are subtler variants. Token standards with callbacks (ERC-777, ERC-721 `safeTransferFrom`, ERC-1155) can also trigger reentrancy. Treat every external call as handing control to an attacker.

## Checks, effects, interactions

Update state before calling out, so a callback sees the new state.

![Three numbered steps in a row: a checklist, a ledger being updated, then an arrow leaving the contract towards an external contract.](assets/figures/solidity/section-6-map.svg) — Figure 6.1 — The CEI ordering.

## A vulnerable vault and the fixed version

The order of state update and external call is the whole difference.

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

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

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

    function withdraw() external {
        uint256 amount = balances[msg.sender];
        (bool ok, ) = msg.sender.call{value: amount}("");   // INTERACTION first: attacker re-enters here
        require(ok, "send failed");
        balances[msg.sender] = 0;                            // EFFECT too late
    }
}

contract Vault is ReentrancyGuard {
    mapping(address => uint256) public balances;

    error InsufficientBalance(uint256 available, uint256 requested);
    error TransferFailed();

    event Deposited(address indexed account, uint256 amount);
    event Withdrawn(address indexed account, uint256 amount);

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

    function withdraw(uint256 amount) external nonReentrant {
        uint256 bal = balances[msg.sender];                          // CHECKS
        if (amount > bal) revert InsufficientBalance(bal, amount);
        balances[msg.sender] = bal - amount;                         // EFFECTS
        (bool ok, ) = msg.sender.call{value: amount}("");           // INTERACTIONS
        if (!ok) revert TransferFailed();
        emit Withdrawn(msg.sender, amount);
    }
}
```

## CEI first, guard second

`nonReentrant` is a safety net, not a substitute for correct ordering. Apply CEI everywhere, and add the guard on functions that make external calls or share state with ones that do.

**Quiz:** In the checks-effects-interactions pattern, when should external calls happen?

- [ ] First
- [ ] Before checking inputs
- [x] After state has been updated
- [ ] In the constructor only

*Answer:* After state has been updated. Interactions come last so that re-entrant calls see updated state.
