# Borrower can close a credit without repaying debt

LineOfCredit.close lacks an existence check, so a borrower closing a non-existent credit ID decrements count and marks the facility REPAID while debt stays unpaid.

- Payout: Contest reward
- Severity: high
- Program: Debt DAO
- Credited: Jeiwan
- Disclosed: 2023-02-07
- Write-up quality: 7/10
- Original: https://github.com/code-423n4/2022-11-debtdao-findings/issues/258
- Library page: https://bugbountylibrary.com/r/c4%3A2022-11-debtdao%3AH-04
- 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:** LineOfCredit smart contract close() function
**Root cause:** close() reads credits[id] without verifying the credit exists; for an unknown id the zero-value Credit passes the principal check in _close, which deletes the id, decrements count, and flips status to REPAID.
**Steps:**
1. Borrower opens a credit via _addCredit and borrows 1 ether with line.borrow(id, 1 ether).
2. Borrower calls `line.close(bytes32(uint256(31337)))` with a non-existent credit ID.
3. close() loads an empty Credit struct, _accrue/_repay are no-ops, _close sees principal==0 and proceeds.
4. _close deletes the fake id, calls ids.removePosition, decrements count, and when count hits 0 sets status to REPAID.
5. Borrower keeps the borrowed funds; lender balance remains reduced by the loan.
**How found:** Code review of close()/_close() noting the missing existence check on credits[id] and that a zero-value Credit bypasses the principal>0 revert, then confirmed with a Foundry PoC.
**Exploitation:** Borrower takes a loan, then calls close() with an arbitrary unused bytes32 id. The facility's count is decremented and status set to REPAID without any repayment, leaving the lender with bad debt while the borrower retains principal and collateral.
**Escalation:** Repeated calls with fresh fake IDs drive count to zero, marking the whole facility REPAID and stranding all lenders' funds.
**Insight:** When a mapping lookup returns a default struct, any guard checking struct fields (principal>0) is bypassed; audit close/delete paths for explicit existence checks (e.g. ids.contains(id)) before mutating counters or status.
