zkLend wstETH Empty-Market Accumulator-Inflation Exploit (2025-02-11)
- Name: zkLend wstETH empty-market accumulator-inflation exploit
- Date: 2025-02-11
- Protocol: zkLend
- Chain: Starknet
- Category: empty-market accumulator inflation / precision-loss withdrawal / cross-market lending drain
- Loss: about $9.57 million
- Summary: The attacker targeted zkLend’s empty wstETH market and used its flash-loan donation path to pump the market-wide
lending_accumulatorfrom1to roughly4.069297906051644020e18. With the accumulator inflated, they deposited about4.069297906051644020wstETH to obtain araw_balanceof2, then repeatedly ran deposit/withdraw loops whose truncating burn math reducedraw_balancetoo slowly. That let a microscopic starting position grow into roughly1,724raw units worth about7,015.4wstETH on paper. zkLend then treated that fabricated collateral as real solvency, letting the attacker borrow ETH, USDC, USDT, STRK, and other assets before bridging much of the haul to Ethereum. - Trigger / entry point:
- The decisive target was zkLend’s empty wstETH market at
0x04c0a5193d58f74fbace4b74dcf65481e734ed1714121bdc571da345540efa05. - zkLend represented collateral as
lending_accumulator * raw_balance, so a global accounting scaler rather than spot oracle price became the printable variable. - The market’s flash-loan settlement path accepted over-repayment as donation and folded that extra reserve balance into
lending_accumulatorthroughthesettle_extra_reserve_balance(). - Withdrawal / burn logic used floor-rounded scaling, so once
lending_accumulatorbecame enormous, burning large zwstETH amounts could reduceraw_balanceby only1.
- The decisive target was zkLend’s empty wstETH market at
- Root cause:
- zkLend let an empty market start from effectively zero real liquidity and accepted arbitrary early deposits into that state.
- Its flash-loan donation mechanism fed directly into the global collateral scaler for suppliers, so excess repayment could print paper collateral value instead of merely donating idle reserves.
- Its mint / burn conversions truncated during scale-down, which became exploitable once the denominator had been inflated to absurd size.
- In short: an empty market, a donation-sensitive accumulator, and attacker-favorable precision loss combined into fake collateral that the rest of the protocol honored as real.
- Exploit path:
- Around 17:30 UTC on 2025-02-11, the attacker initialized the empty wstETH market with a
1 weideposit in transaction0x01c0f62dc1c0c013fff273b8d25c2e761364c036e327285c3838b9415500d2f1, confirming the market had previously held0wstETH and0zwstETH supply. - In transaction
0x039b6587b9d545cfde7c0f6646085ab0c39cc34e15c665613c30f148b569687c, they flash-borrowed1 weiwstETH and repaid1000 wei, causing the extra999 weidonation to raiselending_accumulatorfrom1to about851. - They repeated that
1 weiflash-loan / over-repayment pattern ten times untillending_accumulatorreached roughly4,069,297,906,051,644,020. - With the scaler inflated, they deposited
4.069297906051644020wstETH and received araw_balanceof2, giving themselves a tiny but strategically positioned share base inside the manipulated market. - In transaction
0x001e3c2ebb5b4eafc4800c178dcbd6aa36233d40733bc419d6bce47f8c48d6e6, and then many follow-on loops, the attacker repeatedly deposited amounts equal to integer multiples of the accumulator and withdrew about1.5xaccumulator-sized chunks. - Because the burn path scaled down with floor rounding, those withdrawals removed more wstETH than the deposits added while decrementing
raw_balancetoo little. The first loop sequence pushedraw_balancefrom2to3; later sequences repeated the same trick at larger size. - A representative later loop transaction,
0x24cd75cd7cf619718bb62df404ce6d53c17f601645ea882ceae7933d5b07b8a, performed 30 deposits and 30 withdrawals, extracting about61.39wstETH in one cycle and re-feeding it into the attack. - After enough iterations, the attacker had inflated
raw_balanceto about1,724, which zkLend priced as roughly7,015.4wstETH of collateral. - They then borrowed stronger assets from the rest of the protocol, including ETH, USDC, USDT, and STRK, turning the printed collateral into real withdrawable value.
- The attacker bridged a large share of the stolen assets to Ethereum via StarkGate, including a message identified by
0xee7887af574928f92cc80384cd9febb41aa4f18c97133c974206f8a2b9baba09, which settled on Ethereum in transaction0xe107aca6535c8b67356dfa2d741bb3d4c4374f558b58a70f8e34e4c1bf1328e7.
- Around 17:30 UTC on 2025-02-11, the attacker initialized the empty wstETH market with a
- What the attacker needed to be true:
- The wstETH market had to be empty so the first depositor could control the initial accounting state.
- Flash-loan over-repayment had to count as a donation that immediately increased
lending_accumulatorfor suppliers. - The protocol had to let the same actor monopolize the tiny raw share supply while inflating the accumulator.
- Mint / burn scaling had to floor-round in a way that under-burned
raw_balanceonce the accumulator was huge. - zkLend had to accept the resulting paper collateral quickly enough to allow cross-asset borrowing from deeper pools.
- Technical anatomy worth remembering:
- This was not a direct theft of other users’ wstETH deposits. The attacker used a tiny amount of wstETH to manufacture collateral value, then stole other assets against that fabricated solvency.
- The printable variable was neither a classic price oracle nor a Compound-style exchange rate. It was zkLend’s
lending_accumulator, a market-wide scaler intended to distribute supplier earnings. - The donation path mattered as much as the rounding bug. Without the flash-loan over-repayment mechanism, the attacker could not have blown the denominator up to the level needed for the truncation to matter.
- The exploit shows the same family resemblance as Hundred, Midas, Onyx, Sonne, and Radiant even though the implementation stack was different: empty market first, inflated accounting variable second, under-burn or under-accounting third, real cross-market borrowing last.
- zk systems do not get a special exemption from bad integer math. The Starknet setting changed the codebase, not the invariant.
- Representative transactions and artifacts:
- Attacker Starknet account:
0x04d7191dc8eac499bac710dd368706e3ce76c9945da52535de770d06ce7d3b26 - Attack contract:
0x0193da87dc0b317f8418ae0c8fb3e0301698ed2d1a4047191d4641ddabc1e2bf - Target wstETH market:
0x04c0a5193d58f74fbace4b74dcf65481e734ed1714121bdc571da345540efa05 - Empty-market initialization tx:
0x01c0f62dc1c0c013fff273b8d25c2e761364c036e327285c3838b9415500d2f1 - First accumulator-pump tx:
0x039b6587b9d545cfde7c0f6646085ab0c39cc34e15c665613c30f148b569687c - First
raw_balance-inflation loop tx:0x001e3c2ebb5b4eafc4800c178dcbd6aa36233d40733bc419d6bce47f8c48d6e6 - Representative later loop tx:
0x24cd75cd7cf619718bb62df404ce6d53c17f601645ea882ceae7933d5b07b8a - Starknet-to-Ethereum bridge message:
0xee7887af574928f92cc80384cd9febb41aa4f18c97133c974206f8a2b9baba09 - Ethereum bridge settlement tx:
0xe107aca6535c8b67356dfa2d741bb3d4c4374f558b58a70f8e34e4c1bf1328e7
- Attacker Starknet account:
- Response / mitigation:
- zkLend paused withdrawals and later published a post-mortem describing the exploit as the combination of empty-market conditions, donation-driven accumulator inflation, and precision loss.
- The team publicly offered the attacker a 10% white-hat settlement if funds were returned, but the deadline passed without recovery.
- Railgun publicly rejected at least one laundering attempt under its exclusion-policy flow, sending funds back rather than shielding them.
- SlowMist argued onchain links connected the operator to the July 2023 EraLend exploit, suggesting this was not just a copycat technique but potentially a repeat exploiter reusing an empty-market playbook.
Three reserve-authority promotions this incident collapsed
-
An empty reserve was still allowed to remain attacker-owned denominator state
- The decisive setup was not only
wstETH market exists on zkLend. It was that the reserve still had effectively zero prior supplier breadth, so the attacker could define what the next unit of supplier-side value meant. - A
1 weiopening deposit plus repeated flash-loan over-repayments was enough because zkLend let a live collateral market stay economically equivalent to genesis.
- The decisive setup was not only
-
That attacker-shaped denominator was still promoted into canonical supplier-side solvency truth
- The over-repayment path raised
lending_accumulator, and zkLend treated that accumulator jump as ordinary supplier value even though the market’s meaningfulraw_balancedenominator was still attacker-owned dust. - In other words, the protocol promoted
extra reserve balance appearedintosuppliers are now richer in a way collateral math should trust, instead of treating empty-market accumulator growth as immature accounting that still needed quarantine.
- The over-repayment path raised
-
That published supplier-side truth still deserved immediate reserve-spending authority elsewhere
- Once
lending_accumulator * raw_balancebecame enormous on paper, zkLend let the attacker borrow ETH, USDC, USDT, STRK, and other assets from deeper pools. - The later under-burn loops mattered because the protocol had already crossed the decisive boundary: a zero-breadth reserve whose collateral meaning was still attacker-authored retained live cross-market borrowing authority instead of staying locally visible but economically quarantined.
- Once
The durable lesson is that the reserve is deployed, the reserve has published a supplier-side value, and that value deserves immediate cross-market borrowing power are three separate promotions. zkLend failed all three in the same exploit window.
Pattern tags
- supplier-index-inflation
- entrypoint-only-policy-off-path-state-mutation
- The preserved shape is sharper than generic
empty market on Starknet: zkLend let a zero-breadth reserve accept flash-loan over-repayment as supplier-side revenue, let that donation-sensitivelending_accumulatorredefine collateral value over a microscopicraw_balancedenominator, and then let the printed mark unlock deeper pools. - This was also a clean
listed market != mature collateral objectfailure. The wstETH reserve did not need an external oracle failure or a large user base; it only needed enough borrow-side authority that one actor could turn an empty-state accounting object into protocol-wide solvency fiction.
Why the exploit worked
- zkLend let a market that had previously held
0wstETH and0zwstETH supply behave like a mature collateral source the moment the first depositor arrived. That meant the attacker could define the opening denominator for the entire supplier-side accounting story instead of being diluted by an existing deposit base. - The flash-loan repayment path then turned over-repayment into supplier-side value rather than quarantined surplus. In an empty market, that is not harmless protocol revenue; it is attacker-directed accumulator inflation. Once
lending_accumulatormoved from1to about4.069297906051644020e18, zkLend had already allowed one user to decide what the next unit of supplier income meant. - The burn path completed the exploit pipeline. Because mint / withdraw conversions floor-rounded when scaling back down to
raw_balance, the inflated accumulator made tiny accounting errors economically huge. Withdrawals could remove much more wstETH than the correspondingraw_balancedecrement justified, so the attacker could keep growing the same microscopic position while extracting real assets from the reserve. - Downstream borrowing then treated
lending_accumulator * raw_balanceas ordinary collateral truth. There was no second distrust boundary where a zero-breadth market, a market showing impossible accumulator growth, or a market whose supplier-side value came from flash-loan over-repayment had to remain locally observable but still economically quarantined. - The preserved lesson is therefore broader than
Starknet rounding bug: empty-market lifecycle control, donation handling, accumulator publication, burn rounding, and collateral admission all belonged to one security object, and zkLend split them apart.
Detection and audit pivots
- Ask the lifecycle question explicitly: what onchain fact proved this wstETH market had passed out of its dangerous opening state, and what kept that fact true once the first meaningful supplier controlled all
raw_balance? If the answer is onlythe market exists, the reserve was still economically at genesis. - Treat flash-loan over-repayment and donation handling as solvency code. If surplus repayment can increase a market-wide supplier scaler in a thin reserve, then flash-loan settlement is already part of collateral design rather than just fee plumbing.
- Reconstruct the whole supplier-value object, not only
lending_accumulator: reserve balance, extra-reserve settlement paths,raw_balance, mint / burn conversions, and cross-market collateral checks should all tell one coherent story before the market can matter system-wide. - Simulate the exact edge state the exploit used: one meaningful supplier, repeated micro flash loans with over-repayment, then mint / withdraw loops under extreme accumulator values. If the harness only tests normal-size deposits and withdrawals, it will miss the state where floor rounding becomes catastrophic.
- Check for a downstream distrust boundary. Can an empty or effectively fresh market stay observable while collateral factor is
0, borrowing is disabled, flash loans are off, or per-market debt caps stay too small for one printed accumulator to drain sibling pools?
Failure-prevention checklist
- Do not let a newly listed or zero-breadth reserve become borrow-relevant until a non-burnable seed floor or another irreversible denominator guard already exists onchain.
First depositor arrivedis not a security boundary if that opening state can still be monopolized or recreated. - Treat flash-loan over-repayment, donations, and surplus-settlement paths as solvency code. If extra reserve balance can raise a supplier-side scaler like
lending_accumulator, that path needs the same review as collateral valuation itself. - Keep
reserve_balance,lending_accumulator,raw_balance, and cross-market collateral value on one coherent accounting story. If one path can raise supplier-side value without proportionally increasing irreversible supplier ownership, the reserve is still printable. - Quarantine empty and effectively empty markets. Collateral factor
0, flash loans off, tight debt caps, or other local-only modes should remain in force until the reserve has real supplier breadth and ordinary accumulator behavior. - Make burn math safe at pathological accumulator values and minimum-unit asset granularities. If the economically correct burn is just under
2raw units, the protocol still needs to consume enough state that a withdrawal cannot mint value. - Re-run launch-state protections when a reserve drifts back toward dust supply. A market that later becomes one-supplier or near-empty should lose the same authorities a fresh reserve lacks.
- Monitor production-impossible growth explicitly: one meaningful supplier, accumulator jumps that imply absurd APY, or supplier-side value expanding much faster than the reserve’s organic earnings should trigger automatic quarantine rather than only dashboards.
- Keep blast-radius controls underneath the market anyway: per-reserve debt caps, fast pauses, flash-loan toggles, and correlated-borrow restrictions should stop one manipulated scaler from draining the rest of the book.
Audit prompts
- What exact onchain fact proves this reserve is out of its dangerous opening state, and what keeps that fact true if the first supplier later redeems back toward dust?
- Can flash-loan over-repayment, direct donation, rescue transfer, or other surplus path raise
lending_accumulatorwhile one actor still controls essentially all meaningfulraw_balance? - At extreme accumulator values, how many raw units should a withdrawal burn economically, and where does floor rounding let the protocol destroy less supplier state than the withdrawal consumes?
- Are flash-loan settlement, reserve-surplus accounting, supplier-scaler updates, and collateral publication all reconciled over the same denominator, or can one path publish value faster than supplier ownership grows?
- Can an empty or freshly manipulated reserve immediately define borrow power for deeper sibling assets, or is there a second distrust boundary that keeps local accounting anomalies from becoming system-wide solvency truth?
- What automated signal would catch this early: one meaningful supplier, impossible accumulator growth, reserve-balance jumps sourced by over-repayment, or collateral-value expansion unsupported by real earnings?
- If the reserve later becomes dormant or nearly empty again, does it automatically fall back into the same quarantine a fresh reserve should start in?
Lessons that should already be standard
- Empty markets are privileged accounting states, not harmless low-usage corners. Whoever controls the first meaningful raw units often controls the meaning of the next unit of supplier income.
- Flash-loan over-repayment is not benign fee plumbing when it can raise a market-wide collateral scaler. In thin markets it becomes an attacker-directed solvency input.
accumulator * raw_balanceis an oracle by another name once downstream borrowing trusts it. It deserves the same skepticism as any published collateral price.- Precision loss only looks small while denominators are healthy. Once one actor can inflate the scaler that prices withdrawals, rounding turns into balance-sheet policy.
- Cross-market lending monetizes local accounting lies. A manipulated empty reserve becomes dangerous the moment the rest of the protocol agrees to honor its paper collateral.
- Different execution stacks do not change the invariant. Starknet-specific code still failed on the old question: who was allowed to define borrowable truth in an empty market?
Why this incident matters
- zkLend broadened the empty-market exploit corpus beyond Compound-v2 and Aave-v2 forks into Starknet-specific accumulator accounting.
- It shows that
accumulator * raw_balancedesigns can fail in the same structural way as exchange-rate orliquidityIndexsystems when an empty market lets one user define the first meaningful state. - It is also a strong reminder that flash-loan repayment handling is part of solvency design, not just a convenience feature.
- The stronger lifecycle lesson is that
market deployed on a different stackdid not change the underlying launch-state problem: a zero-breadth reserve was still allowed to publish borrowable collateral truth before any irreversible seed floor or borrow-side quarantine existed.
Internal linkages
- Core recurring pattern: supplier-index-inflation.
- Broader empty-market family: empty-market-exchange-rate-inflation.
- Adjacent pattern: entrypoint-only-policy-off-path-state-mutation — useful when the sharper question is why flash-loan over-repayment or other off-path balance changes were allowed to redefine supplier-side accounting truth.
- Adjacent pattern: donation-share-price-manipulation — the same high-level
attacker-authored accounting growth becomes collateral truthfamily, but here the printable object was a lending-market accumulator rather than a vault share rate. - Positive-design companion: fresh-market-seeding-and-activation-discipline — the sharper upstream follow-up is why an empty wstETH reserve should not have gained cross-market solvency authority before any irreversible opening-state hardening existed.
- Positive-design companion: collateral-admission-lag-and-borrow-side-quarantine — the downstream follow-up is why a market with impossible accumulator growth was allowed to define borrow power for deeper assets instead of remaining locally observable but economically quarantined.
- Useful comparison: radiant-capital-native-usdc-empty-market-liquidityindex-exploit-2024-01-02 — the cleaner Aave-v2-style fresh-market replay where
liquidityIndex, notlending_accumulator, was the printable supplier-side variable. - Useful comparison: zerolend-pt-lbtc-actual-balance-scaled-liquidity-liquidityindex-exploit-2025-02-23 — the sharper case where off-path balance growth versus supplier-index denominator mismatch is itself the decisive accounting boundary.
Control surface
-
The decisive leverage sat in empty-market initialization, flash-loan donation accounting,
lending_accumulatorupdate rules, floor-rounded mint / burn scaling, and collateral policy that let one manipulated market unlock the rest of the book. -
zkLend did not merely have a rounding bug. It let a user print a market-wide collateral multiplier in an empty market and then borrow real assets against that fiction.
-
The recurring systems mistake was to treat flash-loan settlement, market-wide supplier accounting, and downstream collateral admission as separate implementation concerns even though the attacker only needed one path between them.
-
Sources:
- https://x.com/zkLend/status/1889424818967371779
- https://x.com/zkLend/status/1890389052492509362
- https://community.starknet.io/t/zklend-emergency-relief-plan/115342
- https://blocksec.com/blog/zklend-exploit-post-mortem-unraveling-the-details-and-clarifying-misunderstandings-of-the-10m-flash-loan-attack
- https://rekt.news/zklend-rekt
- https://slowmist.medium.com/in-depth-analysis-of-zklend-hack-linked-to-eralend-hack-fba4af9b66ef
- https://app.zklend.com/dashboard
-
Last reviewed: 2026-08-02 UTC