Reproduced Exploit
Accountable — Cancelling redeem requests permanently blocks the withdrawal queue
1. Redeem requests sit in a FIFO queue keyed by nextRequestId (head). 2. Cancelling a request fully deletes the entry (controller = address(0)) without advancing the head. 3. Processing reads the empty head, _processRequest returns (0, 0, true), and the outer loop does if (shares_ == 0) break befor…
Chain
Other
Category
liveness
Date
Oct 2025
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: 62968-cancelling-redeem-requests-permanently-blocks-the-withdrawal. Standalone Foundry PoC and full write-up: 62968-cancelling-redeem-requests-permanently-blocks-the-withdrawal_exp in the
evm-hack-registrymirror.
Vulnerability classes: vuln/liveness/queue-deadlock · vuln/logic/pointer-not-advanced · frozen-funds
Reproduction: a self-contained Foundry PoC that compiles & runs in an isolated project with only
forge-std— no fork, no RPC, noanvil_state. Full trace: output.txt. PoC: test/62968-cancelling-redeem-requests-permanently-blocks-the-withdrawal_exp.sol.
AuditVault taxonomy: severity/high · sector/lending · sector/staking · platform/cyfrin · frozen-funds · permanent · cross-contract-state-consistency
Key info#
| Impact | HIGH — withdrawal queue permanently stuck; no subsequent user can withdraw |
| Protocol | Accountable (credit vaults) — AccountableWithdrawalQueue |
| Vulnerable code | AccountableWithdrawalQueue::_processUpToShares — if (shares_ == 0) break; before advancing nextRequestId |
| Bug class | Empty-head queue deadlock (pointer not advanced on deleted entry) |
| Finding | Cyfrin 2025-10-16 Accountable v2.0 · #62968 · reporter Immeas |
| Report | Cyfrin Accountable v2.0 |
| Source | AuditVault |
| Status | Audit finding — fixed by Accountable; verified by Cyfrin. Reproduced as a standalone local PoC. |
| Compiler | ^0.8.24 (PoC) |
TL;DR#
- Redeem requests sit in a FIFO queue keyed by
nextRequestId(head). - Cancelling a request fully deletes the entry (
controller = address(0)) without advancing the head. - Processing reads the empty head,
_processRequestreturns(0, 0, true), and the outer loop doesif (shares_ == 0) breakbefore++nextRequestId. - Every later process call deadlocks on the same empty head; tail redeemers never become claimable.
The vulnerable code#
(uint256 shares_, uint256 assets_, bool processed_) =
_processRequest(request_, liquidity, maxShares_, precision_);
if (shares_ == 0) break; // @> VULN: empty head returns (0,0,true) but break skips ++nextRequestId
// FIX: if (shares_ == 0) { if (processed_) { ++nextRequestId; continue; } break; }
Root cause#
Deleted head slots are treated as “processed” but the loop still breaks on shares_ == 0 without advancing the pointer. The cancel path clears storage without moving nextRequestId, so a single dust cancel at the head bricks the entire queue.
Preconditions#
- Instant (or any) cancel path that fully deletes a request currently at the head.
- At least one subsequent redeem request enqueued behind the deleted head.
Attack walkthrough#
- Alice enqueues a tiny redeem → becomes head (
requestId = 1). - Alice cancels; head entry deleted;
nextRequestIdstays1. - Charlie enqueues a large, fully funded redeem (
requestId = 2). processUpToShares/processUpToRequestIdhit empty head, break, return 0.- Charlie remains fully pending and unclaimable forever.
Diagrams#
Impact#
Permanent liveness failure of the withdrawal queue. All subsequent redeemers cannot exit; funds remain locked behind a bricked head pointer.
Sources#
- AuditVault finding #62968
- Cyfrin Accountable v2.0 report
- Reduced source: Accountable-Protocol
AccountableWithdrawalQueue.sol@fc43546—_processUpToShares/_delete
Sources & further analysis#
Reproductions & code
- Standalone PoC + full trace: 62968-cancelling-redeem-requests-permanently-blocks-the-withdrawal_exp (evm-hack-registry mirror).
- AuditVault finding: 62968-cancelling-redeem-requests-permanently-blocks-the-withdrawal.
Alerts & third-party analyses
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.