# When borrowers repay USDS, it is sent to the wrong address, allowing anyone to burn Protocol Owned Liquidity and build bad debt for USDS

Salty.IO's repayUSDS sends USDS to the USDS token contract instead of the Liquidizer, so every repayment forces POL liquidation to cover phantom burn debt.

- Payout: Contest reward
- Severity: high
- Program: Salty.IO
- Credited: nonseodion
- Disclosed: 2024-04-19
- Write-up quality: 7/10
- Original: https://github.com/code-423n4/2024-01-salty-findings/issues/137
- Library page: https://bugbountylibrary.com/r/c4%3A2024-01-salty%3AH-06
- Tags: domain:web3 impact:funds-loss impact:integrity kind:contest platform:code4rena sev:high tech:evm tech:solidity technique:source-review vuln:accounting-error vuln:business-logic

**Target:** USDS stablecoin repayment / Liquidizer burn accounting
**Root cause:** repayUSDS transfers USDS to address(usds) rather than the Liquidizer, while incrementing usdsThatShouldBeBurned on the Liquidizer; the Liquidizer's balance never grows, so _possiblyBurnUSDS always sees a shortfall and withdraws POL.
**Steps:**
1. Deploy with DAO holding SALT/USDS and DAI/USDS POL via depositLiquidityAndIncreaseShare.
2. Alice deposits WBTC/WETH collateral.
3. Loop 100x: `borrowUSDS(maxBorrowableUSDS(alice))` then `repayUSDS(usdsBorrowedByUsers(alice))` — each repayment sends USDS to the USDS contract and increments `liquidizer.usdsThatShouldBeBurned`.
4. Call `liquidizer.performUpkeep()` repeatedly as upkeep; each call finds `usdsBalance < usdsThatShouldBeBurned` and calls `dao.withdrawPOL(salt, usds, ...)` and `dao.withdrawPOL(dai, usds, ...)`.
5. Observe `liquidizer.usdsThatShouldBeBurned() > usds.balanceOf(liquidizer)` and POL drained.
**How found:** Code reading of repayUSDS: noticed the transfer target is address(usds) instead of the Liquidizer, then traced _possiblyBurnUSDS to confirm the balance check would always fail and trigger POL withdrawal.
**Exploitation:** Attacker borrows and repays USDS repeatedly (no capital cost beyond collateral, repaid each cycle) to inflate usdsThatShouldBeBurned without adding USDS to the Liquidizer. Each upkeep then liquidates Protocol Owned Liquidity to cover the phantom deficit, draining POL and leaving the protocol unable to cover liquidation bad debt, depressing USDS price.
**Escalation:** Repeat borrow/repay cycles to exhaust POL faster; can also occur passively as normal users repay over time.
**Insight:** When a contract tracks a 'should be burned' counter separately from token custody, verify the tokens actually land in the contract that performs the burn — a mismatched transfer recipient turns every user repayment into forced POL liquidation.
