# The `CouncilMember` contract DoS due to the `_retrieve` function revert

CouncilMember._retrieve reverts when Sablier withdrawable amount is 0, causing DoS of mint/burn/removeFromOffice and enabling front-run griefing of claims.

- Payout: Contest reward
- Severity: medium
- Program: Telcoin
- Credited: 0xadrii
- Disclosed: 2024-01-04
- Write-up quality: 6/10
- Original: https://github.com/sherlock-audit/2024-01-telcoin-judging/issues/47
- Library page: https://bugbountylibrary.com/r/sherlock%3A2024-01-telcoin%3AM-1
- Tags: domain:web3 impact:dos kind:contest platform:sherlock sev:medium tech:evm tech:solidity technique:chained technique:source-review vuln:business-logic vuln:dos

**Target:** CouncilMember.sol (Sablier stream integration)
**Root cause:** _retrieve calls SablierV2Lockup.withdraw which reverts when withdrawable amount is 0; _retrieve is invoked at the start of state-changing functions and again inside _update on transfers, so the second call always reverts after the first drains the stream.
**Steps:**
1. Deploy CouncilMember with a Sablier stream as _target where withdrawable amount is 0 (e.g., just after a withdrawal or before unlock).
2. Call `mint()`: first `_retrieve()` succeeds (or reverts if 0), then `_update` calls `_retrieve()` again with 0 withdrawable -> revert.
3. Call `removeFromOffice()`: `_retrieve()` at start drains stream, then transfer triggers `_update` -> `_retrieve()` again -> revert.
4. Front-run a member's `retrieve()`/claim with a `retrieve()` call so the victim's `_retrieve()` sees 0 and reverts.
**How found:** Code reading of CouncilMember's overridden _update and state-changing functions revealed _retrieve is called twice per transaction; cross-referencing SablierV2Lockup source showed withdraw reverts on zero amount.
**Exploitation:** A semi-trusted council member or any actor can call the public `retrieve()` to drain the currently withdrawable amount, then the next `_retrieve()` in any user transaction (mint, burn, removeFromOffice, transfer) reverts because Sablier reverts on zero withdrawable. This blocks council operations and can prevent removal of a malicious member.
**Escalation:** Only one council member can be minted because subsequent mint() calls revert, concentrating 100% of rewards on that member; governance cannot add intended additional members.
**Insight:** When integrating streaming protocols, check whether the withdraw primitive reverts on zero amount and whether your hook (_update/_beforeTokenTransfer) re-invokes the same withdraw within one transaction; double-withdraw patterns turn a benign zero into a hard DoS.
