The $3.64 million exploit of the dForce protocol in February 2023 exposed a class of vulnerability that many smart contract developers have historically underestimated: read-only reentrancy. Unlike traditional reentrancy attacks where an external call reenters a state-modifying function, read-only reentrancy targets view functions that are typically considered safe from reentrancy because they do not alter contract state. This tutorial provides a comprehensive technical walkthrough for developers seeking to understand, identify, and prevent this increasingly prevalent attack vector.
The Objective
This tutorial aims to equip experienced Solidity developers with a deep understanding of read-only reentrancy vulnerabilities. By the end of this guide, you will understand how the dForce exploit worked at the bytecode level, be able to identify read-only reentrancy patterns in your own code and dependencies, and implement robust countermeasures that protect your protocols from similar attacks.
Understanding this vulnerability is essential for anyone building DeFi protocols that integrate with external systems. With the crypto market showing signs of recovery in February 2023 — Bitcoin at approximately $22,220 and Ethereum at $1,556 — the financial stakes for securing DeFi protocols continue to grow.
Prerequisites
This tutorial assumes familiarity with Solidity development, including smart contract deployment, function visibility modifiers, and the Ethereum Virtual Machine execution model. You should understand the basics of reentrancy attacks, including the checks-effects-interactions pattern. Experience with DeFi protocols, particularly lending platforms and automated market makers, will provide helpful context for understanding the attack scenarios discussed.
You will need a development environment with Solidity 0.8.x or later, Foundry or Hardhat for testing, and access to Ethereum mainnet or testnet RPC endpoints. Familiarity with Curve Finance’s liquidity pool contracts and the ERC-4626 vault standard will be beneficial for understanding the specific exploit mechanics.
Step-by-Step Walkthrough
Step 1: Understanding Traditional Reentrancy
Before diving into read-only reentrancy, let us establish the baseline. Traditional reentrancy occurs when an external contract call during state modification allows the called contract to reenter the original function before state changes are finalized. The classic mitigation is the checks-effects-interactions pattern: perform all checks first, update all state variables second, and only then make external calls.
Step 2: The Read-Only Reentrancy Concept
Read-only reentrancy subverts the assumption that view functions are safe. Consider a view function that reads state from an external contract. If that external contract’s state is temporarily inconsistent — for example, during a token transfer that has debited the pool but has not yet updated the internal balance accounting — the view function will return an incorrect value. When this incorrect value is used by another protocol for financial calculations, the result is exploitable.
In the dForce exploit, the attacker manipulated the get_virtual_price() function of the Curve LP Oracle. This view function calculates the virtual price of LP tokens based on the pool’s total supply and balances. By exploiting the timing of liquidity removal — where ETH was transferred out but LP tokens were not yet burned — the attacker created a state where the virtual price was artificially deflated.
Step 3: Reproducing the Vulnerability Pattern
The vulnerable pattern can be simplified as follows: A DeFi lending protocol queries an oracle’s view function to determine collateral values. The oracle reads state from a Curve pool. During a liquidity removal transaction in the Curve pool, there is a brief window where the pool’s ETH balance has decreased but the LP token total supply has not been updated. If a callback during this window triggers the oracle’s price query, the oracle returns a manipulated price.
The attacker exploited this by using flash loans to amplify the price manipulation. They borrowed 68,429 ETH, added and removed liquidity from the Curve pool in a specific sequence that triggered the inconsistent state, and then used the manipulated oracle price to liquidate other users’ positions in dForce’s lending vaults at deflated collateral values.
Step 4: Implementing Countermeasures
The primary defense against read-only reentrancy is to add reentrancy guards to view functions that feed financial logic. While this adds gas costs, the security benefit far outweighs the overhead. Implement a simple mutex pattern using a storage variable:
mapping(bytes32 => bool) private _lockedSlots;
modifier nonReentrantView(bytes32 slot) {
require(!_lockedSlots[slot], "ReentrancyGuard: reentrant call");
_lockedSlots[slot] = true;
_;
_lockedSlots[slot] = false;
}
Apply this modifier to any view function whose output is consumed by financial logic. Additionally, implement price deviation checks that flag and reject oracle readings that deviate significantly from recent historical values or cross-reference multiple independent price sources.
Step 5: Testing Your Defenses
Create comprehensive test suites that simulate attack scenarios. Use Foundry’s cheatcodes to manipulate state during test execution and verify that your reentrancy guards prevent exploitation. Test edge cases including nested reentrancy, cross-contract reentrancy, and flash-loan-amplified attacks.
Troubleshooting
If your reentrancy guard is causing gas limit issues, consider using transient storage (EIP-1153) for the mutex variable, which is significantly cheaper than persistent storage. If you are integrating with external protocols and cannot add guards to their view functions, implement a caching layer that records the last known-good price and rejects suspicious updates.
When auditing third-party integrations, pay particular attention to any external contract that your view functions call. Trace the full call chain to identify any state modifications that occur during what should be read-only operations. Tools like Slither can help identify potential reentrancy paths, but manual analysis remains essential for complex integration patterns.
Mastering the Skill
Read-only reentrancy is part of a broader category of state inconsistency vulnerabilities that arise from the composability of smart contracts. To truly master DeFi security, study the full history of oracle manipulation attacks, including the bZx exploits, the Cream Finance hacks, and the various Curve-related incidents. Each exploit reveals a new dimension of the attack surface that interconnected protocols create.
Stay engaged with the security research community through forums, audit reports, and post-mortem analyses. The dForce exploit was identified and documented by QuillAudits, and the detailed post-mortem provided invaluable information for the broader developer community. By contributing to and learning from this collective knowledge base, you can help raise the security standard for the entire DeFi ecosystem.
Disclaimer: This article is for educational purposes only. Always conduct thorough security audits and engage professional auditors before deploying smart contracts that handle real value.
read-only reentrancy is sneaky because everyone assumes view functions are safe. the dForce exploit was a masterclass in why that assumption kills
the dForce team literally had a reentrancy guard too, just not on the view function. $3.64M gone because of one missing modifier
$3.64M because someone wrote reentrancyGuard everywhere except the one function marked view. the irony is painful
^ this. i assign the dForce postmortem to every junior on my team. read-only reentrancy should be in every security checklist
this is why i never trust audited protocols without reading the audit scope myself. half the time read-only reentrancy is explicitly excluded from scope
Jana K. excluded from scope is the biggest red flag in any audit report. if a protocol pays $100k for an audit but excludes the attack surface that actually gets exploited, whats the point
exactly. had an auditor tell me reentrancy was out of scope because view functions cant be reentered. that audit cost $80k
the dForce exploit was $3.64M on a view function. imagine how many protocols have the same pattern but havent been targeted yet because their TVL isnt big enough to attract attackers
the pattern is always the same. view functions assumed safe, attacker reads stale state mid-operation, exploits the inconsistency. every new dev should study the dForce postmortem before touching production code
the dForce exploit is required reading for any new solidity dev. $3.64M lost to a pattern most people didnt even know existed