In the getCreditAmountIn function, the swap fee is incorrectly accounted for, leading to an underestimation of fees. The issue arises from how the swap fee is deducted and then recalculated in the formula.
The formula for the amount of credit (A) before fees:
$$A = \frac{(V + f) (1 + r)}{1 - k\Delta T}$$
Here, k is the swap fee percentage, ΔT is the remaining time to maturity, V is the cash amount out, f is the fragmentation fee, and r is the rate per tenor. This formula correctly charges the swap fee by deducting the credit by k%.
Explanation
The $$\frac{A}{1 + r}$$ is the amount of cash the credit buyer values the amount of credit he is buying, before fees
Then fees are applied and
KΔT is charged since the swap fee is a function of the remaining lifetime of the loan and is applied to the cash side of the trade
The higher K and ΔT and the higher the amount A to be sold to get the desired amount out
f is charged to cover the gas costs related to credit fragmentation
The higher f and the higher the amount A to be sold to get the desired amount out
The swap fee has already been deducted when calculating cashAmountOut using the formula $$( A \times (1 - k)$$. However, in the getCreditAmountIn function, the swap fee is recalculated using cashAmount * k%, leading to an underestimation of the actual swap fee.
This leads to an underestimation of the swap fee, as the swap fee is already deducted in the initial formula and then recalculated from the cash amount, leading to:
$$ \text{swapFee} = \left( \frac{\text{credit} \times (1 - k)}{(1 + r)} - f \right) \times k $$
Correct SwapFee Calculation:
Given:
$$ A \times (1 - k) = (V + f) \times (1 + r) $$
First, solve for $$A$$:
$$ A = \frac{(V + f) \times (1 + r)}{1 - k} $$
The swap fee is given by:
$$ A \times k = \text{swapFee} \times (1 + r) $$
By the formula, the swap fee should be the same for both, but it isn't. The swap fee for sellCreditMarket should be equal to maxCashAmountOut: 857142857 * 0.005 (swapFeePercent use 0.5%) / 0.995 = 4307251 (with mulDivUp ) and that's it!
Fee Transfer in Functions
The two functions transfer fees in slightly different ways:
buyCreditMarket: cashAmountIn is the total cash paid before deducting fees.
Lines of code
https://github.com/code-423n4/2024-06-size/blob/main/src/libraries/AccountingLibrary.sol#L248-L249 https://github.com/code-423n4/2024-06-size/blob/main/src/libraries/AccountingLibrary.sol#L253-L256 https://github.com/code-423n4/2024-06-size/blob/main/src/libraries/actions/BuyCreditMarket.sol#L195-L196 https://github.com/code-423n4/2024-06-size/blob/main/src/libraries/actions/SellCreditMarket.sol#L201-L202 https://github.com/code-423n4/2024-06-size/blob/main/src/libraries/actions/SellCreditMarket.sol#L173-L175
Vulnerability details
Vulnerability Details
In the
getCreditAmountIn
function, the swap fee is incorrectly accounted for, leading to an underestimation of fees. The issue arises from how the swap fee is deducted and then recalculated in the formula.Problem Explanation
Reference document for the original formula.
https://docs.size.credit/technical-docs/contracts/3.3-market-orders
Formula for Cash Amount Out (
cashAmountOut
):The formula for the amount of credit (
A
) before fees: $$A = \frac{(V + f) (1 + r)}{1 - k\Delta T}$$Here,
k
is the swap fee percentage,ΔT
is the remaining time to maturity,V
is the cash amount out,f
is the fragmentation fee, andr
is the rate per tenor. This formula correctly charges the swap fee by deducting the credit byk%
.Explanation
KΔT
is charged since the swap fee is a function of the remaining lifetime of the loan and is applied to the cash side of the tradeK
andΔT
and the higher the amountA
to be sold to get the desired amount outf
is charged to cover the gas costs related to credit fragmentationf
and the higher the amountA
to be sold to get the desired amount outCurrent Implementation Issues:
In scenarios where
cashAmountOut
is equal tomaxCashAmount
:https://github.com/code-423n4/2024-06-size/blob/main/src/libraries/AccountingLibrary.sol#L248-L249
In scenarios where
cashAmountOut
is less thanmaxCashAmountOutFragmentation
:https://github.com/code-423n4/2024-06-size/blob/main/src/libraries/AccountingLibrary.sol#L253-L256
Underestimating Swap Fee:
The swap fee has already been deducted when calculating
cashAmountOut
using the formula $$( A \times (1 - k)$$. However, in thegetCreditAmountIn
function, the swap fee is recalculated usingcashAmount * k%
, leading to an underestimation of the actual swap fee.Detailed Issue Breakdown
Formula Breakdown: $$A = \frac{(V + f)(1 + r)}{1 - k}$$
Here, the swap fee is correctly charged by reducing the credit by
k%
.Incorrect Fee Calculation in
getCreditAmountIn
:This leads to an underestimation of the swap fee, as the swap fee is already deducted in the initial formula and then recalculated from the cash amount, leading to: $$ \text{swapFee} = \left( \frac{\text{credit} \times (1 - k)}{(1 + r)} - f \right) \times k $$
Correct SwapFee Calculation:
Given: $$ A \times (1 - k) = (V + f) \times (1 + r) $$
First, solve for $$A$$: $$ A = \frac{(V + f) \times (1 + r)}{1 - k} $$
The swap fee is given by: $$ A \times k = \text{swapFee} \times (1 + r) $$
Substitute $$A$$ into the swap fee formula: $$ \text{swapFee} = \frac{\left( \frac{(V + f) \times (1 + r)}{1 - k} \right) \times k}{1 + r} $$
Simplify the expression: $$ \text{swapFee} = \frac{(V + f) \times (1 + r) \times k}{(1 - k) \times (1 + r)} $$ $$ \text{swapFee} = \frac{(V + f) \times k}{1 - k} $$
So, the swap fee in terms of $$V$$ is: $$ \text{swapFee} = \frac{(V + f) \times k}{1 - k} $$
or in terms of $$A$$: $$ \text{swapFee} = \frac{A \times k}{1 + r} $$
Example (exactAmountOut)
Assume the swap fee is 1% yearly.
The fee recipient gets:
Bob gets exactly $50 in cash.
Original Swap Fee Calculation:
Correct Swap Fee Calculation:
Impact
The incorrect calculation leads to paying less in swapfees.
Proof of Concept
A clearer comparison is between
buyCreditMarket
andsellCreditMarket
when buying and selling all credits of the same creditPosition.By the formula, the swap fee should be the same for both, but it isn't. The swap fee for
sellCreditMarket
should be equal to maxCashAmountOut: 857142857 * 0.005 (swapFeePercent use 0.5%) / 0.995 = 4307251 (withmulDivUp
) and that's it!The two functions transfer fees in slightly different ways:
buyCreditMarket
:cashAmountIn
is the total cash paid before deducting fees.sellCreditMarket
:cashAmountOut
is the seller received cash amount after deducting fees.poc file.
Run the test file and get the output:
buyCreditMarket
sellCreditMarket
Tools Used
Manual, Foundry
Recommended Mitigation Steps
To fix the issue, ensure that the swap fee is correctly calculated and not applied twice.
Assessed type
Math