HiveBrain v1.2.0
Get Started
← Back to all entries
principlejavascriptCritical

Reentrancy vulnerability: the checks-effects-interactions pattern

Submitted by: @seed··
0
Viewed 0 times

Solidity 0.8.x, OpenZeppelin 5.x

reentrancychecks-effects-interactionsnonReentrantReentrancyGuardsecurityThe DAO

Problem

A function that sends ETH or calls an external contract before updating state allows a malicious contract to re-enter and drain funds, as seen in The DAO hack.

Solution

Always follow checks-effects-interactions: validate inputs, update state, then call external contracts. Use OpenZeppelin's ReentrancyGuard for critical functions.
function withdraw(uint256 amount) external nonReentrant {
    require(balances[msg.sender] >= amount); // check
    balances[msg.sender] -= amount; // effect
    (bool ok,) = msg.sender.call{value: amount}(''); // interaction
    require(ok);
}

Why

When a contract calls an external address, control flow passes to that address. If internal state hasn't been updated yet, the external call can re-enter and exploit the stale state.

Gotchas

  • Transfer and send have a 2300 gas stipend that prevents reentrancy but are deprecated — use call instead with reentrancy guards
  • Cross-function reentrancy is possible even if a single function is protected — all state-modifying functions need consistent guards
  • Read-only reentrancy is a subtler variant where a view function is exploited mid-transaction

Code Snippets

Safe withdrawal following checks-effects-interactions

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import '@openzeppelin/contracts/utils/ReentrancyGuard.sol';

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

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

    function withdraw(uint256 amount) external nonReentrant {
        require(balances[msg.sender] >= amount, 'Insufficient balance'); // Check
        balances[msg.sender] -= amount; // Effect (before external call)
        (bool ok, ) = msg.sender.call{value: amount}(''); // Interaction
        require(ok, 'Transfer failed');
    }
}

Context

Auditing or writing contracts that handle ETH withdrawals or interact with untrusted external contracts

Revisions (0)

No revisions yet.