ZeroLend PT-LBTC Actual-Balance / Scaled-Liquidity liquidityIndex Exploit (2025-02-23)
- Name: ZeroLend PT-LBTC actual-balance / scaled-liquidity
liquidityIndexexploit - Date: 2025-02-23
- Protocol: ZeroLend
- Chain: Base
- Category: supplier-index inflation / actual-balance-vs-scaled-liquidity boundary mismatch / rounding-window collateral drain
- Loss: about
3.92307607 LBTCborrowed and left unpaid (roughly$370Kat the time) - Summary: A tiny ZeroLend PT Lombard LBTC 29MAY2025 reserve on Base was exploitable because flash loans could borrow against the aToken contract’s real ERC-20 balance while supplier accounting and
cumulateToLiquidityIndex()still divided by a dust-sized scaled-liquidity denominator. The attacker first transferred PT-LBTC directly to the aToken contract instead of supplying through the pool, then repeatedly flash-borrowed and repaid that balance so the flash-loan premium compounded intoliquidityIndexeven though bookkepttotalLiquiditystayed near the market’s tiny seed level. Once the index was inflated to roughly2015 × 1e27, the attacker usedrayDivrounding windows to run repeated smallsupply()/ largerwithdraw()round trips that drained PT-LBTC from the same reserve, re-supplied the drained PT-LBTC as collateral, and borrowed real LBTC from the connected LBTC reserve in three borrow transactions. - Trigger / entry point:
- ZeroLend had a PT Lombard LBTC 29MAY2025 market on Base whose initialization transaction
0xf7d6adc8bebacb6897277334949b2d952a7266eaae47085f9035923d0f5f94a5seeded only32,680base units of PT-LBTC, about0.000327PT-LBTC. - That left
liquidityIndex = 1e27,variableBorrowIndex = 1e27, and essentially no meaningful supplier base. - The attacker then transferred about
5,032,680base units of PT-LBTC directly to the aToken contract address instead of usingPool.supply(), creating a large real token balance that flash loans could touch without increasing scaled supplier accounting. - Because the same lending pool also held a deeper LBTC reserve, any fake collateral value created in PT-LBTC could be turned into real LBTC outflows.
- ZeroLend had a PT Lombard LBTC 29MAY2025 market on Base whose initialization transaction
- Root cause:
- ZeroLend inherited an Aave-v3-style accounting boundary where flash loans settled against the aToken’s real ERC-20 balance, but
totalLiquidityforcumulateToLiquidityIndex()was derived from scaled supplier accounting. - Direct token transfers to the aToken contract did not pass through
supply(), so they increased flash-loanable balance without increasing the divisor used for supplier-index updates. - Flash-loan premiums were immediately socialized through
cumulateToLiquidityIndex(), which was catastrophic when the divisor remained at dust scale. - After index inflation, the same
liquidityIndexwidened therayDiv()rounding window used by bothsupply()andwithdraw(), letting the attacker drain PT-LBTC from the reserve via round trips that netted zero scaled balance but positive underlying token flow. - The protocol then accepted that freshly drained PT-LBTC back as collateral at face value, because post-maturity PT-LBTC was priced
1:1versus LBTC by the oracle, and allowed borrowing against the deeper LBTC reserve.
- ZeroLend inherited an Aave-v3-style accounting boundary where flash loans settled against the aToken’s real ERC-20 balance, but
- Exploit path:
- The attacker targeted the PT-LBTC market after it had been initialized with only
32,680base units of supply, leavingtotalLiquiditytiny. - They transferred about
0.0503268PT-LBTC (5,032,680base units) directly to the aToken contract instead of callingsupply(). That increased the reserve’s real token balance but did not mint scaled supplier balance or grow bookkepttotalLiquidity. - In transaction
0xa14b926bd19ce0e23b17d94802816700e1ea2353f7ceb8e493ae0ddcca4cbad6, they repeatedly flash-loaned and repaid that PT-LBTC balance. Each loop borrowed5,032,680base units, repaid5,035,196, and pushed a2,516base-unit premium intocumulateToLiquidityIndex(). - Because the denominator stayed around
32,680, each premium increased the index by roughly1 + 2516 / 32680, or about7.7%per iteration. Repetition compounded the supplier index until it reached about2,015 × 1e27. - With the index now extreme,
rayDiv(amount, liquidityIndex)mapped a wide range of raw PT-LBTC amounts into the same integer scaled amount. Around2015 × 1e27, thescaledAmount = 1window was roughly[1008, 3022]base units. - The attacker then ran repeated
supply(small)/withdraw(larger)round trips that minted and burned the same scaled amount while moving more underlying out than in. A representative transaction was0xbaf678512e38435f869aa811686a5f835036784576fa5bd4dc171586576130f8, which packed1,151round trips and drained about1,154,739base units (~0.01155 PT-LBTC) from the aToken balance. - Across three larger exploit iterations, the attacker accumulated and re-supplied about
10.9955337 PT-LBTCof collateral. That collateral was largely the same PT-LBTC previously drained from the protocol itself. - Against that collateral, the attacker borrowed LBTC in three transactions:
0xdf1c69feb8e63c70f874cdff22bba7c53eb42a5245e9695713e850966c54ce2afor0.95324998 LBTC,0x47fbcdc986c08bf779cb66267c3f6baa0dd43d6a8591f548dbcda5a1c9fce2d2for1.47687998 LBTC, and0xc02cea219b2748ccb8e28b2b23c14d7f6d3d144724ba1b9e17adbf07e70e51a3for1.492946 LBTC. - The attacker never repaid. ZeroLend was left with unpaid LBTC debt backed by PT-LBTC collateral that had been fabricated economically from the protocol’s own accounting mismatch and same-market draining loop.
- The attacker targeted the PT-LBTC market after it had been initialized with only
- What the attacker needed to be true:
- The PT-LBTC reserve had to have dust-sized scaled liquidity so flash-loan premiums would produce outsized
liquidityIndexjumps. - Flash loans had to settle against the aToken’s real ERC-20 balance rather than the scaled supplier book.
- Direct transfers to the aToken contract had to remain off-path from
supply()accounting, so the attacker could enlarge lendable balance without enlargingtotalLiquidity. - The same
liquidityIndexhad to drive both collateral presentation andrayDiv()conversions insupply()/withdraw(). - ZeroLend had to let the PT-LBTC reserve count as collateral against a connected, deeper LBTC reserve.
- The PT-LBTC reserve had to have dust-sized scaled liquidity so flash-loan premiums would produce outsized
- Technical anatomy worth remembering:
- This is an Aave-v3-family supplier-index exploit, but the key setup is not only “empty market.” It is the sharper accounting boundary where flash loans see real token balance while supplier accounting and fee distribution still see only scaled liquidity.
- The PT-LBTC direct transfer was not itself recognized as a deposit. It was valuable because it created a flash-loanable pool balance that
cumulateToLiquidityIndex()later treated as if premiums were being earned on a healthy reserve. - The drain phase was not classic same-path precision loss like Radiant or HopeLend. Here the inflated index widened the
rayDiv()rounding window enough that repeated supply / withdraw round trips could pull net PT-LBTC from the reserve while finishing each loop with zero scaled balance. - The collateral the protocol later honored was economically circular: the attacker drained PT-LBTC from the market, re-supplied that same PT-LBTC, and then borrowed a stronger asset from a sibling reserve.
- Asset choice mattered operationally. PT-LBTC was an exotic Pendle principal token with tiny seed liquidity, while LBTC was the deeper, cash-out-worthy reserve in the same pool.
- Representative transactions and artifacts:
- Attacker EOA:
0x218C572b1Ab6065D74bEbcB708a3f523D14F7719 - Market initialization tx:
0xf7d6adc8bebacb6897277334949b2d952a7266eaae47085f9035923d0f5f94a5 - Flash-loan index-inflation tx:
0xa14b926bd19ce0e23b17d94802816700e1ea2353f7ceb8e493ae0ddcca4cbad6 - Representative rounding-window arb tx:
0xbaf678512e38435f869aa811686a5f835036784576fa5bd4dc171586576130f8 - Borrow txs:
0xdf1c69feb8e63c70f874cdff22bba7c53eb42a5245e9695713e850966c54ce2a,0x47fbcdc986c08bf779cb66267c3f6baa0dd43d6a8591f548dbcda5a1c9fce2d2,0xc02cea219b2748ccb8e28b2b23c14d7f6d3d144724ba1b9e17adbf07e70e51a3
- Attacker EOA:
- Response / mitigation:
- Public disclosure appears to have lagged badly; outside researchers later pointed to the unpaid debt token and the unchanged collateral position as evidence the borrow had never been repaid.
- The incident is a strong argument for treating direct underlying transfers to receipt-token contracts as a security boundary, not harmless dust.
- More generally, Aave-style forks need explicit minimum-liquidity launch discipline, safer fee-routing in empty markets, and controls preventing exotic collateral markets from underwriting deeper sibling reserves before they are operationally mature.
Three reserve-ledger promotions this incident collapsed
-
Raw PT-LBTC sitting at the
aTokenwas still allowed to become reserve-like state- The attacker did not begin by minting new supplier claims through
Pool.supply(). - They first created a wider raw-balance object by transferring PT-LBTC directly to the
aToken, so the reserve now physically held inventory that flash-loan paths could touch even though scaled-liquidity accounting had not adopted the same value. - The dangerous fact was not merely
extra tokens arrived; it was that a fresh, attacker-authored custody balance was already close enough to reserve truth to matter operationally.
- The attacker did not begin by minting new supplier claims through
-
That wider raw-balance object was still promoted into canonical supplier-side solvency truth through
liquidityIndex- Flash-loan premiums generated off the enlarged raw balance were socialized through
cumulateToLiquidityIndex()while the denominator stayed near the tiny initialized scaled-liquidity book. - ZeroLend therefore treated attacker-authored fee loops as if they were ordinary supplier earnings on a healthy reserve, even though the reserve’s real-balance ledger and supplier-denominator ledger still disagreed.
- This was the decisive accounting promotion: the protocol converted
raw balance can be usedintosupplier-side collateral truth has grownwithout first restoring denominator health or reserve-ledger coherence.
- Flash-loan premiums generated off the enlarged raw balance were socialized through
-
That published supplier-side truth still inherited immediate same-reserve extraction and sibling-reserve borrowing authority
- Once the inflated
liquidityIndexwidened therayDiv()rounding window, the attacker could drain PT-LBTC from the same reserve through repeatedsupply()/withdraw()loops while finishing with the same scaled balance. - ZeroLend then accepted that locally drained PT-LBTC back as ordinary collateral and let it underwrite real LBTC borrowing from the sibling reserve.
- The terminal failure was therefore not just
the index got big; it was that the reserve’s weaker local accounting story still retained full extraction and cross-market debt authority.
- Once the inflated
Pattern tags
- supplier-index-inflation
- entrypoint-only-policy-off-path-state-mutation
- balance-delta-provenance-failure
- The preserved shape is sharper than generic
empty market: a dust-seeded exotic reserve was already allowed to matter cross-market, direct transfers to theaTokenenlarged real flash-loanable balance without enlarging scaled liquidity, and the same inflatedliquidityIndexlater widened therayDiv()rounding window used to drain PT-LBTC and print borrowable LBTC-facing collateral. - This was also a clean
listed reserve != mature collateral objectfailure. The PT-LBTC market did not need to be large to be dangerous; it only needed enough borrow-side authority that one actor could turn a tiny seed denominator and off-path balance growth into sibling-market debt.
Why the exploit worked
- ZeroLend let a reserve initialized with only
32,680base units of PT-LBTC — about0.000327tokens — stay economically meaningful against a deeper LBTC market. That meant the protocol had already granted cross-market solvency authority to a market whose denominator could still be dominated by one actor. - The attacker’s direct transfer to the
aTokencreated the decisive accounting split. Flash loans and raw balance checks could see about5,032,680base units of PT-LBTC sitting in the contract, while supplier accounting andcumulateToLiquidityIndex()still socialized premiums over the tiny scaled-liquidity view left by initialization. - Once that split existed, flash-loan premiums stopped behaving like ordinary LP income and became attacker-directed index growth. Public reconstruction shows the reserve’s
liquidityIndexclimbing from1e27to roughly2015e27whileliquidityRateitself stayed at0, which is the tell that protocol-authored fee recycling — not real lending demand — was manufacturing the solvency story. - The inflated index then widened the
rayDiv()rounding window enough that repeatedsupply(small)/withdraw(larger)round trips could drain net PT-LBTC from the same reserve while ending each loop with the same scaled balance. ZeroLend was not only fooled at collateral admission time; it also let the same printed index make same-market extraction mechanically profitable. - Downstream borrowing completed the failure. ZeroLend accepted the freshly drained PT-LBTC back as collateral at face value against the deeper LBTC reserve, so the protocol effectively treated its own previously extracted exotic collateral as new security for real money-good assets.
Detection and audit pivots
- Reconstruct the whole reserve object, not only
liquidityIndex:actual underlying balance, scaled supply,totalLiquidity, flash-loan premium routing, and collateral checks should all reconcile before the reserve can matter outside itself. - Force raw ERC-20 transfers into the
aTokenduring testing and then run flash loans, fee settlement, and borrow-cap checks. If direct transfers can enlarge flash-loanable balance without enlarging the supplier-index denominator, the reserve still has a live off-path balance mutation problem. - Ask the lifecycle question explicitly: what onchain fact proved this PT-LBTC reserve had earned cross-market collateral relevance rather than merely being listed with a dust seed? If the answer is only
the market exists, the launch-state hardening was never real. - Fuzz
rayDiv()-driven mint / burn windows at extreme index values for 8-decimal assets. The interesting bug surface here was not just index inflation, but that the inflated index widened an exploitablescaledAmount = 1window big enough to make repeated round trips net-positive. - Check whether a reserve can accept locally drained assets back as ordinary collateral against a sibling reserve without any second distrust boundary. If same-market extraction can immediately finance cross-market borrowing, the protocol is still collapsing
local accounting artifactintosystem-wide borrow truth.
Failure-prevention checklist
- Keep exotic or newly listed reserves at collateral factor
0until they have irreversible seed liquidity, accounting-parity checks, and reserve-specific monitoring in place.Market existsis not enough evidence thatmarket may underwrite sibling-market debt. - Make raw transfers into the
aTokeneconomically inert until one explicit recognition path adopts them. Direct transfers, rescue top-ups, over-repayments, and settlement leftovers should not become flash-loanable balance, supplier-index-bearing value, or collateral-visible backing by accident. - Reconcile
underlying.balanceOf(aToken), scaled supply,totalLiquidity, flash-loan capacity, and collateral value after every off-path balance mutation. If those ledgers can disagree, document exactly which one each subsystem trusts and fail closed when they diverge. - Stop fee socialization when the denominator is dust. Flash-loan premiums and similar reserve income should route to treasury, suspense accounting, or a quarantined lane whenever scaled liquidity is too thin for safe supplier distribution.
- Bound same-reserve extraction loops before the reserve can matter cross-market. If an inflated
liquidityIndexcan widen a profitablerayDiv()window, the reserve should not still be allowed to mint collateral power against a deeper sibling market. - Fuzz
supply()/withdraw()/ flash-loan settlement at extreme index values for low-decimal assets. The interesting bug surface here was not only fee-driven index growth, but thatscaledAmount = 1remained a profitable steady state once the index became enormous. - Alert on production-impossible states:
liquidityRate == 0whileliquidityIndexjumps sharply, real reserve balance exceeding scaled-liquidity accounting by large multiples, one meaningful supplier, or a reserve whose collateral position can only be explained by locally drained assets being re-supplied. - Keep borrow-side blast radius small even if local accounting slips: isolated mode, collateral caps, reserve freezes, and fast collateral-factor zeroing should stop one thin exotic reserve from draining a deeper money-good market.
Audit prompts
- What exact ledger defines flash-loanable PT-LBTC here: the
aToken’s raw ERC-20 balance, scaled supply, reserve cache, or some hybrid? Can a direct transfer enlarge that ledger without minting any supplier claim? - When raw underlying arrives at the
aTokenoutsidePool.supply(), which variables move immediately: flash-loan capacity,totalLiquidity,liquidityIndex, collateral value, utilization, or none of the above? - What onchain fact proved this PT-LBTC market had graduated from
dust-seeded launch objecttoordinary collateral objectbefore it was trusted against LBTC? - Can the same reserve both create the fake collateral and serve as the extraction source? If locally drained PT-LBTC can be re-supplied moments later and honored at face value against LBTC, where is the second distrust boundary?
- At extreme
liquidityIndexvalues, what range of raw amounts still maps toscaledAmount = 1orscaledAmount = 2underrayDiv()? Which mint / burn / withdraw paths become net-positive because integer rounding stopped tracking economics? - Does fee routing assume that any flash-loan premium is honest supplier income, or is there a minimum-liquidity / minimum-breadth condition before the reserve is allowed to socialize it?
- If the reserve is post-maturity and oracle-priced
1:1, what additional check proves the posted collateral is economically external value rather than the protocol’s own freshly drained inventory returning throughsupply()? - Which control would have stopped this exploit first in production: reserve freeze, flash-loan disable, collateral-factor zeroing, unexpected-balance quarantine, or per-block index-growth clamp?
Lessons that should already be standard
- A listed reserve is not automatically a mature collateral object. Dust-seeded exotic markets should be treated as effectively fresh until code proves otherwise.
- Flash-loan fee routing is solvency code. If the same actor can manufacture the premium and monopolize the denominator it is socialized over, the protocol is printing collateral for them.
- Raw token balance sitting in an
aTokenis not the same thing as supplier-owned liquidity. If flash loans trust one ledger while supplier accounting trusts another, the gap is already part of the attack surface. - Extreme index values turn tiny rounding choices into real money. Once
scaledAmount = 1spans a wide raw-amount interval,rounding bugis just another name forwithdraw authority. - Exotic collateral needs a second trust boundary beyond local pricing.
Oracle says PT-LBTC is 1:1 post-maturitywas not enough to justify treating freshly drained PT-LBTC as money-good security for LBTC. - Protocols should assume an attacker will try to borrow against the protocol’s own previously extracted inventory. If that sounds impossible, the reserve and collateral ledgers are probably still too loosely coupled.
- Silence is not containment. An unpaid debt token and frozen-withdrawal symptoms are incident signals, not optics problems, and should trigger user disclosure and hard containment rather than UI excuses.
Why this incident matters
- ZeroLend preserves a distinct supplier-index-inflation variant beyond Radiant and HopeLend. The printable variable was still
liquidityIndex, but the exploit path hinged on real-balance-versus-scaled-liquidity divergence before the rounding drain and collateral borrow. - It is a strong exemplar for why off-path balance changes and flash-loan accounting cannot be analyzed separately. The dangerous state came from their composition.
- It also shows how a protocol can end up accepting its own freshly drained exotic collateral as security for a deeper reserve, which is a particularly vivid form of cross-market balance-sheet self-deception.
- The note is also useful as a launch-discipline reminder: a market seeded with dust can still be effectively fresh even if it is technically live, and an exotic reserve should not earn sibling-market borrow authority before the protocol can prove that off-path balance changes, fee socialization, and rounding behavior still share one coherent balance sheet.
Internal linkages
- Closest recurring pattern: supplier-index-inflation.
- Broader empty-market family: empty-market-exchange-rate-inflation.
- Adjacent pattern: entrypoint-only-policy-off-path-state-mutation.
- Adjacent pattern: balance-delta-provenance-failure.
- Positive-design companion: fresh-market-seeding-and-activation-discipline — the sharper upstream follow-up is why a dust-seeded exotic reserve should not become cross-market collateral truth before irreversible floor or maturity checks exist.
- Positive-design companion: collateral-admission-lag-and-borrow-side-quarantine — the downstream follow-up is why PT-LBTC’s local accounting and post-maturity face-value pricing were allowed to become immediate LBTC borrow power.
- Positive-design companion: actual-balance-and-scaled-liquidity-ledger-coherence-discipline — the sharper reserve-design follow-up is how flash-loanable balance, recognized liquidity, supplier-index denominator state, and collateral-visible backing should have been forced to read one coherent reserve object instead of letting direct
aTokentransfers create an exploitable ledger split. - Useful comparison: radiant-capital-native-usdc-empty-market-liquidityindex-exploit-2024-01-02.
- Useful comparison: hopelend-hethwbtc-empty-market-liquidityindex-exploit-2023-10-18.
- Useful contrast: dtrinity-dlend-cbbtc-fresh-market-liquidityindex-inflation-exploit-2026-03-17 — another Aave-family reserve where dust supplier state plus direct
aTokenbalance growth sharpened a fresh-marketliquidityIndexexploit into an accounting-boundary problem.
Control surface
-
The decisive leverage sat in dust-liquidity market initialization, direct-transfer accounting gaps at the aToken boundary, flash-loan premium routing into
liquidityIndex,rayDiv()rounding windows at extreme indexes, and cross-market collateral admission. -
ZeroLend did not fail because PT-LBTC was exotic in the abstract. It failed because a tiny exotic reserve was allowed to turn off-path balance, fee accrual, and rounding behavior into borrowable truth for a deeper money-good reserve.
-
Sources:
- https://www.kyp.one/notes/zerolend-liquidity-index-manipulation/
- https://rekt.news/zero-to-lend
- https://x.com/torakapa/status/1996645966632702295
- https://basescan.org/tx/0xf7d6adc8bebacb6897277334949b2d952a7266eaae47085f9035923d0f5f94a5
- https://basescan.org/tx/0xa14b926bd19ce0e23b17d94802816700e1ea2353f7ceb8e493ae0ddcca4cbad6
- https://basescan.org/tx/0xbaf678512e38435f869aa811686a5f835036784576fa5bd4dc171586576130f8
- https://basescan.org/tx/0xdf1c69feb8e63c70f874cdff22bba7c53eb42a5245e9695713e850966c54ce2a
- https://basescan.org/tx/0x47fbcdc986c08bf779cb66267c3f6baa0dd43d6a8591f548dbcda5a1c9fce2d2
- https://basescan.org/tx/0xc02cea219b2748ccb8e28b2b23c14d7f6d3d144724ba1b9e17adbf07e70e51a3
-
Last reviewed: 2026-08-02 UTC