This is a lower bound: only assets with reliable historical USD prices are counted, so the actual loss may be higher.
0x90f374ca33fbd5aaa0d01f5fcf5dee4c7af49a98dc56b47459d8b7ad52ef1e930x8fBCC81E5983d8347495468122c65E2Dc274eed9BSC0x5E5e28029eF37fC97ffb763C4aC1F532bbD4C7A2BSCOn BNB Smart Chain block 36145772, transaction 0x90f374ca33fbd5aaa0d01f5fcf5dee4c7af49a98dc56b47459d8b7ad52ef1e93 exploited Venus Protocol's old isolated-pool dLINK market at 0x8fBCC81E5983d8347495468122c65E2Dc274eed9. The attacker EOA 0x4645863205b47a0a3344684489e8c446a437d66c used helper contract 0x38721b0d67dfdba1411bb277d95af3d53fa7200e to source temporary liquidity, borrow 11,500 LINK from Venus Core, mint only 2 dLINK units, donate the remaining LINK directly into the dLINK market, and then use the resulting exchange-rate inflation to borrow WBNB, BTCB, ETH, ADA, and USDT from the old isolated pool.
The root cause is a donation-sensitive collateral accounting flaw. The old dLINK implementation treated raw LINK balance as market cash, and the old comptroller trusted the resulting inflated getAccountSnapshot() exchange rate as collateral value during borrow and redeem checks. This let a position with only 2 dLINK units appear massively overcollateralized and support borrows that were not backed by legitimately minted collateral.
Venus isolated pools value entered collateral markets through getAccountSnapshot(), which returns (error, vTokenBalance, borrowBalance, exchangeRateMantissa). The comptroller then combines the vToken balance and exchange rate to determine account liquidity. In a sound design, exchange-rate growth should only come from assets entering through mint accounting so that collateral shares and underlying assets remain synchronized.
The affected old dLINK market broke that expectation because its cash accounting was based on the contract's raw underlying LINK balance. A direct token transfer to the market increased the balance seen by the vToken implementation even though no new dLINK shares were minted. Once the attacker held a tiny positive dLINK balance, the donated LINK was divided across an almost zero share count, producing an abnormally large exchange rate that the old comptroller accepted as real collateral.
The exploit remained permissionless under the ACT model. The attacker only needed public chain state, permissionless flash liquidity, and live Venus liquidity in the old isolated pool at execution time. No privileged keys, governance action, or private off-chain artifact was required.
The vulnerability class is an accounting-integrity failure in collateral valuation. The victim market's raw-cash logic allowed an unsolicited token donation to change the exchange rate seen by downstream risk checks, while the comptroller treated that exchange rate as if it reflected properly minted collateral. The invariant that collateral value must only grow when share supply and underlying backing grow together was therefore broken.
The critical victim-side code path is explicit. In the dLINK implementation, VBep20.getCashPrior() returns token.balanceOf(address(this)), so any direct LINK transfer increases market cash immediately:
function getCashPrior() internal view returns (uint256) {
EIP20Interface token = EIP20Interface(underlying);
return token.balanceOf(address(this));
}
The vToken layer then feeds that cash into exchange-rate computation and snapshots:
function getAccountSnapshot(address account) external view returns (uint, uint, uint, uint) {
uint vTokenBalance = accountTokens[account];
(mErr, exchangeRateMantissa) = exchangeRateStoredInternal();
return (uint(Error.NO_ERROR), vTokenBalance, borrowBalance, exchangeRateMantissa);
}
function exchangeRateStoredInternal() internal view returns (MathError, uint) {
uint totalCash = getExchangeCash();
(mathErr, cashPlusBorrowsMinusReserves) = addThenSubUInt(totalCash, totalBorrows, totalReserves);
}
For this market, getExchangeCash() resolves back to getCashPrior() plus trade-model adjustments:
function getExchangeCash() public view returns(uint cashPlusUSDMinusLoss) {
cashPlusUSDMinusLoss = tradeModel.cashAddUSDMinusLoss(iUSDbalance, getCashPrior(), getPriceToken());
}
Because the attacker first minted only 2 dLINK units and then donated nearly all borrowed LINK directly into the market, the snapshot returned a huge exchange rate without corresponding dLINK supply growth. The old comptroller then allowed multiple borrows and a near-full redeemUnderlying() against that synthetic collateral value.
The exploit started by using permissionless flash liquidity to prepare a temporary Venus Core position. The trace shows the helper contract entering Venus Core markets, minting core collateral, and borrowing 11,500 LINK, creating the inventory used to manipulate the old dLINK market.
The decisive breakpoint came next. The trace records dLINK.mint(2), then a direct LINK transfer of 11499999999999999999998 wei LINK into the dLINK market, and then CompDP::enterMarkets([dLINK]). After that donation, repeated getAccountSnapshot() calls for the attacker helper returned:
0, 2, 0, 5750000000000000000000000000000000000000
That return value is the concrete manifestation of the broken invariant: the helper still held only 2 dLINK units, but the exchange rate had jumped to 5.75e39 because the market counted donated LINK as backing. The old comptroller then reused that snapshot while approving old-pool borrows of WBNB, BTCB, ETH, ADA, and USDT.
The same inflated collateral remained valid during redemption checks. The trace shows redeemUnderlying(11499999999999999999898) succeeding and transferring almost the entire donated LINK back to the attacker helper. In other words, the attacker was able both to borrow against the donated cash and to reclaim nearly all of that same cash, proving that the accounting path treated the donation as redeemable collateral rather than inert surplus.
The balance-diff artifact confirms the economic impact on victim markets and the realized attacker gains. Victim old-pool balances fell by 911577466008813446041 USDT, 50074554968631063877 WBNB, 171600491149762551 BTCB, 3992080348227829799 ETH, and 6378808120780430189153 ADA. The attacker EOA ended the transaction with corresponding positive balances in those assets, while gas cost was recorded separately as 4607081713085244498 wei BNB.
The attacker flow was a single transaction with three stages.
First, the helper contract sourced temporary liquidity and established a Venus Core borrowing position. The trace shows flash-loan activity, enterMarkets([vUSDT, vBNB]), vUSDT minting, WBNB unwrap and vBNB minting, and then a core borrow of 11,500 LINK.
Second, the helper converted that LINK into fake old-pool collateral. It minted 2 dLINK units, transferred the remaining borrowed LINK directly to dLINK, and entered dLINK as collateral in the old isolated-pool comptroller. This is the only state transition needed to inflate the helper's collateral snapshot.
Third, the helper monetized the inflated collateral. It borrowed WBNB, BTCB, ETH, ADA, and USDT from old-pool markets, redeemed 11499999999999999999898 wei LINK back out of dLINK, repaid the Venus Core LINK debt and temporary liquidity legs, and left residual profit in the attacker EOA. The balance diff shows the final EOA received positive USDT, WBNB, BTCB, ETH, and ADA, which satisfies the ACT success predicate of a strictly positive post-repayment portfolio.
The directly affected victim components were the old isolated-pool dLINK market at 0x8fBCC81E5983d8347495468122c65E2Dc274eed9 and the old isolated-pool comptroller at 0x5E5e28029eF37fC97ffb763C4aC1F532bbD4C7A2. Together they allowed donation-inflated collateral to support borrows from multiple old-pool markets and a near-complete recovery of the donated LINK.
Measured token losses from the victim side were:
9115774660088134460415007455496863106387717160049114976255139920803482278297996378808120780430189153The attacker EOA's corresponding positive deltas were slightly lower for USDT and WBNB because temporary funding fees and gas were paid within the same transaction path. The exploit therefore left the pool undercollateralized while still satisfying all temporary-funding obligations inside the transaction.
0x90f374ca33fbd5aaa0d01f5fcf5dee4c7af49a98dc56b47459d8b7ad52ef1e93 on BNB Smart Chain block 36145772.dLINK.mint(2), direct LINK donation, repeated inflated snapshots, old-pool borrows, and redeemUnderlying(11499999999999999999898).0xFfFed596EdCc9C557f67222C9AfF0FD1a27c5b66, especially VBep20.getCashPrior() and VToken.exchangeRateStoredInternal().0x5E5e28029eF37fC97ffb763C4aC1F532bbD4C7A2, evidenced in the trace through repeated getAccountSnapshot()-driven borrow and redeem permission checks.