Both functions use Math.mulDivUp(credit, PERCENT, PERCENT + ratePerTenor) as a base for swap fee, while it's to be paid by borrower using their borrow amount as a base, which is only a part of credit present value, while the other part are fees that were transferred by lender on behalf of borrower, i.e. fees are also borrowed and have to be covered by the total credit.
This way instead of amount * feePercent it now is amount * (1 + feePercent) * feePercent = amount * feePercent + amount * feePercent ^ 2, where the last part is charged incorrectly being fee on fee.
Impact
Swap fees are calculated from the amount that already includes them, i.e. what is charged is a swap fee and an another swap fee off the first swap fee. This is a violation of the stated protocol logic leading to user's loss on each BuyCreditMarket operation without any additional prerequisites.
Since the base can be misstated rather substantially when tenor is large with swap fees being double charged and there are no additional prerequisites for the impact, placing the severity to be high.
Proof of Concept
Swap fees are paid by the caller/lender on behalf of the borrower (cash receiver), who pay the fee off the amount borrowed, which is cashAmountIn - fees, not cashAmountIn:
cashAmountIn paid by the lender is a total sum inclusive of the swap fees, paid by the borrower/cash receiver, and fragmentation fees (if any) paid by the lender/caller.
I.e. it is borrower who receives the cash and they pay the swap fee and for them the base is amount borrowed, cashAmountIn - fees:
Lines of code
https://github.com/code-423n4/2024-06-size/blob/8850e25fb088898e9cf86f9be1c401ad155bea86/src/libraries/AccountingLibrary.sol#L311-L332 https://github.com/code-423n4/2024-06-size/blob/8850e25fb088898e9cf86f9be1c401ad155bea86/src/libraries/AccountingLibrary.sol#L274-L297
Vulnerability details
Both functions use
Math.mulDivUp(credit, PERCENT, PERCENT + ratePerTenor)
as a base for swap fee, while it's to be paid by borrower using their borrow amount as a base, which is only a part of credit present value, while the other part are fees that were transferred by lender on behalf of borrower, i.e. fees are also borrowed and have to be covered by the total credit.This way instead of
amount * feePercent
it now isamount * (1 + feePercent) * feePercent = amount * feePercent + amount * feePercent ^ 2
, where the last part is charged incorrectly being fee on fee.Impact
Swap fees are calculated from the amount that already includes them, i.e. what is charged is a swap fee and an another swap fee off the first swap fee. This is a violation of the stated protocol logic leading to user's loss on each
BuyCreditMarket
operation without any additional prerequisites.Since the base can be misstated rather substantially when tenor is large with swap fees being double charged and there are no additional prerequisites for the impact, placing the severity to be high.
Proof of Concept
Swap
fees
are paid by the caller/lender on behalf of the borrower (cash receiver), who pay the fee off the amount borrowed, which iscashAmountIn - fees
, notcashAmountIn
:AccountingLibrary.sol#L311-L332
The same situation is with
getCreditAmountOut()
:AccountingLibrary.sol#L274-L297
cashAmountIn
paid by the lender is a total sum inclusive of the swap fees, paid by the borrower/cash receiver, and fragmentation fees (if any) paid by the lender/caller.I.e. it is borrower who receives the cash and they pay the swap fee and for them the base is amount borrowed,
cashAmountIn - fees
:https://github.com/code-423n4/2024-06-size/blob/8850e25fb088898e9cf86f9be1c401ad155bea86/src/libraries/actions/BuyCreditMarket.sol#L156-L196
Tools Used
Manual Review
Recommended Mitigation Steps
Consider calculating the net amount of the loan for the borrower,
cashAmountIn / (1 + swapFeePercent)
, e.g. forgetCashAmountIn()
:AccountingLibrary.sol#L311-L332
getCreditAmountOut()
can be modified to usecashAmountIn / (1 + swapFeePercent)
as the swap fee base in the same manner.Assessed type
Math