On March 2, 2026, an attacker exploited a fundamental design flaw in Curve Lend's LlamaLend protocol, draining approximately $240,000 from the sDOLA market. The attack didn't rely on a classic reentrancy bug or access control flaw — it weaponized Curve's own soft-liquidation mechanism against its users through a carefully orchestrated donation attack that manipulated the sDOLA oracle price.
This incident highlights a class of vulnerability that's becoming increasingly common in DeFi: atomic oracle manipulation in vault-based collateral systems.
What Is LlamaLend's Soft-Liquidation?
Unlike traditional lending protocols (Aave, Compound) that use hard liquidations — selling all collateral once a health factor drops below 1.0 — Curve's LlamaLend uses an innovative LLAMMA (Lending-Liquidating AMM Algorithm) soft-liquidation mechanism.
In this system, as a borrower's collateral value drops, their position is gradually converted between collateral and debt through an internal AMM. Think of it as a continuous rebalancing that's supposed to protect borrowers from sudden, catastrophic liquidations.
The key insight: soft-liquidation behaves like an AMM position, which means it's subject to impermanent loss — especially when prices change atomically.
The Attack: Step by Step
Here's how the attacker (address: 0x33a0...4be2) executed the exploit:
Step 1: Trigger Soft-Liquidations
The attacker first identified all positions in the sDOLA/crvUSD market that were in soft-liquidation range. By pushing market conditions, they triggered soft-liquidations on these positions, forcing the AMM to begin "selling" sDOLA collateral into crvUSD.
Step 2: Donate to Manipulate the Oracle
The critical move: the attacker donated assets to the sDOLA vault, atomically changing the sDOLA exchange rate:
Before: 1.188 sDOLA = 1 DOLA
After: 1.358 sDOLA = 1 DOLA (~14% increase)
Because sDOLA is a vault token, its price is determined by the ratio of underlying assets to shares. A donation directly inflates this ratio — instantly, in a single transaction.
Step 3: Exploit Impermanent Loss
Here's the devastating part. The soft-liquidation AMM had already begun converting users' sDOLA into crvUSD at the old price. When the oracle price jumped 14% atomically, the AMM positions experienced instant impermanent loss.
Even though the collateral was now worth more, the AMM had already partially converted at the lower price, locking in losses.
// Simplified: The AMM's internal price lags behind the oracle
// When oracle jumps atomically, the gap becomes instant IL
// Before donation: AMM converting at price P
// After donation: Oracle says price is 1.14 * P
// AMM positions are now "stale" — they sold low, can't buy back
Step 4: Hard-Liquidate and Extract
With positions now underwater due to impermanent loss (not actual collateral value decline), the attacker:
- Hard-liquidated all affected positions
- Repaid users' crvUSD debt with crvUSD from the liquidation
- Kept the remaining sDOLA collateral
- Deposited gained collateral back into Curve Lend
- Borrowed crvUSD to repay flash loans
- Routed profits through Tornado Cash
Transaction: 0xb935...d8a4
The Root Cause: Atomic Oracle Incompatibility
The fundamental issue is an incompatibility between Curve Lend's soft-liquidation mechanism and atomically-changeable oracles.
Traditional price oracles (Chainlink, TWAP-based) change gradually over time. The LLAMMA soft-liquidation mechanism was designed with this assumption — prices drift, and the AMM rebalances smoothly.
But vault tokens like sDOLA have a different price mechanism. Their "price" is the exchange rate between the vault share and the underlying asset. This rate can change atomically — in a single transaction — via deposits (donations) to the vault.
# Vault token price = total_assets / total_shares
# A donation increases total_assets without changing total_shares
# Result: instant, atomic price increase
class VaultOracle:
def price(self):
return self.vault.totalAssets() / self.vault.totalShares()
# Attacker calls: vault.donate(large_amount)
# price() instantly jumps — no TWAP protection
Why This Matters Beyond Curve
This vulnerability pattern affects any lending protocol that:
- Uses vault/receipt tokens as collateral (sDOLA, stETH wrappers, yield-bearing tokens)
- Has liquidation mechanisms that assume gradual price changes
- Doesn't sanitize oracle inputs for atomic manipulation
As DeFi moves toward yield-bearing collateral everywhere, this attack surface is expanding rapidly.
Defense Patterns
1. Oracle Rate-of-Change Guards
Reject oracle updates that exceed a maximum rate of change within a single block:
contract SafeVaultOracle {
uint256 public lastPrice;
uint256 public lastBlock;
uint256 public constant MAX_CHANGE_BPS = 500; // 5% max per block
function getPrice() external returns (uint256 price) {
price = _rawVaultPrice();
if (lastBlock == block.number) {
// Same block — enforce rate limit
uint256 change = price > lastPrice
? ((price - lastPrice) * 10000) / lastPrice
: ((lastPrice - price) * 10000) / lastPrice;
require(change <= MAX_CHANGE_BPS, "Oracle: atomic change too large");
}
lastPrice = price;
lastBlock = block.number;
return price;
}
function _rawVaultPrice() internal view returns (uint256) {
return IVault(vault).totalAssets() * 1e18 / IVault(vault).totalShares();
}
}
2. TWAP Wrappers for Vault Tokens
Never use spot vault exchange rates directly. Wrap them in a time-weighted average:
contract VaultTWAPOracle {
struct Observation {
uint256 timestamp;
uint256 priceCumulative;
}
Observation[] public observations;
uint256 public constant WINDOW = 30 minutes;
function consult() external view returns (uint256) {
uint256 currentCumulative = _currentCumulative();
// Find observation at least WINDOW seconds ago
for (uint i = observations.length; i > 0; i--) {
Observation memory obs = observations[i - 1];
if (block.timestamp - obs.timestamp >= WINDOW) {
uint256 elapsed = block.timestamp - obs.timestamp;
return (currentCumulative - obs.priceCumulative) / elapsed;
}
}
revert("TWAP: insufficient history");
}
}
3. Donation-Resistant Vault Design
Implement virtual reserves that dilute the impact of donations:
// ERC-4626 with virtual offset (OpenZeppelin pattern)
function _convertToAssets(uint256 shares, Math.Rounding rounding)
internal view override returns (uint256)
{
return shares.mulDiv(
totalAssets() + 1, // +1 virtual asset
totalSupply() + 1e3, // +1000 virtual shares (offset)
rounding
);
}
// The offset makes donation attacks prohibitively expensive
// To move price by 14%, attacker needs to donate 14% of (totalAssets + offset)
4. Soft-Liquidation Circuit Breakers
Pause soft-liquidations when oracle volatility exceeds thresholds:
# Monitoring script for vault oracle manipulation
from web3 import Web3
def monitor_vault_oracle(vault_address, threshold_pct=5):
w3 = Web3(Web3.HTTPProvider(RPC_URL))
vault = w3.eth.contract(address=vault_address, abi=VAULT_ABI)
last_rate = vault.functions.convertToAssets(10**18).call()
while True:
current_rate = vault.functions.convertToAssets(10**18).call()
change_pct = abs(current_rate - last_rate) / last_rate * 100
if change_pct > threshold_pct:
alert(f"⚠️ Vault rate changed {change_pct:.1f}% — possible donation attack")
# Trigger emergency pause or alert
last_rate = current_rate
time.sleep(12) # Every block
Audit Checklist: Vault Collateral Oracle Safety
When auditing any protocol that accepts vault/receipt tokens as collateral:
- [ ] Can the vault exchange rate change atomically? (donation, direct transfer, flash mint)
- [ ] Is there a TWAP or rate-of-change guard on the oracle?
- [ ] Does the liquidation mechanism assume gradual price changes?
- [ ] Are virtual offsets used in share-to-asset conversion? (ERC-4626 inflation attack mitigation)
- [ ] Can flash loans be used to temporarily inflate the vault rate?
- [ ] Is there a minimum time between oracle updates?
- [ ] Are circuit breakers in place for abnormal price movements?
The Bigger Picture
The Curve LlamaLend donation attack represents a growing class of DeFi vulnerabilities at the intersection of composability and oracle design. As protocols increasingly use yield-bearing vault tokens (stETH, sDAI, sDOLA, PT tokens) as collateral, the assumption that "prices change gradually" breaks down.
Every lending protocol needs to ask: What happens when my oracle price changes by 14% in a single transaction? If the answer involves impermanent loss, bad debt, or cascading liquidations — you have a bug.
The fix isn't complicated, but it requires acknowledging that vault tokens are fundamentally different from spot-priced assets. Treat them accordingly.
DreamWork Security researches DeFi vulnerabilities and publishes technical analysis to help protocol teams build safer systems. Follow for weekly deep dives into the latest exploits and defense patterns.
Tags: #security #defi #web3 #solidity
Top comments (0)