# User can erase their position debt for free

paybackBadDebtNoReward accepts a token the position never borrowed, causing _removePositionData to pop the real borrow token and erase the user's debt.

- Payout: Contest reward
- Severity: high
- Program: Wise Lending
- Credited: Dup1337
- Disclosed: 2024-05-03
- Write-up quality: 8/10
- Original: https://github.com/code-423n4/2024-02-wise-lending-findings/issues/215
- Library page: https://bugbountylibrary.com/r/c4%3A2024-02-wise-lending%3AH-02
- Tags: domain:web3 impact:funds-loss impact:integrity kind:contest platform:code4rena sev:high tech:evm tech:solidity technique:incomplete-fix technique:source-review vuln:access-control vuln:accounting-error vuln:business-logic

**Target:** FeeManager.paybackBadDebtNoReward / MainHelper._removePositionData (Solidity lending protocol)
**Root cause:** _removePositionData assumes the passed _poolToken exists in the user's token array; when length==1 or no match is found it deletes the last token unconditionally. paybackBadDebtNoReward never validates that _paybackToken is actually a borrow token of the position.
**Steps:**
1. Create a position with non-zero badDebt (e.g. small underwater position, or crash price via oracle so partial liquidation accrues bad debt).
2. Confirm getPositionBorrowTokenLength(nftId)==1 and badDebtPosition[nftId]>0.
3. Call `FEE_MANAGER_INSTANCE.paybackBadDebtNoReward(nftId, address(MOCK_ERC20_1), 0)` with a token the position never borrowed and 0 shares.
4. 0 shares avoids underflow in _decreasePositionMappingValue; _corePayback reaches _removePositionData which deletes the last (real) borrow token.
5. getPositionBorrowTokenLength(nftId) now returns 0; health check iterates zero borrow tokens.
6. Borrow ~95% of pool ETH via borrowExactAmountETH; position shows no debt and funds are withdrawn.
**How found:** Code reading of _removePositionData revealed it deletes the last token when length==1 or when no matching token is found in iteration; tracing callers found paybackBadDebtNoReward passes an unvalidated _paybackToken, and 0 shares bypasses underflow, letting an arbitrary token trigger deletion of the real borrow token.
**Exploitation:** Attacker needs a position with accrued bad debt (small underwater position or oracle crash). Calling paybackBadDebtNoReward with an unrelated token and 0 shares pops the real borrow token from the position's borrow array, zeroing tracked debt. Attacker then borrows up to ~95% of protocol ETH with no recorded debt, draining lender deposits.
**Escalation:** After debt erasure, borrow against remaining collateral up to 95% of all pool ETH, converting a forgiven debt into full protocol fund theft.
**Insight:** When a helper removes array entries by matching a caller-supplied key, check the fallback branches (length==1, no-match) — they often delete the last element regardless of match. Pair with a caller that accepts an unvalidated key and a zero-amount path to skip underflow guards.
