Calculated from recorded token losses using historical USD prices at the incident time.
0x6a75ac4b8d8e76d15502e69be4cb6325422833b4BSC0x7c5e04894410e98b1788fbdb181ffacbf8e60617BSC0xf436f8fe7b26d87eb74e5446acec2e8ad4075e47BSCOpenLeverage on BSC was exploited through a permissionless three-transaction sequence centered on market 24. In tx 0xf78a85eb32a193e3ed2e708803b57ea8ea22a7f25792851e3de2d7945e6d02d5, attacker EOA 0x5bb5b6d41c3e5e41d9b9ed33d12f1537a1293d5f funded helper contract 0xd0c8af170397c04525a02234b65e9a39969f4e93, opened a leveraged position, and pushed the protocol into the liquidation shortfall path. In that path, OpenLeverage repaid only 14812739983287788 WBNB to the WBNB lending pool, but the lending pool still erased 33112849873074429985 WBNB of borrower debt and treated it as bad debt. Three blocks later, tx 0x210071108f3e5cd24f49ef4b8bcdc11804984b0c0334e18a9a2cdb4cd5186067 closed the now debt-free position and returned 22495575756855855691599 USDT to the helper, which was swapped into 37687090507128961835 wei of BNB/WBNB. Tx 0x62d32a6d4e047488e44396c840a9748c60ad5294e09364b6377ac4b11bb682c5 then withdrew the helper balance to the originating EOA, leaving a net profit of about 32.676904400128961838 BNB after gas.
The root cause is a protocol bug, not pure MEV. OpenLevV1.liquidate legitimately reaches an end-of-liquidation repayment branch when liquidation proceeds are smaller than debt, but LPool.repayBorrowFresh zeroes borrower principal whenever isEnd=true even if the actual transferred repayment is only dust. later trusts , so it repays zero and releases the remaining position value to the attacker. The lending pool absorbs the unpaid WBNB as bad debt while the attacker recovers collateral and realizes profit.
0x62d32a6d4e047488e44396c840a9748c60ad5294e09364b6377ac4b11bb682c5OpenLevV1._closeTradeForborrowCurrent(...) == 0OpenLeverage tracks leveraged positions in activeTrades[owner][marketId][longToken]. Each trade records the trader's deposited amount and held position size, while the borrow-side liability is tracked separately in the corresponding LPool. Market 24 on BSC uses OpenLeverage at 0x6a75ac4b8d8e76d15502e69be4cb6325422833b4, OPBorrowing at 0xf436f8fe7b26d87eb74e5446acec2e8ad4075e47, and the WBNB lending pool at 0x7c5e04894410e98b1788fbdb181ffacbf8e60617.
The exploit does not require privileged roles. Any EOA can open a position, trigger public liquidation when the health check fails, and later call the public close/payoff path. The attacker-controlled helper contract merely packages the sequence on-chain; the critical vulnerability is that the protocol itself accepts underpayment while still zeroing debt.
The collected account history shows the lifecycle clearly. EOA 0x5bb5... deploys helper 0xd0c8... in tx 0x7478dbbeac7b9e21c5ae91ec87ce03f02e78edc1ed6ccd799e687fd02c0f7e31, sends the exploit transactions from the same EOA, and then calls the helper's withdrawal entrypoint. The decompiled helper also shows a balance-forwarding function that transfers the contract's native balance to tx.origin, matching the final profit-withdrawal step.
The vulnerability is a debt-accounting flaw in the OpenLeverage liquidation settlement path. When OpenLevV1.liquidate cannot recover enough of the borrowed asset from selling the position, it falls back to marketVars.buyPool.repayBorrowEndByOpenLev(owner, finalRepayAmount). That call reaches LPool.repayBorrowFresh, where the contract first measures the actual repayment transferred in and computes any residual shortfall as badDebtsAmount. However, if isEnd=true, the function then unconditionally forces accountBorrowsNew = 0, regardless of whether the borrower actually repaid the full debt. Because totalBorrows is reduced by the entire principal delta implied by that forced zeroing, the unpaid portion is socialized to the lending pool.
The violated invariant is straightforward: a borrower's principal must only become zero when repayment plus seized value fully covers the debt, or when a trusted loss-allocation mechanism explicitly absorbs the remainder. The code-level breakpoint is in LPool.repayBorrowFresh, where the isEnd branch overwrites the naturally computed post-repayment debt with zero. The downstream consequence appears in OpenLevV1._closeTradeFor, which reads the now-zero borrow balance, computes repayAmount = 0, and releases the position value as if the liability were fully settled. This is why the exploit is deterministic and repeatable from public state.
The victim-side liquidation logic is visible in OpenLeverage's verified source:
if (liquidateVars.receiveAmount >= liquidateVars.borrowed) {
OpenLevV1Lib.repay(marketVars.buyPool, owner, liquidateVars.borrowed);
liquidateVars.depositReturn = liquidateVars.receiveAmount.sub(liquidateVars.borrowed);
doTransferOut(owner, marketVars.buyToken, liquidateVars.depositReturn);
} else {
liquidateVars.finalRepayAmount = reduceInsurance(...);
liquidateVars.outstandingAmount = liquidateVars.borrowed.sub(liquidateVars.finalRepayAmount);
marketVars.buyPool.repayBorrowEndByOpenLev(owner, liquidateVars.finalRepayAmount);
}
That branch comes from the collected OpenLevV1.sol source and shows that liquidation explicitly delegates shortfall handling to the lending pool when sale proceeds are insufficient. The collected LPool.sol source then reveals the flawed write-off:
vars.actualRepayAmount = doTransferIn(payer, vars.repayAmount, false);
if (isEnd && vars.accountBorrows > vars.actualRepayAmount) {
vars.badDebtsAmount = vars.accountBorrows - vars.actualRepayAmount;
}
if (vars.accountBorrows < vars.actualRepayAmount) {
vars.accountBorrowsNew = 0;
} else {
vars.accountBorrowsNew = vars.accountBorrows - vars.actualRepayAmount;
}
if (isEnd) {
vars.accountBorrowsNew = 0;
}
The decisive problem is the final if (isEnd) { vars.accountBorrowsNew = 0; }. Once OpenLeverage routes liquidation through this branch, any partial repayment is enough to erase the entire borrower principal.
The on-chain trace for tx 0xf78a85... confirms that this exact path executed. Before liquidation, borrowBalanceStored(0xd0c8...) returns nonzero debt. During liquidation, the trace shows:
LPool::repayBorrowEndByOpenLev(0xd0c8..., 14812739983287788)
emit RepayBorrow(
payer = OPBorrowing,
borrower = 0xd0c8...,
actualRepayAmount = 14812739983287788,
badDebtsAmount = 33112849873074429985,
accountBorrowsNew = 0,
totalBorrowsNew = 41520147417617677981
)
This is the incident in one event: only 0.014812739983287788 WBNB enters the pool, but 33.112849873074429985 WBNB is booked as bad debt and the borrower slot becomes zero. The transaction's balance diff is consistent with this flow: the attacker funds the sequence with 5 BNB, OpenLev receives the large USDT deposit used to open the position, and the helper acquires the xOLE dust token and LP side effects needed to satisfy protocol checks.
The follow-up trace for tx 0x210071... shows why the bug is exploitable for profit. Right before OpenLeverage closes the trade, borrowBalanceStored(0xd0c8...) returns 0. OpenLeverage therefore transfers 22495575756855855691599 USDT back to the helper and emits TradeClosed(...) with the full returned value. The helper then swaps that USDT through Pancake and receives 37687090507128961835 wei of BNB/WBNB, which matches the economic realization phase described in the root cause. No privileged state, private key compromise, or off-chain secret is needed anywhere in the sequence.
The exploit starts with deployment. Tx 0x7478dbbe... is a create transaction from EOA 0x5bb5... that deploys helper 0xd0c8.... The same account history later shows txs 0xf78a85..., 0x210071..., and 0x62d32..., establishing a single adversary cluster.
In tx 0xf78a85..., the EOA sends 5 BNB to the helper and calls its exploit entrypoint. The helper opens a market-24 long position through OpenLeverage. The trace shows LPool-WBNB::borrowBehalf(0xd0c8..., 33127662613057717773), which creates the initial WBNB debt. The same transaction then reaches public liquidation through OPBorrowing, causing OpenLeverage to sell the position, discover that proceeds are insufficient, and call repayBorrowEndByOpenLev with only 14812739983287788 wei of WBNB. LPool records the remaining 33112849873074429985 wei as bad debt and zeroes the helper's borrow balance.
Tx 0x210071... occurs three blocks later. The close path reads the borrow slot and sees zero debt, so OpenLeverage transfers 22495575756855855691599 USDT to the helper instead of using those funds to retire the original WBNB liability. The helper immediately swaps the returned USDT to 37687090507128961835 wei of WBNB/BNB through Pancake. The trace and balance diff both show this value landing at the helper.
Finally, tx 0x62d32a6d... calls the helper's withdrawal function. The decompiled helper's 0xfe7dec9e entrypoint forwards address(this).balance to tx.origin, and the account history shows the creator EOA sending exactly this transaction. That realizes the profit at the EOA and completes the attack sequence.
The measured protocol loss is the bad debt written onto the WBNB lending pool: 33112849873074429985 wei, or 33.112849873074429985 WBNB. This loss is borne by the lending side of OpenLeverage market 24, because the borrower's principal is erased without collecting matching value from the borrower.
The attacker's gross realization is 37687090507128961835 wei of BNB/WBNB returned after the close-and-swap transaction, against an initial 5 BNB funding transaction. After subtracting gas across the exploit transactions, the root cause artifact computes net attacker profit as about 32.676904400128961838 BNB. The exploit therefore converts protocol insolvency directly into attacker-controlled native asset.
0xf78a85eb32a193e3ed2e708803b57ea8ea22a7f25792851e3de2d7945e6d02d5, showing borrowBehalf, public liquidation, and RepayBorrow(... badDebtsAmount=33112849873074429985, accountBorrowsNew=0).0x210071108f3e5cd24f49ef4b8bcdc11804984b0c0334e18a9a2cdb4cd5186067, showing borrowBalanceStored(...) = 0, TradeClosed, the USDT transfer back to the helper, and the Pancake swap to 37687090507128961835 wei.OpenLevV1.sol source for the liquidation shortfall branch and close-path repayment logic.LPool.sol source for repayBorrowFresh, including the unconditional isEnd debt-zeroing behavior.0x5bb5..., confirming helper deployment and the exploit transaction sequence.0xd0c8..., confirming the balance-forwarding withdrawal behavior.