# Loss of user funds when completing CASH redemptions

CashManager.completeRedemptions fails to persist refunded amounts into totalBurned, so later redemptions in the same epoch are underpaid, causing user fund loss.

- Payout: Contest reward
- Severity: high
- Program: Ondo Finance
- Credited: adriro
- Disclosed: 2023-02-28
- Write-up quality: 7/10
- Original: https://github.com/code-423n4/2023-01-ondo-findings/issues/325
- Library page: https://bugbountylibrary.com/r/c4%3A2023-01-ondo%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:** CashManager.completeRedemptions (Solidity contract)
**Root cause:** completeRedemptions computes quantityBurned as totalBurned minus refundedAmt locally but never writes the reduced value back to redemptionInfoPerEpoch[epoch].totalBurned, so subsequent calls recompute with the un-decremented total.
**Steps:**
1. Deploy CashManager, KYC alice/bob/charlie, mint 200e18 CASH to alice and bob, 50e18 to charlie.
2. Each calls requestRedemption for their full balance.
3. Advance one epoch; manager sets mint exchange rate and seeds assetSender with 300e6 USDC.
4. Call `completeRedemptions([alice],[charlie],300e6,0,0)` — alice receives 200e18*300e6/(450e18-50e18)=150e6.
5. Call `completeRedemptions([bob],[],300e6,0,0)` — bob receives 200e18*300e6/450e18≈133e6 instead of 150e6.
**How found:** Code reading of completeRedemptions: noticed refundedAmt is subtracted only into a local quantityBurned used for the current call's payout math, while the storage totalBurned is never updated, implying state divergence across calls.
**Exploitation:** Manager (or normal operational flow) processes refunds and redemptions across multiple completeRedemptions calls for one epoch; every redemption serviced after a refund is priced against an inflated quantityBurned, shortchanging users by the refunded share.
**Insight:** When a function derives an adjusted total from storage but only uses it locally, check whether later calls recompute from the unadjusted storage value — split-batch processing of refunds vs redemptions exposes the drift.
