Fragmentation fee is not taken if user compensates with newly created position
mediumLines of code
https://github.com/code-423n4/2024-06-size/blob/main/src/libraries/actions/Compensate.sol#L119-L125 https://github.com/code-423n4/2024-06-size/blob/main/src/libraries/actions/Compensate.sol#L136
Vulnerability details
Impact
User can split his debt credit using the compensate function without paying the fragmentation fee.
Proof of Concept
The fragmentation fee normally applies when the initial credit amount is split, creating a new credit position with the fractionalized amount. However, under specific conditions when the compensate function is invoked, this fee is not charged.
Consider the following scenario, the user compensates half of his debt (amountToCompensate = creditPositionWithDebtToRepay.credit / 2) with a new credit position (params.creditPositionToCompensateId == RESERVED_ID) to the lender:
solidityfunction executeCompensate(State storage state, CompensateParams calldata params) external { emit Events.Compensate( params.creditPositionWithDebtToRepayId, params.creditPositionToCompensateId, params.amount ); CreditPosition storage creditPositionWithDebtToRepay = state.getCreditPosition(params.creditPositionWithDebtToRepayId); DebtPosition storage debtPositionToRepay = state.getDebtPositionByCreditPositionId(params.creditPositionWithDebtToRepayId); >> uint256 amountToCompensate = Math.min(params.amount, creditPositionWithDebtToRepay.credit); CreditPosition memory creditPositionToCompensate; if (params.creditPositionToCompensateId == RESERVED_ID) { >> creditPositionToCompensate = state.createDebtAndCreditPositions({ lender: msg.sender, borrower: msg.sender, futureValue: amountToCompensate, dueDate: debtPositionToRepay.dueDate }); ---SNIP---
In the above code snippet, a new credit position is created with credit = futureValue = amountToCompensate. This results in creditPositionToCompensate.credit always equating to amountToCompensate, leading exiterCreditRemaining to consistently be zero:
solidity---SNIP--- >> uint256 exiterCreditRemaining = creditPositionToCompensate.credit - amountToCompensate; // credit emission state.createCreditPosition({ exitCreditPositionId: params.creditPositionToCompensateId == RESERVED_ID ? state.data.nextCreditPositionId - 1 : params.creditPositionToCompensateId, lender: creditPositionWithDebtToRepay.lender, credit: amountToCompensate }); >> if (exiterCreditRemaining > 0) { // charge the fragmentation fee in collateral tokens, capped by the user balance uint256 fragmentationFeeInCollateral = Math.min( state.debtTokenAmountToCollateralTokenAmount(state.feeConfig.fragmentationFee), state.data.collateralToken.balanceOf(msg.sender) ); state.data.collateralToken.transferFrom( msg.sender, state.feeConfig.feeRecipient, fragmentationFeeInCollateral );
As a result, despite splitting creditPositionWithDebtToRepay.credit in half and creating a new credit position, the fragmentation fee is not deducted as expected.
Tools Used
Manual review
Recommended Mitigation Steps
diff+ uint256 initialCredit = params.creditPositionToCompensateId == RESERVED_ID ? creditPositionWithDebtToRepay.credit : creditPositionToCompensate.credit; + uint256 exiterCreditRemaining = initialCredit - amountToCompensate;
Assessed type
Other
