xanoutas/solidity-security-findings huggingface dataset:
Protocol swap fees accidentally sent to lender instead of the protocol due to wrong fees calculation in SellCreditMarket.
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#L168-L181
https://github.com/code-423n4/2024-06-size/blob/8850e25fb088898e9cf86f9be1c401ad155bea86/src/libraries/AccountingLibrary.sol#L228-L263


# Vulnerability details

## Impact

Whenever a ```SellCreditMarket``` is executed with ```params.exactAmountIn``` = ```false```, some amount of protocol swap fees will always be sent to the lender instead of the protocol.

Meaning whenever a borrower borrows cash, or when a credit owner sells his credit to exit cash early, some portion of swap fees always goes to the credit buyer(lender incase of borrowing). 

## Proof of Concept
Example,
a borrower wants to borrow 1000 USDC using ```SellCreditMarket``` any say fee is 5% APR and tenor is 5 years, then ratePerTenor = 20%. So, he calls it with ```RESERVED_ID``` and ```exactAmountIn = false``` because he wants to borrow exactly 1000 cash.
```javascript
} else {
            cashAmountOut = params.amount;

            (creditAmountIn, fees) = state.getCreditAmountIn({
                cashAmountOut: cashAmountOut,
                maxCashAmountOut: params.creditPositionId == RESERVED_ID
                    ? cashAmountOut
                    : Math.mulDivDown(creditPosition.credit, PERCENT - state.getSwapFeePercent(tenor), PERCENT + ratePerTenor),
                maxCredit: params.creditPositionId == RESERVED_ID
                    ? Math.mulDivUp(cashAmountOut, PERCENT + ratePerTenor, PERCENT - state.getSwapFeePercent(tenor))
                    : creditPosition.credit,
                ratePerTenor: ratePerTenor,
                tenor: tenor
            });
        }
```
creditAmountIn and fees are calculated using ```getCreditAmountIn``` 
```javascript
if (cashAmountOut == maxCashAmountOut) {
            // no credit fractionalization

            creditAmountIn = maxCredit;
            fees = Math.mulDivUp(cashAmountOut, swapFeePercent, PERCENT)
```

Now, we have ratePerTenor = 0.2e18(20%) and swapFeePercent = 0.1e18(10%). 
We get:
* cashAmountOut = 1000
* creditAmountIn = 1334
* fees = 100
* Positions are created with the calculated amounts.

This means the borrower has to pay 1334 back for borrowing 1000 at 20%. 
Here, we do not get ```creditAmountIn = 1200``` because the fees are to be paid by the cash reciever(borrower) and in this case, the borrower is also borrowing the fees and hence 20% for each borrowed fees.
```javascript
state.data.borrowAToken.transferFrom(params.lender, msg.sender, cashAmountOut); 
        state.data.borrowAToken.transferFrom(params.lender, state.feeConfig.feeRecipient, fees); 
        //@audit fees are paid by the lender at the time of execution so the borrower has to pay back the fees along with its interest accrued
```
When ```fees = 100``` is borrowed, think it of as a new borrow execution, then interest have to be paid to lender, and also swap fees need to be taken which is `
Normal `Liquidate` action incentives are too low due to decimals misshandling
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#L98


# Vulnerability details

---

### **Explanation && Impact** 📌📈

The `liquidatorProfitCollateralToken` reward that incentivizes liquidators is always too small. We are talking about `10^(-12)` orders of magnitude smaller than it should.

This is because `liquidatorProfitCollateralToken` is determined like so:

```solidity
// 🟢1️⃣ `debtInCollateralToken` has 18 decmals but `liquidatorReward` always has 6 decimals
liquidatorProfitCollateralToken = debtInCollateralToken + liquidatorReward;

// 🟢2️⃣ This is because `liquidatorReward` is always taken from this _min_ comparison. And it compares
// `assignedCollateral - debtInCollateralToken` amount, which is in collateral decimals => 18, with the Math
// operation over a future value on credit, with 6 decimals. Clearly, the 18 decimals value will always be bigger. Around 10^(12) times bigger.
uint256 liquidatorReward = Math.min(
                assignedCollateral - debtInCollateralToken,
                Math.mulDivUp(debtPosition.futureValue, state.feeConfig.liquidationRewardPercent, PERCENT)
);
```

Liquidations of risky unhealthy positions are essential to the system and its incentives for them to happen must exit. Yet receiving just some wei's of value for that is not enough. It is true that the protocol expects up-to 5% of the credit value as extra reward for the liquidator, and this amount is so small that it is inside the 5%. But as said it is an incredibly small, `10^(-12)` orders of magnitude smaller, which can not incentivize liquidators to act as just gas cost will be bigger.

The code has already some features that tend to make positions' liqudations profitable. Like having a minimum credit amount for each loan, yet all becomes useless if you are only taking such a small fraction as the extra incentive.

---

### **Proof Of Concept (PoC)** 👨‍💻💻

Import `import "forge-std/console.sol";` in `Liquidate.sol` and add the following loggings in the `executeLiquidate()` functon [(see where in the code here)](https://github.com/code-423n4/2024-06-size/blob/main/src/libraries/actions/Liquidate.sol#L96):

```solidity
console.log("These are the 2 amounts being compared to take the min:");
console.log(assignedCollateral - debtInCollateralToken);
console.log(Math.mulDivUp(debtPosition.futureValue, state.feeConfig.liquidationRewardPercent, PERCENT));
uint256 liquidatorReward = Math.min(
            assignedCollateral - debtInCollateralToken,
            Math.mulDivUp(debtPosition.futureValue, state.feeConfig.liquidationRewardPercent, PERCENT)
);
console.log("Chosen one: ", liquidatorReward);
liquidatorProfitCollateralToken = debtInCollateralToken + liquidatorReward; 
```

Run one of the code's liquidation tests to see the logs:

```bash
forge test --match-test "test_Liquidate_example" -vvv
```

---

### **Recommended Actions** 🛠️

Use the `futureValue` converted to collateral units. This value is
Liquidator reward is not calculated correctly
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#L95-L100


# Vulnerability details

## Impact
When the liquidation is profitable, the liquidator receives a liquidation reward, which is calculated as follows.

```solidity
uint256 liquidatorReward = Math.min(
                assignedCollateral - debtInCollateralToken,
                Math.mulDivUp(
                    debtPosition.futureValue,
                    state.feeConfig.liquidationRewardPercent,
                    PERCENT
                )
            ); 
```
The assigned collateral has 18 decimals.

```solidity
function getDebtPositionAssignedCollateral(State storage state, DebtPosition memory debtPosition)
        public
        view
        returns (uint256)
    {
        uint256 debt = state.data.debtToken.balanceOf(debtPosition.borrower);
        uint256 collateral = state.data.collateralToken.balanceOf(debtPosition.borrower);

        if (debt != 0) {
            return Math.mulDivDown(collateral, debtPosition.futureValue, debt);
        } else {
            return 0;
        }
    }
```
The debtInCollateralToken also has 18 decimals.

```solidity
function debtTokenAmountToCollateralTokenAmount(State storage state, uint256 debtTokenAmount)
        internal
        view
        returns (uint256 collateralTokenAmount)
    {
        uint256 debtTokenAmountWad = Math.amountToWad(debtTokenAmount, state.data.underlyingBorrowToken.decimals());
        collateralTokenAmount = Math.mulDivUp(
            debtTokenAmountWad, 10 ** state.oracle.priceFeed.decimals(), state.oracle.priceFeed.getPrice()
        );
    }
```
When the liquidator reward is calculated, it uses the minimum value. The problem is that the futureValue has a 6 decimal value, while the assignedCollateral and debtInCollateralToken have an 18 decimal value. Because of that, it will always use the second calculation in the Math.min function, and the liquidators will lose rewards.


## Proof of Concept
Liquidate.t.sol

```solidity
function test_Liquidate_liquidate_reduces_borrower_debt() public {
        _setPrice(1e18);

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

        _buyCreditLimit(alice, block.timestamp + 365 days, YieldCurveHelper.pointCurve(365 days, 0.03e18));
        uint256 amount = 15e6;
        uint256 debtPositionId = _sellCreditMarket(bob, alice, RESERVED_ID, amount, 365 days, false);

        _setPrice(0.2e18);

        assertTrue(size.isDebtPositionLiquidatable(debtPositionId));

        _liquidate(liquidator, debtPositionId);

        assertEq(_state().bob.debtBalance, 0);
    }
```
I just emitted an event in the executeLiquidate function to see the liquidator reward.

```
├─ emit liqReward(: 776382 [7.763e5]) //@audit !!
Liquidator cannot achieve proper liquidator reward as protocol promised
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#L95-L99


# Vulnerability details

## Root cause and summary

In executeLiquidate, if liquidation is profitable, liquidatorReward is assignedCollateral(collateral amount assigned to target debtPosition) - debtInCollateralToken(debt position futureValue as collateral token).
This calculated reward capped to liquidationRewardPercent(5%) on future value of debt position with below code.
https://github.com/code-423n4/2024-06-size/blob/main/src/libraries/actions/Liquidate.sol#L96-L99

However, 5% of debt position future value is not converted to collateral token amount.
In the current code, the future value of debtPosition is unit of USDC but calculated reward should be unit of CollateralToken(ETH).
The decimal of USDC is 1e6, and the decimal of ETH is 1e18. In this wrong comparison, most of time liquidation reward should be miscalculated in favor against liquidator.
The future value (decimal of 6) will always be less than collateral token (decilmal of 18).
When it adds liquidate reward to liquidator at the end of function, liquidatorReward is considered as ETH unit, as if the comparison was correct.
So the liquidator receives much less liquidation reward than that of the procotocl promised.

## Proof of Concept

Actually, test code for this section exists and it is passed, as the test code is wrongly designed.
https://github.com/code-423n4/2024-06-size/blob/main/test/local/actions/Liquidate.t.sol#L163-L220
https://github.com/code-423n4/2024-06-size/blob/main/test/local/actions/Liquidate.t.sol#L184-L187

If using correctly converted one like below,

```solidity
uint256 debtInCollateralToken = size.debtTokenAmountToCollateralTokenAmount(futureValue);

uint256 liquidatorReward = Math.min(
  _state().bob.collateralTokenBalance - debtInCollateralToken,
Math.mulDivUp(debtInCollateralToken, size.feeConfig().liquidationRewardPercent, PERCENT)
);
```

It reverts at

```
assertEq(
  _after.liquidator.collateralTokenBalance,
  _before.liquidator.collateralTokenBalance + liquidatorProfitCollateralToken
);
```

## Recommended Mitigation Steps

The future value of the Debt position should be converted to collateral token unit when choosing proper liquidator reward.
As debtInCollateralToken already represents the value of debtposition in collateral token with proper decimal, it should be used instead of debtPosition.futureValue.

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


## Assessed type

Math
Incorrect validation to determine liquidatorReward 
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#L95-L99


# Vulnerability details

## Impact
When trying to determine liquidator value, a check of `Math.min` is made to determine smaller between two values, but this kind of direct check is incorrect, as first parameter passed to the min function is in eth tokens, and the second is in usdc, which means an invalid check will be performed with a wrong return value

## Proof of Concept
```solidity
if (assignedCollateral > debtInCollateralToken) {
            uint256 liquidatorReward = Math.min(
                assignedCollateral - debtInCollateralToken,
                Math.mulDivUp(debtPosition.futureValue, state.feeConfig.liquidationRewardPercent, PERCENT)
            );
```
first parameter passed to `Math::min` is in ETH and the second will be returned in usdc, leading to an invalid return.
## Tools Used
Manual Review
## Recommended Mitigation Steps
The result of Math.mulDivUp should be returned a an equivalent value of collateral token(ETH) to effect an equivalent check


## Assessed type

Invalid Validation
`Fees` is calculated wrongly in `AccountingLibrary.sol#getCreditAmountIn()` function.
Audit: 2024-06-size-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
When `sellCreditMarket` and `params.exactAmountIn == false`, it calculates `fees` wrongly.

## Proof of Concept
`AccountingLibrary.sol#getCashAmountOut()` function which calculates `cashAmountOut` and `fees` for input credit amount is as follows.
```solidity
    function getCashAmountOut(
        State storage state,
        uint256 creditAmountIn,
        uint256 maxCredit,
        uint256 ratePerTenor,
        uint256 tenor
    ) internal view returns (uint256 cashAmountOut, uint256 fees) {
192     uint256 maxCashAmountOut = Math.mulDivDown(creditAmountIn, PERCENT, PERCENT + ratePerTenor);

        if (creditAmountIn == maxCredit) {
            // no credit fractionalization

            fees = getSwapFee(state, maxCashAmountOut, tenor);

            if (fees > maxCashAmountOut) {
                revert Errors.NOT_ENOUGH_CASH(maxCashAmountOut, fees);
            }

            cashAmountOut = maxCashAmountOut - fees;
        } else if (creditAmountIn < maxCredit) {
            // credit fractionalization

207         fees = getSwapFee(state, maxCashAmountOut, tenor) + state.feeConfig.fragmentationFee;

            if (fees > maxCashAmountOut) {
                revert Errors.NOT_ENOUGH_CASH(maxCashAmountOut, fees);
            }

213         cashAmountOut = maxCashAmountOut - fees;
        } else {
            revert Errors.NOT_ENOUGH_CREDIT(creditAmountIn, maxCredit);
        }
    }
```
As we can see above, on L213, `cashAmountOut = creditAmountIn / (1 + ratePerTenor) * (1 - feePercent) - fragmentationFee` and `fees = creditAmountIn / (1 + ratePerTenor) * feePercent + fragmentationFee`.   

And `getCreditAmountIn()` function which calculates `credit` and `fees` for `cashAmountOut` is as follows.
```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;
249         fees = Math.mulDivUp(cashAmountOut, swapFeePercent, PERCENT);
        } else if (cashAmountOut < maxCashAmountOutFragmentation) {
            // credit fractionalization

253         creditAmountIn = Math.mulDivUp(
                cashAmountOut + s
Wrong calculation of `fees` in `SellCreditMarket` when `params.exactAmountIn == false` and `params.creditPositionId != RESERVED_ID`
Audit: 2024-06-size-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
In `SellCreditMarket`, when `params.exactAmountIn == false` and `params.creditPositionId != RESERVED_ID`, `params.amount` is used as `cashAmountOut` in `getCreditAmountIn` 

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

then `fees` is calculated based on `cashAmountOut` in `getCreditAmountIn`

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

The calculation of `fees` is wrong because `fees` should be calculated based on the amount of cash swapped (`cashAmountOut + fees`), in other words, the right formula for `fees` is `fees = ((cashAmountOut + state.feeConfig.fragmentationFee)) * swapFeePercent) / (1 - swapFeePercent)`

As a result, the protocol always loses `fees` when users sell credit using market order with `params.exactAmountIn == false`, `params.creditPositionId != RESERVED_ID`.


## Proof of Concept
```solidity
// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import {AccountingLibrary} from "@src/libraries/AccountingLibrary.sol";
import {State} from "@src/SizeStorage.sol";
import {Math, PERCENT, YEAR} from "@src/libraries/Math.sol";
import {Test, console} from "forge-std/Test.sol";

contract POC is Test {
    using AccountingLibrary for State;

    State state;
    function test() public {
        state.feeConfig.swapFeeAPR = 5e15;
        state.feeConfig.fragmentationFee = 5e6;
        uint256 ratePerTenor = 1e17;
        uint256 tenor = YEAR;
        uint256 nextCashAmountOut;
        uint256 maxCredit = 100e6;
        uint256 creditAmountIn = 22e6;

        {
            (uint256 cashAmountOut, uint256 fees) = state.getCashAmountOut({
                creditAmountIn: creditAmountIn,
                maxCredit: maxCredit,
                ratePerTenor: ratePerTenor,
                tenor: tenor
            });
            console.log("getCashAmountOut");
            console.log("creditAmountIn :", creditAmountIn);
            console.log("cashAmountOut: ", cashAmountOut);
            console.log("fees: ", fees);

            nextCashAmountOut = cashAmountOut;
        }


        {
            (uint256 creditAmountIn, uint256 fees) = state.getCreditAmountIn({
                cashAmountOut: nextCashAmountOut,
                maxCashAmountOut: Math.mulDivDown(maxCredit, PERCENT - state.getSwapFeePercent(tenor), PERCENT + ratePerTenor), 
                maxCredit: maxCredit,
                ratePerTenor: ratePerTenor,
                tenor: tenor
            });
            console.log("getCreditAmountIn");
            console.log("creditAmountIn: ", creditAmountIn);
            console.log("cashAmountOut: ", nextCashAmountOut);
            console.log("fees: ", fees);
        }
    }
Liquidator Reward is calculated wrong due to incorrect comparison- Liquidation.sol
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#L75-L126


# Vulnerability details


## Impact
Liquidator Reward is calculated wrong resulting to am unexpected amount of reward ofr the liquidator.

## Proof of Concept

In the `executeLiquidate` we are calculating liquidation reward.Inorder to do that we are first converting the `debt.futurevalue` to the unit of collateralToken so that we can compare it with assigned collateralToken to that debtPosition using `debtTokenAmountToCollateralTokenAmount()`



For finding out the minimum of remaining collateral and liquidationRewardPercent of the futureValue , the protocol is passing first parameter in the collateralToken unit and later  as the debtToken unit.



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

```solidity
    function executeLiquidate(State storage state, LiquidateParams calldata params)
        external
        returns (uint256 liquidatorProfitCollateralToken)
    {
        ....

        uint256 assignedCollateral = state.getDebtPositionAssignedCollateral(debtPosition);
        uint256 debtInCollateralToken = state.debtTokenAmountToCollateralTokenAmount(debtPosition.futureValue);
        uint256 protocolProfitCollateralToken = 0;


        // profitable liquidation
        if (assignedCollateral > debtInCollateralToken) {
            uint256 liquidatorReward = Math.min(
    =>           assignedCollateral - debtInCollateralToken, //collateralToken
    =>          Math.mulDivUp(debtPosition.futureValue, state.feeConfig.liquidationRewardPercent, PERCENT)//debtToken or borrowAtoken
            );
           ....
        } else {
            // unprofitable liquidation
            liquidatorProfitCollateralToken = assignedCollateral;
        }

        ....
    }
```

## Tools Used
Manual review
## Recommended Mitigation Steps
```solidity
if (assignedCollateral > debtInCollateralToken) {
            uint256 liquidatorReward = Math.min(
                assignedCollateral - debtInCollateralToken,
             Math.mulDivUp(debtInCollateralToken, state.feeConfig.liquidationRewardPercent, PERCENT)
            );

```





## Assessed type

Context
Pausing Prevents Closure and Liquidation of Outstanding Loan 
Audit: 2024-06-size-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-06-size/blob/8850e25fb088898e9cf86f9be1c401ad155bea86/src/Size.sol#L132
https://github.com/code-423n4/2024-06-size/blob/8850e25fb088898e9cf86f9be1c401ad155bea86/src/Size.sol#L210


# Vulnerability details

summary

When liquidations are paused, closing and liquidating loans becomes impossible. The issue with this is that the protocol could end up being stuck with a lot of bad debt

Impact
Outstanding loans cannot be closed or liquidated, freezing user funds and causing the protocol to take on bad debt

Tool used
Manual Review

Recommendation
Allow liquidations and loan closure when protocol is paused


## Assessed type

Context