xanoutas/solidity-security-findings huggingface dataset:
The flash loan fee is never accounted in `PoolV3::expectedLiquidity`, which may cause lenders to incur a loss
Audit: 2024-07-loopfi-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-07-loopfi/blob/57871f64bdea450c1f04c9a53dc1a78223719164/src/Flashlender.sol#L106
https://github.com/code-423n4/2024-07-loopfi/blob/57871f64bdea450c1f04c9a53dc1a78223719164/src/Flashlender.sol#L134
https://github.com/code-423n4/2024-07-loopfi/blob/57871f64bdea450c1f04c9a53dc1a78223719164/src/PoolV3.sol#L549


# Vulnerability details

## Impact
Due to never accounting for flashloan fee profits, the stakers that receive the distributed shares might steal from the lenders shares.

## Proof of Concept

When a borrowed amount is repaid, the protocol calculates the profit and mints shares for the treasury, then later the treasury will either distribute these shares to the LP stakers, dLP stakers and the DAO or burn the shares if the protocol incurs a loss.

The function `PoolV3::repayCreditAccount` handles the repayment of debt and minting the profit for the treasury.

```solidity
    function repayCreditAccount(
        uint256 repaidAmount,
        uint256 profit,
        uint256 loss
    )
        external
        override
        creditManagerOnly // U:[LP-2C]
        whenNotPaused // U:[LP-2A]
        nonReentrant // U:[LP-2B]
    {
        uint128 repaidAmountU128 = repaidAmount.toUint128();

        DebtParams storage cmDebt = _creditManagerDebt[msg.sender];
        uint128 cmBorrowed = cmDebt.borrowed;
        if (cmBorrowed == 0) {
            revert CallerNotCreditManagerException(); // U:[LP-2C,14A]
        }

        if (profit > 0) {
@>          _mint(treasury, convertToShares(profit)); // U:[LP-14B]
        } else if (loss > 0) {
            address treasury_ = treasury;
            uint256 sharesInTreasury = balanceOf(treasury_);
            uint256 sharesToBurn = convertToShares(loss);
            if (sharesToBurn > sharesInTreasury) {
                unchecked {
                    emit IncurUncoveredLoss({
                        creditManager: msg.sender,
                        loss: convertToAssets(sharesToBurn - sharesInTreasury)
                    }); // U:[LP-14D]
                }
                sharesToBurn = sharesInTreasury;
            }
            _burn(treasury_, sharesToBurn); // U:[LP-14C,14D]
        }

        _updateBaseInterest({
            expectedLiquidityDelta: -loss.toInt256(),
            availableLiquidityDelta: 0,
            checkOptimalBorrowing: false
        }); // U:[LP-14B,14C,14D]

        _totalDebt.borrowed -= repaidAmountU128; // U:[LP-14B,14C,14D]
        cmDebt.borrowed = cmBorrowed - repaidAmountU128; // U:[LP-14B,14C,14D]

        emit Repay(msg.sender, repaidAmount, profit, loss); // U:[LP-14B,14C,14D]
    }
```

The `PoolV3::_updateBaseInterest` is called on every deposit/withdraw/borrow/repayment to ensure that actual and expected amounts are correct.

```solidity
    function _updateBaseInterest(
        int256 expectedLiquidityDelta,
        int256 availableLiquidityDelta,
        bool checkOptimalBorrowing
    ) internal {
        uint
BalancerOracle.update() uses the hugely inaccurate totalSupply() function when calculating the balancer LP token price which will lead to an inaccurate price for all non-legacy pools
Audit: 2024-07-loopfi-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-07-loopfi/blob/57871f64bdea450c1f04c9a53dc1a78223719164/src/oracle/BalancerOracle.sol#L121


# Vulnerability details

## Vulnerability Details 
The `update()` function in the BalancerOracle contract is called by the keeper to update the safePrice and currentPrice of the specific Balancer Pool Token (BPT).

The issue is that when getting the totalSupply of BPT tokens in the pool, it uses `IWeightedPool(pool).totalSupply()` which is grossly inaccurate for all balancer pools except the legacy ones.

Calling `IWeightedPool(pool).totalSupply()` will always return `type(uint211).max` for all non legacy pools because they have pre-minted BPT.

The correct function to call to get the real totalSupply is `getActualSupply` because it accounts for pre-minted BPT as well as due protocol fees.

[Here is a link to the relevant balancer docs for proof](https://docs.balancer.fi/concepts/advanced/valuing-bpt/valuing-bpt.html#overview)

## Impact
A wrong `totalSupply()` causes the resulting BPT price to be totally wrong


## Recommended Mitigation Steps
Call the `getActualSupply` function instead

```diff

function update() external virtual onlyRole(KEEPER_ROLE) returns (uint256 safePrice_) {
        if (block.timestamp - lastUpdate < updateWaitWindow) revert BalancerOracle__update_InUpdateWaitWindow();
        // update the safe price first
        safePrice = safePrice_ = currentPrice;
        lastUpdate = block.timestamp;

        uint256[] memory weights = IWeightedPool(pool).getNormalizedWeights();
-       uint256 totalSupply = IWeightedPool(pool).totalSupply();
+       uint256 totalSupply = IWeightedPool(pool).getActualSupply();

        uint256 totalPi = WAD;
        uint256[] memory prices = new uint256[](weights.length);
        // update balances in 18 decimals
        for (uint256 i = 0; i < weights.length; i++) {
            // reverts if the price is invalid or stale
            prices[i] = _getTokenPrice(i);
            uint256 val = wdiv(prices[i], weights[i]);
            uint256 indivPi = uint256(wpow(int256(val), int256(weights[i])));

            totalPi = wmul(totalPi, indivPi);
        }

        currentPrice = wdiv(wmul(totalPi, IWeightedPool(pool).getInvariant()), totalSupply);
    }

```


## Assessed type

Other
Insufficient Repay Amount Handling in Liquidation Process Can Lead to Bad Debt (`CDPVault::liquidatePosition`)
Audit: 2024-07-loopfi-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-07-loopfi/blob/57871f64bdea450c1f04c9a53dc1a78223719164/src/CDPVault.sol#L509-L575
https://github.com/code-423n4/2024-07-loopfi/blob/57871f64bdea450c1f04c9a53dc1a78223719164/src/CDPVault.sol#L528-L532


# Vulnerability details

## Description

The `CDPVault` contract is designed to manage collateralized debt positions, allowing users to deposit collateral and borrow against it. The [`liquidatePosition()` function](https://github.com/code-423n4/2024-07-loopfi/blob/57871f64bdea450c1f04c9a53dc1a78223719164/src/CDPVault.sol#L509-L575) is responsible for liquidating positions that fall below the required collateralization ratio. However, there is a critical issue in the way this function handles the `repayAmount` provided by the liquidator.

The `liquidatePosition()` function calculates the amount of collateral to take (`takeCollateral`) based on the `repayAmount` and the `discountedPrice`. If the `repayAmount` is insufficient to cover the total debt, the function does not properly account for this, leading to an incorrect calculation of `takeCollateral`. This can result in the vault being left with bad debt, as the debt is not fully repaid.

The relevant part of the code is as follows:
[CDPVault.sol#L528-L532](https://github.com/code-423n4/2024-07-loopfi/blob/57871f64bdea450c1f04c9a53dc1a78223719164/src/CDPVault.sol#L528-L532)
```solidity
File: CDPVault.sol
509:     function liquidatePosition(address owner, uint256 repayAmount) external whenNotPaused {

    // ... existing code ...

528:         // compute collateral to take, debt to repay and penalty to pay
529:         uint256 takeCollateral = wdiv(repayAmount, discountedPrice);
530:         uint256 deltaDebt = wmul(repayAmount, liqConfig_.liquidationPenalty);
531:         uint256 penalty = wmul(repayAmount, WAD - liqConfig_.liquidationPenalty);
532:         if (takeCollateral > position.collateral) revert CDPVault__tooHighRepayAmount();

    // ... existing code ...
}
```

The function does not ensure that the `repayAmount` is sufficient to cover the total debt before calculating `takeCollateral`. This can lead to scenarios where the vault is left with bad debt, undermining the financial stability of the protocol.

## Impact
The incorrect handling of insufficient `repayAmount` in the `liquidatePosition()` function can lead to the vault accumulating bad debt. This can have significant financial implications, including potential insolvency and loss of funds for other users of the vault. 

## Proof of Concept
1. A user has a position with a total debt of 1000 units.
2. The user’s position falls below the required collateralization ratio and is eligible for liquidation.
3. A liquidator calls the `liquidatePosition()` function with a `repayAmount` of 800 units, which is insufficient to cover the total debt.
4. The function calculates `takeCollateral` based on the insufficient `repayAmount`, leading to an incorrect amount of collateral being taken.
5.
Incorrect Calculation of Liquidation Penalty and Debt Repayment Will Lead to Financial Discrepancies (`CDPVault::liquidatePosition`)
Audit: 2024-07-loopfi-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-07-loopfi/blob/57871f64bdea450c1f04c9a53dc1a78223719164/src/CDPVault.sol#L509-L574
https://github.com/code-423n4/2024-07-loopfi/blob/57871f64bdea450c1f04c9a53dc1a78223719164/src/CDPVault.sol#L530-L531


# Vulnerability details

 
## Description 

The `CDPVault` contract is designed to manage collateralized debt positions, allowing users to deposit collateral, borrow against it, and handle liquidations. The [`liquidatePosition()` function](https://github.com/code-423n4/2024-07-loopfi/blob/57871f64bdea450c1f04c9a53dc1a78223719164/src/CDPVault.sol#L509-L574) is responsible for liquidating unsafe positions by repaying debt and releasing collateral. However, there is a critical issue in the calculation of `deltaDebt` and `penalty` within this function.

The `deltaDebt` is intended to represent the amount of debt to be repaid, while the `penalty` is a fee applied during liquidation. The current implementation incorrectly calculates `deltaDebt` using the `liquidationPenalty` parameter, which should instead be used solely for calculating the penalty. This miscalculation can lead to incorrect debt repayment and collateral release, causing financial discrepancies.

> Specifically, the code snippet:
[CDPVault.sol#L530-L531](https://github.com/code-423n4/2024-07-loopfi/blob/57871f64bdea450c1f04c9a53dc1a78223719164/src/CDPVault.sol#L530-L531)
```solidity
File: CDPVault.sol
530:         uint256 deltaDebt = wmul(repayAmount, liqConfig_.liquidationPenalty);
531:         uint256 penalty = wmul(repayAmount, WAD - liqConfig_.liquidationPenalty);
```
incorrectly calculates `deltaDebt` and `penalty`. The correct approach should be:
```solidity
uint256 deltaDebt = repayAmount;
uint256 penalty = wmul(repayAmount, liqConfig_.liquidationPenalty);
```

This ensures that `deltaDebt` accurately represents the debt to be repaid, and the `penalty` is correctly calculated based on the `liquidationPenalty`.

## Impact
Incorrect calculations during the liquidation process can lead to financial losses for the protocol and its users. Specifically, users may end up repaying incorrect amounts of debt or receiving incorrect amounts of collateral, undermining the financial integrity of the protocol. 

## Proof of Concept
1. **User** has a debt of 100 units and a liquidation penalty of 10%.
2. **User** triggers the `liquidatePosition()` function with a `repayAmount` of 100 units.
3. **Current Code** calculates `deltaDebt` as 10 units (incorrect) and `penalty` as 90 units (incorrect).
4. **Correct Code** should calculate `deltaDebt` as 100 units (correct) and `penalty` as 10 units (correct).
5. The incorrect calculation leads to the user repaying only 10 units of debt instead of 100, causing financial discrepancies.

## Tools Used
Manual review

## Recommended Mitigation Steps
Update the `liquidatePosition()` function to correctly calculate `deltaDebt` and `penalty` as follows:

```solidity
function liquidatePosition(address owner, uin
Inconsistent bad debt check between liquidatePosition() and liquidatePositionBadDebt()
Audit: 2024-07-loopfi-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-07-loopfi/blob/57871f64bdea450c1f04c9a53dc1a78223719164/src/CDPVault.sol#L509-L527
https://github.com/code-423n4/2024-07-loopfi/blob/57871f64bdea450c1f04c9a53dc1a78223719164/src/CDPVault.sol#L579-L599


# Vulnerability details

## Impact
One position may be liquidated by function liquidatePosition() and function liquidatePositionBadDebt() at the same time. Considering that liquidators need to pay penalty to liquidate position via `liquidatePosition()`. Liquidators may call liquidatePositionBadDebt() to liquidate positions to avoid the penalty if the below condition matches.

## Proof of Concept
In liquidatePosition(), bad debt's definition is `calcTotalDebt(debtData) > wmul(position.collateral, spotPrice_)`.
In liquidatePositionBadDebt(), bad debt's definition is `calcTotalDebt(debtData) > wmul(position.collateral, discountedPrice)`.
The inconsistence will cause that one position can be taken as not bad debt in 
liquidatePosition() and be taken as bad debt in liquidatePositionBadDebt() if ` collateral * discountedPrice < totalDebt < collateral * spotPrice`.
```javascript
    function liquidatePosition(address owner, uint256 repayAmount) external whenNotPaused {
        ...
        if (calcTotalDebt(debtData) > wmul(position.collateral, spotPrice_)) revert CDPVault__BadDebt();
        ...
}
    function liquidatePositionBadDebt(address owner, uint256 repayAmount) external whenNotPaused {
    ...
    if (calcTotalDebt(debtData) <= wmul(position.collateral, discountedPrice)) revert CDPVault__noBadDebt();
    ...
    }
```


## Tools Used
Manual

## Recommended Mitigation Steps
Change the bad debt check in liquidatePosition() to match the bad debt check in liquidatePosition().
When the position is unhealthy and there is not bad debt, the position can only be liquidated by liquidatePosition().
When the position is bad debt, the position can only be liquidated by liquidatePositionBadDebt().


## Assessed type

Error
BalancerOracle.update() wrongly assumes that all of the weighted pools uses totalSupply
Audit: 2024-07-loopfi-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-07-loopfi/blob/57871f64bdea450c1f04c9a53dc1a78223719164/src/oracle/BalancerOracle.sol#L114-L136


# Vulnerability details

## Impact
The protocol uses the `totalSupply()` function to obtain the number of LP tokens in a Balancer weighted pool. However, this function does not always return the correct count, and the [Balancer documentation](https://docs.balancer.fi/concepts/advanced/valuing-bpt/valuing-bpt.html#getting-bpt-supply) recommends using `getActualSupply()` instead.

Impact is a high one since the calculation of Weighted Pool Token Price is very important for the protocol, and any miscalculations could hurt the protocol and the users a lot. Most recent pools used will return wrong pool token price.


## Proof of Concept

https://github.com/code-423n4/2024-07-loopfi/blob/57871f64bdea450c1f04c9a53dc1a78223719164/src/oracle/BalancerOracle.sol#L114-L136

```solidity
 function update() external virtual onlyRole(KEEPER_ROLE) returns (uint256 safePrice_) {
        if (block.timestamp - lastUpdate < updateWaitWindow) revert BalancerOracle__update_InUpdateWaitWindow();
        // update the safe price first
        safePrice = safePrice_ = currentPrice;
        lastUpdate = block.timestamp;

        uint256[] memory weights = IWeightedPool(pool).getNormalizedWeights();
        uint256 totalSupply = IWeightedPool(pool).totalSupply();    <-------@

        uint256 totalPi = WAD;
        uint256[] memory prices = new uint256[](weights.length);
        // update balances in 18 decimals
        for (uint256 i = 0; i < weights.length; i++) {
            // reverts if the price is invalid or stale
            prices[i] = _getTokenPrice(i);
            uint256 val = wdiv(prices[i], weights[i]);
            uint256 indivPi = uint256(wpow(int256(val), int256(weights[i])));

            totalPi = wmul(totalPi, indivPi);
        }

        currentPrice = wdiv(wmul(totalPi, IWeightedPool(pool).getInvariant()), totalSupply);
    }
```

As seen above, `BalancerOracle::update()` which is used specifically for weighted balancer pools wrongly uses `totalSupply` all the time to get the total supply of the pool, but this would not be true for newer weighted pools.

Balancer pools have different methods to get their total supply of minted LP tokens, which is also specified in the balancer documentation.

Reference link: https://docs.balancer.fi/concepts/advanced/valuing-bpt/valuing-bpt.html#getting-bpt-supply

The documentation states that `totalSupply` is only used for older stable and weighted pools, and should not be used without checking it first.

>totalSupply
>In general, totalSupply only makes sense to call for older "legacy" pools. The original Weighted and Stable Pools do not have >pre-minted BPT, so they follow the typical convention of using totalSupply to account for issued pool shares

since the newer pools have pre-minted BPT and getActualSupply should be used in that case.

>getActualSupply
>This is th
Partial liquidation leading to bad debts
Audit: 2024-07-loopfi-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-07-loopfi/blob/57871f64bdea450c1f04c9a53dc1a78223719164/src/CDPVault.sol#L529-L530


# Vulnerability details

## Impact
The ```discountPrice``` and ```penalty``` might lead to partial liquidation always leading to bad debt.


## Proof of Concept
Consider an Instance;
```
collateral value    =========== 1,200
debt value          =========== 1,100
repayAmount         =========== 900
discountPrice       =========== 0.9
penalty             =========== 5%
```
effects;
```
takeCollateral.    ============ 1,000
penalty            ============ 0.05 * 900 = 45
deltaDebt          ============ 900 - 45 = 855
remainingDebt      ============ 1,100 - 855 = 245
remaingCollateral  ============ 200
```
This shows that, the remaining debt of this position will be a bad debt.

## Tools Used
Manual review

## Recommended Mitigation Steps
Review how liquidator reward is calculated.

I will suggest that;
1.  ```liquidatePosition``` ensures that the remains debt is not a bad debt by validating remaining collateral to be more than debt, else only full liquidation should be allowed for such position.

2. To ensure the 1, liquidator reward should be a % of the remaining position collateral after debt value have been deducted. There should be a base for this so that penalty fee can also be deducted and incase of the remainder is low, all should be given to the liquidator to encourage liquidation of bad positions


## Assessed type

Math
The implementation of the LiquidatePosition function is not efficient and liquidators will earn above the slated 3-4% by the Docs.
Audit: 2024-07-loopfi-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-07-loopfi/blob/57871f64bdea450c1f04c9a53dc1a78223719164/src/CDPVault.sol#L530-L531
https://github.com/code-423n4/2024-07-loopfi/blob/57871f64bdea450c1f04c9a53dc1a78223719164/src/CDPVault.sol#L545-L557


# Vulnerability details

## Impact

The current implementation of the `LiquidatePosition` function is inefficient and can lead to liquidators earning more than the intended 3-4% profit margin stated in the documentation. The flow of the liquidation process causes liquidators to repay more debt than necessary, which results in them acquiring more collateral than they should, leading to higher profits and LESSER penalties than intended.

## Proof of Concept
In the `LiquidatePosition` function, the logic uses percentages to determine the amount of debt to repay and calculates the penalty based on these values. The issue arises from the following code snippet:

```solidity
uint256 deltaDebt = wmul(repayAmount, liqConfig_.liquidationPenalty);
uint256 penalty = wmul(repayAmount, WAD - liqConfig_.liquidationPenalty);
```

This approach can cause the liquidator to repay more than the actual debt available, leading to higher collateral acquisition and reduced penalties.

### Example

1. **Debt Floor**: 50 * 10**18
2. **Position Collateral**: 500 * 10**18
3. **Attempted Liquidation / RepayAmount**: 1015228426395939086295
4. **Spot Price**: $2.15
5. **Liquidation Ratio**: 1.2
6. **Discounted Price**: $2.064 (2.15 * (100 - 4) / 100)
7. **Debt**: 1000 * 10**18

In this example, the liquidator ends up repaying more than necessary, leading to an excessive penalty and collateral acquisition.

Eventhough debt is 1000 * 10**18. repayment has to be 1015228426395939086295 before all debt can be cleared. Note 1.5% as penalty.

Step 1. 

    takeCollateral = 1015228426395939086295/2.064 =  491,874,237,594,931,727,856.


    deltaDebt = 1015228426395939086295 * 98.5% = 
    1,000,000,000,000,000,000,000.575 = 1,000,000,000,000,000,000,000.  

 
     penalty = 1015228426395939086295 * 1.5 % =  15,228,426,395,939,086,294.


delta debt == debt, hence repayment is successful. But note

     Penalty =   15,228,426,395,939,086,294 / 1015228426395939086295  = 1.49%

     Collateral value taken = 491,874,237,594,931,727,856 * 2.15 = 
     1,057,529,610,829,103,214,890.

    profit = 1,057,529,610,829,103,214,890 - ( repayment ) 1015228426395939086295 
    = 42,301,184,433,164,128,595.4

    Percentage profit = 42,301,184,433,164,128,595.4 * 100 % / 
    1015228426395939086295 = 4.1667%




### Code Snippet

```solidity
function liquidatePosition(address owner, uint256 repayAmount) external whenNotPaused {
    // validate params
    if (owner == address(0) || repayAmount == 0) revert CDPVault__liquidatePosition_invalidParameters();

    // load configs
    VaultConfig memory config = vaultConfig;
    LiquidationConfig memory liqConfig_ = liquidationConfig;

    // load liquidated position
    Position memory position = position