dForce lending protocol was exploited for $3.6M via a read-only reentrancy vulnerability on Arbitrum and Optimism. The attacker exploited the wstETH/ETH Curve pool to manipulate the oracle price during a reentrancy callback. The vulnerability existed because the price oracle read balances mid-transaction while the pool was in an inconsistent state.
Vulnerability Detected
Our scanners would have identified the vulnerability that led to this exploit.
Multiple lending functions (redeem, borrow, liquidateBorrow) lack reentrancy guards. Combined with price oracle manipulation during callbacks, this enabled the $3.6M exploit.
Recommendation: Add nonReentrant modifier to all lending functions. Implement same-block borrow restrictions for flash loans. Update state before external calls.
Function mint is vulnerable to AMM liquidity manipulation. Liquidity operations lack minimum liquidity lock or time-based restrictions, enabling flash loan pool manipulation. This was part of the attack vector exploiting oracle price during reentrancy.
Recommendation: Implement minimum liquidity locks, use TWAP oracles instead of spot prices, add reentrancy guards, validate reserves before and after trades.
Functions _doTransferIn and _doTransferOut rely on a single oracle source, creating centralization risk. The oracle can return manipulated prices during reentrancy callbacks when pool state is inconsistent.
Recommendation: Use multiple oracle sources with price deviation checks. Implement circuit breakers for large price movements. Add time-weighted average price (TWAP) validation.
Function _getCurrentCash uses balanceOf(address(this)) for share price calculation without internal balance tracking. Vulnerable to direct token donation manipulation. Attacker can manipulate share price by directly transferring tokens to vault.
// iToken.sol - Vault Donation Vulnerability
// Scanner detected: vault-donation-attack
function _getCurrentCash() internal view virtual returns (uint256) {
// VULNERABILITY: Uses balanceOf for price calculation
// Attacker can donate tokens to manipulate this value
return IERC20Upgradeable(underlying).balanceOf(address(this));
// Should use internal tracking:
// return _internalCash;
}Recommendation: Track internal balances separately from actual token balances. Add validation that balance changes only occur through expected functions.
Function _doTransferIn is vulnerable to NFT callback reentrancy. Uses safe NFT operations without reentrancy guard. This is related to the read-only reentrancy attack vector that was exploited in the $3.6M hack.
// iToken.sol - Reentrancy Vulnerability
// Scanner detected: erc721-callback-reentrancy
function _doTransferIn(address _from, uint256 _amount)
internal
virtual
returns (uint256)
{
// VULNERABILITY: No reentrancy guard
// External call can trigger callback before state update
IERC20Upgradeable _underlying = IERC20Upgradeable(underlying);
uint256 _balanceBefore = _underlying.balanceOf(address(this));
// External call - reentrancy point
_underlying.safeTransferFrom(_from, address(this), _amount);
// State read after external call - can be manipulated
return _underlying.balanceOf(address(this)).sub(_balanceBefore);
}Recommendation: Add nonReentrant modifier. Follow checks-effects-interactions pattern. Complete state updates before safe operations.
BlockSecOps scans your smart contracts with 580+ security detectors, catching vulnerabilities before attackers do.