xanoutas/solidity-security-findings huggingface dataset:
Upgraded Q -> 2 from #532 [1727788160722]
Audit: 2024-07-loopfi-findings
Severity: MEDIUM

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

 [QA-06] Incorrect reward updates for scheduled emissions in ChefIncentivesController
Impact
Although the protocol team made a comment stating that:

// Although we would want the pools to be as up to date as possible when users
        // transfer rTokens or dTokens, updating all pools on every r-/d-Token interaction would be too gas intensive.
        // _updateEmissions();
The recommendations in this report can help balance the need for accurate reward calculations with the potential gas costs of frequent updates.

The ChefIncentivesController contract may distribute incorrect reward amounts when using scheduled emissions. This occurs because the rewardsPerSecond value is not updated according to the emission schedule in all relevant functions. Specifically, the _handleActionAfterForToken and afterLockUpdate functions do not call _updateEmissions, leading to potential discrepancies in reward calculations until a new pool is added or a claim is triggered. This can result in users receiving more or fewer rewards than intended.

Proof of Concept
ChefIncentivesController supports two methods of distributing rewards:

Fixed rewardsPerSecond (when persistRewardsPerSecond is true)
Scheduled emissions (when persistRewardsPerSecond is false)
The setScheduledRewardsPerSecond function is responsible for updating the rewardsPerSecond according to the emission schedule. This function is called within _updateEmissions as seen here.

  function setScheduledRewardsPerSecond() internal {
      if (!persistRewardsPerSecond) {
          uint256 length = emissionSchedule.length;
          uint256 i = emissionScheduleIndex;
          uint128 offset = uint128(block.timestamp - startTime);
          for (; i < length && offset >= emissionSchedule[i].startTimeOffset; ) {
              unchecked {
                  i++;
              }
          }
          if (i > emissionScheduleIndex) {
              emissionScheduleIndex = i;
              _massUpdatePools();
>              rewardsPerSecond = uint256(emissionSchedule[i - 1].rewardsPerSecond);
          }
      }
  }
However, _updateEmissions is only called in two places:

In the addPool function:
  function addPool(address _token, uint256 _allocPoint) external {
      if (msg.sender != poolConfigurator) revert NotAllowed();
      if (vaultInfo[_token].lastRewardTime != 0) revert PoolExists();
>     _updateEmissions();
      totalAllocPoint = totalAllocPoint + _allocPoint;
      registeredTokens.push(_token);
      VaultInfo storage pool = vaultInfo[_token];
      pool.allocPoint = _allocPoint;
      pool.lastRewardTime = block.timestamp;
      validRTokens[_token] = true;
  }
In the claim function:
    function claim(address _user, address[] memory _tokens) public whenNotPaused {
        if (eligibilityMode != EligibilityModes.DISABLED) {
            if (!eligibleDataProvider.isEligibleForRewards(_user)) revert EligibleRequired();
Upgraded Q -> 2 from #533 [1727787881191]
Audit: 2024-07-loopfi-findings
Severity: MEDIUM

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

 [L-05] Improper slippage control on pool deposit
Impact
In AuraVault::deposit the function mints shares on depositing assets but function doesn’t have a slippage control parameters to set the minimum mint shares, could lead to loss of funds by mev, frontrunning attacks.

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

     * @notice Mints `shares` Vault shares to `receiver`.
     * @dev Because `asset` is not actually what is collected here, first wrap to required token in the booster.
     *
     * TODO: account for unclaimed rewards
     */
    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;
    }
Recommendation
We recommend adding slippage control parameters on depositing assets to the vault.
Upgraded Q -> 2 from #533 [1727787785803]
Audit: 2024-07-loopfi-findings
Severity: MEDIUM

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

 [L-03] Without try-catch, permit can be DOSed.
Impact
Attacker can frontrun the transaction and extracts signature parameters from _transferFrom() call and frontruns it with a direct permit(). In this case, the end result is harmful, since the user loses the functionality that follows the permit().

In fact, any function call that unconditionally performs permit() can be forced to revert this way. In case there is a fallback code path (using direct user approval), the DOS is short-term, as eventually the user / dApp would switch to using an alternative. Otherwise, the DOS is long-term (there are bypasses through flashbots in some chains, but that’s a really bad scenario to resort to).

This would consume the nonce associated with the user’s permit and cause the original transaction to fail due to the now-invalid nonce.

  function _transferFrom(
        address token,
        address from,
        address to,
        uint256 amount,
        PermitParams memory params
    ) internal {
        if (params.approvalType == ApprovalType.PERMIT2) {
            // Consume a permit2 message and transfer tokens.
            ISignatureTransfer(permit2).permitTransferFrom(
                ISignatureTransfer.PermitTransferFrom({
                    permitted: ISignatureTransfer.TokenPermissions({token: token, amount: params.approvalAmount}),
                    nonce: params.nonce,
                    deadline: params.deadline
                }),
                ISignatureTransfer.SignatureTransferDetails({to: to, requestedAmount: amount}),
                from,
                bytes.concat(params.r, params.s, bytes1(params.v)) // Construct signature
            );
        } else if (params.approvalType == ApprovalType.PERMIT) {
            // Consume a standard ERC20 permit message
            IERC20Permit(token).safePermit(
                from,
                to,
                params.approvalAmount,
                params.deadline,
                params.v,
                params.r,
                params.s
            );
            IERC20(token).safeTransferFrom(from, to, amount);
        } else {
            // No signature provided, just transfer tokens.
            IERC20(token).safeTransferFrom(from, to, amount);
        }
    }
https://github.com/code-423n4/2024-07-loopfi/blob/57871f64bdea450c1f04c9a53dc1a78223719164/src/proxy/TransferAction.sol#L67
Upgraded Q -> 2 from #535 [1727787465807]
Audit: 2024-07-loopfi-findings
Severity: MEDIUM

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

 [Low-17] No checks for staleness in AuraVault.sol
In the Aura Vault contract, the protocol uses a ChainLink aggregator to fetch the latestRoundData(), but there is no check if the return value indicates stale data. The only check present is for the quoteAnswer to be > 0; however, this alone is not sufficient.

    function _chainlinkSpot() private view returns (uint256 price) {
        bool isValid;
        try AggregatorV3Interface(BAL_CHAINLINK_FEED).latestRoundData() returns (
            uint80 /*roundId*/,
            int256 answer,
            uint256 /*startedAt*/,
            uint256 /*updatedAt*/,
            uint80 /*answeredInRound*/
        ) {
            price = wdiv(uint256(answer), BAL_CHAINLINK_DECIMALS); // @audit no checks for staleness
            isValid = (price > 0);
        } catch {}

        if (!isValid) revert AuraVault__chainlinkSpot_invalidPrice();
    }
Upgraded Q -> 2 from #536 [1727787075969]
Audit: 2024-07-loopfi-findings
Severity: MEDIUM

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

 Title [Low]: ChainlinkOracle::_fetchAndValidate function doesn’t check for min/max price.
Summary :
ChainlinkOracle::_fetchAndValidate function do not check for staleness of the fetched data

Vulnerability details:
The ChainlinkOracle::_fetchAndValidate function is using the aggregator v3 to get/call the latestRoundData.

    /// @notice Fetches and validates the latest price from Chainlink
    /// @return isValid Whether the price is valid based on the value range and staleness
    /// @return price Asset price [WAD]
    function _fetchAndValidate(address token) internal view returns (bool isValid, uint256 price) {
        Oracle memory oracle = oracles[token];
        //@audit not checking if value if between min and max answer
        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 {//@audit catch block is empty
            // return the default values (false, 0) on failure
        }
    }
link to code

The function should check for the min and max amount return to prevent some case happen, something like this:

https://solodit.xyz/issues/missing-checks-for-chainlink-oracle-spearbit-connext-pdf https://solodit.xyz/issues/m-16-chainlinkadapteroracle-will-return-the-wrong-price-for-asset-if-underlying-aggregator-hits-minanswer-sherlock-blueberry-blueberry-git

Also as we can see above from the function code, the catch block is empty. In case the try block throws any exception there is nothing to handle it. Which can lead to silent failures and unwanted behavior

Impact
If case like luna happen then the oracle will return the minimum price and not the crashed price. Function can show unwanted or unexpected behavior in case try block throws an exception.
Upgraded Q -> 2 from #537 [1727786736874]
Audit: 2024-07-loopfi-findings
Severity: MEDIUM

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

 3. No check for min-max values when handling chainlink prices
Links to affected code *

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

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

Impact
ChainlinkOracle.sol and PendleOracle.sol both query token prices from chainlink. Depending on the token, When its deviates significantly from a predefined price range, Chainlink aggregators activate a circuit breaker mechanism. In crash situations, this mechanism causes the oracle to consistently return the minimum price instead of the actual price of the asset. Consequently, protcol wil, continue to work with this asset but at an incorrect price.

    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));
        try AggregatorV3Interface(aggregator).latestRoundData() returns (
            uint80 roundId, int256 answer, uint256 /*startedAt*/, uint256 updatedAt, uint80 answeredInRound
        ) {
            isValid = (answer > 0 && answeredInRound >= roundId && block.timestamp - updatedAt <= stalePeriod);
            return (isValid, wdiv(uint256(answer), aggregatorScale));
Recommended Mitigation Steps
Recomend checking the returned answer against the minPrice/maxPrice and revert if the answer is outside of the bounds.
Upgraded Q -> 2 from #538 [1727786260314]
Audit: 2024-07-loopfi-findings
Severity: MEDIUM

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

 🔵 L02 - vulnerable to permit front-run causing a revert
https://github.com/code-423n4/2024-07-loopfi/blob/main/src/proxy/TransferAction.sol#L67-L67

Vulnerability details
When calling a permit, the data of the permit will be logged in the blockchain, and anyone is able to frontrun the permit by duplicating the TX arguments. This is not an issue according to the EIP since the permit creator can just create another permit.

However, if the permit is used in conjunction with an external function call, frontrunning the permit will cause the function to be griefed.

This is what’s hapenning in TransferAction::_transferFrom:

File: src/proxy/TransferAction.sol
46:     function _transferFrom(
47:         address token,
48:         address from,
49:         address to, //! to is not part of the permit signature
50:         uint256 amount,
51:         PermitParams memory params
52:     ) internal {
...:
...:      //* ------ some code ----- *//
...:
65:         } else if (params.approvalType == ApprovalType.PERMIT) {
66:             // Consume a standard ERC20 permit message
67:             IERC20Permit(token).safePermit(
68:                 from,
69:                 to,
70:                 params.approvalAmount,
71:                 params.deadline,
72:                 params.v,
73:                 params.r,
74:                 params.s
75:             );
76:             IERC20(token).safeTransferFrom(from, to, amount);
77:         } else {
...:
...:      //* ------ some code ----- *//
...:
81:     }
Impact
User actions can be DoS’d repeatedly by a malicious actor.
Upgraded Q -> 2 from #541 [1727784215398]
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();
https://github.com/code-423n4/2024-07-loopfi/blob/57871f64bdea450c1f04c9a53dc1a78223719164/src/oracle/ChainlinkOracle.sol#L96-L110
Upgraded Q -> 2 from #542 [1727784100266]
Audit: 2024-07-loopfi-findings
Severity: MEDIUM

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

 [L-05] Incorrect Prices Returned During Flash Crashes
The ChainlinkOracle contract currently implements a function _fetchAndValidate to retrieve the price from an oracle. it lacks a direct check for both minimum and maximum acceptable prices (minAnswer and maxAnswer).

 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
        }
    }
The absence of a minimum acceptable price check leaves the protocol vulnerable to accepting abnormally low prices, particularly during flash crashes or sudden market downturns. Introducing a minAnswer parameter would allow the protocol to reject prices below a specified threshold.

Similarly, the lack of a maximum acceptable price check poses risks associated with accepting excessively high prices from the oracle. Without an upper limit, the protocol may incorrectly value assets or allocate resources based on inflated prices, leading to potential financial losses and disruptions in operations.
Upgraded Q -> 2 from #543 [1727783721379]
Audit: 2024-07-loopfi-findings
Severity: MEDIUM

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

 [L01] When users use _transferFrom() to transfer funds via ApprovalType.PERMIT approval type, the transaction may be reverted if malicious user frontrun the related permit.

    function _transferFrom(
        address token,
        address from,
        address to,
        uint256 amount,
        PermitParams memory params
    ) internal {
        if (params.approvalType == ApprovalType.PERMIT2) {
            ...
        } else if (params.approvalType == ApprovalType.PERMIT) {
            // Consume a standard ERC20 permit message
            // @audit this will be blocked.
            IERC20Permit(token).safePermit(
                from,
                to,
                params.approvalAmount,
                params.deadline,
                params.v,
                params.r,
                params.s
            );
            IERC20(token).safeTransferFrom(from, to, amount);
        } else {
            ...
        }
    }
Upgraded Q -> 2 from #546 [1727783054858]
Audit: 2024-07-loopfi-findings
Severity: MEDIUM

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

 QA2. CDPVault.deposit() and CDPVault.withdraw() have the modifier whenNotPaused to ensure these two functions can only be called when the contract is not paused. However, a user can simply call modifyCollateralAndDebt to bypass the check enforced by this modifier. As a result, a user can still perform withdraw and deposit when the contract is paused.

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

Mitigation: the modifier whenNotPaused should be added to modifyCollateralAndDebt() instead.
The last user to withdraw will be DOS and unexpectedly revert due to the _checkMinShares in the StakingLPEth contract.
Audit: 2024-07-loopfi-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-loopfi/blob/4f508781a49ffa53511e7e5ed6cda0ff0eb5bdc5/src/StakingLPEth.sol#L28-L28


# Vulnerability details

## Impact
User who withdraws last will be DOS and his funds will be locked.
Initially when shares are worth 1:1 with assets shares locked is about $32 dollars worth but this is subject to increase as the share value increases. Suppose if in the future with more rewards coming in shares are now worth 1:2 i.e. 1 share = 2 assets then the user's loss will also double in value, as can also be seen from [docs](https://docs.loopfi.xyz/the-protocol/lending-passive-eth-yield#:~:text=At%20launch%20the%20value%20will%20be%201%20slpETH%20%3D%201%20lpETH%2C%20but%20slpETH%20is%20expected%20to%20slowly%20increase%20in%20value%20as%20protocol%20yield%20is%20transferred%20into%20the%20Staking%20smart%20contract.). 

## Proof of Concept
Whenever withdraw is called, it checks for min shares 
```javascript
    function _withdraw(
        address caller,
        address receiver,
        address _owner,
        uint256 assets,
        uint256 shares
    ) internal override nonReentrant {
        super._withdraw(caller, receiver, _owner, assets, shares);
        _checkMinShares();
    }
```
```javascript
    function _checkMinShares() internal view {
        uint256 _totalSupply = totalSupply();
        if (_totalSupply > 0 && _totalSupply < MIN_SHARES) revert MinSharesViolation();
    }
```
and min shares is ```0.01e18``` ;
```javascript
uint256 private constant MIN_SHARES = 0.01 ether;
```
The problem here is that there can be leftover dust which is not owned by a withdrawer which can DOS his transaction due to how the ```_chekMinShares``` works:
```if (_totalSupply > 0 && _totalSupply < MIN_SHARES) revert MinSharesViolation();```.

There are two ways in which the last withdrawer can be DOS, 
* unintentionally accumulated dust amount of shares due to roundings when other users withdraw, OR
* intentionally by a malicious actor by leaving just 1(dust amount) of shares when he withdraws his shares.

Example-
* there are five users with 10e18 shares each 
* first four user has withdrawn, leaving 1(dust) amount of shares 
* now 10e18 + 1 shares are left i.e. ```totalSupply() = 10e18 + 1```
* last user(Bob) tries to withdraw and calls redeem with 10e18 but it will revert because of ```_checkMinShares```,
```if (_totalSupply > 0 && _totalSupply < MIN_SHARES) revert MinSharesViolation();``` and bob cannot withdraw all because of that 1 dust leftover: ```1 > 0 && 1 < 0.01eth``` so it reverts.

Only way Bob can withdraw is to abandon his 0.01e18 shares which is worth around $32 dollars currently.
The problem is ```0.01 ether``` as ```MIN_SHARES``` is too much since we are dealing with an eth derivative token and 0.01 eth lost by a user is not a negligible amount. 

## Tools Used
manual

## Recommended Mitigation Steps
reduce the amount of min shares required
```diff
-      uint256 private constant MIN_SHARES = 0.