# A rogue borrower can split the lender's credit position into multiple positions without pay- ing fragmentation fees

Incomplete fix in Size::compensate lets a borrower split a lender's credit position repeatedly without paying fragmentation fees, griefing keepers and lenders.

- Payout: Contest reward
- Severity: medium
- Program: Size
- Credited: serial-coder9
- Disclosed: 2025-04-03
- Write-up quality: 7/10
- Original: https://cdn.cantina.xyz/reports/cantina_competition_size_february2025.pdf#3.1.4
- Library page: https://bugbountylibrary.com/r/cantina%3Asize_february2025%3A3.1.4
- Tags: domain:web3 impact:dos kind:contest platform:cantina sev:medium tech:solidity technique:incomplete-fix technique:source-review vuln:access-control vuln:business-logic vuln:fee-bypass

**Target:** Size::compensate() / AccountingLibrary.createCreditPosition (Solidity lending market)
**Root cause:** When loading an existing creditPositionToCompensate, shouldChargeFragmentationFee is computed only as amountToCompensate != creditPositionToCompensate.credit, ignoring that creditPositionWithDebtToRepay still retains credit; fully exiting the compensate position with amount < lender credit splits the lender position fee-free.
**Steps:**
1. Borrower takes a loan from lender, creating creditPositionWithDebtToRepay (e.g. 100 USDC).
2. Borrower self-borrows a small amount (e.g. 20 USDC) to create creditPositionToCompensate with credit == amountToCompensate.
3. Call `compensate(creditPositionWithDebtToRepayId, creditPositionToCompensateId, 20e6)`; shouldChargeFragmentationFee evaluates false because amountToCompensate == creditPositionToCompensate.credit.
4. reduceDebtAndCredit leaves 80 USDC in lender position; createCreditPosition fully exits compensate position, transferring it to lender.
5. Repeat loop 4x to split lender's single 100 USDC position into 5 positions of 20 USDC each, paying zero fragmentation fees.
6. Lender/keeper must call claim() 5 times to aggregate, draining keeper gas or griefing lender.
**How found:** Researcher revisited their prior C4 M-13 finding after PR 120; noticed the fix only patched the RESERVED_ID branch (@1.2) and left the existing-position branch (@2.2) comparing against creditPositionToCompensate.credit instead of also checking creditPositionWithDebtToRepay.credit, enabling the same split via the second approach.
**Exploitation:** Borrower repeatedly self-borrows small amounts to mint creditPositionToCompensate positions matching the desired split size, then calls compensate() against the lender's credit position. Each call fully exits the compensate position (fee-free) while leaving residual credit in the lender position, fragmenting the lender's liquidity into many positions. Attacker repays all debts afterward; no fee is charged.
**Escalation:** Looping compensate() N times fragments a single lender position into N+1 positions, amplifying keeper claim costs and draining collected fragmentation fees.
**Insight:** When auditing a fix for a fee-bypass, check every branch of the patched conditional separately; a fix that compares the wrong operand (compensate position vs. target position) leaves the sibling branch exploitable. Fragmentation/anti-split fees are a common griefing surface in lending markets.
