# UserManager's cancelVouchInternal breaks up voucher accounting on voucher removal

UserManager._cancelVouchInternal swaps the last voucher into the removed slot but updates the wrong index arrays, corrupting vouch accounting and potentially freezing staker funds.

- Payout: Contest reward
- Severity: high
- Program: Union
- Credited: seyni
- Disclosed: 2023-02-07
- Write-up quality: 7/10
- Original: https://github.com/sherlock-audit/2023-02-union-judging/issues/41
- Library page: https://bugbountylibrary.com/r/sherlock%3A2023-02-union%3AH-1
- Tags: domain:web3 impact:funds-loss kind:contest platform:sherlock sev:high tech:evm tech:solidity technique:source-review vuln:accounting-error vuln:business-logic

**Target:** UserManager.sol vouch accounting (vouchers/vouchees arrays)
**Root cause:** _cancelVouchInternal uses voucherIndexes[borrower][lastVoucher.staker].idx to index vouchees[staker], mixing two independent index arrays, and never updates voucherIndexes for the moved last voucher.
**Steps:**
1. Call cancelVouch(staker, borrower) where borrower has multiple vouchers and the removed voucher is not the last element.
2. Observe _cancelVouchInternal moves lastVoucher into removeVoucherIndex.idx and pops the array.
3. Note it reads `voucheeIdx = voucherIndexes[borrower][lastVoucher.staker].idx` and writes `vouchees[staker][voucheeIdx].voucherIndex`, using the wrong array's index.
4. Note voucherIndexes[borrower][lastVoucher.staker].idx is never updated to removeVoucherIndex.idx.
5. Add a new vouch for the same borrower; the stale index now points to the wrong staked/locked amount.
6. Observe updateTrust() can set trust below real locked amount, or cancelVouch reverts LockedStakeNonZero, freezing staker funds.
**How found:** Code reading of the swap-and-pop pattern in _cancelVouchInternal; noticed the vouchee update used voucherIndexes (a different array) instead of voucheeIndexes, and that voucherIndexes for the moved last voucher was never rewritten.
**Exploitation:** A borrower whose vouch was moved can exploit the stale/unremovable trust entry: the staker cannot cancel the misplaced vouch (LockedStakeNonZero), so the borrower retains trust that would normally be revoked, locking extra staker funds and enabling over-borrowing against that trust.
**Escalation:** Repeated vouch add/cancel cycles pile up misplaced indices, compounding accounting corruption and locking more staker funds.
**Insight:** In swap-and-pop array removal, every parallel index map for BOTH the removed and the moved element must be rewritten; check that the index source array matches the array being indexed and that the moved element's own index map is updated.
