Understanding Read-Only Reentrancy in Solidity: Are view Functions Truly Safe?
Introduction
In Solidity, a reentrancy attack occurs when an external call from a contract inadvertently allows that contract—or another malicious contract—to call back into the original function before it has finished executing, potentially causing unexpected behavior or theft of funds. Typically, developers think of reentrancy in the context of state-changing functions—ones that modify balances or contract storage. However, there’s a subtle phenomenon known as read-only reentrancy, where even a view function (which doesn’t alter contract state) can lead to unintended outcomes if other parts of the contract or ecosystem rely on the accuracy of that “read” data at a specific moment.
In the code snippet provided, the stake and withdraw functions are protected by nonReentrant from OpenZeppelin’s ReentrancyGuard, preventing reentrant calls. Meanwhile, displayBalance is declared as view, so it doesn’t consume a state-changing function’s gas nor does it modify any contract storage. At first glance, this might seem perfectly safe—after all, view functions are supposed to be read-only. But in more complex systems, a reentrant call triggered from a read-only function could exploit external dependencies or unprotected logic, leading to security vulnerabilities.
In this article, we’ll explain read-only reentrancy, show how it can be abused in more complex scenarios, and discuss how to mitigate it using best practices like Checks-Effects-Interactions (CEI). If you need professional assistance in securing your contracts against reentrancy and other exploits, consider Bailsec’s audit services.
1. Revisiting Reentrancy: The Classic Scenario
A typical reentrancy attack goes like this:
- Checks: A contract checks that a caller has sufficient balance.
- Effects: The contract updates the caller’s balance.
- Interactions: The contract sends Ether to the caller.
If the contract uses call to send Ether, the caller’s fallback function might re-enter the contract—before the state changes are finalized—leading to repeated withdrawals or logic bypass. Modern patterns mitigate this by rearranging steps (Checks-Effects-Interactions) or by using nonReentrant modifiers from a library like OpenZeppelin’s ReentrancyGuard.
However, these solutions typically focus on state-changing functions. Many developers don’t realize that read-only or view functions can also open the door to reentrancy—albeit in a different manner.
2. Introducing Read-Only Reentrancy
Read-only reentrancy refers to a scenario where an ostensibly safe function—marked view or pure—is used in a context that can be reentered, leading to inconsistent or manipulated data. While the function itself doesn’t modify state, it might rely on certain assumptions about the contract’s state that are no longer accurate if reentrant calls are triggered.
Example: Displaying Balances
In the snippet, the contract includes:

At first glance, this function is harmless—it just returns the user’s balance. However, imagine a scenario where displayBalance is called from another contract’s function that also triggers some state changes in the original contract. If the second contract reenters the original contract—perhaps via a fallback or another function call—displayBalance might return an outdated or incorrect value that influences further logic.
Key Insight: If any external call (even from a view function) triggers a chain of events leading back to the original contract, the “read-only” function’s data might be used incorrectly by an attacker who can reorder or manipulate calls.
3. Why Read-Only Reentrancy Can Be Dangerous
In trivial examples, returning a user’s balance from balances[user] doesn’t create a direct exploit. However, real-world contracts often have more complex logic. For instance:
- Conversion Rates: A contract might calculate how many tokens a user can claim based on a ratio or conversion that references the user’s balance. If that calculation is done through a view function in a reentrant manner, an attacker might cause the ratio to be read incorrectly.
- Oracle Queries: A contract might call an external oracle or aggregator within a view function. If the aggregator can reenter the contract, it might supply manipulated data.
- DAO Governance: A read-only function might check a user’s voting power or proposal count. If reentrancy is possible, the attacker could artificially inflate the data read by the function, influencing governance outcomes.
In short, read-only reentrancy can lead to subtle, logic-based vulnerabilities rather than direct state theft. The function doesn’t need to change state to be exploited; it only needs to provide data that influences other state changes.
4. Mitigation Techniques
4.1. Checks-Effects-Interactions (CEI)
The golden rule for reentrancy prevention is:
- Checks: Verify conditions, like user balances or deadlines, at the start.
- Effects: Update the contract’s state to reflect these checks immediately.
- Interactions: Make external calls (to other contracts or msg.sender) last.
Even for read-only or view logic, you can adapt the CEI principle by ensuring that your “read” does not rely on a state that could be manipulated mid-execution. If your read-only function might trigger external calls, confirm it does so after your logic has captured any critical state data. Or, better yet, separate logic so that your read-only function does not call untrusted external contracts at all.
4.2. Reentrancy Guards
A nonReentrant modifier can also protect read-only functions if needed. While it’s less common, you can mark a view function with nonReentrant if you suspect it could be used in a reentrancy exploit chain. This is unusual in practice, as view functions typically don’t charge gas when called externally, but it’s an option if your system design calls for it.
4.3. Minimizing External Calls in view Functions
If possible, avoid external calls (even indirect ones) in your read-only functions. If you must query an external aggregator or contract, consider pulling that data asynchronously (e.g., via a state-changing function that caches the result) and then reading from local storage in your view function.
4.4. Thorough Testing and Audits
Read-only reentrancy attacks often slip under the radar because they don’t appear to threaten direct state changes. However, thorough unit testing, integration testing, and professional audits can reveal scenarios where reentrancy might occur. If you suspect your contract might have such vulnerabilities, Bailsec’s reviews can help detect hidden logic flaws.
5. Example Code Snippet
A simplified version of the snippet (with relevant parts) might look like:

In this contract:
- stake and withdraw are protected by nonReentrant.
- displayBalance is a view function without reentrancy protection, typically considered safe.
For this minimal example, read-only reentrancy is not harmful, but in more complex designs with ratio calculations or external calls, an attacker might exploit displayBalance reentrancy.
6. Conclusion
Read-only reentrancy is a subtle but potentially serious vulnerability in Solidity smart contracts. While view functions don’t alter state, they can still facilitate reentrancy if they’re used in a way that influences or triggers external calls, manipulates oracles, or otherwise disrupts the intended logic flow. The best defense is to follow standard reentrancy prevention patterns:
- Checks-Effects-Interactions: Even for logic that seems “read-only,” ensure any critical data is checked before external calls occur.
- Reentrancy Guards: Don’t hesitate to protect read-only functions if they’re part of a complex system.
- Minimize External Dependencies: Limit external calls within view functions, or isolate them carefully.
- Thorough Testing and Audits: Seek professional reviews if you suspect a read-only reentrancy vector might exist.
By understanding how read-only reentrancy arises and implementing these countermeasures, you can build safer, more reliable smart contracts. For deeper insight or a professional audit of your contract architecture, consider Bailsec’s services. And for more articles on Solidity security and best practices, visit our blog.
Disclaimer: This article is intended for informational purposes only and does not constitute legal or financial advice. Always conduct extensive testing and consider professional audits to ensure the security of your smart contracts.