# BalancedVault.sol: loss of funds + global settlement flywheel / user settlement flywheels getting out of sync

BalancedVault's per-user pending epoch can be pushed forward by a tiny third-party deposit, desyncing user and global settlement flywheels and causing share mispricing and fund loss.

- Payout: Contest reward
- Severity: high
- Program: Perennial
- Credited: 0xGoodess
- Disclosed: 2023-05-05
- Write-up quality: 8/10
- Original: https://github.com/sherlock-audit/2023-05-perennial-judging/issues/45
- Library page: https://bugbountylibrary.com/r/sherlock%3A2023-05-perennial%3AH-1
- Tags: domain:web3 impact:funds-loss impact:integrity kind:contest platform:sherlock sev:high tech:solidity technique:source-review vuln:accounting-error vuln:business-logic

**Target:** BalancedVault.sol settlement flywheel accounting
**Root cause:** deposit/redemption sets _pendingEpochs[account]=context.epoch+1 unconditionally, so any later deposit in a stale intermediate epoch overwrites the pending epoch, delaying user-side settlement while global settlement still processes at the original epoch.
**Steps:**
1. Enter stale epoch 1; user1 calls `deposit(smallDeposit)` -> `_pendingEpochs[user1]=2`.
2. Advance to epoch 2 (stale); global flywheel shows `_deposit==_pendingDeposit`, scheduling processing in epoch 3.
3. user2 calls `deposit(0.000001, user1)` in epoch 2 -> `_pendingEpochs[user1]=3`.
4. Advance to epoch 3; global flywheel increases totalSupply using epoch-3 context, but user settlement flywheel condition `accountContext.epoch > _pendingEpochs[account]` (3>3 false) defers user balance update to epoch 4.
5. Call `syncAccount(user1)` in epoch 4; observe totalSupply > balanceUser1+balanceUser2, confirming share loss.
**How found:** Code-reading of the settlement flywheel logic: noticed `_pendingEpochs` is overwritten on every deposit/redemption without checking for an existing pending entry, and that the global flywheel uses `_pendingDeposit`/`_pendingRedemption` while the user flywheel gates on `accountContext.epoch > _pendingEpochs[account]`, implying the two can diverge when a new deposit lands in the intermediate stale epoch.
**Exploitation:** Attacker (or any user) makes an arbitrarily small deposit on behalf of a victim during a stale intermediate epoch, shifting the victim's pending epoch forward by one. Global settlement processes the original deposit at epoch 3 while the user's shares are computed at epoch 4 context; if PnL between epochs is positive the victim receives fewer shares, and vault invariants (sum balances == totalSupply, sum unclaimed == totalUnclaimed) break, corrupting accounting for all users.
**Escalation:** Repeatedly pushing pending epochs over time compounds the desync, corrupting vault-wide accounting rather than a single user's position; also extends PnL exposure window for depositors.
**Insight:** When a protocol has two settlement layers (global aggregate vs per-account), check whether per-account scheduling state is overwritten by later deposits; any permissionless deposit-for-others that mutates the victim's pending epoch is a desync primitive.
