← back to lessons

Lesson: Dead-oracle-feed liquidation-DoS - confirm LIVE not just MECHANICAL, and gate scope HARD

Banked: 2026-06-11 Source cycle: SEAMS/oracle dead-feed follow-up on ZeroLend mainnet-lrt (Ethereum mainnet, fork block 25295046) Finding doc: /home/babakinzo/bounty/findings/zerolend-deadfeed-dos-2026-06-11.md Verdict that produced this lesson: WALK-CLEAN (mechanically real + LIVE, but OUT OF SCOPE)

The rule

A reverting oracle feed in an Aave-v3 fork is a real DoS MECHANISM, but a finding requires (a) a LIVE victim, (b) the right dead-state class, and (c) a live in-scope program. Skip any of the three and it is a WALK even if the feed genuinely reverts.

1. Mechanism: how a dead source bricks the pool

A reverting AaveOracle source bricks getUserAccountData + liquidationCall for ANY position touching that reserve (collateral OR debt), because GenericLogic.calculateUserAccountData prices EVERY reserve the user has. If getFallbackOracle() == 0 (no fallback), the source revert propagates all the way up. Result: that user is un-liquidatable and cannot compute health -> bad-debt risk with no remedy. Aave's if (price > 0) ... else fallback never runs because the source REVERTS rather than returning <=0.

2. Distinguish 3 dead-feed sub-states EMPIRICALLY (different severity classes)

3. Prove LIVE, not just mechanical

"The feed reverts" is only the mechanism. To confirm a LIVE DoS: passive-scan the variable-debt token Transfer logs (mint-from-0x0 = borrower) to enumerate borrowers, then call getUserAccountData(user) on each and show it reverts. A bricked account-data on a REAL address is the confirmed-live signal. Found 3 such live users on ZeroLend (rsETH-debt + pzETH-debt) whose account-data calls revert at HEAD.

4. Severity collapse checklist (any of these -> downgrade/WALK)

5. SCOPE-GATE before staging ANY oracle-misconfig

Check in order: - (i) Is there a LIVE program, or only a FINISHED audit competition? (ZeroLend's only Immunefi program was the "Boost" competition, FINISHED 2024-03-14.) - (ii) Does it cover THIS chain? A zkSync-Era-only program does NOT cover Ethereum-mainnet contracts. (ZeroLend Boost = "deployed on zkSync Era exclusively, no Ethereum mainnet scope.") - (iii) Is "incorrect data supplied by third party oracles" out-of-scope? (ZeroLend: yes, explicit.) A dead RedStone push feed is precisely "incorrect third-party-oracle data." Even a real, live mainnet DoS is a WALK when the only program is finished, wrong-chain, and excludes third-party-oracle data.

Reusable cast recipe (passive)

cast call $ORACLE "getReservesList()(address[])"
cast call $ORACLE "getSourceOfAsset(address)(address)" $ASSET
cast call $ORACLE "getAssetPrice(address)(uint256)" $ASSET        # revert? 0? fine?
cast call $ORACLE "getFallbackOracle()(address)"                  # 0 => revert propagates
cast storage $SOURCE 0 ; cast storage $SOURCE 1                   # 2-feed adapter slots
cast storage $FEED 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc  # EIP1967 impl
cast call $VARDEBT "totalSupply()(uint256)" ; cast call $ATOKEN "totalSupply()(uint256)"
cast logs --address $VARDEBT "Transfer(address,address,uint256)" --from-block X --to-block Y --json  # find borrowers
cast call $POOL "getUserAccountData(address)(...)" $BORROWER      # reverts => live DoS confirmed

Decode RedStone staleness: 0x4f319ffe = InvalidLastUpdateDetails(bytes32,uint256,uint256,uint256); word1 ascii = feedId, word2 = data-package ts (prove age).

Generated 2026-07-02 13:15:05 UTC | auto-sync /15min