msc-smart-contract-auditing/vulnerable-functions-base huggingface dataset:
Vulnerability: `deleverageAccount` can be used by an address to enter a vault that would otherwise be restricted by the `requireValidAccount` check in `enterVault`
Severity: medium

Description:
`deleverageAccount` can be used by an address to enter a vault that would otherwise be restricted by the `requireValidAccount` check in `enterVault`\\nWhen `enterVault` in `VaultAccountAction.sol` is called, the first function that is called is `requireValidAccount`. This function checks to ensure that the passed-in `account` parameter is not a system-level `account` address:\\n```\\nrequire(account != Constants.RESERVE); // Reserve address is address(0)\\nrequire(account != address(this));\\n(\\n    uint256 isNToken,\\n    /* incentiveAnnualEmissionRate */,\\n    /* lastInitializedTime */,\\n    /* assetArrayLength */,\\n    /* parameters */\\n) = nTokenHandler.getNTokenContext(account);\\nrequire(isNToken == 0);\\n```\\n\\nWith the above checks, `requireValidAccount` ensures that any Notional system-level account cannot enter a vault. However, `deleverageAccount` in `VaultAccountAction.sol` allows liquidators to transfer vault shares from a liquidated account into their own account. In the case that a liquidator is not already entered into a vault, then `deleverageAccount` will instantiate a vault account for them (using _transferLiquidatorProfits) before depositing the liquidated account's vault shares into the newly-instantiated account. This effectively circumvents the `requireValidAccount` check in `enterVault`.

Impact:
Any address that would otherwise be restricted from entering vaults via the `requireValidAccount` check would be able to circumvent that function using `deleverageAccount`. I assume these system-level accounts are restricted from entering vaults as they have access to internal Notional state and are used across the protocol, so having them be able to enter vaults could negatively impact Notional.\\nAssuming that all the relevant Notional system accounts are smart contracts that do not allow arbitrary calls, then having any of the system accounts themselves trigger this issue is infeasible. However, as a result of another issue it is possible for a vault to force an arbitrary address to deleverage accounts, which could be used to force a Notional system account to enter into a vault.

Recommended mitigation:
Consider updating the `require` statement in `_transferLiquidatorProfits` to the following:\\n```\\nrequire(liquidator.maturity == maturity, "Vault Shares Mismatch"); // dev: has vault shares\\n```\\n\\nRemoving the option of allowing addresses that do not have a maturity in the respective vault to receive shares and therefore implicitly enter a vault prevents Notional system accounts from being able to enter into vaults.
Vulnerability: UniV2Adapter#getExecutionData doesn't properly handle native ETH swaps
Severity: medium

Description:
UniV2Adapter#getExecutionData doesn't properly account for native ETH trades which makes them impossible. Neither method selected supports direct ETH trades, and sender/target are not set correctly for TradingUtils_executeTrade to automatically convert\\n```\\nspender = address(Deployments.UNIV2_ROUTER);\\ntarget = address(Deployments.UNIV2_ROUTER);\\n// msgValue is always zero for uniswap\\n\\nif (\\n    tradeType == TradeType.EXACT_IN_SINGLE ||\\n    tradeType == TradeType.EXACT_IN_BATCH\\n) {\\n    executionCallData = abi.encodeWithSelector(\\n        IUniV2Router2.swapExactTokensForTokens.selector,\\n        trade.amount,\\n        trade.limit,\\n        data.path,\\n        from,\\n        trade.deadline\\n    );\\n} else if (\\n    tradeType == TradeType.EXACT_OUT_SINGLE ||\\n    tradeType == TradeType.EXACT_OUT_BATCH\\n) {\\n    executionCallData = abi.encodeWithSelector(\\n        IUniV2Router2.swapTokensForExactTokens.selector,\\n        trade.amount,\\n        trade.limit,\\n        data.path,\\n        from,\\n        trade.deadline\\n    );\\n}\\n```\\n\\nUniV2Adapter#getExecutionData either returns the swapTokensForExactTokens or swapExactTokensForTokens, neither of with support native ETH. It also doesn't set spender and target like UniV3Adapter, so _executeTrade won't automatically convert it to a WETH call. The result is that all Uniswap V2 calls made with native ETH will fail. Given that Notional operates in native ETH rather than WETH, this is an important feature that currently does not function.

Impact:
Uniswap V2 calls won't support native ETH

Recommended mitigation:
There are two possible solutions:\\nChange the way that target and sender are set to match the implementation in UniV3Adapter\\nModify the return data to return the correct selector for each case (swapExactETHForTokens, swapTokensForExactETH, etc.)\\nGiven that the infrastructure for Uniswap V3 already exists in TradingUtils_executeTrade the first option would be the easiest, and would give the same results considering it's basically the same as what the router is doing internally anyways.
Vulnerability: Deployments.sol uses the wrong address for UNIV2 router which causes all Uniswap V2 calls to fail
Severity: medium

Description:
Deployments.sol accidentally uses the Uniswap V3 router address for UNIV2_ROUTER which causes all Uniswap V2 calls to fail\\n```\\nIUniV2Router2 internal constant UNIV2_ROUTER = IUniV2Router2(0xE592427A0AEce92De3Edee1F18E0157C05861564);\\n```\\n\\nThe constant UNIV2_ROUTER contains the address for the Uniswap V3 router, which doesn't contain the "swapExactTokensForTokens" or "swapTokensForExactTokens" methods. As a result, all calls made to Uniswap V2 will revert.

Impact:
Uniswap V2 is totally unusable

Recommended mitigation:
Change UNIV2_ROUTER to the address of the V2 router:\\n```\\nIUniV2Router2 internal constant UNIV2_ROUTER = IUniV2Router2(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D);\\n```\\n
Vulnerability: stakingContext.auraRewardPool.withdrawAndUnwrap boolean return value not handled in Boosted3TokenPoolUtils.sol and TwoTokenPoolUtils.sol
Severity: medium

Description:
stakingContext.auraRewardPool.withdrawAndUnwrap boolean return value not handled in Boosted3TokenPoolUtils.sol and TwoTokenPoolUtils.sol\\nWhen calling function _unstakeAndExitPool,\\nthe contract withdraw BPT tokens back to the vault for redemption\\nby calling\\n```\\nstakingContext.auraRewardPool.withdrawAndUnwrap(bptClaim, false);\\n```\\n\\nhowever, the underlying call withdrawAndUnwrap returns boolean value, the contract does not handle the return value.\\nThe see the interface of the IAuraRewardPool already indicate that the underlying call returns value\\n```\\ninterface IAuraRewardPool {\\n    function withdrawAndUnwrap(uint256 amount, bool claim) external returns(bool);\\n```\\n\\nand the underlying call with BaseRewardConvexPool.sol also returns the boolean\\n```\\n    function withdrawAndUnwrap(uint256 amount, bool claim) public updateReward(msg.sender) returns(bool){\\n```\\n

Impact:
Because there are stacks of external call:\\nNotional -> auraRewardPool -> BaseRewardPool,\\nwithout handling the return value explicitly, the transaction may risk fails silently.

Recommended mitigation:
We recommend the project handle the return value when unstaking explicitly\\n```\\nbool unstaked = stakingContext.auraRewardPool.withdrawAndUnwrap(bptClaim, false);\\nrequire(unstaked, 'unstake failed');\\n```\\n
Vulnerability: stakingContext.auraBooster.deposit boolean return value not handled in Boosted3TokenPoolUtils.sol
Severity: medium

Description:
stakingContext.auraBooster.deposit boolean return value not handled in Boosted3TokenPoolUtils.sol\\nthe function _joinPoolAndStake in Boosted3TokenPoolUtils.sol is used extensively when handling the token stake.\\nHowever, when entering the stake and interacting with external contract, the logic does not handle the returned boolean value in the code below\\n```\\n        // Transfer token to Aura protocol for boosted staking\\n        stakingContext.auraBooster.deposit(stakingContext.auraPoolId, bptMinted, true); // stake = true\\n```\\n\\nIn the AuraBooster implmenetation, a Boolean is indeed returned to acknowledge that deposit is completely successfully.\\n```\\n    /**\\n     * @notice  Deposits an "_amount" to a given gauge (specified by _pid), mints a `DepositToken`\\n     *          and subsequently stakes that on Convex BaseRewardPool\\n     */\\n    function deposit(uint256 _pid, uint256 _amount, bool _stake) public returns(bool){\\n```\\n

Impact:
Notional -> AuraBooster -> BaseRewardPool\\nWithout handling the boolean value explitily, there is risk that transaction may be fail sliently.\\nBecause there are two layers of external call

Recommended mitigation:
We recommend the project handle the stakingContext.auraBooster.deposit boolean return value explicitly.\\n```\\n  // Transfer token to Aura protocol for boosted staking\\n    bool staked = stakingContext.auraBooster.deposit(stakingContext.auraPoolId, bptMinted, true); // stake = true\\n    require(staked, 'stake failed');\\n```\\n
Vulnerability: When one of the plugins is broken or paused, `deposit()` or `withdraw()` of the whole Vault contract can malfunction
Severity: medium

Description:
One malfunctioning plugin can result in the whole Vault contract malfunctioning.\\nA given plugin can temporally or even permanently becomes malfunctioning (cannot deposit/withdraw) for all sorts of reasons.\\nEg, Aave V2 Lending Pool can be paused, which will prevent multiple core functions that the Aave v2 plugin depends on from working, including `lendingPool.deposit()` and `lendingPool.withdraw()`.\\n```\\n  modifier whenNotPaused() {\\n    _whenNotPaused();\\n    _;\\n  }\\n```\\n\\n```\\n  function withdraw(\\n    address asset,\\n    uint256 amount,\\n    address to\\n  ) external override whenNotPaused returns (uint256) {\\n```\\n\\nThat's because the deposit will always goes to the first plugin, and withdraw from the last plugin first.

Impact:
When Aave V2 Lending Pool is paused, users won't be able to deposit or withdraw from the vault.\\nNeither can the owner remove the plugin nor rebalanced it to other plugins to resume operation.\\nBecause withdrawal from the plugin can not be done, and removing a plugin or rebalancing both rely on this.

Recommended mitigation:
Issue When one of the plugins is broken or paused, `deposit()` or `withdraw()` of the whole Vault contract can malfunction\\nConsider introducing a new method to pause one plugin from the Vault contract level;\\nAave V2's Lending Pool contract has a view function `paused()`, consider returning `0` for `availableForDeposit()` and ``availableForWithdrawal() when pool paused in AaveV2Plugin:\\n```\\nfunction availableForDeposit() public view override returns (uint256) {\\n    if (lendingPool.paused()) return 0;\\n    return type(uint256).max - balance();\\n}\\n```\\n\\n```\\nfunction availableForWithdrawal() public view override returns (uint256) {\\n    if (lendingPool.paused()) return 0;\\n    return balance();\\n}\\n```\\n
Vulnerability: `_withdrawFromPlugin()` will revert when `_withdrawalValues[i] == 0`
Severity: medium

Description:
When `_withdrawalValues[i] == 0` in `rebalancePlugins()`, it means NOT to rebalance this plugin.\\nHowever, the current implementation still tries to withdraw 0 from the plugin.\\nThis will revert in AaveV2Plugin as Aave V2's `validateWithdraw()` does not allow `0` withdrawals:\\n```\\n  function validateWithdraw(\\n    address reserveAddress,\\n    uint256 amount,\\n    uint256 userBalance,\\n    mapping(address => DataTypes.ReserveData) storage reservesData,\\n    DataTypes.UserConfigurationMap storage userConfig,\\n    mapping(uint256 => address) storage reserves,\\n    uint256 reservesCount,\\n    address oracle\\n  ) external view {\\n    require(amount != 0, Errors.VL_INVALID_AMOUNT);\\n```\\n\\n`removePlugin()` will also always `_withdrawFromPlugin()` even if the plugin's balance is 0, as it will also tries to withdraw 0 in that case (balance is 0).

Impact:
For AaveV2Plugin (and any future plugins that dont allow withdraw 0):\\nIn every rebalance call, it must at least withdraw 1 wei from the plugin for the rebalance to work.\\nThe plugin can not be removed or rebalanced when there is no balance in it.\\nIf such a plugin can not deposit for some reason (paused by gov, AaveV2Plugin may face that), this will further cause the whole system unable to be rebalanced until the deposit resumes for that plugin.

Recommended mitigation:
Only call `_withdrawFromPlugin()` when IPlugin(pluginAddr).balance() > 0:\\n```\\nfunction removePlugin(uint256 _index) external onlyOwner {\\n    require(_index < pluginCount, "Index out of bounds");\\n    address pluginAddr = plugins[_index];\\n    if (IPlugin(pluginAddr).balance() > 0){\\n        _withdrawFromPlugin(pluginAddr, IPlugin(pluginAddr).balance());\\n    }\\n    uint256 pointer = _index;\\n    while (pointer < pluginCount - 1) {\\n        plugins[pointer] = plugins[pointer + 1];\\n        pointer++;\\n    }\\n    delete plugins[pluginCount - 1];\\n    pluginCount--;\\n\\n    IERC20(LINK).approve(pluginAddr, 0);\\n\\n    emit PluginRemoved(pluginAddr);\\n}\\n```\\n\\n```\\nfunction rebalancePlugins(uint256[] memory _withdrawalValues) external onlyOwner {\\n    require(_withdrawalValues.length == pluginCount, "Invalid withdrawal values");\\n    for (uint256 i = 0; i < pluginCount; i++) {\\n        if (_withdrawalValues[i] > 0)\\n            _withdrawFromPlugin(plugins[i], _withdrawalValues[i]);\\n    }\\n    _distributeToPlugins();\\n}\\n```\\n
Vulnerability: Unregulated joining fees
Severity: medium

Description:
Observe the _deposit function\\nThis makes call to join function\\n```\\nfunction join(uint256 amount) external override joiningNotPaused {\\n        uint256 fee = amount.mul(joiningFee).div(BASIS_PRECISION);\\n        uint256 mintedAmount = mint(amount.sub(fee));\\n        claimableFees = claimableFees.add(fee);\\n\\n        // TODO: tx.origin will be deprecated in a future ethereum upgrade\\n        latestJoinBlock[tx.origin] = block.number;\\n        token.safeTransferFrom(msg.sender, address(this), amount);\\n\\n        emit Joined(msg.sender, amount, mintedAmount);\\n    }\\n```\\n\\nAs we can see this join function deducts a fees from the deposited amount before minting. Lets see this joining fees\\nThe joining fees is introduced using setJoiningFee function\\n```\\nfunction setJoiningFee(uint256 fee) external onlyOwner {\\n        require(fee <= BASIS_PRECISION, "TrueFiPool: Fee cannot exceed transaction value");\\n        joiningFee = fee;\\n        emit JoiningFeeChanged(fee);\\n    }\\n```\\n\\nThis means the joiningFee will always be in between 0 to BASIS_PRECISION. This BASIS_PRECISION can be 100% as shown\\n```\\nuint256 private constant BASIS_PRECISION = 10000;\\n```\\n\\nThis means if joiningFee is set to BASIS_PRECISION then all user deposit will goto joining fees with user getting nothing

Impact:
Contract will lose all deposited funds

Recommended mitigation:
Issue Unregulated joining fees\\nPost calling join, check amount of shares minted for this user (use balanceOF on TrueFiPool2.sol) and if it is below minimum expected revert the transaction\\n```\\nuint256 tfUsdcBalance = tfUSDC.balanceOf(address(this));\\nrequire(tfUsdcBalance>=minSharesExpected, "Too high fees");\\n```\\n
Vulnerability: CTokenOracle.sol#getCErc20Price contains critical math error
Severity: high

Description:
CTokenOracle.sol#getCErc20Price contains a math error that immensely overvalues CTokens\\nCTokenOracle.sol#L66-L76\\n```\\nfunction getCErc20Price(ICToken cToken, address underlying) internal view returns (uint) {\\n    /*\\n        cToken Exchange rates are scaled by 10^(18 - 8 + underlying token decimals) so to scale\\n        the exchange rate to 18 decimals we must multiply it by 1e8 and then divide it by the\\n        number of decimals in the underlying token. Finally to find the price of the cToken we\\n        must multiply this value with the current price of the underlying token\\n    */\\n    return cToken.exchangeRateStored()\\n    .mulDivDown(1e8 , IERC20(underlying).decimals())\\n    .mulWadDown(oracle.getPrice(underlying));\\n}\\n```\\n\\nIn L74, IERC20(underlying).decimals() is not raised to the power of 10. The results in the price of the LP being overvalued by many order of magnitudes. A user could deposit one CToken and drain the reserves of every liquidity pool.

Impact:
All lenders could be drained of all their funds due to excessive over valuation of CTokens cause by this error

Recommended mitigation:
Issue CTokenOracle.sol#getCErc20Price contains critical math error\\nFix the math error by changing L74:\\n```\\nreturn cToken.exchangeRateStored()\\n.mulDivDown(1e8 , 10 ** IERC20(underlying).decimals())\\n.mulWadDown(oracle.getPrice(underlying));\\n   \\n```\\n\\nSentiment Team\\nFixed as recommended. PR here.\\nLead Senior Watson\\nConfirmed fix.
Vulnerability: ERC4626Oracle Vulnerable To Price Manipulation
Severity: medium

Description:
ERC4626 oracle is vulnerable to price manipulation. This allows an attacker to increase or decrease the price to carry out various attacks against the protocol.\\nThe `getPrice` function within the `ERC4626Oracle` contract is vulnerable to price manipulation because the price can be increased or decreased within a single transaction/block.\\nBased on the `getPrice` function, the price of the LP token of an ERC4626 vault is dependent on the `ERC4626.previewRedeem` and `oracleFacade.getPrice` functions. If the value returns by either `ERC4626.previewRedeem` or `oracleFacade.getPrice` can be manipulated within a single transaction/block, the price of the LP token of an ERC4626 vault is considered to be vulnerable to price manipulation.\\n```\\nFile: ERC4626Oracle.sol\\n    function getPrice(address token) external view returns (uint) {\\n        uint decimals = IERC4626(token).decimals();\\n        return IERC4626(token).previewRedeem(\\n            10 ** decimals\\n        ).mulDivDown(\\n            oracleFacade.getPrice(IERC4626(token).asset()),\\n            10 ** decimals\\n        );\\n    }\\n```\\n\\nIt was observed that the `ERC4626.previewRedeem` couldbe manipulated within a single transaction/block. As shown below, the `previewRedeem` function will call the `convertToAssets` function. Within the `convertToAssets`, the number of assets per share is calculated based on the current/spot total assets and current/spot supply that can be increased or decreased within a single block/transaction by calling the vault's deposit, mint, withdraw or redeem functions. This allows the attacker to artificially inflate or deflate the price within a single block/transaction.\\n```\\nFile: ERC4626.sol\\n    function previewRedeem(uint256 shares) public view virtual returns (uint256) {\\n        return convertToAssets(shares);\\n    }\\n```\\n\\n```\\nFile: ERC4626.sol\\n    function convertToAssets(uint256 shares) public view virtual returns (uint256) {\\n        uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.\\n\\n        return supply == 0 ? shares : shares.mulDivDown(totalAssets(), supply);\\n    }\\n```\\n

Impact:
The attacker could perform price manipulation to make the apparent value of an asset to be much higher or much lower than the true value of the asset. Following are some risks of price manipulation:\\nAn attacker can increase the value of their collaterals to increase their borrowing power so that they can borrow more assets than they are allowed from Sentiment.\\nAn attacker can decrease the value of some collaterals and attempt to liquidate another user account prematurely.

Recommended mitigation:
Avoid using `previewRedeem` function to calculate the price of the LP token of an ERC4626 vault. Consider implementing TWAP so that the price cannot be inflated or deflated within a single block/transaction or within a short period of time.\\nSentiment Team\\nDepends on the integration itself, so there's no action that can be taken right now.\\nLead Senior Watson\\nAcknowledged.
Vulnerability: LToken's implmentation is not fully up to EIP-4626's specification
Severity: medium

Description:
Note: This issue is a part of the extra scope added by Sentiment AFTER the audit contest. This scope was only reviewed by WatchPug and relates to these three PRs:\\nLending deposit cap\\nFee accrual modification\\nCRV staking\\nLToken's implmentation is not fully up to EIP-4626's specification. This issue is would actually be considered a Low issue if it were a part of a Sherlock contest.\\n```\\nfunction maxMint(address) public view virtual returns (uint256) {\\n    return type(uint256).max;\\n}\\n```\\n\\nMUST return the maximum amount of shares mint would allow to be deposited to receiver and not cause a revert, which MUST NOT be higher than the actual maximum that would be accepted (it should underestimate if necessary). This assumes that the user has infinite assets, i.e. MUST NOT rely on balanceOf of asset.\\nmaxMint() and maxDeposit() should reflect the limitation of maxSupply.

Impact:
Could cause unexpected behavior in the future due to non-compliance with EIP-4626 standard.

Recommended mitigation:
maxMint() and maxDeposit() should reflect the limitation of maxSupply.\\nConsider changing maxMint() and maxDeposit() to:\\n```\\nfunction maxMint(address) public view virtual returns (uint256) {\\n    if (totalSupply >= maxSupply) {\\n        return 0;\\n    }\\n    return maxSupply - totalSupply;\\n}\\n```\\n\\n```\\nfunction maxDeposit(address) public view virtual returns (uint256) {\\n    return convertToAssets(maxMint(address(0)));\\n}\\n```\\n\\nSentiment Team\\nFixed as recommended. PR here.\\nLead Senior Watson\\nConfirmed fix.
Vulnerability: `UniV2LPOracle` will malfunction if token0 or token1's `decimals != 18`
Severity: high

Description:
When one of the LP token's underlying tokens `decimals` is not 18, the price of the LP token calculated by `UniV2LPOracle` will be wrong.\\n`UniV2LPOracle` is an implementation of Alpha Homora v2's Fair Uniswap's LP Token Pricing Formula:\\nThe Formula ... of combining fair asset prices and fair asset reserves:\\n$$ P = 2\\cdot \\frac{\\sqrt{r_0 \\cdot r_1} \\cdot \\sqrt{p_0\\cdot p_1}}{totalSupply}, $$\\nwhere $r_i$ is the asset ii's pool balance and $p_i$ is the asset $i$'s fair price.\\nHowever, the current implementation wrongful assumes $r_0$ and $r_1$ are always in 18 decimals.\\n```\\nfunction getPrice(address pair) external view returns (uint) {\\n    (uint r0, uint r1,) = IUniswapV2Pair(pair).getReserves();\\n\\n    // 2 * sqrt(r0 * r1 * p0 * p1) / totalSupply\\n    return FixedPointMathLib.sqrt(\\n        r0\\n        .mulWadDown(r1)\\n        .mulWadDown(oracle.getPrice(IUniswapV2Pair(pair).token0()))\\n        .mulWadDown(oracle.getPrice(IUniswapV2Pair(pair).token1()))\\n    )\\n    .mulDivDown(2e27, IUniswapV2Pair(pair).totalSupply());\\n}\\n```\\n\\n```\\nuint256 internal constant WAD = 1e18; // The scalar of ETH and most ERC20s.\\n\\nfunction mulWadDown(uint256 x, uint256 y) internal pure returns (uint256) {\\n    return mulDivDown(x, y, WAD); // Equivalent to (x * y) / WAD rounded down.\\n}\\n```\\n\\n```\\nfunction mulDivDown(\\n    uint256 x,\\n    uint256 y,\\n    uint256 denominator\\n) internal pure returns (uint256 z) {\\n    assembly {\\n        // Store x * y in z for now.\\n        z := mul(x, y)\\n\\n        // Equivalent to require(denominator != 0 && (x == 0 || (x * y) / x == y))\\n        if iszero(and(iszero(iszero(denominator)), or(iszero(x), eq(div(z, x), y)))) {\\n            revert(0, 0)\\n        }\\n\\n        // Divide z by the denominator.\\n        z := div(z, denominator)\\n    }\\n}\\n```\\n

Impact:
When the decimals of one or both tokens in the pair is not 18, the price will be way off.

Recommended mitigation:
Issue `UniV2LPOracle` will malfunction if token0 or token1's `decimals != 18`\\nConsider normalizing r0 and r1 to 18 decimals before using them in the formula.\\nSentiment Team\\nFixed as recommended. PRs here and here.\\nLead Senior Watson\\nConfirmed fix.