Back to Blog
Reentrancy Attacks in Solidity — Understand Them and Prevent Them
SolidityWeb3 Security

Reentrancy Attacks in Solidity — Understand Them and Prevent Them

11 min
10 views
Learn what a reentrancy attack is, how it works step by step, and the three latest verified methods to protect all the smart contracts in your project.

What is a Reentrancy Attack?

Firstly, I am going to help you understand in a simple way what is a reentrancy attack and how you can prevent it, and then, I will dive deeper in code examples in order to show where are the vulnerabilities, what would be the attacker's code and most importantly I will show you the latest verified methods to protect not only one but all the smart contracts in your project.
Spoiler: If you have already heard about the nonReentrant() modifier, keep reading because you are about to discover the globalNonReentrant() modifier and the Checks-Effects-Interactions pattern.
Contract A and Contract B interaction
In the image above we have ContractA and ContractB. Now, as you know a smart contract can interact with another smart contract, like in this case ContractA can call ContractB. So, the very basic idea of reentrancy is that ContractB is able to call back to ContractA while ContractA is still executing.

So, how can the attacker use this?

Above we have ContractA which has 10 Ethers and we see that ContractB has stored 1 Ether in ContractA. In this case, ContractB will be able to use the withdraw function from ContractA and send Ether back to itself as it passes the check where its balance is greater than 0, to then have its total balance modified to 0.
Reentrancy attack flow
Let's now see how can ContractB use reentrancy to exploit the withdraw function and steal all the Ethers from ContractA. Basically, the attacker is going to need two functions: attack() and fallback().
In Solidity, a fallback function is an external function with neither a name, parameters, or return values. Anyone can call a fallback function by: calling a function that doesn't exist inside the contract; calling a function without passing in required data; sending Ether without any data to the contract.
The way reentrancy works (let's follow the arrows step by step) is with the attacker calling the attack() function which inside is calling the withdraw() function from ContractA. Inside the function it will verify if the balance of ContractB is greater than 0 and if so it will continue the execution.
Fallback trigger sending Ether
Since ContractB's balance is greater than 0, it sends that 1 Ether back and it triggers the fallback function. Notice that at this moment ContractA has 9 Ethers and ContractB has already 1 Ether.
Fallback re-enters withdraw
Next, when fallback function gets executed it triggers again ContractA's withdraw function, checking again if ContractB's balance is greater than 0. If you check again the image above you will notice that its balance is still 1 Ether.
Balance drain loop
That means that the check passes and it sends another Ether to ContractB, which triggers the fallback function. Notice that since the line where we have "balance=0" never gets executed, this will continue until all Ether from ContractA is gone.

Identifying Reentrancy in Solidity Code

Let's now take a look at a smart contract where we can identify reentrancy with the Solidity code.
EtherStore contract with deposit and withdrawAll
In EtherStore contract we have the function deposit() that stores and updates the balances of the sender and then the withdrawAll() function that will take all the balance stored at once. Please, notice the implementation of withdrawAll() where it checks first with the require that the balance is greater than 0 and right after sends the Ether, again, leaving for the end the update of the sender's balance to 0.
Attack contract exploiting EtherStore
Here we have the contract Attack that is going to use the reentrancy to drain EtherStore contract. Let's analyse its code:
  • In its constructor the attacker will pass the EtherStore address in order to create an instance and so being able to use its functions.
  • There we see the fallback() function which is going to be called when EtherStore sends Ether to this contract. Inside it will be calling withdraw from EtherStore as long as the balance is equal or greater than 1.
  • And inside the attack() function we have the logic that will be exploiting EtherStore. As we can see, first we will initiate the attack by making sure we have enough ether, then deposit 1 ether in order to have a balance greater than 0 in EtherStore and hence passing the checks before starting to withdraw.
I explained above in ContractA and ContractB's example step by step how the code will run, so now, let's make a summary of how will it be. First of all the attacker will call attack(), which inside will call withdrawAll() from EtherStore, which then will send Ether to Attack contract's fallback function. And there it will start the reentrancy and drain the EtherStore's balance.

How to Protect Your Contracts from Reentrancy Attacks

I am going to show you three prevention techniques to fully protect them. I am going to cover how to prevent reentrancy in a single function, reentrancy cross-function and reentrancy cross-contract.

Technique 1: The noReentrant Modifier

noReentrant modifier
The first technique to protect a single function is using a modifier called noReentrant.
A modifier is a special type of function that you use to modify the behavior of other functions. Modifiers allow you to add extra conditions or functionality to a function without having to rewrite the entire function.
What we do here is lock the contract while the function is executed. This way it won't be able to reenter the single function since it will need to go through the function's code and then change the locked state variable to false in order to pass again the check done in the require.

Technique 2: Checks-Effects-Interactions Pattern

Checks-Effects-Interactions pattern applied to EtherStore
The second technique is by making use of Checks-Effects-Interactions pattern which will protect our contracts from cross-function reentrancy. Can you spot in the updated EtherStore contract above what has changed?
To deep dive into Check-Effects-Interaction pattern I recommend reading the Solidity patterns documentation.
Vulnerable code — balance updated after sending Ether
Fixed code — balance updated before sending Ether
Above we see the comparison between the code vulnerable from the image on the left where the balance was updated after sending the Ether, which as seen above could potentially never be reached, and on the right what it has been done is to move the balances[msg.sender] = 0 (or effect) right after the require(bal > 0) (check) but before sending ether (interaction).
This way we will be making sure that even if another function is accessing withdrawAll(), this contract will be protected from the attacker because the balance will always be updated before sending the Ether.

Technique 3: GlobalReentrancyGuard for Cross-Contract Protection

GlobalReentrancyGuard contract
Pattern created by GMX_IO
The third technique I am going to show you is creating the GlobalReentrancyGuard contract to protect from cross-contract reentrancy. It is important to understand that this is applicable for projects with multiple contracts interacting with each other.
The idea here is the same as in the noReentrant modifier I have explained in the first technique, it enters the modifier, updates a variable to lock the contract and it doesn't unlock it until it doesn't finish the code. The big difference here is that we are using a variable stored in a separate contract which is used as the place to check if the function was entered or not.
Cross-contract reentrancy example — ScheduledTransfer
Cross-contract reentrancy example — AttackTransfer
Cross-contract reentrancy example — GlobalReentrancyGuard applied
I have created here an example without actual code and just with function names for reference to understand the idea as, from my experience, it can help visualising the situation more than just writing it with words.
Here, the attacker would be calling the function in ScheduledTransfer contract which after meeting the conditions it would send the specified Ether to the AttackTransfer contract which would, therefore, enter the fallback function and hence "cancel" the transaction from the ScheduledTransfer contract's point of view and yet receive the Ether. And this way it would be starting a loop until draining all Ethers from ScheduledTransfer.

Are you audit-ready?

Download the free Pre-Audit Readiness Checklist used by 30+ protocols preparing for their first audit.

No spam. Unsubscribe anytime.

Well, using the GlobalReentrancyGuard I have mentioned above it will avoid such attack scenario.

Real-World Reentrancy Attacks

In case you would like to read about some of the real cases where reentrancy attack has been used:

References

Related Zealynx articles:

Get in touch

At Zealynx, we specialize in smart contract security audits and vulnerability prevention. Whether you need a reentrancy review or a full protocol audit, our team is ready to help you ship secure code. Reach out to start the conversation.
Want to stay ahead with more in-depth analyses like this? Subscribe to our newsletter and ensure you don't miss out on future insights.

FAQ: Reentrancy attacks in Solidity

1. What is a reentrancy attack in simple terms?
A reentrancy attack happens when a malicious contract calls back into the victim contract before the first call finishes executing. This allows the attacker to repeatedly withdraw funds because the balance has not been updated yet, draining the contract's entire balance.
2. What is the difference between single-function and cross-contract reentrancy?
Single-function reentrancy exploits one function calling itself recursively through the fallback. Cross-contract reentrancy exploits interactions between multiple contracts in the same project, where one contract's state update depends on another contract that can be re-entered through an external call.
3. Does the Checks-Effects-Interactions pattern fully prevent reentrancy?
CEI prevents single-function and cross-function reentrancy within the same contract by updating state before making external calls. However, it does not protect against cross-contract reentrancy where multiple contracts share state. For that, you need a GlobalReentrancyGuard.
4. Is reentrancy still a real threat in 2026?
Yes. Despite being well-known for a decade, reentrancy variants continue to cause losses. Read-only reentrancy, cross-contract reentrancy, and reentrancy through ERC-777 token callbacks are still found in audit contests and production code regularly.
5. How does the GlobalReentrancyGuard work?
It stores a lock variable in a separate contract that all contracts in the project check before executing sensitive functions. When any function in the system acquires the lock, no other function in any contract can re-enter until the lock is released, preventing cross-contract reentrancy.

Glossary

TermDefinition
ReentrancyA vulnerability where an external call allows a malicious contract to call back into the calling contract before its state is updated, enabling repeated fund withdrawals.
Fallback FunctionA special Solidity function with no name that executes automatically when a contract receives Ether or when a called function does not exist.
Checks-Effects-InteractionsA Solidity design pattern that prevents reentrancy by ordering operations: validate conditions first, update state second, make external calls last.
nonReentrant ModifierA mutex-based modifier that locks a contract during function execution, preventing the same function from being called again before the first call completes.
Cross-Program InvocationAn interaction between smart contracts where one contract calls a function in another, creating potential reentrancy vectors if state is shared.

Reentrancy Variants Still Slip Through Automated Scanners.

Read-only reentrancy, cross-contract reentrancy via ERC-777, and cross-function reentrancy through shared state are regularly missed by tools like Slither. Our auditors manually trace all external call paths — including callback-heavy integrations with ERC-721, ERC-1155, and hook-enabled AMMs.

Are you audit-ready?

Download the free Pre-Audit Readiness Checklist used by 30+ protocols preparing for their first audit.

No spam. Unsubscribe anytime.

oog
zealynx

Smart Contract Security Digest

Monthly exploit breakdowns, audit checklists, and DeFi security research — straight to your inbox

© 2026 Zealynx