Reproduced Exploit
Lend: liquidation `protocolReward` is permanently stuck
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: 184. Standalone Foundry PoC and full write-up: 58371-lend-protocol-reward-tokens-are-permanently-stuck_exp in the
evm-hack-registrymirror.
Vulnerability classes: vuln/frozen-funds · vuln/logic
Reproduction: a faithful minimal reproduction of the vulnerable finding — the vulnerable
liquidateSeizeUpdatereward accrual is reproduced verbatim (marked@>) with faithful minimal doubles; local deploy, no fork.
Root cause#
In Lend-V2/src/LayerZero/CoreRouter.sol, every liquidation carves out PROTOCOL_SEIZE_SHARE_MANTISSA (2.8%) of the seized collateral and records it in protocolReward via updateProtocolReward. That mapping is only ever incremented — no function in LendStorage.sol, CoreRouter.sol, or CrossChainRouter.sol ever redeems, transfers, or decrements it. The vulnerable lines, reproduced verbatim:
uint256 currentReward = mul_(seizeTokens, Exp({mantissa: lendStorage.PROTOCOL_SEIZE_SHARE_MANTISSA()}));
@> lendStorage.updateProtocolReward(lTokenCollateral, lendStorage.protocolReward(lTokenCollateral) + currentReward);
updateProtocolReward can only raise the balance; the 2.8% share's backing collateral is left in the router with no owner and no withdrawal path. The borrower is debited the full seizeTokens, the liquidator is credited seizeTokens - currentReward, and the protocol's cut is stranded forever.
Why it's exploitable here#
Following the finding with a single 1000e18 liquidation:
- The router custodies
1000e18collateral backing the borrower's position; the borrower'stotalInvestmentis1000e18. - A liquidator seizes the full
1000e18. The protocol bookscurrentReward = 1000e18 * 0.028 = 28e18intoprotocolRewardand credits the liquidator972e18. - The liquidator redeems its full
972e18viaredeem, which is keyed ontotalInvestment. - The borrower's
totalInvestmentis now0and nobody holds a claim on the28e18. That collateral sits in the router with no function able to move it — a permanent loss of protocol revenue on every liquidation.
Attack path#
Marked-line walkthrough (Playground)#
The EVM Playground pins each step to the exact executed source line in 0xe3a787a4…:
- L55 — Collateral token custodied by router: Setup: the ERC20 collateral double the router holds for suppliers, and where the seized 2.8% protocol share ends up stranded.
- L133 — Liquidator enters liquidate(): The liquidator calls the thin external entrypoint, which forwards straight into the verbatim internal liquidateSeizeUpdate on the position.
- L148 — Seize calculation succeeds: The Lendtroller returns seizeTokens (1000e18) for the liquidation and the error-code guard passes, so reward accrual proceeds.
- L161 — Protocol reward accrued, never paid: Root cause: 2.8% of the seized collateral is added to protocolReward, a mapping only ever incremented with no function anywhere that pays it out.
- L164 — Distribute supplier LEND rewards: LEND rewards flow to liquidator and borrower, but the 2.8% collateral share just booked is left untouched by any distribution.
- L184 — Only redeem path ignores reward: The sole collateral exit, redeem, is keyed on the caller's totalInvestment; nothing reads protocolReward, so the seized share stays stranded forever.
PoC#
Registry (Foundry, local deploy — verbatim vulnerable source + harm-asserting test):
cd 58371-lend-protocol-reward-tokens-are-permanently-stuck_exp && forge test -vvv
The browser Playground replays the same synthetic opcode-for-opcode and measures the harm: seize 1000e18, credit the liquidator 972e18 and let it redeem, and prove the protocol's 28e18 (2.8%) is stranded in the router with no withdrawal path. Both gates are green (registry forge test PASS + Playground _verify-poc VERDICT: PASS).
Sources & further analysis#
Reproductions & code
- Standalone PoC + full trace: 58371-lend-protocol-reward-tokens-are-permanently-stuck_exp (evm-hack-registry mirror).
- AuditVault finding: 184.
- Upstream DeFiHackLabs PoC directory: src/test.
Alerts & third-party analyses
- DeFiHackLabs incident explorer: search "Lend: liquidation
protocolRewardis permanently stuck". - 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.