msc-smart-contract-auditing/vulnerable-functions-base huggingface dataset:
Vulnerability: Users are forced to swap all reward tokens with no slippage protection
Severity: high

Description:
AuraSpell forces users to swap their reward tokens to debt token but doesn't allow them to specify any slippage values.\\nAuraSpell.sol#L193-L203\\n```\\n    for (uint256 i = 0; i < rewardTokens.length; i++) {\\n        uint256 rewards = _doCutRewardsFee(rewardTokens[i]);\\n        _ensureApprove(rewardTokens[i], address(swapRouter), rewards);\\n        swapRouter.swapExactTokensForTokens(\\n            rewards,\\n            0,\\n            swapPath[i],\\n            address(this),\\n            type(uint256).max\\n        );\\n    }\\n```\\n\\nAbove all reward tokens are swapped and always use 0 for min out meaning that deposits will be sandwiched and stolen.

Impact:
All reward tokens can be sandwiched and stolen

Recommended mitigation:
Allow user to specify slippage parameters for all reward tokens
Vulnerability: ConvexSpell#closePositionFarm removes liquidity without any slippage protection
Severity: high

Description:
ConvexSpell#closePositionFarm removes liquidity without any slippage protection allowing withdraws to be sandwiched and stolen. Curve liquidity has historically been strong but for smaller pairs their liquidity is getting low enough that it can be manipulated via flashloans.\\nConvexSpell.sol#L204-L208\\n```\\n        ICurvePool(pool).remove_liquidity_one_coin(\\n            amountPosRemove,\\n            int128(tokenIndex),\\n            0\\n        );\\n```\\n\\nLiquidity is removed as a single token which makes it vulnerable to sandwich attacks but no slippage protection is implemented. The same issue applies to CurveSpell.

Impact:
User withdrawals can be sandwiched

Recommended mitigation:
Issue ConvexSpell#closePositionFarm removes liquidity without any slippage protection\\nAllow user to specify min out
Vulnerability: WAuraPools will irreversibly break if reward tokens are added to pool after deposit
Severity: high

Description:
WAuraPools will irreversibly break if reward tokens are added to pool after deposit due to an OOB error on accExtPerShare.\\nWAuraPools.sol#L166-L189\\n```\\n    uint extraRewardsCount = IAuraRewarder(crvRewarder)\\n        .extraRewardsLength(); <- @audit-issue rewardTokenCount pulled fresh\\n    tokens = new address[](extraRewardsCount + 1);\\n    rewards = new uint256[](extraRewardsCount + 1);\\n\\n    tokens[0] = IAuraRewarder(crvRewarder).rewardToken();\\n    rewards[0] = _getPendingReward(\\n        stCrvPerShare,\\n        crvRewarder,\\n        amount,\\n        lpDecimals\\n    );\\n\\n    for (uint i = 0; i < extraRewardsCount; i++) {\\n        address rewarder = IAuraRewarder(crvRewarder).extraRewards(i);\\n\\n        @audit-issue attempts to pull from array which will be too small if tokens are added\\n        uint256 stRewardPerShare = accExtPerShare[tokenId][i];\\n        tokens[i + 1] = IAuraRewarder(rewarder).rewardToken();\\n        rewards[i + 1] = _getPendingReward(\\n            stRewardPerShare,\\n            rewarder,\\n            amount,\\n            lpDecimals\\n        );\\n    }\\n```\\n\\naccExtPerShare stores the current rewardPerToken when the position is first created. It stores it as an array and only stores values for reward tokens that have been added prior to minting. This creates an issue if a reward token is added because now it will attempt to pull a value for an index that doesn't exist and throw an OOB error.\\nThis is problematic because pendingRewards is called every single transaction via the isLiquidatable subcall in BlueBerryBank#execute.

Impact:
WAuraPools will irreversibly break if reward tokens are added to pool after

Recommended mitigation:
Use a mapping rather than an array to store values
Vulnerability: UserData for balancer pool exits is malformed and will permanently trap users
Severity: high

Description:
UserData for balancer pool exits is malformed and will result in all withdrawal attempts failing, trapping the user permanently.\\nAuraSpell.sol#L184-L189\\n```\\nwAuraPools.getVault(lpToken).exitPool(\\n    IBalancerPool(lpToken).getPoolId(),\\n    address(this),\\n    address(this),\\n    IBalancerVault.ExitPoolRequest(tokens, minAmountsOut, "", false)\\n);\\n```\\n\\nWe see above that UserData is encoded as "". This is problematic as it doesn't contain the proper data for exiting the pool, causing all exit request to fail and trap the user permanently.\\n```\\nfunction exactBptInForTokenOut(bytes memory self) internal pure returns (uint256 bptAmountIn, uint256 tokenIndex) {\\n    (, bptAmountIn, tokenIndex) = abi.decode(self, (WeightedPool.ExitKind, uint256, uint256));\\n}\\n```\\n\\nUserData is decoded into the data shown above when using ExitKind = 0. Since the exit uses "" as the user data this will be decoded as 0 a.k.a EXACT_BPT_IN_FOR_ONE_TOKEN_OUT. This is problematic because the token index and bptAmountIn should also be encoded in user data for this kind of exit. Since it isn't the exit call will always revert and the user will be permanently trapped.

Impact:
Users will be permanently trapped, unable to withdraw

Recommended mitigation:
Encode the necessary exit data in userData
Vulnerability: UniswapV3 sqrtRatioLimit doesn't provide slippage protection and will result in partial swaps
Severity: high

Description:
The sqrtRatioLimit for UniV3 doesn't cause the swap to revert upon reaching that value. Instead it just cause the swap to partially fill. This is a known issue with using sqrtRatioLimit as can be seen here where the swap ends prematurely when it has been reached. This is problematic as this is meant to provide the user with slippage protection but doesn't.\\n```\\n    if (amountToSwap > 0) {\\n        SWAP_POOL = IUniswapV3Pool(vault.pool());\\n        uint160 deltaSqrt = (param.sqrtRatioLimit *\\n            uint160(param.sellSlippage)) / uint160(Constants.DENOMINATOR);\\n        SWAP_POOL.swap(\\n            address(this),\\n            // if withdraw token is Token0, then swap token1 -> token0 (false)\\n            !isTokenA,\\n            amountToSwap.toInt256(),\\n            isTokenA\\n                ? param.sqrtRatioLimit + deltaSqrt\\n                : param.sqrtRatioLimit - deltaSqrt, // slippaged price cap\\n            abi.encode(address(this))\\n        );\\n    }\\n```\\n\\nsqrtRatioLimit is used as slippage protection for the user but is ineffective and depending on what tokens are being swapped, tokens may be left the in the contract which can be stolen by anyone.

Impact:
Incorrect slippage application can result in partial swaps and loss of funds

Recommended mitigation:
Check the amount received from the swap and compare it against some user supplied minimum
Vulnerability: Balance check for swapToken in ShortLongSpell#_deposit is incorrect and will result in nonfunctional contract
Severity: high

Description:
The balance checks on ShortLongSpell#_withdraw are incorrect and will make contract basically nonfunctional\\nswapToken is always vault.uToken. borrowToken is always required to be vault.uToken which means that swapToken == borrowToken. This means that the token borrowed is always required to be swapped.\\nShortLongSpell.sol#L83-L89\\n```\\n    uint256 strTokenAmt = _doBorrow(param.borrowToken, param.borrowAmount);\\n\\n    // 3. Swap borrowed token to strategy token\\n    IERC20Upgradeable swapToken = ISoftVault(strategy.vault).uToken();\\n    // swapData.fromAmount = strTokenAmt;\\n    PSwapLib.megaSwap(augustusSwapper, tokenTransferProxy, swapData);\\n    strTokenAmt = swapToken.balanceOf(address(this)) - strTokenAmt; <- @audit-issue will always revert on swap\\n```\\n\\nBecause swapToken == borrowToken if there is ever a swap then the swapToken balance will decrease. This causes L89 to always revert when a swap happens, making the contract completely non-functional

Impact:
ShortLongSpell is nonfunctional

Recommended mitigation:
Remove check
Vulnerability: ShortLongSpell#openPosition can cause user unexpected liquidation when increasing position size
Severity: high

Description:
When increasing a position, all collateral is sent to the user rather than being kept in the position. This can cause serious issues because this collateral keeps the user from being liquidated. It may unexpectedly leave the user on the brink of liquidation where a small change in price leads to their liquidation.\\nShortLongSpell.sol#L129-L141\\n```\\n    {\\n        IBank.Position memory pos = bank.getCurrentPositionInfo();\\n        address posCollToken = pos.collToken;\\n        uint256 collSize = pos.collateralSize;\\n        address burnToken = address(ISoftVault(strategy.vault).uToken());\\n        if (collSize > 0) {\\n            if (posCollToken != address(wrapper))\\n                revert Errors.INCORRECT_COLTOKEN(posCollToken);\\n            bank.takeCollateral(collSize);\\n            wrapper.burn(burnToken, collSize);\\n            _doRefund(burnToken);\\n        }\\n    }\\n```\\n\\nIn the above lines we can see that all collateral is burned and the user is sent the underlying tokens. This is problematic as it sends all the collateral to the user, leaving the position collateralized by only the isolated collateral.\\nBest case the user's transaction reverts but worst case they will be liquidated almost immediately.

Impact:
Unfair liquidation for users

Recommended mitigation:
Don't burn the collateral
Vulnerability: Pending CRV rewards are not accounted for and can cause unfair liquidations
Severity: high

Description:
pendingRewards are factored into the health of a position so that the position collateral is fairly assessed. However WCurveGauge#pendingRewards doesn't return the proper reward tokens/amounts meaning that positions aren't valued correctly and users can be unfairly liquidated.\\nBlueBerryBank.sol#L408-L413\\n```\\n        (address[] memory tokens, uint256[] memory rewards) = IERC20Wrapper(\\n            pos.collToken\\n        ).pendingRewards(pos.collId, pos.collateralSize);\\n        for (uint256 i; i < tokens.length; i++) {\\n            rewardsValue += oracle.getTokenValue(tokens[i], rewards[i]);\\n        }\\n```\\n\\nWhen BlueBerryBank is valuing a position it also values the pending rewards since they also have value.\\nWCurveGauge.sol#L106-L114\\n```\\nfunction pendingRewards(\\n    uint256 tokenId,\\n    uint256 amount\\n)\\n    public\\n    view\\n    override\\n    returns (address[] memory tokens, uint256[] memory rewards)\\n{}\\n```\\n\\nAbove we see that WCurveGauge#pendingRewards returns empty arrays when called. This means that pending rewards are not factored in correctly and users can be liquidated when even when they should be safe.

Impact:
User is liquidated when they shouldn't be

Recommended mitigation:
Change WCurveGauge#pendingRewards to correctly return the pending rewards
Vulnerability: Deadline check is not effective, allowing outdated slippage and allow pending transaction to be unexpected executed
Severity: high

Description:
Deadline check is not effective, allowing outdated slippage and allow pending transaction to be unexpected executed\\nIn the current implementation in CurveSpell.sol\\n```\\n{\\n // 2. Swap rewards tokens to debt token\\n uint256 rewards = _doCutRewardsFee(CRV);\\n _ensureApprove(CRV, address(swapRouter), rewards);\\n swapRouter.swapExactTokensForTokens(\\n  rewards,\\n  0,\\n  swapPath,\\n  address(this),\\n  type(uint256).max\\n );\\n}\\n```\\n\\nthe deadline check is set to type(uint256).max, which means the deadline check is disabled!\\nIn IChiSpell. the swap is directedly call on the pool instead of the router\\n```\\nSWAP_POOL.swap(\\n address(this),\\n // if withdraw token is Token0, then swap token1 -> token0 (false)\\n !isTokenA,\\n amountToSwap.toInt256(),\\n isTokenA\\n  ? param.sqrtRatioLimit + deltaSqrt\\n  : param.sqrtRatioLimit - deltaSqrt, // slippaged price cap\\n abi.encode(address(this))\\n);\\n```\\n\\nand it has no deadline check for the transaction when swapping

Impact:
AMMs provide their users with an option to limit the execution of their pending actions, such as swaps or adding and removing liquidity. The most common solution is to include a deadline timestamp as a parameter (for example see Uniswap V2 and Uniswap V3). If such an option is not present, users can unknowingly perform bad trades:\\nAlice wants to swap 100 tokens for 1 ETH and later sell the 1 ETH for 1000 DAI.\\nThe transaction is submitted to the mempool, however, Alice chose a transaction fee that is too low for miners to be interested in including her transaction in a block. The transaction stays pending in the mempool for extended periods, which could be hours, days, weeks, or even longer.\\nWhen the average gas fee dropped far enough for Alice's transaction to become interesting again for miners to include it, her swap will be executed. In the meantime, the price of ETH could have drastically changed. She will still get 1 ETH but the DAI value of that output might be significantly lower.\\nShe has unknowingly performed a bad trade due to the pending transaction she forgot about.\\nAn even worse way this issue can be maliciously exploited is through MEV:\\nThe swap transaction is still pending in the mempool. Average fees are still too high for miners to be interested in it.\\nThe price of tokens has gone up significantly since the transaction was signed, meaning Alice would receive a lot more ETH when the swap is executed. But that also means that her maximum slippage value (sqrtPriceLimitX96 and minOut in terms of the Spell contracts) is outdated and would allow for significant slippage.\\nA MEV bot detects the pending transaction. Since the outdated maximum slippage value now allows for high slippage, the bot sandwiches Alice, resulting in significant profit for the bot and significant loss for Alice.

Recommended mitigation:
We recommend the protocol use block.timstamp for swapping deadline for Uniswap V2 and swap with Unsiwap Router V3 instead of the pool directly!
Vulnerability: The protocol will not be able to add liquidity on the curve with another token with a balance.
Severity: medium

Description:
The `CurveSpell` protocol only ensure approve curve pool to spend its borrow token. Hence, it will not be able to add liquidity on the curve with another token with a balance.\\nThe `openPositionFarm()` function enables user to open a leveraged position in a yield farming strategy by borrowing funds and using them to add liquidity to a Curve pool, while also taking into account certain risk management parameters such as maximum LTV and position size. When add liquidity on curve ,the protocol use the borrowed token and the collateral token, it checks the number of tokens in the pool and creates an array of the supplied token amounts to be passed to the add_liquidity function. Then the curve will transfer the tokens from the protocol and mint lp tokens to the protocol. However, the protocol only ensure approve curve pool to spend its borrow token. Hence, it will not be able to add liquidity on the curve with another token with a balance.\\n```\\n // 3. Add liquidity on curve\\n        _ensureApprove(param.borrowToken, pool, borrowBalance);\\n        if (tokens.length == 2) {\\n            uint256[2] memory suppliedAmts;\\n            for (uint256 i = 0; i < 2; i++) {\\n                suppliedAmts[i] = IERC20Upgradeable(tokens[i]).balanceOf(\\n                    address(this)\\n                );\\n            }\\n            ICurvePool(pool).add_liquidity(suppliedAmts, minLPMint);\\n        } else if (tokens.length == 3) {\\n            uint256[3] memory suppliedAmts;\\n            for (uint256 i = 0; i < 3; i++) {\\n                suppliedAmts[i] = IERC20Upgradeable(tokens[i]).balanceOf(\\n                    address(this)\\n                );\\n            }\\n            ICurvePool(pool).add_liquidity(suppliedAmts, minLPMint);\\n        } else if (tokens.length == 4) {\\n            uint256[4] memory suppliedAmts;\\n            for (uint256 i = 0; i < 4; i++) {\\n                suppliedAmts[i] = IERC20Upgradeable(tokens[i]).balanceOf(\\n                    address(this)\\n                );\\n            }\\n            ICurvePool(pool).add_liquidity(suppliedAmts, minLPMint);\\n        }\\n```\\n

Impact:
The protocol will not be able to add liquidity on the curve with another token with a balance.

Recommended mitigation:
Allow the curve pool to spend tokens that have a balance in the protocol to add liquidity
Vulnerability: `getPositionRisk()` will return a wrong value of risk
Severity: medium

Description:
In order to interact with SPELL the users need to `lend()` some collateral which is known as Isolated Collateral and the SoftVault will deposit them into Compound protocol to generate some lending interest (to earn passive yield)\\nto liquidate a position this function `isLiquidatable()` should return `true`\\n```\\n    function isLiquidatable(uint256 positionId) public view returns (bool) {\\n        return\\n            getPositionRisk(positionId) >=\\n            banks[positions[positionId].underlyingToken].liqThreshold;\\n    }\\n```\\n\\nand it is subcall to `getPositionRisk()`\\n```\\n    function getPositionRisk(\\n        uint256 positionId\\n    ) public view returns (uint256 risk) {\\n        uint256 pv = getPositionValue(positionId);          \\n        uint256 ov = getDebtValue(positionId);             \\n        uint256 cv = getIsolatedCollateralValue(positionId);\\n\\n        if (\\n            (cv == 0 && pv == 0 && ov == 0) || pv >= ov // Closed position or Overcollateralized position\\n        ) {\\n            risk = 0;\\n        } else if (cv == 0) {\\n            // Sth bad happened to isolated underlying token\\n            risk = Constants.DENOMINATOR;\\n        } else {\\n            risk = ((ov - pv) * Constants.DENOMINATOR) / cv;\\n        }\\n    }\\n```\\n\\nas we can see the `cv` is a critical value in terms of the calculation of `risk` the `cv` is returned by `getIsolatedCollateralValue()`\\n```\\n    function getIsolatedCollateralValue(\\n        uint256 positionId\\n    ) public view override returns (uint256 icollValue) {\\n        Position memory pos = positions[positionId];\\n        // NOTE: exchangeRateStored has 18 decimals.\\n        uint256 underlyingAmount;\\n        if (_isSoftVault(pos.underlyingToken)) {\\n            underlyingAmount =                                              \\n                (ICErc20(banks[pos.debtToken].bToken).exchangeRateStored() * \\n                    pos.underlyingVaultShare) /\\n                Constants.PRICE_PRECISION; \\n        } else {\\n            underlyingAmount = pos.underlyingVaultShare;\\n        }\\n        icollValue = oracle.getTokenValue(\\n            pos.underlyingToken,\\n            underlyingAmount\\n        );\\n    }\\n```\\n\\nand it uses `exchangeRateStored()` to ask Compound (CToken.sol) for the exchange rate from `CToken` contract\\n```\\nThis function does not accrue interest before calculating the exchange rate\\n```\\n\\nso the `getPositionRisk()` will return a wrong value of risk because the interest does not accrue for this position

Impact:
the user (position) could get liquidated even if his position is still healthy

Recommended mitigation:
You shoud use `exchangeRateCurrent()` to Accrue interest first.
Vulnerability: BlueBerryBank#getPositionValue causes DOS if reward token is added that doens't have an oracle
Severity: medium

Description:
collToken.pendingRewards pulls the most recent reward list from Aura/Convex. In the event that reward tokens are added to pools that don't currently have an oracle then it will DOS every action (repaying, liquidating, etc.). While this is only temporary it prevents liquidation which is a key process that should have 100% uptime otherwise the protocol could easily be left with bad debt.\\nBlueBerryBank.sol#L408-L413\\n```\\n      (address[] memory tokens, uint256[] memory rewards) = IERC20Wrapper(\\n          pos.collToken\\n      ).pendingRewards(pos.collId, pos.collateralSize);\\n      for (uint256 i; i < tokens.length; i++) {\\n          rewardsValue += oracle.getTokenValue(tokens[i], rewards[i]);\\n      }\\n```\\n\\nUsing the pendingRewards method pulls a fresh list of all tokens. When a token is added as a reward but can't be priced then the call to getTokenValue will revert. Since getPostionValue is used in liquidations, it temporarily breaks liquidations which in a volatile market can cause bad debt to accumulate.

Impact:
Temporary DOS to liquidations which can result in bad debt

Recommended mitigation:
Return zero valuation if extra reward token can't be priced.
Vulnerability: asking for the wrong address for `balanceOf()`
Severity: medium

Description:
ShortLongSpell.openPosition() pass to `_doPutCollateral()` wrong value of `balanceOf()`\\n```\\n        // 5. Put collateral - strategy token\\n        address vault = strategies[param.strategyId].vault;\\n        _doPutCollateral(\\n            vault,\\n            IERC20Upgradeable(ISoftVault(vault).uToken()).balanceOf(\\n                address(this)\\n            )\\n        );\\n```\\n\\nthe balance should be of `address(vault)`

Impact:
`openPosition()` will never work

Recommended mitigation:
```\\n        // 5. Put collateral // Remove the line below\\n strategy token\\n        address vault = strategies[param.strategyId].vault;\\n        _doPutCollateral(\\n            vault,\\n// Remove the line below\\n            IERC20Upgradeable(ISoftVault(vault).uToken()).balanceOf(\\n// Remove the line below\\n                address(this)\\n// Add the line below\\n                IERC20Upgradeable(vault).balanceOf(address(this))\\n            )\\n        );\\n```\\n
Vulnerability: AuraSpell#closePositionFarm requires users to swap all reward tokens through same router
Severity: medium

Description:
AuraSpell#closePositionFarm requires users to swap all reward tokens through same router. This is problematic as it is very unlikely that a UniswapV2 router will have good liquidity sources for all tokens and will result in users experiencing forced losses to their reward token.\\nAuraSpell.sol#L193-L203\\n```\\n    for (uint256 i = 0; i < rewardTokens.length; i++) {\\n        uint256 rewards = _doCutRewardsFee(rewardTokens[i]);\\n        _ensureApprove(rewardTokens[i], address(swapRouter), rewards);\\n        swapRouter.swapExactTokensForTokens(\\n            rewards,\\n            0,\\n            swapPath[i],\\n            address(this),\\n            type(uint256).max\\n        );\\n    }\\n```\\n\\nAll tokens are forcibly swapped through a single router.

Impact:
Users will be forced to swap through a router even if it doesn't have good liquidity for all tokens

Recommended mitigation:
Issue AuraSpell#closePositionFarm requires users to swap all reward tokens through same router\\nAllow users to use an aggregator like paraswap or multiple routers instead of only one single UniswapV2 router.