# Fee-vault can be made insolvent in case of defaults

Fee-vault skips b_rate refresh when timestamp matches, so a same-block default lets a withdrawer burn too few b_tokens, leaving the vault insolvent.

- Payout: Contest reward
- Severity: medium
- Program: Blend
- Credited: carrotsmuggler
- Disclosed: 2025-08-13
- Write-up quality: 7/10
- Original: https://code4rena.com/reports/2025-02-blend-v2-audit-certora-formal-verification#m-11
- Library page: https://bugbountylibrary.com/r/c4%3A2025-02-blend-v2-audit-certora-formal-verification%3AM-11
- Tags: domain:web3 impact:funds-loss kind:contest platform:code4rena sev:medium tech:solidity technique:race-window technique:source-review vuln:accounting-error vuln:business-logic

**Target:** Soroban fee-vault contract (update_rate / withdraw)
**Root cause:** update_rate returns early when now == last_update_timestamp, so a b_rate drop from a same-block default is not picked up; withdraw then computes b_tokens_amount with the stale higher rate while the pool burns at the new lower rate.
**Steps:**
1. Bundle three txs in one block: (1) deposit to fee-vault to set last_update_timestamp and cache b_rate=1.1, (2) liquidation causing default_liabilities that drops pool b_rate to 1.05, (3) withdraw 1000 from fee-vault.
2. In tx 3, update_rate sees now == last_update_timestamp and returns without refreshing, keeping b_rate=1.1.
3. Vault computes b_tokens_amount = 1000/1.1 = 910 and decrements total_b_tokens by 910.
4. Pool::withdraw burns 1000/1.05 = 953 b_tokens from the vault, creating a 43 b_token deficit.
**How found:** Code review of update_rate's timestamp equality early-return combined with the observation that b_rate is normally block-invariant except during default_liabilities, which can change it within a block.
**Exploitation:** Attacker already holding fee-vault shares bundles deposit, a default-triggering liquidation (requires backstop exhaustion and winning the bad debt auction), and withdrawal in one block to exit at the stale higher b_rate, shifting the loss to the last withdrawer.
**Insight:** Timestamp-equality caching guards assume state is block-invariant; audit any 'same block, skip refresh' shortcut against state that CAN change intra-block (defaults, liquidations, oracle updates) and check accounting that mixes cached and live rates.
