xanoutas/solidity-security-findings huggingface dataset:
`VAULT_ADMIN_ROLE` and `VAULT_CONFIG_ROLE` not granted in AuraVault.sol 
Audit: 2024-07-loopfi-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

### Impact

The Admin and vault config roles are not granted upon contract initialization, and as a result the two functions that can only be called by these admins will be inaccessible.

### Proof of Concept

In AuraVault.sol, the vault admin and vault config roles are declared.

```solidity
bytes32 constant VAULT_ADMIN_ROLE = keccak256("VAULT_ADMIN_ROLE");

bytes32 constant VAULT_CONFIG_ROLE = keccak256("VAULT_CONFIG_ROLE");
```

And the contract also imports openzeppelin AccessControl.sol contract.
```solidity
contract AuraVault is IERC4626, ERC4626, AccessControl {
```

But upon deployment, in the constructor, the roles are not declared.

```solidity
    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_;
    }
```

And as a result, the default admin is address 0

```solidity
    bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
```
And the vault admin and vault config roles' admin will also be address 0 since its not declared.
```solidity
    function getRoleAdmin(bytes32 role) public view virtual override returns (bytes32) {
        return _roles[role].adminRole;
    }
```

As a result the roles can not be granted later
```solidity
    function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
        _grantRole(role, account);
    }
```
And therefore, the functions `setParameter` and `setVaultConfig` will be unavailable.
```solidity
    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({
            claimerIncen
Wrong formula for BalancerOracle price calculation
Audit: 2024-07-loopfi-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
The BalancerOracle uses the wrong formula to calculate the LP token's price. It prices the token higher than it should, allowing the user to borrow more than they should.

## Proof of Concept
The oracle calculates the price as:

$totalPi = \prod_{n} (price_n / weight_n)^{weight_n}$
$price = totalPi * invariant / totalSupply$

```sol
    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);
    }
```

But, the price of the LP token is calculated using:

$totalLiqudity = \sum_{n} balance_n * price_n$
$price = totalLiquidity / totalSupply$

As described here: https://docs.balancer.fi/reference/lp-tokens/valuing.html#directly-calculating-nav

## Tools Used
none

## Recommended Mitigation Steps
Use the correct formula to calculate the BPT price


## Assessed type

Oracle
Roles not set
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-L135


# Vulnerability details

## Impact
In `AuraVault` the roles `VAULT_ADMIN_ROLE`, `VAULT_CONFIG_ROLE` and `DEFAULT_ADMIN_ROLE` are never set. This makes all functions made specifically for these roles (`setParameter`, `setVaultConfig`) uncallable breaking core functionality of the contract.
For reference, in `VaultRegistry` roles are set.

## Proof of Concept
OpenZeppelin's AccessControl library requires you to setup the roles you use or else they are not usable. Setting them up looks like this:

```solidity
constructor() {
	_setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
}
```

## Tools Used
Manual Review

## Recommended Mitigation Steps
Consider setting the required roles as shown in the Proof of Concept. For reference `VaultRegistry` can also be used.



## Assessed type

Invalid Validation
Upgraded Q -> 2 from #541 [1730295620971]
Audit: 2024-07-loopfi-findings
Severity: MEDIUM

Judge has assessed an item in Issue #541 as 2 risk. The relevant finding follows:

 [L-02] Min-max answer not checked in ChainlinkOracle.sol
Chainlink aggregators have a built in circuit breaker if the price of an asset goes outside of a predetermined price band. The result is that if an asset experiences a huge drop in value (i.e. LUNA crash) the price of the oracle will continue to return the minPrice instead of the actual price of the asset. This would allow user to continue borrowing with the asset but at the wrong price. This is exactly what happened to Venus on BSC when LUNA imploded.

ChainlinkFeedRegistry#latestRoundData pulls the associated aggregator and requests round data from it. ChainlinkAggregators have minPrice and maxPrice circuit breakers built into them. This means that if the price of the asset drops below the minPrice, the protocol will continue to value the token at minPrice instead of it’s actual value. This will allow users to take out huge amounts of bad debt and bankrupt the protocol.

Example: TokenA has a minPrice of $1. The price of TokenA drops to $0.10. The aggregator still returns $1 allowing the user to borrow against TokenA as if it is $1 which is 10x it’s actual value.

ChainlinkAdapterOracle should check the returned answer against the minPrice/maxPrice and revert if the answer is outside of the bounds:

    (, int256 answer, , uint256 updatedAt, ) = registry.latestRoundData(
        token,
        USD
    );
+   if (answer >= maxPrice or answer <= minPrice) revert();
Upgraded Q -> 2 from #90 [1730234995957]
Audit: 2024-07-loopfi-findings
Severity: MEDIUM

Judge has assessed an item in Issue #90 as 2 risk. The relevant finding follows:

 [L-22] PositionAction20/PositionAction4626/PositionActionPendle onWithdraw don’t use correct token scale
Bug Description
The token scale is incorrect for all onWithdraw() functions. The scale used for CDPVault.withdraw(position, amount) should be tokenScale, and the return value is WAD.

However, all use cases got them reversed, and they assume CDPVault.withdraw(position, amount) should be WAD, and the return value is tokenScale.

This would be an issue if tokenScale != 1e18.

PositionAction20.sol

    /// @param amount Amount of collateral to withdraw [wad]
    /// @return Amount of collateral withdrawn [CDPVault.tokenScale()]
    function _onWithdraw(address vault, address position, address /*dst*/, uint256 amount) internal override returns (uint256) {
        return ICDPVault(vault).withdraw(position, amount);
    }
PositionAction4626.sol

    /// @param amount Amount of collateral to withdraw [wad]
    /// @return Amount of collateral withdrawn [CDPVault.tokenScale()]
    function _onWithdraw(address vault, address /*position*/, address dst, uint256 amount) internal override returns (uint256) {
        uint256 collateralWithdrawn = ICDPVault(vault).withdraw(address(this), amount);

        // if collateral is not the dst token, we need to withdraw the underlying from the ERC4626 vault
        address collateral = address(ICDPVault(vault).token());
        if (dst != collateral) {
            collateralWithdrawn = IERC4626(collateral).redeem(collateralWithdrawn, address(this), address(this));
        }

        return collateralWithdrawn;
    }
    /// @param amount Amount of collateral to withdraw [wad]
    /// @return Amount of collateral withdrawn [CDPVault.tokenScale()]
    function _onWithdraw(address vault, address position, address /*dst*/, uint256 amount) internal override returns (uint256) {
        return ICDPVault(vault).withdraw(address(position), amount);
    }
Code Snippet
Upgraded Q -> 2 from #90 [1730070852611]
Audit: 2024-07-loopfi-findings
Severity: MEDIUM

Judge has assessed an item in Issue #90 as 2 risk. The relevant finding follows:

 [L-12] ChefIncentivesController.claim() does not update price before checking eligibility
Bug Description
The eligibility of claiming LOOP rewards is related to the oracle price of the borrowed debt and lpETH-LOOP LP. eligibleDataProvider.updatePrice(); should be called before claiming to make sure the price is up to date, since eligibleDataProvider.isEligibleForRewards() does not update the price.

    function claim(address _user, address[] memory _tokens) public whenNotPaused {
        ...
        if (eligibilityMode != EligibilityModes.DISABLED) {
            if (!eligibleDataProvider.isEligibleForRewards(_user)) revert EligibleRequired();
            checkAndProcessEligibility(_user, true, true);
        }
    }
Bringing a position from unsafe to safe by liquidation paritally
Audit: 2024-07-loopfi-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details


## Impact

The CDPVault's liquidation mechanism allows partial liquidations to temporarily bring unsafe positions back to a safe state. This can delay necessary liquidations if collateral prices continue to fall, potentially leading to increased risk and larger losses for the protocol. 

## Proof of Concept

The issue arises from `liquidatePosition` function in the CDPVault contract. An attacker can exploit this to avoid full liquidation even when their position should be unsafe.

**Break down the Scenario:**
- Alice creates a position with 100 ether collateral and 60 ether debt.
- The collateral price drops, making Alice's position unsafe.
- Bob performs a partial liquidation on Alice's position.
- Alice's position becomes temporarily safe due to the partial liquidation.
- The collateral price drops further, but a small liquidation attempt by Carol fails because the position is still considered safe.
- Only after an even further price drop can the position be liquidated again.

Alice can do the same flow to herself (self liqudation) to protect her position instead of maintaining it by increasing the colleteral, which costs less and goes against how the protocol is intended to work.

[at function liquidatePosition](https://github.com/code-423n4/2024-07-loopfi/blob/57871f64bdea450c1f04c9a53dc1a78223719164/src/CDPVault.sol#L509)

```solidity
function liquidatePosition(address owner, uint256 repayAmount) external whenNotPaused {
    // ... (validation and state loading)
    if (_isCollateralized(calcTotalDebt(debtData), wmul(position.collateral, spotPrice_), config.liquidationRatio))
        revert CDPVault__liquidatePosition_notUnsafe();
    // ... (liquidation calculations)
    position = _modifyPosition(owner, position, newDebt, newCumulativeIndex, -toInt256(takeCollateral), totalDebt);
    // ... (state updates and transfers)
}
```

The issue occurs because after a partial liquidation, the position can become temporarily safe, preventing further immediate liquidations even if the collateral value continues to decrease.

## Tools Used

Manual Review

## Recommended Mitigation Steps


Flag positions as unsafe when they become unsafe, and revert them to safe status upon additional collateral deposits. However, this approach is suboptimal. A comprehensive reevaluation of the liquidation mechanism is necessary.


## Assessed type

Other
Upgraded Q -> 2 from #540 [1728827786267]
Audit: 2024-07-loopfi-findings
Severity: MEDIUM

Judge has assessed an item in Issue #540 as 2 risk. The relevant finding follows:

 QA-11 No checks for min/max circuit breakers while querying chainlink data
Proof of Concept
Take a look at the functionality used for getting prices from Chainlink https://github.com/code-423n4/2024-07-loopfi/blob/57871f64bdea450c1f04c9a53dc1a78223719164/src/oracle/ChainlinkOracle.sol#L87-L110

function spot(address token) external view virtual override returns (uint256 price) {
  bool isValid;
  (isValid, price) = _fetchAndValidate(token);
  if (!isValid) revert ChainlinkOracle__spot_invalidValue();
}

function _fetchAndValidate(address token) internal view returns (bool isValid, uint256 price) {
  Oracle memory oracle = oracles[token];
  try AggregatorV3Interface(oracle.aggregator).latestRoundData() returns (
    uint80, /*roundId*/
    int256 answer,
    uint256, /*startedAt*/
    uint256 updatedAt,
    uint80 /*answeredInRound*/
  ) {
    isValid = (answer > 0 && block.timestamp - updatedAt <= oracle.stalePeriod);
    return (isValid, wdiv(uint256(answer), oracle.aggregatorScale));
  } catch {
    // return the default values (false, 0) on failure
  }
}

Would be key to note that Chainlink’s latestRoundData is being queried, but no min/max checks are applied, leading to contracts working with flawed pricing.

Would also be key to note that the price returned from this is directly used to check the accounts status before liquidations.

Impact
If the prices ever go over the min/max boundary then protocol is going to ingest heavily inflated/deflated pricing data as the prices returned would be wrong.

Which in our case would mean that an account that could be unsafe would be assumed to be safe when the price being reported is at the min boundary and way higher than the real price, which means this has quite a high impact with a very low likelihood, but would be key to note that this has happened before with LUNA.

Recommended Mitigation Steps
Consider attaching the min/max checkers for aggregators that have it
Upgraded Q -> 2 from #540 [1728827643729]
Audit: 2024-07-loopfi-findings
Severity: MEDIUM

Judge has assessed an item in Issue #540 as 2 risk. The relevant finding follows:

 QA-16 Lack of slippage protection in AuraVault's mint/deposit
Proof of Concept
Take a look at https://github.com/code-423n4/2024-07-loopfi/blob/57871f64bdea450c1f04c9a53dc1a78223719164/src/vendor/AuraVault.sol#L214-L222

function deposit(uint256 assets, address receiver)
  public
  virtual
  override(IERC4626, ERC4626)
  returns (uint256)
{
  uint256 shares = previewDeposit(assets);
  _deposit(_msgSender(), receiver, assets, shares);

  // Deposit  in reward pool
  IERC20(asset()).safeApprove(rewardPool, assets);
  IPool(rewardPool).deposit(assets, address(this));

  return shares;
}

function mint(uint256 shares, address receiver)
  public
  virtual
  override(IERC4626, ERC4626)
  returns (uint256)
{
  uint256 assets = previewMint(shares);
  _deposit(_msgSender(), receiver, assets, shares);

  // Deposit assets in reward pool
  IERC20(asset()).safeApprove(rewardPool, assets);
  IPool(rewardPool).deposit(assets, address(this));

  return assets;
}

Both functions help with minting vault shares to the receiver, however there are no slippage protection provided, leaving the depositor exposed to front/run lack of slippage risks

Impact
The lack of slippage protection in the mint and deposit functions could lead to front-running attacks & unexpected losses for users.

Recommended Mitigation Steps
Implement a slippage parameter in the mint and deposit functions.
Upgraded Q -> 2 from #529 [1727788947408]
Audit: 2024-07-loopfi-findings
Severity: MEDIUM

Judge has assessed an item in Issue #529 as 2 risk. The relevant finding follows:

 [L-10] TITLE:Unauthorized Access to Deposit and Withdraw Functions When Contract is Paused

IMPACT:The deposit and withdraw functions are designed to be restricted during a paused state to prevent alterations to collateral and debt balances, safeguarding against unintended changes. However, the modifyCollateralAndDebt function, which underpins these operations, lacks the necessary whenNotPaused modifier, allowing direct modifications even when the contract is paused, undermining pause effectiveness.

VULNERABILITY DETAIL: Despite deposit and withdraw functions being correctly restricted during pauses via the whenNotPaused modifier, modifyCollateralAndDebt, accessible publicly, permits bypassing these restrictions, enabling unauthorized transactions.

RECOMMENDATION: Incorporate whenNotPaused in modifyCollateralAndDebt to align with security protocols.

LINK:https://github.com/code-423n4/2024-07-loopfi/blob/57871f64bdea450c1f04c9a53dc1a78223719164/src/CDPVault.sol#L367
Upgraded Q -> 2 from #529 [1727788877930]
Audit: 2024-07-loopfi-findings
Severity: MEDIUM

Judge has assessed an item in Issue #529 as 2 risk. The relevant finding follows:

 [L-8] TITLE:Incorrect Use of convertToShares Instead of Custom _convertToShares in repayCreditAccount Function

IMPACT: Using convertToShares from OpenZeppelin’s ERC4626 contract instead of a custom _convertToShares function defined on the contract can lead to incorrect calculations of the number of shares to be minted or burned in the repayCreditAccount function. This can result in an inaccurate number of shares being minted or burned.

VULNERABILITY DETAIL: The PoolV3 contract overrides the totalAssets() function to include not just the current assets in the contract but also unrealized potential earnings (accrued interest and quota revenue):

function totalAssets() public view override(ERC4626, IERC4626) returns (uint256 assets) {
    return expectedLiquidity();
}

function expectedLiquidity() public view override returns (uint256) {
    return _expectedLiquidityLU + _calcBaseInterestAccrued() + _calcQuotaRevenueAccrued();
}

This leads to an incorrect calculation because:

totalAssets() includes unrealized interest and revenue. The total supply of shares doesn’t reflect these unrealized gains. Only realized interest has been minted as shares, not unrealized interest. Dividing new assets by the inflated totalAssets() value results in fewer shares being minted than should be.

RECOMMENDATION: To ensure accurate calculations of the number of shares to be minted or burned, replace the usage of convertToShares with the custom _convertToShares function in the repayCreditAccount function. This will ensure that the calculations are based on the correct asset-to-share conversion logic, excluding unrealized interest.

LINK:https://github.com/code-423n4/2024-07-loopfi/blob/57871f64bdea450c1f04c9a53dc1a78223719164/src/PoolV3.sol#L549
Upgraded Q -> 2 from #532 [1727788244108]
Audit: 2024-07-loopfi-findings
Severity: MEDIUM

Judge has assessed an item in Issue #532 as 2 risk. The relevant finding follows:

 [QA-13] Chainlink oracle price validation lacks min/max bounds, risking use of incorrect asset prices
Impact
The ChainlinkOracle contract does not implement checks for minimum and maximum price boundaries when fetching prices from Chainlink oracles.

Chainlink aggregators have a built in circuit breaker if the price of an asset goes outside of a predetermined price band. The result is that if an asset experiences a huge drop in value (i.e. LUNA crash) the price of the oracle will continue to return the minPrice instead of the actual price of the asset. This would allow user to continue borrowing with the asset but at the wrong price. This is exactly what happened to Venus on BSC when LUNA imploded. However, the protocol misses to implement such a check.

Proof of Concept
Look at the _fetchAndValidate function: https://github.com/code-423n4/2024-07-loopfi/blob/57871f64bdea450c1f04c9a53dc1a78223719164/src/oracle/ChainlinkOracle.sol#L96-L110

function _fetchAndValidate(address token) internal view returns (bool isValid, uint256 price) {
    Oracle memory oracle = oracles[token];
    try AggregatorV3Interface(oracle.aggregator).latestRoundData() returns (
        uint80 /*roundId*/,
        int256 answer,
        uint256 /*startedAt*/,
        uint256 updatedAt,
        uint80 /*answeredInRound*/
    ) {
        isValid = (answer > 0 && block.timestamp - updatedAt <= oracle.stalePeriod);
        return (isValid, wdiv(uint256(answer), oracle.aggregatorScale));
    } catch {
        // return the default values (false, 0) on failure
    }
}
This function only checks if the answer is greater than 0 and if the price is not stale. It does not verify if the price falls within an acceptable range, making the protocol vulnerable to using extreme or incorrect prices.

Recommended Mitigation Steps
Add logic along the lines of:

    require(answer >= minPrice && answer <= maxPrice, "invalid price");
Alternatively, Min & Max prices can be gathered using one of these ways.