msc-smart-contract-auditing/vulnerable-functions-base huggingface dataset:
Vulnerability: No slippage protection when withdrawing and providing liquidity in rebalanceAll
Severity: high

Description:
When `rebalanceAll` is called, the liquidity is first withdrawn from the pools and then deposited to new positions. However, there is no slippage protection for these operations.\\nIn the `rebalanceAll` function, it first withdraws all liquidity from the pools and deposits all liquidity to new positions.\\n```\\n    _withdraw(_totalSupply, _totalSupply);\\n```\\n\\n```\\n    _deposit(reserve0, reserve1, _totalSupply, slots);\\n```\\n\\nHowever, there are no parameters for `amount0Min` and `amount1Min`, which are used to prevent slippage. These parameters should be checked to create slippage protections.\\nActually, they are implemented in the `deposit` and `withdraw` functions, but just not in the rebalanceAll function.

Impact:
The withdraw and provide liquidity operations in rebalanceAll are exposed to high slippage and could result in a loss for LPs of multipool.

Recommended mitigation:
Implement slippage protection in rebalanceAll as suggested to avoid loss to the protocol.
Vulnerability: Usage of `slot0` is extremely easy to manipulate
Severity: high

Description:
Usage of `slot0` is extremely easy to manipulate\\nReal Wagmi is using `slot0` to calculate several variables in their codebase:\\nslot0 is the most recent data point and is therefore extremely easy to manipulate.\\nMultipool directly uses the token values returned by `getAmountsForLiquidity`\\n```\\n (uint256 amount0, uint256 amount1) = LiquidityAmounts.getAmountsForLiquidity(\\n                slots[i].currentSqrtRatioX96,\\n                TickMath.getSqrtRatioAtTick(position.lowerTick),\\n                TickMath.getSqrtRatioAtTick(position.upperTick),\\n                liquidity\\n            );\\n```\\n\\nto calculate the reserves.\\n```\\n reserve0 += amount0;\\n reserve1 += amount1;\\n```\\n

Impact:
Pool lp value can be manipulated and cause other users to receive less lp tokens.

Recommended mitigation:
To make any calculation use a TWAP instead of slot0.
Vulnerability: The `_estimateWithdrawalLp` function might return a very large value, result in users losing significant incentives or being unable to withdraw from the Dispatcher contract
Severity: high

Description:
The `_estimateWithdrawalLp` function might return a very large value, result in users losing significant incentives or being unable to withdraw from the Dispatcher contract\\nIn Dispatcher contract, `_estimateWithdrawalLp` function returns the value of shares amount based on the average of ratios `amount0 / reserve0` and `amount1 / reserve1`.\\n```\\nfunction _estimateWithdrawalLp(\\n    uint256 reserve0,\\n    uint256 reserve1,\\n    uint256 _totalSupply,\\n    uint256 amount0,\\n    uint256 amount1\\n) private pure returns (uint256 shareAmount) {\\n    shareAmount =\\n        ((amount0 * _totalSupply) / reserve0 + (amount1 * _totalSupply) / reserve1) /\\n        2;\\n}\\n```\\n\\nFrom `Dispatcher.withdraw` and `Dispatcher.deposit` function, amount0 and amount1 will be the accumulated fees of users\\n```\\nuint256 lpAmount;\\n{\\n    (uint256 fee0, uint256 fee1) = _calcFees(feesGrow, user);\\n    lpAmount = _estimateWithdrawalLp(reserve0, reserve1, _totalSupply, fee0, fee1);\\n}\\nuser.shares -= lpAmount;\\n_withdrawFee(pool, lpAmount, reserve0, reserve1, _totalSupply, deviationBP);\\n```\\n\\nHowever, it is important to note that the values of reserve0 and reserve1 can fluctuate significantly. This is because the positions of the Multipool in UniSwapV3 pools (concentrated) are unstable on-chain, and they can change substantially as the state of the pools changes. As a result, the `_estimateWithdrawalLp` function might return a large value even for a small fee amount. This could potentially lead to reverting due to underflow in the deposit function (in cases where lpAmount > user.shares), or it could result in withdrawing a larger amount of Multipool LP than initially expected.\\nScenario:\\nTotal supply of Multipool is 1e18, and Alice has 1e16 (1%) LP amounts which deposited into Dispatcher contract.\\nAlice accrued fees = 200 USDC and 100 USDT\\nThe reserves of Multipool are 100,000 USDC and 100,000 USDT, `_estimateWithdrawalLp` of Alice fees will be `(0.2% + 0.1%) / 2 * totalSupply` = `0.15% * totalSupply` = 1.5e15 LP amounts\\nHowever, in some cases, UniSwapV3 pools may experience fluctuations, reserves of Multipool are 10,000 USDC and 190,000 USDT, `_estimateWithdrawalLp` of Alice fees will be `(2% + 0.052%) / 2 * totalSupply` = `1.026% * totalSupply` = 1.026e16 LP amounts This result is greater than LP amounts of Alice (1e16), leading to reverting by underflow in deposit/withdraw function of Dispatcher contract.

Impact:
Users may face 2 potential issues when interacting with the Dispatcher contract.\\nThey might be unable to deposit/withdraw\\nSecondly, users could potentially lose significant incentives when depositing or withdrawing due to unexpected withdrawals of LP amounts for their fees.

Recommended mitigation:
Shouldn't use the average ratio for calculation in_estimateWithdrawalLp function
Vulnerability: BalancedVault.sol: Early depositor can manipulate exchange rate and steal funds
Severity: medium

Description:
The first depositor can mint a very small number of shares, then donate assets to the Vault. Thereby he manipulates the exchange rate and later depositors lose funds due to rounding down in the number of shares they receive.\\nThe currently deployed Vaults already hold funds and will merely be upgraded to V2. However as Perennial expands there will surely be the need for more Vaults which enables this issue to occur.\\nYou can add the following test to `BalancedVaultMulti.test.ts`. Make sure to have the `dsu` variable available in the test since by default this variable is not exposed to the tests.\\nThe test is self-explanatory and contains the necessary comments:\\n```\\nit('exchange rate manipulation', async () => {\\n      const smallDeposit = utils.parseEther('1')\\n      const smallestDeposit = utils.parseEther('0.000000000000000001')\\n\\n      // make a deposit with the attacker. Deposit 1 Wei to mint 1 Wei of shares\\n      await vault.connect(user).deposit(smallestDeposit, user.address)\\n      await updateOracle();\\n      await vault.sync()\\n\\n      console.log(await vault.totalSupply());\\n\\n      // donating assets to Vault\\n      await dsu.connect(user).transfer(vault.address, utils.parseEther('1'))\\n\\n      console.log(await vault.totalAssets());\\n\\n      // make a deposit with the victim. Due to rounding the victim will end up with 0 shares\\n      await updateOracle();\\n      await vault.sync()\\n      await vault.connect(user2).deposit(smallDeposit, user2.address)\\n      await updateOracle();\\n      await vault.sync()\\n\\n      console.log(await vault.totalAssets());\\n      console.log(await vault.totalSupply());\\n      // the amount of shares the victim receives is rounded down to 0\\n      console.log(await vault.balanceOf(user2.address));\\n\\n      /*\\n      at this point there are 2000000000000000001 Wei of assets in the Vault and only 1 Wei of shares\\n      which is owned by the attacker.\\n      This means the attacker has stolen all funds from the victim.\\n      */\\n    })\\n```\\n

Impact:
The attacker can steal funds from later depositors.

Recommended mitigation:
This issue can be mitigated by requiring a minimum deposit of assets. Thereby the attacker cannot manipulate the exchange rate to be so low as to enable this attack.
Vulnerability: `BalancedVault` doesn't consider potential break in one of the markets
Severity: medium

Description:
In case of critical failure of any of the underlying markets, making it permanently impossible to close position and withdraw collateral all funds deposited to balanced Vault will be lost, including funds deposited to other markets.\\nAs Markets and Vaults on Perennial are intented to be created in a permissionless manner and integrate with external price feeds, it cannot be ruled out that any Market will enter a state of catastrophic failure at a point in the future (i.e. oracle used stops functioning and Market admin keys are compromised, so it cannot be changed), resulting in permanent inability to process closing positions and withdrawing collateral.\\n`BalancedVault` does not consider this case, exposing all funds deposited to a multi-market Vault to an increased risk, as it is not implementing a possibility for users to withdraw deposited funds through a partial emergency withdrawal from other markets, even at a price of losing the claim to locked funds in case it becomes available in the future. This risk is not mentioned in the documentation.\\nProof of Concept\\nConsider a Vault with 2 markets: ETH/USD and ARB/USD.\\nAlice deposits to Vault, her funds are split between 2 markets\\nARB/USD market undergoes a fatal failure resulting in `maxAmount` returned from `_maxRedeemAtEpoch` to be 0\\nAlice cannot start withdrawal process as this line in `redeem` reverts:\\n```\\n    if (shares.gt(_maxRedeemAtEpoch(context, accountContext, account))) revert BalancedVaultRedemptionLimitExceeded();\\n```\\n

Impact:
Users funds are exposed to increased risk compared to depositing to each market individually and in case of failure of any of the markets all funds are lost. User has no possibility to consciously cut losses and withdraw funds from Markets other than the failed one.

Recommended mitigation:
Implement a partial/emergency withdrawal or acknowledge the risk clearly in Vault's documentation.
Vulnerability: eMode implementation is completely broken
Severity: high

Description:
Enabling eMode allows assets of the same class to be borrowed at much higher a much higher LTV. The issue is that the current implementation makes the incorrect calls to the Aave V3 pool making so that the pool can never take advantage of this higher LTV.\\nAaveLeverageStrategyExtension.sol#L1095-L1109\\n```\\nfunction _calculateMaxBorrowCollateral(ActionInfo memory _actionInfo, bool _isLever) internal view returns(uint256) {\\n    \\n    // Retrieve collateral factor and liquidation threshold for the collateral asset in precise units (1e16 = 1%)\\n    ( , uint256 maxLtvRaw, uint256 liquidationThresholdRaw, , , , , , ,) = strategy.aaveProtocolDataProvider.getReserveConfigurationData(address(strategy.collateralAsset));\\n\\n    // Normalize LTV and liquidation threshold to precise units. LTV is measured in 4 decimals in Aave which is why we must multiply by 1e14\\n    // for example ETH has an LTV value of 8000 which represents 80%\\n    if (_isLever) {\\n        uint256 netBorrowLimit = _actionInfo.collateralValue\\n            .preciseMul(maxLtvRaw.mul(10 ** 14))\\n            .preciseMul(PreciseUnitMath.preciseUnit().sub(execution.unutilizedLeveragePercentage));\\n\\n        return netBorrowLimit\\n            .sub(_actionInfo.borrowValue)\\n            .preciseDiv(_actionInfo.collateralPrice);\\n```\\n\\nWhen calculating the max borrow/repay allowed, the contract uses the getReserveConfigurationData subcall to the pool.\\nAaveProtocolDataProvider.sol#L77-L100\\n```\\nfunction getReserveConfigurationData(\\n  address asset\\n)\\n  external\\n  view\\n  override\\n  returns (\\n      // rest of code\\n  )\\n{\\n  DataTypes.ReserveConfigurationMap memory configuration = IPool(ADDRESSES_PROVIDER.getPool())\\n    .getConfiguration(asset);\\n\\n  (ltv, liquidationThreshold, liquidationBonus, decimals, reserveFactor, ) = configuration\\n    .getParams();\\n```\\n\\nThe issue with using getReserveConfigurationData is that it always returns the default settings of the pool. It never returns the adjusted eMode settings. This means that no matter the eMode status of the set token, it will never be able to borrow to that limit due to calling the incorrect function.\\nIt is also worth considering that the set token as well as other integrated modules configurations/settings would assume this higher LTV. Due to this mismatch, the set token would almost guaranteed be misconfigured which would lead to highly dangerous/erratic behavior from both the set and it's integrated modules. Due to this I believe that a high severity is appropriate.

Impact:
Usage of eMode, a core function of the contracts, is completely unusable causing erratic/dangerous behavior

Recommended mitigation:
Pull the adjusted eMode settings rather than the base pool settings
Vulnerability: _calculateMaxBorrowCollateral calculates repay incorrectly and can lead to set token liquidation
Severity: high

Description:
When calculating the amount to repay, `_calculateMaxBorrowCollateral` incorrectly applies `unutilizedLeveragePercentage` when calculating `netRepayLimit`. The result is that if the `borrowValue` ever exceeds `liquidationThreshold * (1 - unutilizedLeveragPercentage)` then all attempts to repay will revert.\\nAaveLeverageStrategyExtension.sol#L1110-L1118\\n```\\n    } else {\\n        uint256 netRepayLimit = _actionInfo.collateralValue\\n            .preciseMul(liquidationThresholdRaw.mul(10 ** 14))\\n            .preciseMul(PreciseUnitMath.preciseUnit().sub(execution.unutilizedLeveragePercentage));\\n\\n        return _actionInfo.collateralBalance\\n            .preciseMul(netRepayLimit.sub(_actionInfo.borrowValue))\\n            .preciseDiv(netRepayLimit);\\n    }\\n```\\n\\nWhen calculating `netRepayLimit`, `_calculateMaxBorrowCollateral` uses the `liquidationThreshold` adjusted by `unutilizedLeveragePercentage`. It then subtracts the borrow value from this limit. This is problematic because if the current `borrowValue` of the set token exceeds `liquidationThreshold` * (1 - unutilizedLeveragPercentage) then this line will revert making it impossible to make any kind of repayment. Once no repayment is possible the set token can't rebalance and will be liquidated.

Impact:
Once the leverage exceeds a certain point the set token can no longer rebalance

Recommended mitigation:
Don't adjust the max value by `unutilizedLeveragPercentage`
Vulnerability: setIncentiveSettings would be halt during a rebalance operation that gets stuck due to supply cap is reached at Aave
Severity: medium

Description:
setIncentiveSettings would be halt during a rebalance operation that gets stuck due to supply cap is reached at Aave\\nrebalance implement a cap of tradeSize and if the need to rebalance require taking more assets than the maxTradeSize, then `twapLeverageRatio` would be set to the targeted leverage. `twapLeverageRatio` == 0 is required during rebalance.\\nConsider:\\nlever is needed during rebalance, the strategy require to borrow more ETH and sell to wstETH during the 1st call of rebalance the protocol cache the new `twapLeverageRatio` However wstETH market in Aave reach supply cap. rebalance/iterateRebalance comes to a halt. `twapLeverageRatio` remains caching the targeted leverage\\nsetIncentiveSettings requires a condition in which no rebalance is in progress. With the above case, setIncentiveSettings can be halted for an extended period of time until the wstETH market falls under supply cap.\\nWorth-noting, at the time of writing this issue, the wstETH market at Aave has been at supply cap\\nIn this case, malicious actor who already has a position in wstETH can do the following:\\ndeposit into the setToken, trigger a rebalance.\\nmalicious trader withdraw his/her position in Aave wstETH market so there opens up vacancy for supply again.\\nprotocol owner see supply vacancy, call rebalance in order to lever as required. Now twapLeverageRatio is set to new value since multiple trades are needed\\nmalicious trader now re-supply the wstETH market at Aave so it reaches supply cap again.\\nthe protocol gets stuck with a non-zero twapLeverageRatio, `setIncentiveSettings` can not be called.\\n```\\n    function setIncentiveSettings(IncentiveSettings memory _newIncentiveSettings) external onlyOperator noRebalanceInProgress {\\n        incentive = _newIncentiveSettings;\\n\\n        _validateNonExchangeSettings(methodology, execution, incentive);\\n\\n        emit IncentiveSettingsUpdated(\\n            incentive.etherReward,\\n            incentive.incentivizedLeverageRatio,\\n            incentive.incentivizedSlippageTolerance,\\n            incentive.incentivizedTwapCooldownPeriod\\n        );\\n    }\\n```\\n

Impact:
setIncentiveSettings would be halt.

Recommended mitigation:
Add some checks on whether the supply cap of an Aave market is reached during a rebalance. If so, allows a re-set of twapLeverageRatio
Vulnerability: no validation to ensure the arbitrum sequencer is down
Severity: medium

Description:
There is no validation to ensure sequencer is down\\n```\\n int256 rawCollateralPrice = strategy.collateralPriceOracle.latestAnswer();\\n        rebalanceInfo.collateralPrice = rawCollateralPrice.toUint256().mul(10 ** strategy.collateralDecimalAdjustment);\\n        int256 rawBorrowPrice = strategy.borrowPriceOracle.latestAnswer();\\n        rebalanceInfo.borrowPrice = rawBorrowPrice.toUint256().mul(10 ** strategy.borrowDecimalAdjustment);\\n```\\n\\nUsing Chainlink in L2 chains such as Arbitrum requires to check if the sequencer is down to avoid prices from looking like they are fresh although they are not.\\nThe bug could be leveraged by malicious actors to take advantage of the sequencer downtime.

Impact:
when sequencer is down, stale price is used for oracle and the borrow value and collateral value is calculated and the protocol can be forced to rebalance in a loss position

Recommended mitigation:
recommend to add checks to ensure the sequencer is not down.
Vulnerability: Relying solely on oracle base slippage parameters can cause significant loss due to sandwich attacks
Severity: medium

Description:
AaveLeverageStrategyExtension relies solely on oracle price data when determining the slippage parameter during a rebalance. This is problematic as chainlink oracles, especially mainnet, have upwards of 2% threshold before triggering a price update. If swapping between volatile assets, the errors will compound causing even bigger variation. These variations can be exploited via sandwich attacks.\\nAaveLeverageStrategyExtension.sol#L1147-L1152\\n```\\nfunction _calculateMinRepayUnits(uint256 _collateralRebalanceUnits, uint256 _slippageTolerance, ActionInfo memory _actionInfo) internal pure returns (uint256) {\\n    return _collateralRebalanceUnits\\n        .preciseMul(_actionInfo.collateralPrice)\\n        .preciseDiv(_actionInfo.borrowPrice)\\n        .preciseMul(PreciseUnitMath.preciseUnit().sub(_slippageTolerance));\\n}\\n```\\n\\nWhen determining the minimum return from the swap, _calculateMinRepayUnits directly uses oracle data to determine the final output. The differences between the true value and the oracle value can be systematically exploited via sandwich attacks. Given the leverage nature of the module, these losses can cause significant loss to the pool.

Impact:
Purely oracle derived slippage parameters will lead to significant and unnecessary losses

Recommended mitigation:
The solution to this is straight forward. Allow keepers to specify their own slippage value. Instead of using an oracle slippage parameter, validate that the specified slippage value is within a margin of the oracle. This gives the best of both world. It allows for tighter and more reactive slippage controls while still preventing outright abuse in the event that the trusted keeper is compromised.
Vulnerability: Chainlink price feed is `deprecated`, not sufficiently validated and can return `stale` prices.
Severity: medium

Description:
The function `_createActionInfo()` uses Chainlink's deprecated latestAnswer function, this function also does not guarantee that the price returned by the Chainlink price feed is not stale and there is no additional checks to ensure that the return values are valid.\\nThe internal function `_createActionInfo()` uses calls `strategy.collateralPriceOracle.latestAnswer()` and `strategy.borrowPriceOracle.latestAnswer()` that uses Chainlink's deprecated latestAnswer() to get the latest price. However, there is no check for if the return value is a stale data.\\n```\\nfunction _createActionInfo() internal view returns(ActionInfo memory) {\\n        ActionInfo memory rebalanceInfo;\\n\\n        // Calculate prices from chainlink. Chainlink returns prices with 8 decimal places, but we need 36 - underlyingDecimals decimal places.\\n        // This is so that when the underlying amount is multiplied by the received price, the collateral valuation is normalized to 36 decimals.\\n        // To perform this adjustment, we multiply by 10^(36 - 8 - underlyingDecimals)\\n        int256 rawCollateralPrice = strategy.collateralPriceOracle.latestAnswer();\\n        rebalanceInfo.collateralPrice = rawCollateralPrice.toUint256().mul(10 ** strategy.collateralDecimalAdjustment);\\n        int256 rawBorrowPrice = strategy.borrowPriceOracle.latestAnswer();\\n        rebalanceInfo.borrowPrice = rawBorrowPrice.toUint256().mul(10 ** strategy.borrowDecimalAdjustment);\\n// More Code// rest of code.\\n}\\n   \\n```\\n

Impact:
The function `_createActionInfo()` is used to return important values used throughout the contract, the staleness of the chainlinklink return values will lead to wrong calculation of the collateral and borrow prices and other unexpected behavior.

Recommended mitigation:
The `latestRoundData` function should be used instead of the deprecated `latestAnswer` function and add sufficient checks to ensure that the pricefeed is not stale.\\n```\\n(uint80 roundId, int256 assetChainlinkPriceInt, , uint256 updatedAt, uint80 answeredInRound) = IPrice(_chainlinkFeed).latestRoundData();\\n            require(answeredInRound >= roundId, "price is stale");\\n            require(updatedAt > 0, "round is incomplete");\\n```\\n
Vulnerability: Operator is blocked when sequencer is down on Arbitrum
Severity: medium

Description:
When the sequencer is down on Arbitrum state changes can still happen on L2 by passing them from L1 through the Delayed Inbox.\\nUsers can still interact with the Index protocol but due to how Arbitrum address aliasing functions the operator will be blocked from calling onlyOperator().\\nThe `msg.sender` of a transaction from the Delayed Inbox is aliased:\\n```\\nL2_Alias = L1_Contract_Address + 0x1111000000000000000000000000000000001111\\n```\\n\\nAll functions with the `onlyOperator()` modifier are therefore blocked when the sequencer is down.\\nThe issue exists for all modifiers that are only callable by specific EOAs. But the operator of the Aave3LeverageStrategyExtension is the main security risk.

Impact:
The operator has roles that are vital for the safety of the protocol. Re-balancing and issuing/redeeming can still be done when the sequencer is down it is therefore important that the operator call the necessary functions to operate the protocol when the sequencer is down.\\n`disengage()` is an important safety function that the operator should always have access especially when the protocol is still in accessible to other users. Changing methodology and adding/removing exchanges are also important for the safety of the protocol.

Recommended mitigation:
Change the `onlyOperator()` to check if the address is the aliased address of the operator.