# Xocolatl AccountLiquidator: fake reserve address drains vault collateral

AccountLiquidator trusted a caller-supplied HouseOfReserve address; fake reserves returned zero-cost liquidations, letting the attacker seize victims' cbETH/WETH accounting collateral and withdraw 3.25 cbETH + 0.22 WETH.

- Payout: Real-world hack
- Severity: high
- Program: XocolatlLiquidator
- Credited: DeFiHackLabs
- Disclosed: 2026-03-24
- Write-up quality: 7/10
- Original: https://github.com/SunWeb3Sec/DeFiHackLabs/blob/main/src/test/2026-03/XocolatlLiquidator_exp.sol
- Library page: https://bugbountylibrary.com/r/dhl%3A20260324-xocolatlliquidator
- Tags: domain:web3 impact:funds-loss kind:incident platform:defihacklabs sev:high tech:evm tech:solidity technique:source-review vuln:access-control vuln:business-logic vuln:liquidation vuln:oracle-manipulation

**Target:** AccountLiquidator.liquidateUser on Base (HouseOfReserve vault accounting)
**Root cause:** liquidateUser accepted an arbitrary caller-supplied houseOfReserve address and trusted its price/cost return values, while the real AssetsAccountant token IDs were used for collateral movement. A fake reserve returning a zero liquidation cost let the attacker move victims' accounting collateral for free.
**Steps:**
1. Deploy attack contract on Base at fork block 43801482; fund with 0.001 ETH for Pyth update fee.
2. Push a fresh signed Pyth price update via `pyth.updatePriceFeeds{value: fee}(updateData)` so real vault withdrawals aren't stale.
3. Deploy `FakeHouseOfReserve(CBETH, CBETH_RESERVE_TOKEN_ID, CBETH_BACKED_TOKEN_ID)` returning zero liquidation cost.
4. For each victim, call `liquidator.liquidateUser(victim, fakeReserve)` up to 4 times until `accountant.balanceOf(victim, CBETH_RESERVE_TOKEN_ID) == 0`.
5. Call `IHouseOfReserve(CBETH_RESERVE).withdraw(accountant.balanceOf(address(this), CBETH_RESERVE_TOKEN_ID))` to redeem seized collateral for real cbETH.
6. Repeat with `FakeHouseOfReserve(WETH_TOKEN, WETH_RESERVE_TOKEN_ID, WETH_BACKED_TOKEN_ID)` across 12 WETH victims, then withdraw from WETH_RESERVE.
7. Sweep cbETH and WETH to profit receiver; assert >3 cbETH and >0.2 WETH profit.
**Exploitation:** Attacker deployed fake HouseOfReserve contracts that reported the real reserve token IDs but returned a liquidation cost rounded to zero. Calling liquidateUser against each victim moved their accounting collateral to the attacker contract at no cost; the attacker then called withdraw on the genuine cbETH and WETH HouseOfReserve vaults to redeem the seized accounting tokens for real assets, netting 3.25 cbETH and 0.22 WETH.
**Escalation:** Looped liquidateUser up to 4x per victim across 5 cbETH and 12 WETH victims to drain all accounting collateral, then redeemed through both real reserve vaults.
**Insight:** When a liquidation entrypoint takes a reserve/vault address as a parameter, check whether it validates that address against the protocol's registry and whether it trusts that contract's returned cost/price. A fake reserve that echoes real token IDs but returns zero cost turns free liquidations into collateral theft.
