# A reserve’s d_supply is incorrectly updated and stored after flash loan execution

Flash loan liability update to a reserve's d_supply is lost when the same asset is repaid, leaving stale d_supply and corrupting utilization and reward accounting.

- Payout: Contest reward
- Severity: high
- Program: Blend
- Credited: alexxander
- Disclosed: 2025-08-13
- Write-up quality: 8/10
- Original: https://code4rena.com/reports/2025-02-blend-v2-audit-certora-formal-verification#h-01
- Library page: https://bugbountylibrary.com/r/c4%3A2025-02-blend-v2-audit-certora-formal-verification%3AH-01
- Tags: domain:web3 impact:funds-loss impact:integrity kind:contest platform:code4rena sev:high tech:rust technique:source-review vuln:accounting-error vuln:flash-loan vuln:reward-distribution

**Target:** Soroban pool contract submit/actions (flash_loan + repay)
**Root cause:** execute_submit_with_flash_loan loads the flash-loan reserve, mutates d_supply via add_liabilities, but never calls pool.cache_reserve; a later repay for the same asset reloads the stale reserve from storage and overwrites d_supply.
**Steps:**
1. Call `contract.flash_loan(...)` with `Flash_Loan.asset = X`, `amount = 250` and a `Repay` request for asset X amount 250.
2. Reserve X loads from storage with d_supply=500; `add_liabilities` sets d_supply=750 but reserve is not cached.
3. `build_actions_from_request` -> `apply_repay` calls `pool.load_reserve` again, reloading d_supply=500 from storage.
4. `remove_liabilities` subtracts 250 from stale 500 -> d_supply=250, then `pool.cache_reserve` caches it.
5. `pool.store_cached_reserves` persists d_supply=250 instead of ~500.
6. PoC: modify `test_submit_with_flash_loan_process_flash_loan_first` in submit.rs, add `log!` of `get_res_data(&underlying_0).d_supply`, run `cargo test pool::submit::tests::test_submit_with_flash_loan_process_flash_loan_first -- --nocapture --exact`.
**How found:** Code-reading of execute_submit_with_flash_loan: noticed the flash-loan reserve is loaded and mutated but not passed to pool.cache_reserve, unlike apply_repay which does cache; hypothesized a same-asset repay would reload stale storage state and overwrite the liability update.
**Exploitation:** Attacker with existing liability in reserve X flash-borrows N-1 of X and repays it in the same submit, driving d_supply to ~1; after time passes the emission distributor computes an inflated reward-per-token index from the tiny d_supply, and the attacker claims inflated BLEND rewards, draining other reserves' emissions.
**Escalation:** Chaining flash loan + repay in one transaction to arbitrarily shrink d_supply, then exploiting reward index inflation for repeated claims.
**Insight:** In pooled-lending designs with a load/mutate/cache/store pattern, check every code path that mutates a reserve for a matching cache_reserve call; a flash-loan path that mutates d_supply without caching lets a subsequent action on the same asset reload and clobber the update.
