xanoutas/solidity-security-findings huggingface dataset:
Errors in the calculation of the liquidator's reward result in the protocol not being able to correctly allocate the liquidation reward
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


# Vulnerability details

## Vulnerability details

When borrower's collateral value larger than debt value, collateral tokens should be used as units in the calculation of the liquidator's reward, and here a mistake was made: the value of debtpostion.futureValue was directly used to calculate the reward instead of converting it to debtInCollateralToken, which would result in the reward that should have belonged to the liquidator being heavily diluted to almost nothing due to the use of the wrong units for the calculation (debtToken decimals is 6), and as a result the protocolFee was also is affected to become larger than it should be.

## Impact

Liquidator unable to correctly obtain reward

## Proof of Concept

[https://github.com/code-423n4/2024-06-size/blob/main/src/libraries/actions/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)
            );
            liquidatorProfitCollateralToken = debtInCollateralToken + liquidatorReward;
```

Use the liquadation.t test case to help prove.

This test demonstrates that when a borrower's debt position meets the liquidation conditions, the liquidator executes the liquidation action and does not receive the rewards to which it is entitled.

bob has a debt position that is about to be liquidated, this is the borrower bob's userView before liquidate:

```
userView borrowATokenBalance: 115000000
userView collateralTokenBalance: 100000000000000000000
userView debt balance: 15527639
```

And this is the liquidator's user view:

```
userView borrowATokenBalance: 100000000
userView collateralTokenBalance: 100000000000000000000
userView debt balance: 0
```

At the point of the liquidation execution, bob's assignedCollateral value was the balance of all his collateral tokens `100e18`.
the debt amount should convert to collateral token amount `15527639 * 10 ** (18 - 6) * 1e18 / 0.2e18 (collateralPrice) = 77.63819499999999e18`.

Now start calculating the liquidation reward, Compare `100e18 - 77.63819499999999e18` with `debtPosition.futureValue * 5%` of values,
there's a clear attempt here to calculate liquidation profits based on the amount of collateral, but here the debt future value is used.

This results in a liquidatorReward of 776382 which is a very small value, and then as the value of the liquidatorProfitCollateralToken becomes smaller, the liquidation profit is calculated according to the amount of collateral, and the liquidatorProfitCollateralToken is calculated according to the amount of collateral. collateralRemainder becomes larger, which also causes the protocolProfitCollateralToken to beco
Liquidators are unfairly robbed of their liquidation reward
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-L100


# Vulnerability details

## Impact
Liquidators get robbed of the actual liquidation reward they should have got
## Proof of Concept
When liquidating a debt position, the liquidation reward is calculated in the following block of code:
```javascript
if (assignedCollateral > debtInCollateralToken) {
            uint256 liquidatorReward = Math.min(
                assignedCollateral - debtInCollateralToken,
                Math.mulDivUp(
                    debtPosition.futureValue,
                    state.feeConfig.liquidationRewardPercent,
                    PERCENT
                )
            );
            liquidatorProfitCollateralToken =
                debtInCollateralToken +
                liquidatorReward;
```
the `liquidatorReward` is equal to the smaller of 2 numbers. The first number is the result of the subtraction of 2 variables:`assignedCollateral` and `debtInCollateralToken`, both denominated in 18 decimals, because they represent some WETH amounts. The second number is the result of the following equation: `debtPosition.futureValue * state.feeConfig.liquidationRewardPercent / PERCENT`. The problem here is that the `debtPosition.futureValue` variable is denominated in 6 decimals because it represents USDC amount and the `liquidatorProfitCollateralToken` is payed in collateral token (WETH). This leads to `liquidatorReward` being equal to `debtPosition.futureValue * state.feeConfig.liquidationRewardPercent / PERCENT` basically every time when the position is over collateralised, because of the low decimals. This by itself is extremely bad for the liquidator, because he gets robbed of the actual liquidation reward he should have got, but it can lead to many "Below minimum collateral profit" liquidation reverts.
## Tools Used
Manual Review
## Recommended Mitigation Steps
Use the `debtInCollateralToken` variable in the place of `debtPosition.futureValue`, since it is the representation of `debtPosition.futureValue`, but in collateral token (WETH)


## Assessed type

Other
Liquidator would never see intended profit from liquidations, due to bad liquidatorReward calculation
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
Liquidator loss of funds or DoS(minimumCollateralProfit check fail), due to intended liquidatorReward is greatly reduced 

## Proof of Concept
When liquidator call `Size::liquidate`, after the validation, the function executes `Liquidate::executeLiquidate`. Trough the execution, the function checks if the liquidation is profitable. When calculating liquidatorReward in profitable scenario, the function choses the min of 2 values, second of which is not in the intended token. So, if it's profitable unexpectedly liquidatorReward will always be calculated as part of `debtPosition.futureValue`, which leads to liquidatorProfitCollateralToken always be much lower than actual due to 8 decimals in future value and 18 for the intended collateral token.
```solidity
function executeLiquidate(State storage state, LiquidateParams calldata params)
        external
        returns (uint256 liquidatorProfitCollateralToken)
    {
        DebtPosition storage debtPosition = state.getDebtPosition(params.debtPositionId);
        LoanStatus loanStatus = state.getLoanStatus(params.debtPositionId);
        uint256 collateralRatio = state.collateralRatio(debtPosition.borrower);

        emit Events.Liquidate(params.debtPositionId, params.minimumCollateralProfit, collateralRatio, loanStatus);

        // if the loan is both underwater and overdue, the protocol fee related to underwater liquidations takes precedence
        uint256 collateralProtocolPercent = state.isUserUnderwater(debtPosition.borrower)
            ? state.feeConfig.collateralProtocolPercent
            : state.feeConfig.overdueCollateralProtocolPercent;

        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,
                Math.mulDivUp(debtPosition.futureValue, state.feeConfig.liquidationRewardPercent, PERCENT)
            );
            liquidatorProfitCollateralToken = debtInCollateralToken + liquidatorReward;

            // split the remaining collateral between the protocol and the borrower, capped by the crLiquidation
            uint256 collateralRemainder = assignedCollateral - liquidatorProfitCollateralToken;

            // cap the collateral remainder to the liquidation collateral ratio
            //   otherwise, the split for non-underwater overdue loans could be too much
            uint256 collateralRemainderCap =
                Math.mulDivDown(debtInCollateralToken, state.riskConfig.crLiquidation, PERCENT);
Liquidator rewards are inflated.
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

## Impact
Liquidator rewards are inflated up to thousands times.
So the borrower is refunded none of collaterals and lost his/her all collaterals at every liquidation.

## Proof of Concept
`Liquidate.sol#executeLiquidate` function is the following.
```solidity
    function executeLiquidate(State storage state, LiquidateParams calldata params)
        external
        returns (uint256 liquidatorProfitCollateralToken)
    {
        DebtPosition storage debtPosition = state.getDebtPosition(params.debtPositionId);
        LoanStatus loanStatus = state.getLoanStatus(params.debtPositionId);
        uint256 collateralRatio = state.collateralRatio(debtPosition.borrower);

        emit Events.Liquidate(params.debtPositionId, params.minimumCollateralProfit, collateralRatio, loanStatus);

        // if the loan is both underwater and overdue, the protocol fee related to underwater liquidations takes precedence
        uint256 collateralProtocolPercent = state.isUserUnderwater(debtPosition.borrower)
            ? state.feeConfig.collateralProtocolPercent
            : state.feeConfig.overdueCollateralProtocolPercent;

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

        // profitable liquidation
        if (assignedCollateral > debtInCollateralToken) {
96:         uint256 liquidatorReward = Math.min(
97:             assignedCollateral - debtInCollateralToken,
98:             Math.mulDivUp(debtPosition.futureValue, state.feeConfig.liquidationRewardPercent, PERCENT)
99:         );
100:        liquidatorProfitCollateralToken = debtInCollateralToken + liquidatorReward;

            // split the remaining collateral between the protocol and the borrower, capped by the crLiquidation
103:        uint256 collateralRemainder = assignedCollateral - liquidatorProfitCollateralToken;

            // cap the collateral remainder to the liquidation collateral ratio
            //   otherwise, the split for non-underwater overdue loans could be too much
            uint256 collateralRemainderCap =
                Math.mulDivDown(debtInCollateralToken, state.riskConfig.crLiquidation, PERCENT);

            collateralRemainder = Math.min(collateralRemainder, collateralRemainderCap);

            protocolProfitCollateralToken = Math.mulDivDown(collateralRemainder, collateralProtocolPercent, PERCENT);
        } else {
            // unprofitable liquidation
            liquidatorProfitCollateralToken = assignedCollateral;
        }

        state.data.borrowAToken.transferFrom(msg.sender, address(this), debtPosition.futureValue);
        state.data.collateralToken.transferFrom(debtPosition.borrower, msg.sender, liquidatorProfitCollatera
During liquidation, the higher the collateral ratio is, the more the loss of borrower is. 
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#L112


# Vulnerability details

## Impact
During liquidation, the higher the collateral ratio is, the more the loss of borrower is. 
This weakens the incentive for borrowers to over-collateralize their debt positions.
This issue is especially more serious in the case of overdue liquidation.

## Proof of Concept
`Liquidate::executeLiquidate` charges liquidation protocol fee proportional to the remainder of collaterals.
```solidity
File: Liquidate.sol
    function executeLiquidate(State storage state, LiquidateParams calldata params)
        external
        returns (uint256 liquidatorProfitCollateralToken)
    {
        DebtPosition storage debtPosition = state.getDebtPosition(params.debtPositionId);
        LoanStatus loanStatus = state.getLoanStatus(params.debtPositionId);
        uint256 collateralRatio = state.collateralRatio(debtPosition.borrower);

        emit Events.Liquidate(params.debtPositionId, params.minimumCollateralProfit, collateralRatio, loanStatus);

        // if the loan is both underwater and overdue, the protocol fee related to underwater liquidations takes precedence
        uint256 collateralProtocolPercent = state.isUserUnderwater(debtPosition.borrower)
            ? state.feeConfig.collateralProtocolPercent
            : state.feeConfig.overdueCollateralProtocolPercent;

        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,
                Math.mulDivUp(debtPosition.futureValue, state.feeConfig.liquidationRewardPercent, PERCENT)
            );
            liquidatorProfitCollateralToken = debtInCollateralToken + liquidatorReward;

            // split the remaining collateral between the protocol and the borrower, capped by the crLiquidation
            uint256 collateralRemainder = assignedCollateral - liquidatorProfitCollateralToken;

            // cap the collateral remainder to the liquidation collateral ratio
            //   otherwise, the split for non-underwater overdue loans could be too much
            uint256 collateralRemainderCap =
                Math.mulDivDown(debtInCollateralToken, state.riskConfig.crLiquidation, PERCENT);

            collateralRemainder = Math.min(collateralRemainder, collateralRemainderCap);

@>          protocolProfitCollateralToken = Math.mulDivDown(collateralRemainder, collateralProtocolPercent, PERCENT);
        } else {
            // unprofitable liquidation
            liquidatorProfitCollateralToken = assignedCollateral;
        }

        state.data.borrowAToken.transferFrom(
Incorrect computation of liquidator's reward
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


# Vulnerability details

## Impact
The amount of decimals is not properly taken into consideration when computing the liquidator's reward.
Meaning the often, the liquidator, will be awarded less than what they should get.

## Proof of Concept
In the [liquidate](https://github.com/code-423n4/2024-06-size/blob/8850e25fb088898e9cf86f9be1c401ad155bea86/src/Size.sol#L210) function, the amount of collateral award to be sent to the liquidator (on top of the debt amount) is computed as follows:
```
        // profitable liquidation
        if (assignedCollateral > debtInCollateralToken) {
            uint256 liquidatorReward = Math.min(
                assignedCollateral - debtInCollateralToken,
                Math.mulDivUp(debtPosition.futureValue, state.feeConfig.liquidationRewardPercent, PERCENT)
            );
```
The issue is that *debtPosition.futureValue* is supposed to have 6 decimals, as it is derivated from usdc (See Audit's details, also the amount minted during deposit is equal the the amount received by Aave)
While the assigned *assignedCollateral* and the *debtInCollateralToke* will have 18 decimals, as they are derivated from ETH (See Audit's details, also the amount minted during deposit is the same as the wETH transferred).
Meaning that the second argument of the *Math.min* function, having 6 decimals, will almost always be smaller, and therefor selected as *liquidatorReward*. Which implies that the *liquidatorReward* will be computed on a 6 decimals numember, this number, when converted to *collateralToken*, will result in almost dust amount of rewards being sent to the liquidator.

Here is a script that shows the internal computations of liquidate and print the results, run it with the `-vv` option:
```
// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import {LiquidateParams} from "@src/libraries/actions/Liquidate.sol";
import {BaseTest} from "@test/BaseTest.sol";
import {Vars} from "@test/BaseTest.sol";


import "forge-std/console.sol";
import {PERCENT} from "@src/libraries/Math.sol";
import {LoanStatus, RESERVED_ID} from "@src/libraries/LoanLibrary.sol";
import {Math} from "@src/libraries/Math.sol";
import {PERCENT} from "@src/libraries/Math.sol";
import {YieldCurveHelper} from "@test/helpers/libraries/YieldCurveHelper.sol";

contract LiquidateTest is BaseTest {
    function test_Liquidate_with_wrong_decimals() 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 debtPositionId = _sellCreditMarket(bob, alice, RESERVED_ID, 15e6, 365 days, fals
Borrower Gets Immediately Liquidated After Repayments Resume
Audit: 2024-06-size-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Description

The Size protocol employs the `whenNotPaused` mechanism in `Size.sol` to ensure that if the protocol is paused for any reason, borrowers are unable to use any functionality of the protocol. This measure is in place to maintain security and prevent potential misuse during paused states.

## Impact

This pause mechanism is badly impact when a borrower takes out a loan before the protocol enters a paused state but is unable to repay during the pause, they face immediate liquidation once the protocol resumes.This situation unfairly disadvantages borrowers who are unable to repay during the pause period, leading to instant liquidation by automated bots. Consequently, borrowers lose their collateral, which often holds significantly more value than the repayment amount. This vulnerability exposes borrowers to undue risk and can result in substantial financial losses, undermining trust in the protocol's fairness and reliability.

## Code Snippet

Here is the code snippet

```javascript
function validateLiquidate(State storage state, LiquidateParams calldata params) external view {
        DebtPosition storage debtPosition =
       state.getDebtPosition(params.debtPositionId);
        // validate msg.sender
        // N/A
        // validate debtPositionId
        if (!state.isDebtPositionLiquidatable(params.debtPositionId)) {
            revert Errors.LOAN_NOT_LIQUIDATABLE(
                params.debtPositionId,
                state.collateralRatio(debtPosition.borrower),
                state.getLoanStatus(params.debtPositionId)
            );
        }

        // validate minimumCollateralProfit
        // N/A
    }

```

## Proof of Concept

1. Alice deposits 200 usdc into the protocol to mint BorrowAtoken.
2. Bob deposits 100 usdc and 100 weth into the protocol.
3. Liquidator deposits 100 weth and 200 usdc into the protocol.
4. Alice creates a loan offer with a 365-day term and a 3% interest rate to lend.
5. Bob calls the `buyCreditMarket` function to buy the loan offer created by alice.
6. The protocol is paused due to the USDC depeg event.
7. After the protocol is unpaused the liquidation bots would liquidate Bob's loan, making him lose his collateral.

**SetUp**:

Add the following function to the `BaseTest.sol`:

```javascript
function _getState(address user) internal view returns(UserView memory v) {
        return v = size.getUserView(user);
    }
```

Here is the commands to run the POC:

```bash
forge test --mt test_FortisAudits_Immediateliquidation_DueTo_PausedRepayments -vvvv
```

**Proof Of Code**:

```javascript
// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import {BaseTest} from "./BaseTest.sol";
import {YieldCurveHelper} from "@test/helpers/libraries/YieldCurveHelper.sol";
import {Pausable} from "@openzeppelin/contracts/utils/Pausable.sol
Incorrect fee 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#L169-L181
https://github.com/code-423n4/2024-06-size/blob/8850e25fb088898e9cf86f9be1c401ad155bea86/src/libraries/AccountingLibrary.sol#L245-L249


# Vulnerability details

## Impact

High, inacurate fee calculations(even the slightest) would impact protocol earnings in the long run

## Proof of Concept

For a given loan amount, $A$ , the amount claimable by the borrower $A_b$ is
&emsp; &emsp; $Protocol fee$ , $F = A * swapFeePercent$
&emsp; &emsp; $A_b = A - F = A(1 - swapFeePercent)$ &emsp; &emsp; ..1
And the Amount of credit sold $C$ is calculated as
&emsp; &emsp; $Interest$ , $I = A * ratePerTenor$
&emsp; &emsp; $Credit$ , $C = A + I =  A(1 + ratePerTenor)$ &emsp; &emsp; ..2
the relationship between $A_b$, the amount to the borrower and $C$ ,the credit of the position can de derived
&emsp; &emsp; From &emsp; ..1, &emsp; $A = A_b\frac{1}{(1 - swapFeePercent)}$
replacing $A$ in &emsp;..2
&emsp; &emsp; $C = A_b\frac{(1 + ratePerTenor)}{(1 - swapFeePercent)}$ &emsp; & &emsp; $A_b = C\frac{(1 - swapFeePercent)}{(1 + ratePerTenor)}$ &emsp; ..3

When a borrower takes a loan offer on the market, via `sellCreditMarket` with `params.exactAmountIn = false` indicating that the provided amount(`params.amount`) is the exact amount they want to recieve(i.e. $A_b$), for simplicity we're assuming a new loan is being created i.e `params.creditPositionId == RESERVED_ID`.
From [here](https://github.com/code-423n4/2024-06-size/blob/8850e25fb088898e9cf86f9be1c401ad155bea86/src/libraries/actions/SellCreditMarket.sol#L169-L181) we see that the `maxCredit` is calculated using ..3 ![Screenshot 2024-06-28 181620](https://gist.github.com/assets/133224432/1c07314d-cdfb-4490-8a65-b7cc7b3ed3ea)
However, in the `getCreditAmountIn` function as seen [here](https://github.com/code-423n4/2024-06-size/blob/8850e25fb088898e9cf86f9be1c401ad155bea86/src/libraries/AccountingLibrary.sol#L245-L249) the `cashAmountOut`(i.e $A_b$) is used to calculate the fee rather than $A$.
![Screenshot 2024-06-28 182114](https://gist.github.com/assets/133224432/f64b290f-c54d-4acd-8084-6bb5c81e6af2)

### Proof of Code

Add test to test/local/actions/SellCreditMarket.t.sol

```solidity
// /test/local/actions/SellCreditMarket.t.sol

import {UserView} from "@src/SizeViewData.sol";

 function test_swapFee_calculation() public {
        _deposit(alice, usdc, 200e6);
        _deposit(bob, weth, 100e18);
        _buyCreditLimit(alice, block.timestamp + 365 days, YieldCurveHelper.pointCurve(365 days, 0.03e18));

        uint256 loanAmount = 100e6;
        uint256 tenor = 365 days;
        //credit
        uint256 futureValue = Math.mulDivUp(loanAmount, (PERCENT + 0.03e18), PERCENT);
        uint256 protocolFee = size.getSwapFee(loanAmount, tenor);
        //cashAmountOut
        uint256 cashAmountOut = loanAmount - protocolFee;

        UserView memory feeRecipien