Reproduced Exploit
Lend V2: a liquidated CoreRouter overpays early redeemers and strands the last
Chain
Other
Category
untagged
Date
Jan 1970
Source
AuditVault
EVM Playground
Source-level debugger — step opcodes and Solidity in sync
The attack is replayed in an in-browser EVM preloaded with the exact dumped fork state. The execution tree shows every call; step by Solidity line or by opcode across all depths — source, Stack, Memory, Storage, Balances (native / ERC-20 / NFT), Transient storage and Return value stay in sync. Click a tree node, opcode, or source line to jump. No backend, no live RPC.
Source & credit. Reproduction of a public audit finding curated by AuditVault — the original finding: 824. Standalone Foundry PoC and full write-up: 58386-lend-liquidating-corerouter-can-cause-excess-borrower-loss_exp in the
evm-hack-registrymirror.
Vulnerability classes: vuln/logic · vuln/loss-of-funds
Reproduction: a faithful minimal reproduction of the vulnerable finding — the vulnerable
redeembranch ofCoreRouteris reproduced verbatim (marked@>) with faithful minimal doubles; local deploy, no fork.
Root cause#
In Lend-V2/src/LayerZero/CoreRouter.sol, redeem computes each user's payout as _amount * exchangeRateBefore, derived purely from the LToken exchange rate. Because CoreRouter is a single pooled account that is both supplier and borrower in the LToken market, it can be liquidated there — but the payout never consults CoreRouter's actual remaining collateral, so early redeemers are paid in full while the last redeemer is stranded. The vulnerable line, reproduced verbatim:
// Calculate expected underlying tokens
@> uint256 expectedUnderlying = (_amount * exchangeRateBefore) / 1e18;
exchangeRateBefore is unchanged by the liquidation, so the arithmetic keeps paying out full value even after CoreRouter's share balance has been reduced below the total the pooled users are recorded to be owed.
Why it's exploitable here#
Two honest users each supply 1000 — the pool holds 2000 underlying and CoreRouter holds 2000 LToken shares, with per-user totals recorded in LendStorage:
- External event: CoreRouter is liquidated in the LToken market.
500of its shares are seized, so it now backs only1500of the2000recorded. - First redeemer redeems
1000. Line 197 computes1000 * 1e18 / 1e18 = 1000at the stale rate;LToken.redeemburns1000of CoreRouter's1500shares and transfers1000underlying. The pool now holds1000underlying and CoreRouter500shares. - Honest second supplier redeems
1000. CoreRouter holds only500shares, soLToken.redeemreturns the Compound-style non-zero error code, therequirereverts, and the redeem fails. - The second user recovers nothing and loses their entire
1000collateral — the whole liquidation shortfall is concentrated on the last redeemer instead of being shared pro-rata.
Attack path#
Marked-line walkthrough (Playground)#
The EVM Playground pins each step to the exact executed source line in 0xbd4fd5a3…:
- L186 — Balance check passes on stale total: CoreRouter checks the redeemer's recorded per-user investment still covers the amount; the liquidation never touched that mirror, so the guard passes.
- L194 — Read the stale exchange rate: CoreRouter reads the LToken exchange rate, which the liquidation left unchanged, still valuing each share at its full pre-seizure price.
- L197 — Payout ignores seized collateral: Root cause:
expectedUnderlying = _amount * exchangeRateBefore / 1e18uses only the rate, never CoreRouter's actual remaining LToken collateral after liquidation. - L200 — First redeemer drains the pool:
LToken.redeemburns CoreRouter's remaining shares and pays the first user 1000 in full at the stale rate, emptying the shared underlying reserve. - L206 — Book the first exit: CoreRouter distributes rewards and decrements the first redeemer's recorded investment, finalizing a withdrawal the pool can no longer fully back.
- L214 — First redemption succeeds fully:
RedeemSuccessfires for the first user's full 1000 payout, leaving the pool short exactly the collateral the liquidator seized. - L222 — Honest last redeemer steps in: The stranded second supplier calls redeem through its router, but CoreRouter now holds too few shares — the call reverts and their entire 1000 collateral is lost.
PoC#
Registry (Foundry, local deploy — verbatim vulnerable source + harm-asserting test):
cd 58386-lend-liquidating-corerouter-can-cause-excess-borrower-loss_exp && forge test -vvv
The browser Playground replays the same synthetic opcode-for-opcode and measures the harm: a liquidated CoreRouter pays the first redeemer the full 1000 at the stale rate, then reverts on the honest last redeemer, who loses their entire 1000 collateral. Both gates are green (registry forge test PASS + Playground _verify-poc VERDICT: PASS).
Sources & further analysis#
Reproductions & code
- Standalone PoC + full trace: 58386-lend-liquidating-corerouter-can-cause-excess-borrower-loss_exp (evm-hack-registry mirror).
- AuditVault finding: 824.
- Upstream DeFiHackLabs PoC directory: src/test.
Alerts & third-party analyses
- DeFiHackLabs incident explorer: search "Lend V2: a liquidated CoreRouter overpays early redeemers and strands the last".
- Web3Sec X hacked database: search.
- Rekt leaderboard: search.
- Solodit incident search: search.
These dashboards index community alerts tweets, post-mortems, and independent write-ups. Reach them through the protocol name above to cross-check this reproduction against other analyses.