xanoutas/solidity-security-findings huggingface dataset:
Inconsistent fees in `sellCreditMarket` when passing amount as credit or cash
Audit: 2024-06-size-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-06-size/blob/main/src/libraries/AccountingLibrary.sol#L228-L263


# Vulnerability details

## Impact
The protocol allows lenders to sell their credit to other lenders, this can be achieved by calling `sellCreditMarket`. `sellCreditMarket` can be called by either passing the amount as cash or credit, in either case, the results should be the same. In simpler words, if X was passed as cash and Y was the resulting credit, passing Y as credit should also result in X as cash, this is also the same for fess. However, this is not the case here, where the fee calculation is inconsistent between both cases.

When the amount is passed as the credit (i.e. `exactAmountIn` is true), the swap fees are calculated according to the resulting cash which also depends on `creditAmountIn`, i.e. it is the result of the whole credit sold. However, when `exactAmountIn` is false (i.e. the amount is cash), swap fees depend on `cashAmountOut` which != the whole credit balance, as the credit is being altered to consume different fees, in the following:
```
creditAmountIn = Math.mulDivUp(
    cashAmountOut + state.feeConfig.fragmentationFee,
    PERCENT + ratePerTenor,
    PERCENT - swapFeePercent
);
```


This allows users to take advantage of this and pay fewer fees.

## Proof of Concept

The below shows 2 test cases, showing that passing the same "inversed" values results in different fees.

```solidity
function setupStuff() public returns (uint256) {
    _updateConfig("variablePoolBorrowRateStaleRateInterval", 1);
    _setVariablePoolBorrowRate(1e18);

    _deposit(alice, weth, 100e18);
    _deposit(alice, usdc, 500e6);
    _deposit(bob, weth, 100e18);
    _deposit(bob, usdc, 500e6);
    _deposit(candy, weth, 100e18);
    _deposit(candy, usdc, 500e6);

    int256[] memory aprs = new int256[](4);
    uint256[] memory tenors = new uint256[](4);
    uint256[] memory marketRateMultipliers = new uint256[](4);

    aprs[0] = 0.01e18;
    aprs[1] = 0.03e18;
    aprs[2] = 0.05e18;
    aprs[3] = 0.2e18;

    tenors[0] = 100 days;
    tenors[1] = 200 days;
    tenors[2] = 300 days;
    tenors[3] = 365 days;

    marketRateMultipliers[0] = 0;
    marketRateMultipliers[1] = 0;
    marketRateMultipliers[2] = 0;
    marketRateMultipliers[3] = 0;

    vm.prank(bob);
    size.buyCreditLimit(
        BuyCreditLimitParams({
            curveRelativeTime: YieldCurve({
                tenors: tenors,
                marketRateMultipliers: marketRateMultipliers,
                aprs: aprs
            }),
            maxDueDate: block.timestamp + 365 days
        })
    );

    vm.prank(candy);
    size.buyCreditLimit(
        BuyCreditLimitParams({
            curveRelativeTime: YieldCurve({
                tenors: tenors,
                marketRateMultipliers: marketRateMultipliers,
                aprs: aprs
            }),
            maxDueDate: block.timestamp + 365 days
        })
    );

    vm.prank(alice);
    size.sellCreditMarket
Wrong calculation of `liquidatorReward` in `executeLiquidate`
Audit: 2024-06-size-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-06-size/blob/8850e25fb088898e9cf86f9be1c401ad155bea86/src/libraries/actions/Liquidate.sol#L96-L98


# Vulnerability details

## Impact
The current calculation for `liquidatorReward` is

```solidity
uint256 liquidatorReward = Math.min(
	assignedCollateral - debtInCollateralToken,
	Math.mulDivUp(debtPosition.futureValue, state.feeConfig.liquidationRewardPercent, PERCENT)
);
```

The intended reward for the liquidators is `5%` of the loan's face value. But because the value `assignedCollateral - debtInCollateralToken` is in `ETH` and the value `Math.mulDivUp(debtPosition.futureValue, state.feeConfig.liquidationRewardPercent, PERCENT)` is in `USDC`, which makes the reward is only `5% * 10**6 / priceOfETHInUSDC` of the loan's face value, which is less than the intended reward.

As a result, the liquidators will get less reward than expected.

## Tools Used
Manual Review.

## Recommended Mitigation Steps
Convert `Math.mulDivUp(debtPosition.futureValue, state.feeConfig.liquidationRewardPercent, PERCENT)` into amount of ETH.








## Assessed type

Math
Incorrect calculation of the cap on the liquidation reward leads to loss of funds
Audit: 2024-06-size-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-06-size/blob/main/src/libraries/actions/Liquidate.sol#L90-L112


# Vulnerability details

## Impact
An incorrect calculation of the cap on the liquidation reward leads to reduced profits for the liquidator and increased profits for the protocol. Also if the decimals of the collateral are less than the borrow token, then the liquidator will get more profit than should be at expenses of the protocol.
In the case of USDC/WETH market (borrow/collateral) since WETH has more decimals than USDC will happen most of the time and the result is loss of funds for the liquidator.



## Proof of Concept

The problem is a mix of tokens in the calculation of the cap on the liquidation reward that lead to wrong results. Lets do a unit analysis in [Liquidate.sol#90](https://github.com/code-423n4/2024-06-size/blob/main/src/libraries/actions/Liquidate.sol#L90) to see it

```javascript

        // WETH
        uint256 assignedCollateral = state.getDebtPositionAssignedCollateral(debtPosition);
        // WETH
        uint256 debtInCollateralToken = state.debtTokenAmountToCollateralTokenAmount(debtPosition.futureValue /* USDC */);
        // WETH
        uint256 protocolProfitCollateralToken = 0; // WETH

        // WETH > WETH
        if (assignedCollateral > debtInCollateralToken) {
            // @audit liquidation reward Math.min can return either WETH or USDC which is wrong. will return USDC most of the time because of the decimals.
            uint256 liquidatorReward = Math.min(
                assignedCollateral - debtInCollateralToken, // WETH
                Math.mulDivUp(debtPosition.futureValue, state.feeConfig.liquidationRewardPercent, PERCENT) // USDC
            );

            // @audit here happen the mix:
            // ??? = WETH + USDC
            liquidatorProfitCollateralToken = debtInCollateralToken + liquidatorReward;

            // @audit Sum liquidatorProfitCollateralToken leading too more problems
            uint256 collateralRemainder = assignedCollateral - liquidatorProfitCollateralToken;

            //...

            // @audit protocol fees wrong calculated also.
            protocolProfitCollateralToken = Math.mulDivDown(collateralRemainder, collateralProtocolPercent, PERCENT);

```





## Tools Used

- VS Code
- Unit Analysis

## Recommended Mitigation Steps

I would use `debtInCollateralToken` which is the future debt calculated in WETH to avoid the mix of units:

```javascript
        uint256 liquidatorReward = Math.min(
            assignedCollateral - debtInCollateralToken, // WETH
            Math.mulDivUp(debtInCollateralToken, state.feeConfig.liquidationRewardPercent, PERCENT) // WETH
        );
}
```



## Assessed type

Math
Because of a decimal issue, the liquidator gets next to nothing as a liquidation rewards when liquidating a borrow position
Audit: 2024-06-size-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-06-size/blob/8850e25fb088898e9cf86f9be1c401ad155bea86/src/libraries/actions/Liquidate.sol#L96-L99


# Vulnerability details

## Impact

Because the `liquidatorReward` when liquidating a borrow position is next to nothing, liquidations are not profitable and therefore will not be done by anyone. This will lead to a lot of bad debt for the protocol



## Proof of Concept

When liquidating a borrow position which is overdue or where the owner is under water (below the minimum collateral/debt ratio) the liquidator gets a liquidation bonus for repaying the borrow position. This ensures that the liquidator profits from the liquidation and is incentivised to do it.

The `liquidationReward` is calculated by taking the minimum of two values, the collateral available after deducting the value of the debt and the “normal” liquidatorReward. This is done in case the collateralisation is so low that the “normal” liquidatorReward can not be payed out.

```java
uint256 liquidatorReward = Math.min(
    assignedCollateral - debtInCollateralToken,
    Math.mulDivUp(debtPosition.futureValue, state.feeConfig.liquidationRewardPercent, PERCENT)
            );
```

For calculating the “normal” liquidatorReward, the `futureValue`(total debt) of the debt position is multiplied by the target `liquidationRewardPercent` and divided by the constant `PERCENT` to adjust the decimals.

The issue arises from the fact that the `futureValue` has 6 decimals (like USDC), the `liquidationRewardPercent` has 18 decimals and `PERCENT` also has 18 decimals. Therefore, the result for the “normal” liquidatorReward has 6 decimals even though it should have 18. Therefore the “normal” liquidatorReward will always be picked as `liquidatorReward` becasue it is off by 12 decimals and therefore smaller than the remaining collateral value. This leads to no profit for the liquidator and therefore no incentive to liquidate borrow positions.

Add the following test to the file `Liquidate.t.sol` and run it with `forge test -vv --mt  test_LiquidatorReward_to_small`:

```java
    function test_LiquidatorReward_to_small() public {
        _updateConfig("minTenor", 1);
        _updateConfig("maxTenor", 10 * 365 days);
        _updateConfig("swapFeeAPR", 0);
        _updateConfig("overdueCollateralProtocolPercent", 0.123e18);
        _updateConfig("crLiquidation", 1.2e18);
        _setPrice(1e18);
        _deposit(alice, usdc, 100e6);
        _deposit(bob, weth, 180e18);
        _deposit(candy, usdc, 100e6);
        _deposit(liquidator, usdc, 1_000e6);
        _buyCreditLimit(alice, block.timestamp + 365 days, YieldCurveHelper.pointCurve(365 days, 1e18));
        _buyCreditLimit(candy, block.timestamp + 365 days, YieldCurveHelper.pointCurve(365 days, 1e18));
        uint256 debtPositionId = _sellCreditMarket(bob, alice, RESERVED_ID, 50e6, 365 days, false);
        uint256 futureValue = size.getDebtPosition(debtPositionId).futureValue;
        // debt value
Inconsistent Fee Deduction in SellCreditMarket (exactAmountIn=false)
Audit: 2024-06-size-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-06-size/blob/8850e25fb088898e9cf86f9be1c401ad155bea86/src/libraries/actions/SellCreditMarket.sol#L127-L202
https://github.com/code-423n4/2024-06-size/blob/8850e25fb088898e9cf86f9be1c401ad155bea86/src/libraries/AccountingLibrary.sol#L228-L249


# Vulnerability details

## Impact
**Impact of Inconsistent Fee Deduction in `SellCreditMarket`**
The inconsistent fee deduction logic in `SellCreditMarket.sol` can lead to unexpected behavior and potential financial disadvantages for users, particularly when they choose to sell credit with `exactAmountIn = false`.
[src/libraries/actions/SellCreditMarket.sol#executeSellCreditMarket](https://github.com/code-423n4/2024-06-size/blob/8850e25fb088898e9cf86f9be1c401ad155bea86/src/libraries/actions/SellCreditMarket.sol#L127-L202)
```solidity
function executeSellCreditMarket(State storage state, SellCreditMarketParams calldata params)
    external
    returns (uint256 cashAmountOut)
{
    // ...

    if (params.exactAmountIn) {
        creditAmountIn = params.amount;

        (cashAmountOut, fees) = state.getCashAmountOut({
            // ...
        });
    } else {
        cashAmountOut = params.amount;

        (creditAmountIn, fees) = state.getCreditAmountIn({
            // ...
        });
    }

    // ...

    state.data.borrowAToken.transferFrom(params.lender, msg.sender, cashAmountOut);
    state.data.borrowAToken.transferFrom(params.lender, state.feeConfig.feeRecipient, fees);
}
```
[src/libraries/AccountingLibrary.sol#getCreditAmountIn](https://github.com/code-423n4/2024-06-size/blob/8850e25fb088898e9cf86f9be1c401ad155bea86/src/libraries/AccountingLibrary.sol#L228-L249)
```solidity
    function getCreditAmountIn(
        State storage state,
        uint256 cashAmountOut,
        uint256 maxCashAmountOut,
        uint256 maxCredit,
        uint256 ratePerTenor,
        uint256 tenor
    ) internal view returns (uint256 creditAmountIn, uint256 fees) {
        uint256 swapFeePercent = getSwapFeePercent(state, tenor);


        uint256 maxCashAmountOutFragmentation = 0;


        if (maxCashAmountOut >= state.feeConfig.fragmentationFee) {
            maxCashAmountOutFragmentation = maxCashAmountOut - state.feeConfig.fragmentationFee;
        }


        // slither-disable-next-line incorrect-equality
        if (cashAmountOut == maxCashAmountOut) {
            // no credit fractionalization


            creditAmountIn = maxCredit;
            fees = Math.mulDivUp(cashAmountOut, swapFeePercent, PERCENT);
}
```
## Proof of Concept
**Scenario:**
Let's assume:
   - `exactAmountIn = false`
   - User wants to sell credit for `cashAmountOut = 100`
   - `ratePerTenor = 0` (for simplicity)
   - `swapFeePercent = 2` (2% fee)

**Transaction Flow:**
   1. **`executeSellCreditMarket` called:**
     - `params.exactAmountIn = false, so cashAmountOut is set to 100.
     - `getCreditAmountIn is called with cashAmountOut = 100.

   2. **`getCreditAmountIn` calculation:**
liquidatorReward is Incorrectly calculated: Inconsistent Units
Audit: 2024-06-size-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-06-size/blob/main/src/libraries/actions/Liquidate.sol#L96-L100
https://github.com/code-423n4/2024-06-size/blob/main/src/libraries/actions/Liquidate.sol#L103
https://github.com/code-423n4/2024-06-size/blob/main/src/libraries/actions/Liquidate.sol#L119


# Vulnerability details

## Impact
In `executeliquidate` function of `liquidate.sol`, `liquidatorProfitCollateralToken` is wrongly calculated due to an error in computation of `liquidatorReward`.
- `liquidatorProfitCollateralToken` is sum of `debtInCollateralToken` and `liquidatorReward`. `debtInCollateralToken` is measured in terms of collateral tokens (18 decimals), whereas `liquidatorReward` is calculated by comparing two values with different decimals, leading to an inconsistent comparison:

 ``` 
uint256 liquidatorReward = Math.min(
    assignedCollateral - debtInCollateralToken, //18 decimals
   Math.mulDivUp(debtPosition.futureValue, state.feeConfig.liquidationRewardPercent, PERCENT)//6 decimals
);
```
-  a 6 decimal value might often be the output of above comparison which is totally unintended. 
- Instead of using `debtPosition.futureValue`,  `debtInCollateralToken` (18 decimals) should be used in the Math.min(,) comparison to ensure consistency in units. 
- This error in `liquidatorReward` further effects calculations of `collateralRemainder`, `protocolProfitCollateralToken`, leading to a significantly incorrect amount of collateral being transferred to liquidators.

```
state.data.collateralToken.transferFrom(debtPosition.borrower, msg.sender, liquidatorProfitCollateralToken);
```
## Proof of Concept
 -
## Tools Used
- Manual Review

## Recommended Mitigation Steps
- Use `debtInCollateralToken` instead of `debtPosition.futureValue` as shown below:
```
uint256 liquidatorReward = Math.min(
    assignedCollateral - debtInCollateralToken,
----    Math.mulDivUp(debtPosition.futureValue, state.feeConfig.liquidationRewardPercent, PERCENT));
++++ Math.mulDivUp(debtInCollateralToken, state.feeConfig.liquidationRewardPercent, PERCENT));
);
```


## Assessed type

Other
`executeLiquidate()` liquidaterReward is totally underestimated because the computation computes `futureValue` as 6 decimals
Audit: 2024-06-size-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-06-size/blob/8850e25fb088898e9cf86f9be1c401ad155bea86/src/libraries/actions/Liquidate.sol#L98


# Vulnerability details

## Impact

The computation for liquidatorReward extremely changed after the Spearbit audit, look at the size code base commit before the changes [SizeGithubLiquidatorReward](https://github.com/SizeCredit/size-solidity/blob/54b9da570c9435044cb7de9dc3ca07ee73949794/src/libraries/fixed/actions/Liquidate.sol#L76). The liquidatorReward in that commit is correct because of the shift in decimals of  e6 into 1e18 with `debtTokenAmountToCollateralTokenAmount`.

In the current audit scope, debtInCollateralToken is changed from e6 to e18. However when seeing if the liquidator should get the 5% of the debt or leftover collateral. The `futureValue` is not in the right decimals making the minium always smaller than even 5% of the debt. As you can see, it uses `futureValue`, the amount the borrower owes, and is naturally computed in e6.

```js
     if (assignedCollateral > debtInCollateralToken) { 
            uint256 liquidatorReward = Math.min( 
                assignedCollateral - debtInCollateralToken,
                Math.mulDivUp(debtPosition.futureValue, state.feeConfig.liquidationRewardPercent, PERCENT)
```
The example is in the POC below. Moreover, I have placed the issue of high severity because no one liquidates for free and even the protocol will be expecting a return as seen in the tests. Furthermore, liquidators always calculate how much they will be profiting before they start the liquidation process. Either the return of yield will be the leftover collateral or 5% of the debt, in any case, the liquidator won't get either which can be a huge loss of yield. The issue is a loss of matured yield that can have dire consequences on liquidators.

Since the reward for the liquidator would be less than $0.1 in most liquidation cases depending on WETH price at the time of liquidation. It would be effectively zero under the assumption that most people who are doing liquidations want to profit from the debt which would not accomplished by keeping under-water borrowers and their positions from becoming healthy. On the other hand, the liquidator can be the protocol but they will probably want professional liquidators to call liquidate later on in the protocol cycle. As for right now, it's still a great loss that can make users and the protocol lose unexpected Yield.

## Proof of Concept

The best way is to show you in the test and explain the impact.

Steps:
`forge test --match-test test_liquidate_liquidatorReward_Decimal_Is_Wrong -vvv`

```solidity
    function test_liquidate_liquidatorReward_Decimal_Is_Wrong() public {
        _updateConfig("minTenor", 1);
        _updateConfig("maxTenor", 10 * 365 days);
        _updateConfig("swapFeeAPR", 0);
        _updateConfig("overdueCollateralProtocolPercent", 0.123e18);
        _updateConfig("crLiquidation", 1.2e18);
        _setPrice(1e18);
The method of calculate liquidator reward is incorrect
Audit: 2024-06-size-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-06-size/blob/main/src/libraries/actions/Liquidate.sol#L96-L99


# Vulnerability details




## Impact
Incorrectly calculating the liquidator reward can lead to liquidation reward much smaller than expected, result in liquidation DOS.

## Proof of Concept

Firstly the liquidator can specific `minimumCollateralProfit`,which means the minimum profit in collateral tokens expected by the liquidator. 
```solidity
struct LiquidateParams {
    // The debt position ID to liquidate
    uint256 debtPositionId;
    // The minimum profit in collateral tokens expected by the liquidator
    uint256 minimumCollateralProfit;  <@
}
```
Note that the `minimumCollateralProfit` is denominated in collateral tokens, specifically `WETH`, which uses 18 decimals.

[Liquidate.sol#L96-L99](https://github.com/code-423n4/2024-06-size/blob/main/src/libraries/actions/Liquidate.sol#L96-L99)
```solidity
uint256 liquidatorReward = Math.min(
    assignedCollateral - debtInCollateralToken,
    Math.mulDivUp(debtPosition.futureValue, state.feeConfig.liquidationRewardPercent, PERCENT)
);
```

The code above calculates the `liquidatorReward` by comparing the remaining value in `CollateralToken` with a portion of the `futureValue`. It's important to note that `futureValue` and `USDC` both have 6 decimals, while `CollateralToken` (WETH) has 18 decimals. Therefore, a portion of the future value may be significantly lower than anticipated due to this discrepancy in decimal places.


test:
```solidity
function test_liquidate_custom() public {
    _updateConfig("swapFeeAPR", 0);
    _setPrice(1e18);

    _deposit(alice, usdc, 100e6);
    _deposit(bob, weth, 170e18);
    _deposit(liquidator, usdc, 1_000e6);

    Vars memory _before = _state();

    _buyCreditLimit(alice, block.timestamp + 365 days, YieldCurveHelper.pointCurve(365 days, 1e18));

    uint256 debtPositionId = _sellCreditMarket(bob, alice, RESERVED_ID, 50e6, 365 days, false);
    uint256 futureValue = size.getDebtPosition(debtPositionId).futureValue;
    console2.log("futureValue:",futureValue);
    console2.log("liquidationRewardPercent:",size.feeConfig().liquidationRewardPercent);

    uint256 creditId = size.getCreditPositionIdsByDebtPositionId(debtPositionId)[0];

    //liquidator collateral balance init.
    uint256 collateralBefore = _before.liquidator.collateralTokenBalance;
    uint256 usdcBefore = _before.liquidator.borrowATokenBalance;
    console2.log("liquidator weth before:",_before.liquidator.collateralTokenBalance);
    console2.log("liquidator usdc before:",usdcBefore);


    vm.warp(block.timestamp + 365 days + 1);
    _liquidate(liquidator, debtPositionId);

    Vars memory _after = _state();
    console2.log("liquidator weth after:",_after.liquidator.collateralTokenBalance);
    console2.log("add collteral token:",_after.liquidator.collateralTokenBalance - collateralBefore);
    console2.log("cost usdc token:",usdcBefore - _after.liquidator.borrowATokenBalance