# Missing validation of snapshotId makes it possible for the investor to claim unlocked capitals from the same snapshot multiple times

DefaultStateManager resets lastClaimedSnapshotId to zero when a repeat claim yields no amount, letting a seller re-claim the same unlocked capital repeatedly and drain the pool.

- Payout: Contest reward
- Severity: high
- Program: Carapace
- Credited: Koolex
- Disclosed: 2023-02-01
- Write-up quality: 7/10
- Original: https://github.com/sherlock-audit/2023-02-carapace-judging/issues/60
- Library page: https://bugbountylibrary.com/r/sherlock%3A2023-02-carapace%3AH-10
- Tags: domain:web3 impact:funds-loss kind:contest platform:sherlock sev:high tech:solidity technique:source-review vuln:accounting-error vuln:business-logic

**Target:** ProtectionPool / DefaultStateManager claim logic
**Root cause:** calculateAndClaimUnlockedCapital unconditionally writes the returned _snapshotId to lastClaimedSnapshotIds; when no new snapshot is claimable, _calculateClaimableAmount returns _latestClaimedSnapshotId=0, resetting the seller's claim state.
**Steps:**
1. Lending pool transitions Active->Late: snapshot taken, LockedCapitals[0].snapshotId=1, locked=true
2. Lending pool transitions Late->Active: LockedCapitals[0].locked=false
3. Seller calls `ProtectionPool.claimUnlockedCapital` -> receives share, lastClaimedSnapshotIds=1
4. Seller calls `claimUnlockedCapital` again -> claimable=0, _latestClaimedSnapshotId stays 0, lastClaimedSnapshotIds reset to 0
5. Seller calls `claimUnlockedCapital` again -> same snapshot 1 re-claimed, lastClaimedSnapshotIds=1
6. Repeat to drain unlocked capital
**How found:** Code review of claim flow: noticed _calculateClaimableAmount returns _latestClaimedSnapshotId which is only set inside the claimable branch, so a no-op claim returns 0 and the caller overwrites stored state with 0.
**Exploitation:** Attacker joins pool as seller, waits for a lending pool to go Late then Active, claims once, then calls claimUnlockedCapital a second time (zero payout) to reset lastClaimedSnapshotIds to 0, then claims the same snapshot again; loop until funds drained.
**Escalation:** Repeated claim loop drains all unlocked capital across snapshots.
**Insight:** When a function returns a state variable that is only conditionally assigned, check whether the caller unconditionally persists that return value—zero-initialized returns can silently reset stored state and re-enable one-time actions.
