# Loss of vested amounts

VTVLVesting.revokeClaim zeroes the claim without preserving already-vested-but-unwithdrawn amounts, so a user who withdraws after revocation loses rightfully vested tokens.

- Payout: Contest reward
- Severity: high
- Program: VTVL
- Credited: eierina
- Disclosed: 2022-11-01
- Write-up quality: 7/10
- Original: https://github.com/code-423n4/2022-09-vtvl-findings/issues/475
- Library page: https://bugbountylibrary.com/r/c4%3A2022-09-vtvl%3AH-01
- Tags: domain:web3 impact:funds-loss kind:contest platform:code4rena sev:high tech:evm tech:solidity technique:source-review vuln:accounting-error vuln:business-logic

**Target:** VTVLVesting.sol vesting claim contract
**Root cause:** revokeClaim sets isActive=false and computes amountRemaining from finalVestedAmount minus amountWithdrawn, ignoring the amount vested so far but not yet withdrawn; _baseVestedAmount returns 0 once isActive is false, so the vested-but-unclaimed portion is silently reclaimed by the contract.
**Steps:**
1. Deploy VTVLVesting with prefunded token; createClaim(owner2, start, end, cliff, releaseIntervalSecs, linearVestAmount, cliffAmount).
2. Advance chain to terminationTimestamp = start + half period + 50s (less than one release interval past halfway).
3. Call `revokeClaim(owner2)` as admin.
4. Call `withdraw()` as owner2 -> reverts `NO_ACTIVE_CLAIM`; balance delta is 0.
5. Advance to endTimestamp; `finalClaimableAmount(owner2)` returns 0, confirming vested amount was lost.
6. Contrast: calling `withdraw()` before `revokeClaim` yields the vested amount, proving order-dependent outcome.
**How found:** Reasoned about vesting semantics: vested amounts are legally owned by the recipient, so revoke should only cancel future vesting. Noticed revokeClaim ignores amountWithdrawn vs vested-so-far and that _baseVestedAmount gates all math on isActive, making the outcome depend on withdraw/revoke ordering. Wrote two mirrored tests to demonstrate the discrepancy.
**Exploitation:** Admin (or malicious admin) calls revokeClaim at any point after a user has vested tokens but before they withdraw; the user's already-earned cliff + linear portion is absorbed back into the contract and becomes unclaimable, while the admin retains control of the reserved balance.
**Insight:** In vesting/streaming contracts, check whether revocation math uses finalVestedAmount (end-of-period) instead of vestedAmount(now); any state flag that gates the vesting calculation (isActive) will zero out accrued-but-unwithdrawn balances. Test withdraw-before vs withdraw-after revoke to expose order-dependent loss.
