Closed howlbot-integration[bot] closed 2 months ago
Duplicate of #213
hansfriese marked the issue as satisfactory
hansfriese marked the issue as not a duplicate
hansfriese marked the issue as duplicate of #213
hansfriese marked the issue as duplicate of #288
Lines of code
https://github.com/code-423n4/2024-06-size/blob/main/src/libraries/actions/SellCreditMarket.sol#L177 https://github.com/code-423n4/2024-06-size/blob/main/src/libraries/AccountingLibrary.sol#L253-L255
Vulnerability details
Impact
CreditSellers are forced to pay more Credit for the requested amount of cash when SellingCredit with a Market Order.
Proof of Concept
For this report, we are going to follow the execution path when a CreditSeller is a Borrower who wants to take a Loan for an exact amount of cash from a CreditBuyer (Lender) using a MarketOrder.
When a CreditSeller is selling Credit, the CreditSeller is exchanging credit for cash, therefore, the CreditSeller must be responsible for covering the
swapFees
, as well asfragmentationFees
(if any, when selling partial existing credit positions).cashAmountOut + fees
, therefore, the CreditBuyer needs to be compensated in Credit for all the amount of cash that was taken out of his balance.When computing the amount of credit to charge the CreditSeller for the requested amount of cash, the
SellCreditMarket.executeSellCreditMarket() function
invokes thegetCreditAmountIn() function
. ThemaxCredit
parameter that is passed to this function is computed based on thecashAmountOut
requested by the CreditSeller, theratePerTenor
, and, (erronously), theswapFeePercent
.maxCredit
, is the amount of credit that will be charged to the CreditSeller for the requested amount of cash that will be taken from the CreditBuyer. - The formula used to compute themaxCredit
:Math.mulDivUp(cashAmountOut, PERCENT + ratePerTenor, PERCENT - state.getSwapFeePercent(tenor))
, aims to compute the amount of Credit that should be charged for thecashAmountOut
and the correspondingswapFees
that needs to be paid for receivingcashAmountOut
. - The problem is that this formula computes a bigger amount of Credit to repay for thecashAmountOut
+swapFees
that will be taken from the CreditBuyer.The amount of Credit that should be charged to the CreditSeller would be for the exact amount of cash taken from the CreditBuyer, in this case, the total amount of cash that is taken from the CreditBuyer is the
cashAmountOut
+swapFees
. The logic that should be applied to determine the amount of Credit to charge should be as follows:swapFees
is computed based on thecashAmountOut
the CreditSeller is receiving.maxCredit
is computed based on thecashAmountOut
the CreditSeller receives + theswapFees
the CreditSeller needs to pay for receivingcashAmountOut
. -cashAmountOut
+swapFees
are taken from the CreditBuyer, therefore, CreditBuyer must be compensated for that amount of cash in CreditFind below a coded PoC where it demonstrates the flaw with the current formula and shows that the amount of credit is higher than what it should really be.
Coded PoC
Create a new file under the
test/
folder, and, add the below code in it.Expand to see the Coded PoC
``` // SPDX-License-Identifier: MIT pragma solidity 0.8.23; import {Test, console2} from "forge-std/Test.sol"; import {Math, PERCENT, YEAR} from "@src/libraries/Math.sol"; contract H_01_PoC is Test { //@audit => Setting swapFeeApr to 5% //@audit => Same formula as AccountingLibrary.swapFeePercent() //https://github.com/code-423n4/2024-06-size/blob/main/src/libraries/AccountingLibrary.sol#L164-L166 function getSwapFeePercent(uint256 tenor) internal view returns (uint256) { uint256 swapFeeAPR = 0.05e18; return Math.mulDivUp(swapFeeAPR, tenor, YEAR); } //@audit => Same formula as AccountingLibrary.getSwapFee() //https://github.com/code-423n4/2024-06-size/blob/main/src/libraries/AccountingLibrary.sol#L173-L175 function getSwapFee(uint256 cash, uint256 tenor) internal view returns (uint256) { return Math.mulDivUp(cash, getSwapFeePercent(tenor), PERCENT); } function test_wrongFormulaToComputeMaxCredit_PoC() public { //@audit-info => Computing maxCashAmount using the formula that is used when calling `getCreditAmountIn()` uint256 cashAmountOut = 100000e6; //100k USDC uint256 ratePerTenor = 0.3e18; //30% uint256 tenor = 365 days; //1 year //@audit => The below formula is the same formula used to compute `maxCredit` when SellCreditMarket and request `cashAmountOut` //https://github.com/code-423n4/2024-06-size/blob/main/src/libraries/actions/SellCreditMarket.sol#L176-L177 //@audit => Current formula used to compute `maxCredit` for `cashAmountOut` when SellCreditMarket //@audit => The swapFees affects the percentage side of the equation. //@audit-issue => By affecting the percentage side, the result is a bigger amount of credit than what should really be! uint256 maxCredit = Math.mulDivUp(cashAmountOut, PERCENT + ratePerTenor, PERCENT - getSwapFeePercent(tenor)); //@audit => This would be the correct formula to compute the exact amount of Credit to charge for `cashAmountOut` and `swapFees` //@audit => The swapFees affects the cash value of the equation //@audit-ok => Exact credit to charge for the total cash (including fees) taken from the CreditBuyer. uint256 swapFeePercent = getSwapFeePercent(tenor); uint256 swapFees = Math.mulDivUp(cashAmountOut, swapFeePercent, PERCENT); uint256 maxCreditSecondOption = Math.mulDivUp(cashAmountOut + swapFees, PERCENT + ratePerTenor, PERCENT); console2.log("maxCredit: ", maxCredit); console2.log("maxCreditSecondOption: ", maxCreditSecondOption); uint256 spread = maxCredit - maxCreditSecondOption; console2.log("spread: ", spread); //@audit => The current formula of `maxCredit` would charge ~342 USDC more than if `swapFees` were added to the total cash the CreditSeller will take from the CreditBuyer! assertApproxEqAbs(spread, 342e6, 1e6); //@audit => The CreditSeller should be charged Credit worth the exact amount of cash (including fees) he is taking from the CreditBuyer. //@audit-issue => swapFees are accounted on the percentage side of the equation by reducing the swapFeePercent from the PERCENT, this will errounously compute a bigger amount of Credit to pay for cash + swapFees //@audit-recommendation => Account for the fees (swapFees and fragmentationFee) the CreditSeller pays on the total cash taken from the CreditBuyer, in this way, the formula will compute the credit to pay for the exact amount of cash taken. } } ```
Run the test with the command:
forge test --match-test test_wrongFormulaToComputeMaxCredit_PoC -vvvv
Notice the value of
spread
is ~3.421e8, which is the same as ~342e6, which is the equivalent of ~342 USDC that are charged extra with the first formula (the formula of the current implementation).Tools Used
Manual Audit
Recommended Mitigation Steps
The recommendation to fix this problem is to account for the fees on the cash side of the equation, instead of accounting for the fees on the percentage side. In this way, the formula will compute the exact amount of credit for the exact amount of cash that is taken from the CreditBuyer.
On the code, the recommendation would look like this:
Important: Make sure to apply the same logic of accounting the fees on the cash side of the equation instead of the percentage side on all the formulas where the fees are currently affecting the percentage side of the equation.
maxCashAmountOut
a CreditSeller can get for selling his credit, update the current formula:Math.mulDivDown(creditPosition.credit, PERCENT - state.getSwapFeePercent(tenor), PERCENT + ratePerTenor)
for:SellCreditMarket.executeSellCreditMarket()
AccountingLibrary.getCreditAmountIn()
Assessed type
Context