Reentrancy Attacks Explained: How They Work and How to Prevent Them
reentrancysoliditysmart contractblockchain securityweb3ethereum

Reentrancy Attacks Explained: How They Work and How to Prevent Them

4 min read
6 views

Understand one of the most dangerous smart contract vulnerabilities. Learn how reentrancy attacks work, see vulnerable Solidity code, and discover proven techniques to secure your contracts.

Reentrancy Attacks Explained: How They Work and How to Prevent Them

Reentrancy is one of the most well-known and dangerous vulnerabilities in smart contract development. Despite being widely documented, it continues to appear in production contracts and remains a common finding during security audits.

In this guide, you'll learn what a reentrancy attack is, why it happens, how attackers exploit it, and the best practices for preventing it in Solidity.


What Is a Reentrancy Attack?

A reentrancy attack occurs when a smart contract sends Ether or calls another contract before updating its own internal state.

If the receiving contract is malicious, it can call back into the vulnerable function before the first execution finishes.

Because the contract's state hasn't been updated yet, the attacker can repeat the same action multiple times and withdraw more funds than intended.

In simple terms:

  1. The victim contract sends funds.
  2. The attacker receives the funds.
  3. Before the victim updates its balance, the attacker calls the withdrawal function again.
  4. The cycle repeats until the contract is drained.

Why Does It Happen?

The Ethereum Virtual Machine (EVM) allows contracts to call other contracts.

Whenever you make an external call, you temporarily give control to another contract.

If your own contract hasn't updated its internal state before that call, the external contract can exploit the inconsistent state.

This is why developers should always assume that external contracts are untrusted.


A Vulnerable Example

The following contract allows users to deposit and withdraw Ether.

mapping(address => uint256) public balances;

function withdraw() external {
    uint256 amount = balances[msg.sender];

    require(amount > 0, "No balance");

    (bool success, ) = msg.sender.call{value: amount}("");
    require(success);

    balances[msg.sender] = 0;
}

At first glance, this function seems correct.

However, notice that the contract transfers Ether before setting the user's balance to zero.

A malicious contract can repeatedly call withdraw() before the balance is updated.


The Secure Version

The safest approach is to update the contract's state before making any external calls.

function withdraw() external {
    uint256 amount = balances[msg.sender];

    require(amount > 0, "No balance");

    balances[msg.sender] = 0;

    (bool success, ) = msg.sender.call{value: amount}("");
    require(success);
}

Now, even if the receiving contract attempts to call withdraw() again, the stored balance is already zero.

The attack immediately fails.


The Checks-Effects-Interactions Pattern

One of the oldest and most effective Solidity security principles is the Checks-Effects-Interactions pattern.

Every function should execute operations in this order:

1. Checks

Validate all conditions.

Examples:

  • Verify permissions
  • Validate inputs
  • Ensure balances are sufficient

2. Effects

Update your own contract's state.

Examples:

  • Reduce balances
  • Update storage
  • Record events

3. Interactions

Only after the state is safe should the contract interact with external contracts.

Examples:

  • Transfer Ether
  • Call ERC20 contracts
  • Invoke another protocol

Following this pattern prevents many reentrancy vulnerabilities.


Using OpenZeppelin's ReentrancyGuard

Another common defense is OpenZeppelin's ReentrancyGuard.

It prevents a protected function from being entered more than once during the same transaction.

Example:

contract Vault is ReentrancyGuard {

    function withdraw() external nonReentrant {
        // secure logic
    }

}

Although ReentrancyGuard is extremely useful, it should not replace good contract design.

Developers should still follow secure coding practices.


Cross-Function Reentrancy

Not every reentrancy attack targets the same function.

Sometimes an attacker enters one function and re-enters through another function that modifies the same storage variables.

For this reason, auditors review how state variables are shared across the entire contract—not just individual functions.


Cross-Contract Reentrancy

Modern DeFi protocols frequently interact with multiple contracts.

Examples include:

  • Lending protocols
  • DEX routers
  • Staking contracts
  • Vaults

A reentrancy attack may involve several contracts instead of a single vulnerable function.

Security reviews should consider the complete protocol architecture.


How Auditors Detect Reentrancy

Professional auditors combine several techniques:

  • Manual code review
  • Static analysis tools
  • Fuzz testing
  • Invariant testing
  • Attack simulations

Rather than looking only for obvious mistakes, auditors evaluate every external interaction and determine whether state changes occur in the correct order.


Best Practices

When writing Solidity contracts, follow these recommendations:

  • Update storage before external calls.
  • Follow the Checks-Effects-Interactions pattern.
  • Use ReentrancyGuard when appropriate.
  • Minimize unnecessary external interactions.
  • Avoid complex callback-based designs.
  • Write comprehensive unit tests.
  • Include fuzz and invariant tests.
  • Have contracts independently audited before deployment.

Final Thoughts

Reentrancy attacks have shaped the history of Ethereum security and continue to influence modern smart contract development.

Fortunately, they are also among the easiest vulnerabilities to prevent when secure development practices are followed.

By understanding how reentrancy works and consistently applying proven design patterns, developers can significantly reduce the attack surface of their smart contracts.

Security begins with thoughtful architecture—not just defensive libraries.

A

Aref

Blockchain security specialist and technical writer at Secudity.