msc-smart-contract-auditing/vulnerable-functions-base huggingface dataset:
Vulnerability: Destination Vault rewards are not added to idleIncrease when info.totalAssetsPulled > info.totalAssetsToPull
Severity: high

Description:
In the `_withdraw` function, Destination Vault rewards will be first recorded in `info.IdleIncrease` by `info.idleIncrease += _baseAsset.balanceOf(address(this)) - assetPreBal - assetPulled;`.\\nBut when `info.totalAssetsPulled` > info.totalAssetsToPull, `info.idleIncrease` is directly assigned as `info.totalAssetsPulled` - info.totalAssetsToPull, and `info.totalAssetsPulled` is `assetPulled` without considering Destination Vault rewards.\\n```\\n                uint256 assetPreBal = _baseAsset.balanceOf(address(this));\\n                uint256 assetPulled = destVault.withdrawBaseAsset(sharesToBurn, address(this));\\n\\n                // Destination Vault rewards will be transferred to us as part of burning out shares\\n                // Back into what that amount is and make sure it gets into idle\\n                info.idleIncrease += _baseAsset.balanceOf(address(this)) - assetPreBal - assetPulled;\\n                info.totalAssetsPulled += assetPulled;\\n                info.debtDecrease += totalDebtBurn;\\n\\n                // It's possible we'll get back more assets than we anticipate from a swap\\n                // so if we do, throw it in idle and stop processing. You don't get more than we've calculated\\n                if (info.totalAssetsPulled > info.totalAssetsToPull) {\\n                    info.idleIncrease = info.totalAssetsPulled - info.totalAssetsToPull;\\n                    info.totalAssetsPulled = info.totalAssetsToPull;\\n                    break;\\n                }\\n```\\n\\nFor example,\\n```\\n                    // preBal == 100 pulled == 10 reward == 5 toPull == 6\\n                    // idleIncrease = 115 - 100 - 10 == 5\\n                    // totalPulled(0) += assetPulled == 10 > toPull\\n                    // idleIncrease = totalPulled - toPull == 4 < reward\\n```\\n\\nThe final `info.idleIncrease` does not record the reward, and these assets are not ultimately recorded by the Vault.

Impact:
The final `info.idleIncrease` does not record the reward, and these assets are not ultimately recorded by the Vault.\\nMeanwhile, due to the `recover` function's inability to extract the `baseAsset`, this will result in no operations being able to handle these Destination Vault rewards, ultimately causing these assets to be frozen within the contract.

Recommended mitigation:
`info.idleIncrease = info.totalAssetsPulled - info.totalAssetsToPull;` -> `info.idleIncrease += info.totalAssetsPulled - info.totalAssetsToPull;`
Vulnerability: Incorrect number of shares minted as fee
Severity: high

Description:
```\\nFile: LMPVault.sol\\n            profit = (currentNavPerShare - effectiveNavPerShareHighMark) * totalSupply;\\n            fees = profit.mulDiv(performanceFeeBps, (MAX_FEE_BPS ** 2), Math.Rounding.Up);\\n            if (fees > 0 && sink != address(0)) {\\n                // Calculated separate from other mints as normal share mint is round down\\n                shares = _convertToShares(fees, Math.Rounding.Up);\\n                _mint(sink, shares);\\n                emit Deposit(address(this), sink, fees, shares);\\n            }\\n```\\n\\nAssume that the following states:\\nThe `profit` is 100 WETH\\nThe fee is 20%, so the `fees` will be 20 WETH.\\n`totalSupply` is 100 shares and `totalAssets()` is 1000 WETH\\nLet the number of shares to be minted be $shares2mint$. The current implementation uses the following formula (simplified) to determine $shares2mint$.\\n$$ \\begin{align} shares2mint &= fees \\times \\frac{totalSupply}{totalAsset()} \\ &= 20\\ WETH \\times \\frac{100\\ shares}{1000\\ WETH} \\ &= 2\\ shares \\end{align} $$\\nIn this case, two (2) shares will be minted to the `sink` address as the fee is taken.\\nHowever, the above formula used in the codebase is incorrect. The total cost/value of the newly-minted shares does not correspond to the fee taken. Immediately after the mint, the value of the two (2) shares is worth only 19.60 WETH, which does not correspond to the 20 WETH fee that the `sink` address is entitled to.\\n$$ \\begin{align} value &= 2\\ shares \\times \\frac{1000\\ WETH}{100 + 2\\ shares} \\ &= 2\\ shares \\times 9.8039\\ WETH\\ &= 19.6078\\ WETH \\end{align} $$

Impact:
Loss of fee. Fee collection is an integral part of the protocol; thus the loss of fee is considered a High issue.

Recommended mitigation:
The correct formula to compute the number of shares minted as fee should be as follows:\\n$$ \\begin{align} shares2mint &= \\frac{profit \\times performanceFeeBps \\times totalSupply}{(totalAsset() \\times MAX_FEE_BPS) - (performanceFeeBps \\times profit) } \\ &= \\frac{100\\epsilon \\times 2000 \\times 100 shares}{(1000\\epsilon \\times 10000) - (2000 \\times 100\\epsilon)} \\ &= 2.0408163265306122448979591836735\\ shares \\end{align} $$\\nThe following is the proof to show that `2.0408163265306122448979591836735` shares are worth 20 WETH after the mint.\\n$$ \\begin{align} value &= 2.0408163265306122448979591836735\\ shares \\times \\frac{1000\\ WETH}{100 + 2.0408163265306122448979591836735\\ shares} \\ &= 2.0408163265306122448979591836735\\ shares \\times 9.8039\\ WETH\\ &= 20\\ WETH \\end{align} $$
Vulnerability: Maverick oracle can be manipulated
Severity: high

Description:
In the MavEthOracle contract, `getPriceInEth` function utilizes the reserves of the Maverick pool and multiplies them with the external prices of the tokens (obtained from the rootPriceOracle contract) to calculate the total value of the Maverick position.\\n```\\n// Get reserves in boosted position.\\n(uint256 reserveTokenA, uint256 reserveTokenB) = boostedPosition.getReserves();\\n\\n// Get total supply of lp tokens from boosted position.\\nuint256 boostedPositionTotalSupply = boostedPosition.totalSupply();\\n\\nIRootPriceOracle rootPriceOracle = systemRegistry.rootPriceOracle();\\n\\n// Price pool tokens.\\nuint256 priceInEthTokenA = rootPriceOracle.getPriceInEth(address(pool.tokenA()));\\nuint256 priceInEthTokenB = rootPriceOracle.getPriceInEth(address(pool.tokenB()));\\n\\n// Calculate total value of each token in boosted position.\\nuint256 totalBoostedPositionValueTokenA = reserveTokenA * priceInEthTokenA;\\nuint256 totalBoostedPositionValueTokenB = reserveTokenB * priceInEthTokenB;\\n\\n// Return price of lp token in boosted position.\\nreturn (totalBoostedPositionValueTokenA + totalBoostedPositionValueTokenB) / boostedPositionTotalSupply;\\n```\\n\\nHowever, the reserves of a Maverick position can fluctuate when the price of the Maverick pool changes. Therefore, the returned price of this function can be manipulated by swapping a significant amount of tokens into the Maverick pool. An attacker can utilize a flash loan to initiate a swap, thereby changing the price either upwards or downwards, and subsequently swapping back to repay the flash loan.\\nAttacker can decrease the returned price of MavEthOracle by swapping a large amount of the higher value token for the lower value token, and vice versa.\\nHere is a test file that demonstrates how the price of the MavEthOracle contract can be manipulated by swapping to change the reserves.

Impact:
There are multiple impacts that an attacker can exploit by manipulating the price of MavEthOracle:\\nDecreasing the oracle price to lower the totalDebt of LMPVault, in order to receive more LMPVault shares.\\nIncreasing the oracle price to raise the totalDebt of LMPVault, in order to receive more withdrawn tokens.\\nManipulating the results of the Stats contracts to cause miscalculations for the protocol.

Recommended mitigation:
Use another calculation for Maverick oracle
Vulnerability: Aura/Convex rewards are stuck after DOS
Severity: high

Description:
Anyone can claim Convex rewards for any account.\\n```\\nfunction getReward(address _account, bool _claimExtras) public updateReward(_account) returns(bool){\\n    uint256 reward = earned(_account);\\n    if (reward > 0) {\\n        rewards[_account] = 0;\\n        rewardToken.safeTransfer(_account, reward);\\n        IDeposit(operator).rewardClaimed(pid, _account, reward);\\n        emit RewardPaid(_account, reward);\\n    }\\n\\n    //also get rewards from linked rewards\\n    if(_claimExtras){\\n        for(uint i=0; i < extraRewards.length; i++){\\n            IRewards(extraRewards[i]).getReward(_account);\\n        }\\n    }\\n    return true;\\n}\\n```\\n\\nIn ConvexRewardsAdapter, the rewards are accounted for by using balanceBefore/after.\\n```\\nfunction _claimRewards(\\n    address gauge,\\n    address defaultToken,\\n    address sendTo\\n) internal returns (uint256[] memory amounts, address[] memory tokens) {\\n\\n  uint256[] memory balancesBefore = new uint256[](totalLength);\\n    uint256[] memory amountsClaimed = new uint256[](totalLength);\\n// rest of code\\n\\n  for (uint256 i = 0; i < totalLength; ++i) {\\n        uint256 balance = 0;\\n        // Same check for "stash tokens"\\n        if (IERC20(rewardTokens[i]).totalSupply() > 0) {\\n            balance = IERC20(rewardTokens[i]).balanceOf(account);\\n        }\\n\\n        amountsClaimed[i] = balance - balancesBefore[i];\\n\\n return (amountsClaimed, rewardTokens);\\n```\\n\\nAdversary can call the external convex contract's `getReward(tokemakContract)`. After this, the reward tokens are transferred to Tokemak without an accounting hook.\\nNow, when Tokemak calls claimRewards, then no new rewards are transferred, because the attacker already transferred them. `amountsClaimed` will be 0.

Impact:
Rewards are stuck in the LiquidationRow contract and not queued to the MainRewarder.

Recommended mitigation:
Don't use balanceBefore/After. You could consider using `balanceOf(address(this))` after claiming to see the full amount of tokens in the contract. This assumes that only the specific rewards balance is in the contract.
Vulnerability: Malicious or compromised admin of certain LSTs could manipulate the price
Severity: medium

Description:
Important Per the contest detail page, admins of the external protocols are marked as "Restricted" (Not Trusted). This means that any potential issues arising from the external protocol's admin actions (maliciously or accidentally) are considered valid in the context of this audit.\\nQ: Are the admins of the protocols your contracts integrate with (if any) TRUSTED or RESTRICTED?\\nRESTRICTED\\nNote This issue also applies to other support Liquid Staking Tokens (LSTs) where the admin could upgrade the token contract code. Those examples are omitted for brevity, as the write-up and mitigation are the same and would duplicate this issue.\\nPer the contest detail page, the protocol will hold and interact with the Swell ETH (swETH).\\nLiquid Staking Tokens\\nswETH: 0xf951E335afb289353dc249e82926178EaC7DEd78\\nUpon inspection of the swETH on-chain contract, it was found that it is a Transparent Upgradeable Proxy. This means that the admin of Swell protocol could upgrade the contracts.\\nTokemak relies on the `swEth.swETHToETHRate()` function to determine the price of the swETH LST within the protocol. Thus, a malicious or compromised admin of Swell could upgrade the contract to have the `swETHToETHRate` function return an extremely high to manipulate the total values of the vaults, resulting in users being able to withdraw more assets than expected, thus draining the LMPVault.\\n```\\nFile: SwEthEthOracle.sol\\n    function getPriceInEth(address token) external view returns (uint256 price) {\\n        // Prevents incorrect config at root level.\\n        if (token != address(swEth)) revert Errors.InvalidToken(token);\\n\\n        // Returns in 1e18 precision.\\n        price = swEth.swETHToETHRate();\\n    }\\n```\\n

Impact:
Loss of assets in the scenario as described above.

Recommended mitigation:
The protocol team should be aware of the above-mentioned risks and consider implementing additional controls to reduce the risks.\\nReview each of the supported LSTs and determine how much power the Liquid staking protocol team/admin has over its tokens.\\nFor LSTs that are more centralized (e.g., Liquid staking protocol team could update the token contracts or have the ability to update the exchange rate/price to an arbitrary value without any limit), those LSTs should be subjected to additional controls or monitoring, such as implementing some form of circuit breakers if the price deviates beyond a reasonable percentage to reduce the negative impact to Tokemak if it happens.
Vulnerability: Incorrect handling of Stash Tokens within the `ConvexRewardsAdapter._claimRewards()`
Severity: medium

Description:
The primary task of the `ConvexRewardAdapter._claimRewards()` function revolves around claiming rewards for Convex/Aura staked LP tokens.\\n```\\nfunction _claimRewards(\\n    address gauge,\\n    address defaultToken,\\n    address sendTo\\n) internal returns (uint256[] memory amounts, address[] memory tokens) {\\n    // rest of code \\n\\n    // Record balances before claiming\\n    for (uint256 i = 0; i < totalLength; ++i) {\\n        // The totalSupply check is used to identify stash tokens, which can\\n        // substitute as rewardToken but lack a "balanceOf()"\\n        if (IERC20(rewardTokens[i]).totalSupply() > 0) {\\n            balancesBefore[i] = IERC20(rewardTokens[i]).balanceOf(account);\\n        }\\n    }\\n\\n    // Claim rewards\\n    bool result = rewardPool.getReward(account, /*_claimExtras*/ true);\\n    if (!result) {\\n        revert RewardAdapter.ClaimRewardsFailed();\\n    }\\n\\n    // Record balances after claiming and calculate amounts claimed\\n    for (uint256 i = 0; i < totalLength; ++i) {\\n        uint256 balance = 0;\\n        // Same check for "stash tokens"\\n        if (IERC20(rewardTokens[i]).totalSupply() > 0) {\\n            balance = IERC20(rewardTokens[i]).balanceOf(account);\\n        }\\n\\n        amountsClaimed[i] = balance - balancesBefore[i];\\n\\n        if (sendTo != address(this) && amountsClaimed[i] > 0) {\\n            IERC20(rewardTokens[i]).safeTransfer(sendTo, amountsClaimed[i]);\\n        }\\n    }\\n\\n    RewardAdapter.emitRewardsClaimed(rewardTokens, amountsClaimed);\\n\\n    return (amountsClaimed, rewardTokens);\\n}\\n```\\n\\nAn intriguing aspect of this function's logic lies in its management of "stash tokens" from AURA staking. The check to identify whether `rewardToken[i]` is a stash token involves attempting to invoke `IERC20(rewardTokens[i]).totalSupply()`. If the returned total supply value is `0`, the implementation assumes the token is a stash token and bypasses it. However, this check is flawed since the total supply of stash tokens can indeed be non-zero. For instance, at this address, the stash token has `totalSupply = 150467818494283559126567`, which is definitely not zero.\\nThis misstep in checking can potentially lead to a Denial-of-Service (DOS) situation when calling the `claimRewards()` function. This stems from the erroneous attempt to call the `balanceOf` function on stash tokens, which lack the `balanceOf()` method. Consequently, such incorrect calls might incapacitate the destination vault from claiming rewards from AURA, resulting in protocol losses.

Impact:
The `AuraRewardsAdapter.claimRewards()` function could suffer from a Denial-of-Service (DOS) scenario.\\nThe destination vault's ability to claim rewards from AURA staking might be hampered, leading to protocol losses.

Recommended mitigation:
To accurately determine whether a token is a stash token, it is advised to perform a low-level `balanceOf()` call to the token and subsequently validate the call's success.
Vulnerability: `navPerShareHighMark` not reset to 1.0
Severity: medium

Description:
The LMPVault will only collect fees if the current NAV (currentNavPerShare) is more than the last NAV (effectiveNavPerShareHighMark).\\n```\\nFile: LMPVault.sol\\n    function _collectFees(uint256 idle, uint256 debt, uint256 totalSupply) internal {\\n        address sink = feeSink;\\n        uint256 fees = 0;\\n        uint256 shares = 0;\\n        uint256 profit = 0;\\n\\n        // If there's no supply then there should be no assets and so nothing\\n        // to actually take fees on\\n        if (totalSupply == 0) {\\n            return;\\n        }\\n\\n        uint256 currentNavPerShare = ((idle + debt) * MAX_FEE_BPS) / totalSupply;\\n        uint256 effectiveNavPerShareHighMark = navPerShareHighMark;\\n\\n        if (currentNavPerShare > effectiveNavPerShareHighMark) {\\n            // Even if we aren't going to take the fee (haven't set a sink)\\n            // We still want to calculate so we can emit for off-chain analysis\\n            profit = (currentNavPerShare - effectiveNavPerShareHighMark) * totalSupply;\\n```\\n\\nAssume the current LMPVault state is as follows:\\ntotalAssets = 15 WETH\\ntotalSupply = 10 shares\\nNAV/share = 1.5\\neffectiveNavPerShareHighMark = 1.5\\nAlice owned all the remaining shares in the vault, and she decided to withdraw all her 10 shares. As a result, the `totalAssets` and `totalSupply` become zero. It was found that when all the shares have been exited, the `effectiveNavPerShareHighMark` is not automatically reset to 1.0.\\nAssume that at some point later, other users started to deposit into the LMPVault, and the vault invests the deposited WETH to profitable destination vaults, resulting in the real/actual NAV rising from 1.0 to 1.49 over a period of time.\\nThe system is designed to collect fees when there is a rise in NAV due to profitable investment from sound rebalancing strategies. However, since the `effectiveNavPerShareHighMark` has been set to 1.5 previously, no fee is collected when the NAV rises from 1.0 to 1.49, resulting in a loss of fee.

Impact:
Loss of fee. Fee collection is an integral part of the protocol; thus the loss of fee is considered a High issue.

Recommended mitigation:
Consider resetting the `navPerShareHighMark` to 1.0 whenever a vault has been fully exited.\\n```\\nfunction _withdraw(\\n    uint256 assets,\\n    uint256 shares,\\n    address receiver,\\n    address owner\\n) internal virtual returns (uint256) {\\n..SNIP..\\n    _burn(owner, shares);\\n    \\n// Add the line below\\n if (totalSupply() == 0) navPerShareHighMark = MAX_FEE_BPS;\\n\\n    emit Withdraw(msg.sender, receiver, owner, returnedAssets, shares);\\n\\n    _baseAsset.safeTransfer(receiver, returnedAssets);\\n\\n    return returnedAssets;\\n}\\n```\\n
Vulnerability: Vault cannot be added back into the vault registry
Severity: medium

Description:
When removing a vault from the registry, all states related to the vaults such as the `_vaults`, `_assets`, `_vaultsByAsset` are cleared except the `_vaultsByType` state.\\n```\\n    function removeVault(address vaultAddress) external onlyUpdater {\\n        Errors.verifyNotZero(vaultAddress, "vaultAddress");\\n\\n        // remove from vaults list\\n        if (!_vaults.remove(vaultAddress)) revert VaultNotFound(vaultAddress);\\n\\n        address asset = ILMPVault(vaultAddress).asset();\\n\\n        // remove from assets list if this was the last vault for that asset\\n        if (_vaultsByAsset[asset].length() == 1) {\\n            //slither-disable-next-line unused-return\\n            _assets.remove(asset);\\n        }\\n\\n        // remove from vaultsByAsset mapping\\n        if (!_vaultsByAsset[asset].remove(vaultAddress)) revert VaultNotFound(vaultAddress);\\n\\n        emit VaultRemoved(asset, vaultAddress);\\n    }\\n```\\n\\nThe uncleared `_vaultsByType` state will cause the `addVault` function to revert when trying to add the vault back into the registry even though the vault does not exist in the registry anymore.\\n```\\n    if (!_vaultsByType[vaultType].add(vaultAddress)) revert VaultAlreadyExists(vaultAddress);\\n```\\n

Impact:
The `addVault` function is broken in the edge case when the updater tries to add the vault back into the registry after removing it. It affects all the operations of the protocol that rely on the vault registry.

Recommended mitigation:
Clear the `_vaultsByType` state when removing the vault from the registry.\\n```\\n        function removeVault(address vaultAddress) external onlyUpdater {\\n            Errors.verifyNotZero(vaultAddress, "vaultAddress");\\n// Add the line below\\n            ILMPVault vault = ILMPVault(vaultAddress);\\n// Add the line below\\n            bytes32 vaultType = vault.vaultType();\\n\\n            // remove from vaults list\\n            if (!_vaults.remove(vaultAddress)) revert VaultNotFound(vaultAddress);\\n\\n            address asset = ILMPVault(vaultAddress).asset();\\n\\n            // remove from assets list if this was the last vault for that asset\\n            if (_vaultsByAsset[asset].length() == 1) {\\n                //slither-disable-next-line unused-return\\n                _assets.remove(asset);\\n            }\\n\\n            // remove from vaultsByAsset mapping\\n            if (!_vaultsByAsset[asset].remove(vaultAddress)) revert VaultNotFound(vaultAddress);\\n// Add the line below\\n           if (!_vaultsByType[vaultType].remove(vaultAddress)) revert VaultNotFound(vaultAddress);\\n\\n            emit VaultRemoved(asset, vaultAddress);\\n        }\\n```\\n
Vulnerability: LMPVault: DoS when `feeSink` balance hits `perWalletLimit`
Severity: medium

Description:
`_collectFees` mints shares to `feeSink`.\\n```\\nfunction _collectFees(uint256 idle, uint256 debt, uint256 totalSupply) internal {\\n    address sink = feeSink;\\n    // rest of code.\\n    if (fees > 0 && sink != address(0)) {\\n        // Calculated separate from other mints as normal share mint is round down\\n        shares = _convertToShares(fees, Math.Rounding.Up);\\n        _mint(sink, shares);\\n        emit Deposit(address(this), sink, fees, shares);\\n    }\\n    // rest of code.\\n}\\n```\\n\\n`_mint` calls `_beforeTokenTransfer` internally to check if the target wallet exceeds `perWalletLimit`.\\n```\\nfunction _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual override whenNotPaused {\\n    // rest of code.\\n    if (balanceOf(to) + amount > perWalletLimit) {\\n        revert OverWalletLimit(to);\\n    }\\n}\\n```\\n\\n`_collectFees` function will revert if `balanceOf(feeSink) + fee shares > perWalletLimit`. `updateDebtReporting`, `rebalance` and `flashRebalance` call `_collectFees` internally so they will be unfunctional.

Impact:
`updateDebtReporting`, `rebalance` and `flashRebalance` won't be working if `feeSink` balance hits `perWalletLimit`.

Recommended mitigation:
Allow `feeSink` to exceeds `perWalletLimit`.