Learn how Venus Protocol exploited for $5M in a price manipulation exploit caused by a supply cap bypass, with a clear breakdown of the attack flow, exchange rate inflation bug, and key DeFi security lessons.

The Venus Protocol exploit resulted in $5M sitting in attacker-controlled wallets, caused by a logic flaw in the VBep20 contract that allowed the attacker to bypass the vTHE market's 14.5M supply cap entirely through direct ERC-20 transfers. The vulnerability stems from getCashPrior() reading a live balanceOf() a known Compound V2 design flaw, which allowed the attacker to inflate the vToken exchange rate without passing through the mint() function where supply caps are enforced. By combining this exchange rate inflation with sustained DEX price manipulation, the attacker built a self-reinforcing borrowing loop and extracted 20 BTCB, 1.5M CAKE, and 2,172 WBNB from the protocol.
The attacker began by accumulating a large $THE (Thena) position on Venus over approximately nine months, depositing tokens through the standard mint() flow to build a collateral base of ~12.2M THE, representing 84% of the 14.5M vTHE supply cap. This was entirely legitimate protocol usage and technically within all defined limits.

The attacker then deployed an exploit contract and executed the core attack in a single transaction. Within the constructor of this contract, the attacker called transferFrom() on the THE token across six pre-funded wallets, consolidating the THE positions. Critically, instead of depositing the tokens through mint()

Using the inflated collateral, the attack contract then borrowed 1.5M USDC and then despoited through mint() and then borrowed 4.63M THE from the vTHE borrow market and transferred that directly to the vTHE contract as well, inflating the exchange rate a second time. The contract then used the compounded borrowing power to borrow 913,000 CAKE and 1,972 BNB from Venus in the same atomic transaction.



The vTHE contract's underlying token balance simply increased. Because the exchange rate formula in VToken.sol computes totalCash directly from balanceOf(address(this)), this inflated the exchange rate from 10,086,934,836 to 38,420,106,438 a 3.81× increase — without a single new vTHE token being minted. The attacker's existing vTHE collateral, backed by the same number of vTokens, now represented 3.81× more underlying THE and therefore far more USD collateral value.

Following the initial extraction, the attacker executed around 50 additional transactions to amplify the position. Each cycle involved borrowing CAKE or BNB, swapping to THE on Thena DEX to increase its price, transferring THE directly to the vTHE contract to inflate the exchange rate, and leveraging the updated TWAP price to borrow more. As each loop generated more borrowing power than debt, the attack became self-reinforcing.



By repeatedly executing this loop, borrowing CAKE and BNB from Venus, swapping them for THE on-chain to pump the price, and transferring the acquired THE directly to the vTHE contract, the attacker extracted approximately 20 BTCB, 1.5M CAKE, and 2,172 WBNB from the protocol.


As selling pressure hit THE’s already thin liquidity, the price quickly dropped from around $0.51 to $0.22. This sharp decline triggered a wave of liquidations, unwinding nearly 42 million THE in collateral.

The diagram below walks through the exploit step by step, starting from the attacker’s nine-month accumulation of $THE and the supply cap bypass via direct ERC-20 transfers, to the repeated borrow–swap–donate loop that inflated the exchange rate by 3.81× and ultimately enabled the extraction of 20 BTCB, 1.5M CAKE, and 2,172 WBNB from the vTHE market.

In VBep20.sol, the getCashPrior() function reads the live underlying token balance to determine how much cash the vToken market holds:
1 /**
2 * @notice Gets balance of this contract in terms of the underlying
3 * @dev This excludes the value of the current message, if any
4 * @return The quantity of underlying tokens owned by this contract
5 */
6 function getCashPrior() internal view override returns (uint) {
7 return IERC20(underlying).balanceOf(address(this));
8 }
9This value feeds directly into exchangeRateStoredInternal() in VToken.sol:
1/**
2 * @notice Calculates the exchange rate from the underlying to the vToken
3 * @dev This function does not accrue interest before calculating the exchange rate
4 * @return Tuple of error code and calculated exchange rate scaled by 1e18
5 */
6 function exchangeRateStoredInternal() internal view virtual returns (MathError, uint) {
7 uint _totalSupply = totalSupply;
8 if (_totalSupply == 0) {
9 /*
10 * If there are no tokens minted:
11 * exchangeRate = initialExchangeRate
12 */
13 return (MathError.NO_ERROR, initialExchangeRateMantissa);
14 } else {
15 /*
16 * Otherwise:
17 * exchangeRate = (totalCash + totalBorrows + flashLoanAmount - totalReserves) / totalSupply
18 */
19 uint totalCash = _getCashPriorWithFlashLoan();
20 uint cashPlusBorrowsMinusReserves;
21 Exp memory exchangeRate;
22 MathError mathErr;
23
24 (mathErr, cashPlusBorrowsMinusReserves) = addThenSubUInt(totalCash, totalBorrows, totalReserves);
25 if (mathErr != MathError.NO_ERROR) {
26 return (mathErr, 0);
27 }
28
29 (mathErr, exchangeRate) = getExp(cashPlusBorrowsMinusReserves, _totalSupply);
30 if (mathErr != MathError.NO_ERROR) {
31 return (mathErr, 0);
32 }
33
34 return (MathError.NO_ERROR, exchangeRate.mantissa);
35 }
36 }
37
38However, the supply cap is enforced exclusively inside mintFresh() via the Comptroller's mintAllowed() check:
1function mintFresh(address minter, uint mintAmount) internal returns (uint, uint) {
2 uint allowed = comptroller.mintAllowed(address(this), minter, mintAmount);
3 if (allowed != 0) {
4 return (failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.MINT_COMPTROLLER_REJECTION, allowed), 0);
5 }
6 // ...
7}
8The critical gap is that a raw ERC-20 transfer() to the vToken contract address is a call on the underlying token contract, not on Venus. Venus receives no callback, mint() is never invoked, and mintAllowed() never runs. The balance increases silently while the supply cap sees nothing.
When borrowFresh() is called on vCAKE, the Comptroller calls vTHE.getAccountSnapshot(attacker) to assess collateral value:
1 /**
2 * @notice Get a snapshot of the account's balances, and the cached exchange rate
3 * @dev This is used by comptroller to more efficiently perform liquidity checks.
4 * @param account Address of the account to snapshot
5 * @return (possible error, token balance, borrow balance, exchange rate mantissa)
6 */
7 function getAccountSnapshot(address account) external view override returns (uint, uint, uint, uint) {
8 uint borrowBalance;
9 uint exchangeRateMantissa;
10
11 MathError mErr;
12
13 (mErr, borrowBalance) = borrowBalanceStoredInternal(account);
14 if (mErr != MathError.NO_ERROR) {
15 return (uint(Error.MATH_ERROR), 0, 0, 0);
16 }
17
18 (mErr, exchangeRateMantissa) = exchangeRateStoredInternal();
19 if (mErr != MathError.NO_ERROR) {
20 return (uint(Error.MATH_ERROR), 0, 0, 0);
21 }
22
23 return (uint(Error.NO_ERROR), accountTokens[account], borrowBalance, exchangeRateMantissa);
24 }
25accountTokens[attacker] the vTHE balance never changed. The attacker received no new vTokens from the direct transfer. But exchangeRateStoredInternal is recalculated on every call, reading the inflated balanceOf(). The Comptroller then computes:
collateralValue = accountTokens[attacker] × exchangeRate × oraclePrice × collateralFactor
The attacker held the same vTHE count throughout. Only the exchange rate and therefore the underlying value each vTHE represented had grown through direct transfers, the protocol never tracked. This is how the same supply position went from ~$3.3M to >$12M in collateral value without a single new vToken being minted.
At the time of writing this report, all stolen assets remain consolidated within the attacker’s externally owned accounts (EOAs), specifically at 0x737bc98F1D34E19539C074B8Ad1169d5d45dA619 and 0x1a35bd28efd46cfc46c2136f878777d69ae16231.


The team acknowledged the incident and confirmed unusual activity related to the $THE pool.
As part of the incident response, Venus Protocol implemented additional risk mitigation measures by setting the Collateral Factor (CF) of several markets to 0, targeting assets with high single-user collateral concentration and lower liquidity profiles. The affected markets included $BCH, $LTC, $UNI, $AAVE, $FIL, $TWT, and later $lisUSD.
They shared a detailed post-incident clarification outlining the root cause, attack mechanism, and mitigation steps.
A comprehensive hack report has also been released by the Venus Protocol team in collaboration with their risk partner, Allez Labs.
Attacker EOAs:
Attacker Contract:
0x737bc98F1D34E19539C074B8Ad1169d5d45dA619
Attack Transaction:
THE Token:
0xF4C8E32EaDEC4BFe97E0F595AdD0f4450a863a11
vTHE Contract:
0x86e06EAfa6A1eA631Eab51DE500E3D474933739f
The Venus Protocol exploit highlights how a single flawed assumption that balanceOf() only reflects tracked deposits can undermine an entire risk framework. While the supply cap, collateral factor, and oracle worked as intended, the attacker bypassed them via direct ERC-20 transfers, inflating the exchange rate without minting vTokens. This artificially boosted collateral value by 3.81×. The incident underscores the need to enforce supply cap checks on all balance changes.
Contents


From day-zero risk mapping to exchange-ready audits — QuillAudits helps projects grow with confidence. Smart contracts, dApps, infrastructure, compliance — secured end-to-end.