xanoutas/solidity-security-findings huggingface dataset:
`AuraVault::claim` reward calculation does not deduct fees from reward amount, causing DoS or extra rewards lost
Audit: 2024-07-loopfi-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-07-loopfi/blob/main/src/vendor/AuraVault.sol#L275-L310


# Vulnerability details

## Impact

`AuraVault::claim` allows users to claim rewards corresponding to the amount of `WETH` they are depositing in the same call.

Prior to sending rewards to `msg.sender`, a percentage of the rewards is sent to the `vault locker rewards`. However, the percentage of the rewards sent to the `vault locker rewards` is not deducted from the amount that is sent to the caller. The entire `reward amount` is sent to `msg.sender`. 

This is problematic, as it creates two possible scenarios:

1. Contract attempts to send more reward tokens than it holds, causing DoS
2. Contract successfully sends extra reward tokens, essentially stealing rewards from others

Therefore, the impact ranges from `stolen funds` to `Denial of Service`.

## Proof of Concept

As users interact with the `AuraVault` contract, the contract will accumulate rewards through interaction with an external [rewards contract](https://etherscan.io/address/0x2a14db8d09db0542f6a371c0cb308a768227d67d#code), which acts as an ERC-4626 vault.

Users can deposit, withdraw, redeem, and *claim* rewards:

[AuraVault.sol#L275-L310](https://github.com/code-423n4/2024-07-loopfi/blob/main/src/vendor/AuraVault.sol#L275-L310)
```javascript
    /**
     * @notice Allows anyone to claim accumulated rewards by depositing WETH instead
     * @param amounts An array of reward amounts to be claimed ordered as [rewardToken, secondaryRewardToken]
     * @param maxAmountIn The max amount of WETH to be sent to the Vault
     */
    function claim(uint256[] memory amounts, uint256 maxAmountIn) external returns (uint256 amountIn) {
        // Claim rewards from Aura reward pool
        IPool(rewardPool).getReward();

        // Compute assets amount to be sent to the Vault
        VaultConfig memory _config = vaultConfig;
        amountIn = _previewReward(amounts[0], amounts[1], _config);

        // Transfer assets to Vault
        require(amountIn <= maxAmountIn, "!Slippage");
        IERC20(asset()).safeTransferFrom(msg.sender, address(this), amountIn);

        // Compound assets into "asset" balance
        IERC20(asset()).safeApprove(rewardPool, amountIn);
        IPool(rewardPool).deposit(amountIn, address(this));

        // Distribute BAL rewards
@>      IERC20(BAL).safeTransfer(_config.lockerRewards, (amounts[0] * _config.lockerIncentive) / INCENTIVE_BASIS);
@>      IERC20(BAL).safeTransfer(msg.sender, amounts[0]);

        // Distribute AURA rewards
        if (block.timestamp <= INFLATION_PROTECTION_TIME) {
@>          IERC20(AURA).safeTransfer(_config.lockerRewards, (amounts[1] * _config.lockerIncentive) / INCENTIVE_BASIS);
@>          IERC20(AURA).safeTransfer(msg.sender, amounts[1]);
        } else {
            // after INFLATION_PROTECTION_TIME
            IERC20(AURA).safeTransfer(_config.lockerRewards, IERC20(AURA).balanceOf(address(this)));
        }

        emi
Liquidation discount incentivizes self-liquidations to avoid paying full debt, causing a loss for the protocol and lenders
Audit: 2024-07-loopfi-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-07-loopfi/blob/main/src/CDPVault.sol#L402-L426
https://github.com/code-423n4/2024-07-loopfi/blob/main/src/CDPVault.sol#L521-L532


# Vulnerability details

## Impact

`CDPVault` allows users to borrow `underlying` from `PoolV3` by depositing collateral into the vault.

Users must repay their debt fully via `CDPVault::repay`, and the amount must cover the entire `current total debt`, which also includes interest factors. If the value of their collateral divided by liquidatioRatio is less than the debt of their position, then their position is considered `unsafe` and anyone can `liquidate` the position by buying the collateral at a `discount`. The amount spent by the caller is used to cover for the debt.

The problem is that due to the discount, self-liquidating is cheaper for users than repaying their debt via `CDPVault::repay`, allowing them to avoid repaying their full debt. A user can intentionally make their position unsafe and they can self-liquidate for a lower price than simply repaying their debt at the borrowed price.  This will cause a loss for the protocol and lenders since rewards from lending their tokens are lost.

## Proof of Concept

The following block is executed when users repay their debt:

[CDPVault.sol#L402-L426](https://github.com/code-423n4/2024-07-loopfi/blob/main/src/CDPVault.sol#L402-L426)
```javascript
    } else if (deltaDebt < 0) {
@>      uint256 maxRepayment = calcTotalDebt(debtData);
        uint256 amount = abs(deltaDebt);
        if (amount >= maxRepayment) {
            amount = maxRepayment; // U:[CM-11]
            deltaDebt = -toInt256(maxRepayment);
        }

        poolUnderlying.safeTransferFrom(creditor, address(pool), amount);

        uint128 newCumulativeQuotaInterest;
        if (amount == maxRepayment) {
            newDebt = 0;
            newCumulativeIndex = debtData.cumulativeIndexNow;
            profit = debtData.accruedInterest;
            newCumulativeQuotaInterest = 0;
        } else {
            (newDebt, newCumulativeIndex, profit, newCumulativeQuotaInterest) = calcDecrease(
                amount, // delta debt
                position.debt,
                debtData.cumulativeIndexNow, // current cumulative base interest index in Ray
                position.cumulativeIndexLastUpdate,
                debtData.cumulativeQuotaInterest
            );
        }
```

For users to completely repay their loan, they must pay `maxRepayment` amount, which is calculated via a call to `calcTotalDebt`.

If the position is unsafe (collateral value / liquidation ratio > total debt), then anyone can liquidate it for a discount:

[CDPVault.sol#L521-L532](https://github.com/code-423n4/2024-07-loopfi/blob/main/src/CDPVault.sol#L521-L532)
```javascript
        // load price and calculate discounted price
        uint256 spotPrice_ = spotPrice();
@>      uint256 discountedPrice = wmul(spotPrice_, liqConfig_.liquidationDiscount);
        if (spotPrice_ ==
Liquidation doesn't account for penalty when calculating collateral to give, allowing users to profit by borrowing and self-liquidating
Audit: 2024-07-loopfi-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-07-loopfi/blob/main/src/CDPVault.sol#L402-L426
https://github.com/code-423n4/2024-07-loopfi/blob/main/src/CDPVault.sol#L521-L532
https://github.com/code-423n4/2024-07-loopfi/blob/main/src/CDPVault.sol#L503-L504
https://github.com/code-423n4/2024-07-loopfi/blob/main/src/CDPVault.sol#L538-L539
https://github.com/code-423n4/2024-07-loopfi/blob/main/src/CDPVault.sol#L567-L569
https://github.com/code-423n4/2024-07-loopfi/blob/main/src/CDPVault.sol#L530


# Vulnerability details

## Impact

`CDPVault` allows users to borrow `underlying` from `PoolV3` by depositing collateral into the vault, such that the `(collateral value of their position / liquidationRatio) >= their current total debt`.

Users must repay their debt fully via `CDPVault::repay`, and the amount must cover their entire `current total debt`, which also includes various interest factors. If the value of their collateral divided by liquidatioRatio is less than the debt of their position, then their position is considered `unsafe` and anyone can `liquidate` the position by buying the collateral at a `discount`. The amount spent by the caller is used to cover for the debt. 

To ensure that users cannot profit from self liquidations, the `liquidatePosition` function incorporates a penalty mechanism, that is intended to deduct fees from the payment amount, which subsequently goes to the protocol as profit.

The problem is that when the `liquidatePosition` function calculates the collateral to give to the caller, it utilizes the the repay amount *without* the penalty, essentially functioning as if there is no penalty mechanism at all. The caller can specify any `repay amount`, and the collateral they receive will correspond directly to `repay amount / discount`, with no penalty.

This allows malicious users to profit by `deposit collateral -> borrow WETH -> have their position become unsafe -> buy collateral with WETH at a discount`. Malicious users can profit and steal funds from lenders and the protocol.

> The natspec for the `CDPVault::liquidatePosition` states that "From that repay amount a penalty (`liquidationPenalty`) is subtracted to mitigate against profitable self liquidations." However, we will see in the PoC how this has no impact against profitable self liquidations

## Proof of Concept

The following block is executed when users repay their debt:

[CDPVault.sol#L402-L426](https://github.com/code-423n4/2024-07-loopfi/blob/main/src/CDPVault.sol#L402-L426)
```javascript
    } else if (deltaDebt < 0) {
@>      uint256 maxRepayment = calcTotalDebt(debtData);
        uint256 amount = abs(deltaDebt);
        if (amount >= maxRepayment) {
            amount = maxRepayment; // U:[CM-11]
            deltaDebt = -toInt256(maxRepayment);
        }

        poolUnderlying.safeTransferFrom(creditor, address(pool), amount);

        uint128 newCumulativeQuotaInterest;
        if (amount == maxRepayment) {
            newDebt = 0;
            n
`PoolV3::repayCreditAccount` doesn't take into account the `profit` when updating the `expected liquidity`, causing incorrect accounting
Audit: 2024-07-loopfi-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-07-loopfi/blob/main/src/PoolV3.sol#L529-L576
https://github.com/code-423n4/2024-07-loopfi/blob/main/src/PoolV3.sol#L642-L668
https://github.com/code-423n4/2024-07-loopfi/blob/main/src/Flashlender.sol#L87-L109


# Vulnerability details

## Impact

`PoolV3::lendCreditAccount` and `PoolV3::repayCreditAccount` are responsible for lending and repaying credit to users, which can only be called by `credit manager roles`, for example `Flashlender`, which is used for flash loans.

The problem is `PoolV3::repayCreditAccount` does not take into account the `profit` from lending the credit, which causes incorrect accounting for the `expected liquidity` when calling `_updateBaseInterest`.

This will impact several core functionalities of the `PoolV3` contract, such as `_baseInterestRate` (used for interest calculations), `totalAssets()`, `supplyRate()`, etc.

Due to a lower `expected liquidity` value than in actuality, in most cases this will be a loss to the protocol and lenders, where borrowers may benefit from lower interest rates.

In addition, `PoolV3` may not be `ERC4626` compliant due to calculations that utilize the `expected liquidity`, which will be incorrect (i.e, `totalAssets()` will not correctly reflect the total assets in the pool).

## Proof of Concept

`PoolV3::repayCreditAccount` is called to repay credit:

[PoolV3.sol#L529-L576](https://github.com/code-423n4/2024-07-loopfi/blob/main/src/PoolV3.sol#L529-L576)
```javascript
    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(), //@audit-issue doesn't take into account the profit
            availableLiquidityDelta: 0,
            checkOptimalBorrowing: f
In AuraVault, AccessControl is not initialized.
Audit: 2024-07-loopfi-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-07-loopfi/blob/57871f64bdea450c1f04c9a53dc1a78223719164/src/vendor/AuraVault.sol#L120


# Vulnerability details


## Impact
Due to the lack of permissions, the protocol cannot be configured, especially the parameters in VaultConfig, leading to the system being unable to operate properly.

## Proof of Concept
Although the protocol defines two types of roles:

bytes32 constant VAULT_ADMIN_ROLE = keccak256("VAULT_ADMIN_ROLE");

bytes32 constant VAULT_CONFIG_ROLE = keccak256("VAULT_CONFIG_ROLE");
these roles have never been assigned to anyone. From the code, we can see that the constructor does not initialize any permissions.
```javascript

contract AuraVault is IERC4626, ERC4626, AccessControl {
    constructor(
        address rewardPool_,
        address asset_,
        address feed_,
        address auraPriceOracle_,
        uint32 maxClaimerIncentive_,
        uint32 maxLockerIncentive_,
        string memory tokenName_,
        string memory tokenSymbol_
    ) ERC4626(IERC20(asset_)) ERC20(tokenName_, tokenSymbol_) {
        rewardPool = rewardPool_;
        feed = feed_;
        auraPriceOracle = auraPriceOracle_;
        maxClaimerIncentive = maxClaimerIncentive_;
        maxLockerIncentive = maxLockerIncentive_;
    }
}
```
Furthermore, the setParameter() and setVaultConfig() functions require the corresponding permissions to be called, which leads to the protocol being unable to be configured correctly.

## Tools Used
Manual Review

## Recommended Mitigation Steps
``` diff
constructor(
        address rewardPool_,
        address asset_,
        address feed_,
        address auraPriceOracle_,
        uint32 maxClaimerIncentive_,
        uint32 maxLockerIncentive_,
        string memory tokenName_,
        string memory tokenSymbol_
    ) ERC4626(IERC20(asset_)) ERC20(tokenName_, tokenSymbol_) {
        rewardPool = rewardPool_;
        feed = feed_;
        auraPriceOracle = auraPriceOracle_;
        maxClaimerIncentive = maxClaimerIncentive_;
        maxLockerIncentive = maxLockerIncentive_;
+        _setRoleAdmin(VAULT_ADMIN_ROLE, DEFAULT_ADMIN_ROLE);
+        _setRoleAdmin(VAULT_CONFIG_ROLE, DEFAULT_ADMIN_ROLE);

+        _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
+        _grantRole(VAULT_ADMIN_ROLE, msg.sender);
+        _grantRole(VAULT_CONFIG_ROLE, msg.sender);
    }
```


## Assessed type

Access Control
`CDPVault` did not handle external rewards from `PENDLE-LP`
Audit: 2024-07-loopfi-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details


`CDPVault` hold **PendleLP** tokens (e.g: _0x464F5A15Aca6Fe787Cf54fCF1E8AF6207939d297_, _0xb9e8bb1105382b018c6adfd95fd9272542cc1776_) are `PendleMarketV3` contracts.
All of these token have external rewards, mostly $Pendle token.

Rewards are split evenly between user holding `PENDLE-LP` token.

CDPVault hold token belong to user, also hold potential rewards belong to user.

## Impact

If there is any rewarder send PENDLE token to PendleLP contract, `CDPVault` holding LP token belong to user should be able claim rewards belong to user.
Currently, `CDPVault` do not handle this. So any rewards token will simply stuck inside `CDPVault`.

Causing user loss on potential rewards.

## Proof of Concept

_Already tested with Foundry fork. Request BackStage proof on demand._

`PendleMarket` check and accrued rewards for all user on each transfer operation.
<https://github.com/pendle-finance/pendle-core-v2-public/blob/e6184df2d37be670bd058651e68bd98e2723411a/contracts/core/Market/PendleMarket.sol#L334-L347>

Rewards are split evenly between all users holding `PendleMarket`/PENDLE-LP token.
<https://github.com/pendle-finance/pendle-core-v2-public/blob/e6184df2d37be670bd058651e68bd98e2723411a/contracts/core/RewardManager/RewardManager.sol#L16-L59>

If any rewarder send PENDLE token to PendleMarket. All user holding Pendle-LP are eligible to receive rewards.

Looking through all `CDPVault` contracts and its dependencies.
`CDPVault` is not designed to handle token that have internal rewards like Pendle-LP

There is no admin control to handle random token.
There is no function to claim accrued rewards before send back to user.

## Tools Used

## Recommended Mitigation Steps

It look like Pendle reward is received from Gauge controller.
<https://etherscan.io/address/0x47d74516b33ed5d70dde7119a40839f6fcc24e57#readProxyContract>

Because it is unclear when Pendle rewards will be send, the fastest way to handle this admin control swap external rewards to treasury. Then compensate user with higher interest rate for lender.



## Assessed type

Other
No Accesscontrol role is granted on AuraVault.sol thereby cause loss of asset and prevent calling `setVaultConfig` and `setParameter` functions.  
Audit: 2024-07-loopfi-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-07-loopfi/blob/57871f64bdea450c1f04c9a53dc1a78223719164/src/vendor/AuraVault.sol#L26
https://github.com/code-423n4/2024-07-loopfi/blob/main/src/vendor/AuraVault.sol#L120-L135
https://github.com/code-423n4/2024-07-loopfi/blob/main/src/vendor/AuraVault.sol#L145-L169


# Vulnerability details

## Impact
1. BAL and AURA reward tokens that are supposed to be transferred to `_config.lockerRewards` will be locked.
2. Denial of service on the `claim` function after the `INFLATION_PROTECTION_TIME`

## Proof of Concept
The AuraVault.sol inherits from Openzeppelin's AccessControl contract but the AuraVault.sol did not invoke the `_grantRole(...)` function in its constructor to grant the roles most especially the `DEFAULT_ADMIN_ROLE`. 

Not granting the roles will render the `setVaultConfig` and `setParameter` functions of AuraVault.sol useless since the two functions have access control modifiers that require roles which are not granted to any address.

```solidity
File: AuraVault.sol
function setParameter(bytes32 parameter, uint256 data) external onlyRole(VAULT_CONFIG_ROLE) {
        if (parameter == "feed") feed = address(uint160(data));
        else if (parameter == "auraPriceOracle") auraPriceOracle = address(uint160(data));
        else revert AuraVault__setParameter_unrecognizedParameter();
        emit SetParameter(parameter, data);
    }

    function setVaultConfig(
        uint32 _claimerIncentive,
        uint32 _lockerIncentive,
        address _lockerRewards
    ) public onlyRole(VAULT_ADMIN_ROLE) returns (bool) {
        if (_claimerIncentive > maxClaimerIncentive) revert AuraVault__setVaultConfig_invalidClaimerIncentive();
        if (_lockerIncentive > maxLockerIncentive) revert AuraVault__setVaultConfig_invalidLockerIncentive();
        if (_lockerRewards == address(0x0)) revert AuraVault__setVaultConfig_invalidLockerRewards();

        vaultConfig = VaultConfig({
            claimerIncentive: _claimerIncentive,
            lockerIncentive: _lockerIncentive,
            lockerRewards: _lockerRewards
        });

        return true;
    }
```
The vault configs cannot be set with the setVaultConfig(...) and the `setParameter(...)` functions due to the `onlyRole(VAULT_ADMIN_ROLE)` and `onlyRole(VAULT_CONFIG_ROLE)` access control modifiers.

This will try to transfer `AURA` token on line `306` to zero address. This will revert because AURA tokens cannot be transferred to zero address. This will result in denial of service of the `claim` function.

```solidity
File: AuraVault.sol
function claim(uint256[] memory amounts, uint256 maxAmountIn) external returns (uint256 amountIn) {
        // Claim rewards from Aura reward pool
        IPool(rewardPool).getReward();

        // Compute assets amount to be sent to the Vault
        VaultConfig memory _config = vaultConfig;
        amountIn = _previewReward(amounts[0], amounts[1], _config);

        // Transfer assets to Vault
        require(amount
Incorrect Supply Rate Calculation in CDPVault
Audit: 2024-07-loopfi-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-07-loopfi/blob/57871f64bdea450c1f04c9a53dc1a78223719164/src/PoolV3.sol#L603
https://github.com/code-423n4/2024-07-loopfi/blob/57871f64bdea450c1f04c9a53dc1a78223719164/src/PoolV3.sol#L610


# Vulnerability details

## Impact
The `supplyRate()` function calculates the interest rate for depositors using `expectedLiquidity()`, which includes both the principal and the accrued interest the function is supposed to calculate Annual interest rate in ray that liquidity providers receive per unit of deposited capital(principal ONLY) . This results in an inaccurate supply rate, The correct calculation should use the sum of availableLiquidity() and _totalDebt.borrowed which is a true picture of total deposited capital (principal Only) to accurately reflect the total amount of capital deposited.

## Proof of Concept
The current implementation of the supplyRate() function is as follows:
```
/// @notice Annual interest rate in ray that liquidity providers receive per unit of deposited capital,
    ///         consists of base interest and quota revenue,
    /// @dev This function expects all liquidity providers to be staked.
function supplyRate() external view override returns (uint256) {
    uint256 assets = expectedLiquidity();
    uint256 baseInterestRate_ = baseInterestRate();
    if (assets == 0) return baseInterestRate_;
    return
        ((baseInterestRate_ * _totalDebt.borrowed) * (PERCENTAGE_FACTOR - withdrawFee)) /
        PERCENTAGE_FACTOR /
        assets; // U:[LP-15]
}
```
In this implementation, assets is set to expectedLiquidity(), which includes both the principal and the accrued as so;
```
function expectedLiquidity() public view override returns (uint256) {
        return _expectedLiquidityLU + _calcBaseInterestAccrued() + _calcQuotaRevenueAccrued(); // U:[LP-4]
    }
```
This can lead to an inflated assets value, resulting in a lower supply rate than what depositors should actually receive. The actual amount of capital deposited by users can be represented by 
```
uint256 assets = availableLiquidity() + _totaldebt.borrow;

```
where _totaldebt.borrow is the total deposited amount borrowed out and availableLiquidity() is the amount deposited my users left in the contract (excluding intrest)
To demonstrate:

- Assume availableLiquidity = 1000 ETH
- _totaldebt.borrow = 500 ETH
- Accrued interest = 50 ETH
- Current calculation: assets = 1550 ETH (includes interest) Correct calculation: assets = 1500 ETH (excludes interest)

This leads to an artificially inflated supply rate, as the denominator in the rate calculation is larger than it should be.

## Tools Used
Manual

## Recommended Mitigation Steps
```
function supplyRate() external view override returns (uint256) {
  +  uint256 assets = availableLiquidity() + _totalDebt.borrowed;
    uint256 baseInterestRate_ = baseInterestRate();
    if (totalDepositedCapital == 0) return baseInterestRate_;
    return
        ((baseInterestRate_ * _totalDeb