Reentrancy Attack

A Comprehensive Survey and Analysis of Reentrancy Attacks in Smart Contracts and Mitigation Strategies


Abstract
         Reentrancy attacks represent one of the most infamous and financially devastating vulnerabilities in the history of smart contract development. This class of attack exploits the inherent concurrency of external calls in state-changing functions, allowing malicious actors to recursively call back into a vulnerable function before its initial state updates are finalized. The consequences have been severe, culminating in high-profile exploits such as the 2016 DAO hack, which resulted in the loss of over $60 million and a contentious hard fork of the Ethereum blockchain. This paper provides a comprehensive analysis of the reentrancy attack mechanism, breaking down its fundamental cause: the violation of the Checks-Effects-Interactions (CEI) pattern. We will deconstruct a practical, vulnerable contract example written in Solidity, demonstrate a step-by-step exploit, and then systematically present robust mitigation strategies. A primary focus will be on the practical implementation of the OpenZeppelin ReentrancyGuard library, a widely adopted industry standard for preventing such attacks. The discussion concludes that while reentrancy is a well-understood vulnerability, its prevalence underscores the critical need for secure development practices, rigorous testing, and the use of vetted security libraries in the smart contract ecosystem.

Keywords: Blockchain, Smart Contract, Security, Reentrancy Attack, Ethereum, Solidity, OpenZeppelin, DeFi, Cybersecurity.

1. Introduction
       Smart contracts are self-executing programs deployed on a blockchain that automatically enforce the terms of an agreement. Their "code is law" paradigm, while powerful, introduces unique security challenges. Unlike traditional software, deployed smart contracts are typically immutable, meaning that discovered vulnerabilities cannot be easily patched. This immutability elevates the importance of pre-deployment security to a paramount level.

         Among all vulnerabilities, the reentrancy attack is a classic and perilous flaw. It occurs when a malicious contract can interrupt the normal execution flow of a victim contract and force it to execute unintended code paths. This is achieved by making a recursive call back into the vulnerable function before its internal state has been settled. The attack fundamentally exploits the ability of Ethereum Virtual Machine (EVM) based blockchains to handle external calls to other contracts, which can trigger the fallback or receive functions of the calling contract.

         This article aims to dissect the reentrancy attack vector in detail, providing developers with both the theoretical understanding and the practical tools necessary to defend against it.

2. Methods: The Anatomy of a Reentrancy Attack
         To understand the mitigation, one must first understand the exploit. The core of a reentrancy vulnerability lies in the incorrect order of operations within a function.

2.1. The Vulnerable Pattern: Interaction Before Effects
         Consider a simple smart contract acting as a vault, VulnerableBank:

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

// A VULNERABLE CONTRACT - NOTE: DO NOT USE IN PRODUCTION
contract VulnerableBank {
    mapping(address => uint) public balances;

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

    function withdraw() public {
        // Check: Verify the user has sufficient balance and then proceed
        uint balance = balances[msg.sender];
        require(balance > 0, "Insufficient balance");

        // (VULNERABILITY) Interaction: Send Ether first
        (bool sent, ) = msg.sender.call{value: balance}("");
        require(sent, "Failed to send Ether");

        // Effect: Update the balance AFTER the interaction
        balances[msg.sender] = 0;
    }

    //Get balance function
    function getBalance() public view returns (uint) {
        return address(this).balance;
    }
}

         The critical flaw in the withdraw function is its operational sequence:

  • 1. Check: require(balance > 0, ...)
  • 2. Interaction: sender.call{value: balance}("")
  • 3. Effect: balances[msg.sender] = 0

2.2. The Malicious Contract: The Attacker

        An attacker deploys the following contract to exploit the vulnerability:

contract Attacker {
    VulnerableBank public bank;

    constructor(address _bankAddress) {
        bank = VulnerableBank(_bankAddress);
    }

    // Fallback function designed for the attack
    receive() external payable {
        if (address(bank).balance >= 1 ether) {
            bank.withdraw();
        }
    }

    // Attack function
    function attack() external payable {
        require(msg.value == 1 ether, "Send 1 Ether to attack");
        bank.deposit{value: 1 ether}();
        bank.withdraw();
    }

    //Stolen funds function
    function getStolenFunds() public view returns (uint) {
        return address(this).balance;
    }
}

2.3. The Attack Sequence (Step-by-Step)

  • 1. Setup: The attacker funds the Attacker contract with 1 ETH and calls attack().
  • 2. Initial Deposit: The attack function deposits 1 ETH into the VulnerableBank. The balances[attacker] is now 1 ETH.
  • 3. Initial Withdraw: The attack function calls bank.withdraw().
  • 4. Step 1 (Victim): VulnerableBank's withdraw function checks the balance (1 ETH) and proceeds.
  • 5. Step 2 (Victim): It makes an external call msg.sender.call{value: 1 ether}("") to send 1 ETH to the Attacker contract.
  • 6. Step 3 (Attacker): This call triggers the Attacker's receive() function.
  • 7. Step 4 (Attacker): The receive() function checks if the bank has more funds (it does, from other users). The condition is true.
  • 8. Step 5 (Attacker): While the first withdraw call is still ongoing (it hasn't reached the balances[msg.sender]=0 line), the receive() function calls bank.withdraw() again. This is the reentrant call.
  • 9. Step 6 (Victim): The second withdraw call starts. It checks balances[attacker], which is still 1 ETH because the first call hasn't updated it to zero yet. The check passes.
  • 10. Step 7 (Victim): The bank sends another 1 ETH to the attacker.
  • 11. Step 8 (Attacker): The receive function can be triggered again, creating a recursive loop until the bank is drained of all funds.
  • 12. Finalization: Only after the bank is drained and the recursive calls stop, the execution unwinds, and the line balances[msg.sender]=0 from the first call is finally executed. By this time, the attacker has withdrawn the entire bank balance multiple times over.

3. Results: Mitigation Strategies
         The attack can be neutralized by correcting the flawed sequence of operations and/or implementing explicit locks.

3.1. The Checks-Effects-Interactions (CEI) Pattern
         The most fundamental and crucial defense is to strictly adhere to the CEI pattern:

  • 1. Checks: First, perform all condition checks (e.g., require, assert).
  • 2. Effects: Then, update all internal state variables.
  • 3. Interactions: Finally, perform all external calls to other addresses.

    Here is the corrected VulnerableBank contract:
contract SecureBankWithCEI {
    mapping(address => uint) public balances;

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

    //withdraw function
    function withdraw() public {
        // CHECK
        uint balance = balances[msg.sender];
        require(balance > 0, "Insufficient balance");

        // EFFECTS (Update state BEFORE the interaction)
        balances[msg.sender] = 0;

        // INTERACTION
        (bool sent, ) = msg.sender.call{value: balance}("");
        require(sent, "Failed to send Ether");
    }
}

         In this version, even if the attacker's receive function re-enters the withdraw function, the balances[msg.sender] has already been set to zero. The require(balance>0, ...) check in the reentrant call will fail, thwarting the attack.

3.2. Using the OpenZeppelin ReentrancyGuard Library
        While CEI is a best practice, it can be error-prone in complex contracts with multiple functions. The OpenZeppelin ReentrancyGuard provides a simple and effective mutex lock. It uses a boolean state variable (_status) that is set to _ENTERED when a function is executing. If a reentrant call is attempted, the modifier will see the lock is active and revert the transaction.

3.3 Implementation

         First, install the OpenZeppelin Contracts library:

npm install @openzeppelin/contracts

         Then, import and use it in your contract:

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

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

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

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

    // The nonReentrant modifier prevents reentrancy
    function withdraw() public nonReentrant {
        uint balance = balances[msg.sender];
        require(balance > 0, "Insufficient balance");

        // Even with the "wrong" order, the guard prevents the attack.
        (bool sent, ) = msg.sender.call{value: balance}("");
        require(sent, "Failed to send Ether");

        balances[msg.sender] = 0;
    }
}

         The nonReentrant modifier acts as a robust safety net, making the contract secure even if the CEI pattern is not perfectly followed within the modified function.

3.4 Other Mitigations

  • Pull Payment Pattern: Instead of "pushing" funds to users (via call), design the system so that users "pull" their funds from a separate, secure contract. The OpenZeppelin PullPayment library facilitates this.
  • Using transfer or send: Historically, using address.transfer() or address.send() (which limit the forwarded gas to 2300 unit) was a mitigation, as this is insufficient for the attacker to make a recursive call. However, with changes in Ethereum's gas costs (e.g., EIP-1884), this is no longer considered a reliable security measure and call is now the recommended way to send Ether.

4. Discussion

         The reentrancy attack remains a foundational lesson in smart contract security. While the vulnerability itself is well-known, its variants, such as cross-function reentrancy (where a call into a different function exploits shared state), still catch developers off guard.
        The ReentrancyGuard is an excellent tool, but it should not be a substitute for sound design. A robust security posture involves a multi-layered approach:

  • Primary Defense: Strict adherence to the CEI pattern as a fundamental coding discipline.
  • Secondary Defense: Applying the nonReentrant modifier from OpenZeppelin to functions that make external calls, especially those handling funds. This provides defense-in-depth.
  • Tertiary Measures: Comprehensive testing, including unit tests and fuzzing, that specifically simulate reentrancy attempts. Tools like Slither and MythX can automatically detect such patterns.
  • Formal Verification: For high-value contracts, using formal verification can mathematically prove the absence of reentrancy bugs.

5. References
[1] Atzei, N., Bartoletti, M., & Cimoli, T. (2017). A survey of attacks on Ethereum smart contracts. Proceedings of the 6th International Conference on Principles of Security and Trust. Volume 10204, Pages 164 – 186. https://doi.org/10.1007/978-3-662-54455-6_8/
[2] Ethereum Foundation. (2025). Smart Contract Security. https://ethereum.org/en/developers/docs/smart-contracts/security/
[3] OpenZeppelin. (2025). ReentrancyGuard. OpenZeppelin Contracts Documentation. https://docs.openzeppelin.com/contracts/4.x/api/security#ReentrancyGuard/
[4] The DAO Hack. (2016). Wikipedia. https://en.wikipedia.org/wiki/The_DAO_(organization)
[5] ConsenSys. (2025). Ethereum Smart Contract Best Practices - Reentrancy. https://consensysdiligence.github.io/smart-contract-best-practices/attacks/reentrancy/
[6] Ethereum Improvement Proposal (EIP), EIP-1884: Repricing for trie-size-dependent opcodes. https://eips.ethereum.org/EIPS/eip-1884

How do you rate this article?

4


I.T.O.T.O.T.S_&_M.K.A.K.A.U
I.T.O.T.O.T.S_&_M.K.A.K.A.U

Our Goal Is To Raise The Level of Crypto Enthusiasts' Information In a Completely Academic and Scientific Method. Hope Everyone Enjoys It.


XThroneCommunity
XThroneCommunity

Our Goal Is To Raise The Level of Information Among Crypto Enthusiasts Through a Completely Academic and Scientific Method. Moreover, We Are Building a Community For Creating Something Extraordinary, Which Causes a Real Earthquake on Layers 2 and 3 of the Blockchain. Hope Everyone Enjoys It.

Publish0x

Send a $0.01 microtip in crypto to the author, and earn yourself as you read!

20% to author / 80% to me.
We pay the tips from our rewards pool.