Donation Attacks on Compound-Fork Lending Protocols: Dissecting the Venus Protocol THE Exploit

Published: (March 20, 2026 at 05:13 PM EDT)
7 min read
Source: Dev.to

Source: Dev.to

On March 15, 2026, a methodical attacker drained approximately $3.7 million from Venus Protocol on BNB Chain — not through a flash loan, not through a reentrancy bug, but through a vulnerability class that has plagued Compound-forked lending protocols since their inception: the donation attack. The attacker spent nine months preparing. They accumulated 84% of Venus’s THE (Thena) supply cap — roughly 14.5 million tokens — starting in June 2025. Then they bypassed Venus’s deposit mechanisms entirely, directly transferring THE tokens to the vTHE contract to manipulate the exchange rate. The result: $2.15 million in bad debt for Venus and $3.7 million in extracted assets. This is not a novel attack vector. Donation attacks against Compound-forked protocols have been documented since 2022. Yet in 2026, one of the largest lending protocols on BNB Chain fell victim to the exact same pattern. Let’s understand why — and how to prevent it. Every Compound-fork lending protocol uses a cToken model (or equivalent — vTokens in Venus’s case). When you deposit an asset, you receive a proportional share of the pool represented by cTokens. The exchange rate between cTokens and the underlying asset determines how much you can borrow against your position. The exchange rate formula: exchangeRate = (totalCash + totalBorrows - totalReserves) / totalSupply

Where: totalCash = underlying tokens held by the cToken contract totalBorrows = outstanding loans from the pool

totalReserves = protocol-reserved funds totalSupply = total cTokens in circulation Here’s the vulnerability: totalCash includes any tokens sent directly to the contract, not just those deposited through the proper mint() function. // Simplified Compound cToken — the critical flaw function getCashPrior() internal view returns (uint) { // This counts ALL tokens the contract holds // Including tokens sent via direct transfer (donation) return IERC20(underlying).balanceOf(address(this)); }

function exchangeRateStored() public view returns (uint) { uint _totalSupply = totalSupply; if (_totalSupply == 0) { return initialExchangeRateMantissa; } uint totalCash = getCashPrior(); uint cashPlusBorrows = totalCash + totalBorrows; uint exchangeRate = (cashPlusBorrows - totalReserves) * 1e18 / _totalSupply; return exchangeRate; }

By sending tokens directly to the vTHE contract (a “donation”), the attacker inflates totalCash without increasing totalSupply. This skews the exchange rate upward, making each vTHE token worth far more than it should be. Over nine months, the attacker accumulated ~14.5 million THE tokens, representing roughly 84% of Venus’s supply cap for this asset. This wasn’t cheap — on-chain analysis suggests they spent approximately $9.16 million acquiring THE and vTHE positions, funded through 7,400 ETH from Tornado Cash. Why nine months? Because accumulating this much of a single token quickly would have triggered price alerts and community attention. Slow accumulation under the radar is a hallmark of sophisticated attacks. The attacker directly transferred THE tokens to the vTHE contract, bypassing the standard mint() flow. This inflated the exchange rate dramatically: Before donation: totalCash = 17.3M THE totalSupply = 16.8M vTHE
exchangeRate ≈ 1.03

After donation: totalCash = 53.2M THE (3.7x the supply cap!) totalSupply = 16.8M vTHE (unchanged) exchangeRate ≈ 3.17

The attacker’s vTHE holdings were now “worth” 3x what they should have been according to the protocol’s accounting. With massively inflated collateral value, the attacker borrowed assets against their position: ~20 BTC ~1.5 million CAKE ~200 BNB Additional USDC The attacker also used borrowed funds to buy more THE on the open market, driving the spot price from ~$0.27 to nearly $5 — further inflating the oracle-reported collateral value in a feedback loop. The borrowed assets were extracted from the protocol, leaving Venus holding overvalued THE collateral that could never be liquidated at the inflated price. Venus had supply caps configured for THE. But the donation bypass circumvented them entirely: // Venus’s supply cap check occurs in mint() function mintInternal(uint mintAmount) internal { // This check exists… require( totalSupply + mintTokens _lastKnownBalance) { uint256 donation = currentBalance - _lastKnownBalance; // Route donations to reserves instead of inflating exchange rate totalReserves += donation; emit DonationDetected(msg.sender, donation); } _lastKnownBalance = currentBalance; }

// Call _syncBalance() before any exchange rate calculation function exchangeRateStored() public view returns (uint) { _syncBalance(); // … standard calculation, now donation-proof … }

By routing unexpected balance increases to totalReserves, the exchange rate remains unaffected by direct transfers. The donated tokens aren’t lost — they become protocol revenue — but they can’t be used for manipulation. If you must use balanceOf(), enforce supply caps at the balance level, not just the mint level: function exchangeRateStored() public view returns (uint) { uint _totalSupply = totalSupply; if (_totalSupply == 0) { return initialExchangeRateMantissa; }

uint totalCash = getCashPrior();

// Cap effective cash to prevent donation inflation
uint effectiveCash = Math.min(
    totalCash,
    _totalSupply * maxExchangeRate / 1e18
);

uint cashPlusBorrows = effectiveCash + totalBorrows;
uint exchangeRate = (cashPlusBorrows - totalReserves) * 1e18 / _totalSupply;
return exchangeRate;

}

This caps the exchange rate regardless of how many tokens are in the contract. A sudden 3x inflation in contract balance would be clamped to the maximum acceptable exchange rate. The Venus attack was amplified by the low liquidity of THE. A lending protocol should dynamically reduce collateral factors for assets with thin markets: struct AssetConfig { uint256 baseCollateralFactor; // e.g., 65% uint256 minLiquidityUSD; // e.g., $5M uint256 liquidityScaleFactor; // reduction per $1M below minimum }

function getEffectiveCollateralFactor( address asset ) public view returns (uint256) { AssetConfig memory config = assetConfigs[asset]; uint256 liquidity = getMarketLiquidity(asset);

if (liquidity >= config.minLiquidityUSD) {
    return config.baseCollateralFactor;
}

// Reduce collateral factor proportionally to liquidity deficit
uint256 deficit = config.minLiquidityUSD - liquidity;
uint256 reduction = deficit * config.liquidityScaleFactor / 1e18;

if (reduction >= config.baseCollateralFactor) {
    return 0; // Effectively delist as collateral
}

return config.baseCollateralFactor - reduction;

}

If THE’s collateral factor had been dynamically reduced as its on-chain liquidity thinned, the attacker’s inflated position would have had far less borrowing power. No single address should control 84% of a market’s supply. Period. uint256 public constant MAX_POSITION_SHARE = 2000; // 20% in basis points

function mintInternal(uint mintAmount) internal { // … existing checks …

// Position concentration check
uint256 newBalance = balanceOf(msg.sender) + mintTokens;
uint256 newShare = newBalance * 10000 / (totalSupply + mintTokens);

require(
    newShare 20% of a market?

[ ] Collateral factor: Is it static or dynamically scaled by liquidity? [ ] Oracle independence: Does the price feed source from different markets than the one being collateralized? [ ] Borrow velocity limits: Can an attacker open massive borrows in a single block? [ ] Empty market initialization: Can the first depositor manipulate the exchange rate? (the Sonne Finance variant) If any of these boxes are unchecked, you have a donation attack surface. Compound V2’s architecture was groundbreaking in 2020. But its design choices — particularly using balanceOf() for accounting — created a vulnerability template that has been inherited by hundreds of forks across every EVM chain. Compound V3 (Comet) fixed this by moving to internal accounting. Aave V3 never had the issue. But the long tail of Compound V2 forks — Venus, Benqi, Moonwell, Iron B

ank, Cream (RIP), and dozens of smaller protocols — still carry the original DNA. The Venus exploit cost $3.7 million. Across all Compound-fork donation attacks historically, losses exceed $230 million. And every unpatched fork is another potential victim, waiting for an attacker patient enough to spend nine months preparing. For protocol teams running Compound forks: Implement internal accounting or donation detection. Today. The attack playbook is public and the cost of preparation is dropping as attackers get more sophisticated tooling. For auditors: Add donation attack testing to your standard Compound-fork audit checklist. It’s a 30-minute check that catches a multi-million dollar vulnerability class. For users: Check your exposure on Compound-forked protocols with thin-liquidity collateral assets. If the protocol hasn’t addressed donation attacks, your deposits are at risk by proxy. This analysis is part of the DeFi Security Field Notes series. Previously: Oracle Security Design Patterns | Simulation-Execution Divergence Disclaimer: This article is for educational and defensive security research purposes. Always practice responsible disclosure.

0 views
Back to Blog

Related posts

Read more »