msc-smart-contract-auditing/vulnerable-functions-base huggingface dataset:
Vulnerability: claimCOMPAndTransfer() COMP may be locked into the contract
Severity: high

Description:
Malicious users can keep front-run `claimCOMPAndTransfer()` to trigger `COMPTROLLER.claimComp()` first, causing `netBalance` in `claimCOMPAndTransfer()` to be 0 all the time, resulting in `COMP` not being transferred out and locked in the contract\\n`claimCOMPAndTransfer()` use for "Claims COMP incentives earned and transfers to the treasury manager contract" The code is as follows:\\n```\\n    function claimCOMPAndTransfer(address[] calldata cTokens)\\n        external\\n        override\\n        onlyManagerContract\\n        nonReentrant\\n        returns (uint256)\\n    {\\n        uint256 balanceBefore = COMP.balanceOf(address(this));\\n        COMPTROLLER.claimComp(address(this), cTokens);\\n        uint256 balanceAfter = COMP.balanceOf(address(this));\\n\\n        // NOTE: the onlyManagerContract modifier prevents a transfer to address(0) here\\n        uint256 netBalance = balanceAfter.sub(balanceBefore);   //<-------@only transfer out `netBalance`\\n        if (netBalance > 0) {\\n            COMP.safeTransfer(msg.sender, netBalance);\\n        }\\n\\n        // NOTE: TreasuryManager contract will emit a COMPHarvested event\\n        return netBalance;\\n```\\n\\nFrom the above code, we can see that this method only turns out the difference value `netBalance` But `COMPTROLLER.claimComp()` can be called by anyone, if there is a malicious user front-run this transcation to triggers `COMPTROLLER.claimComp()` first This will cause thenetBalance to be 0 all the time, resulting in `COMP` not being transferred out and being locked in the contract.\\nThe following code is from `Comptroller.sol`\\n```\\n    function claimComp(address holder, CToken[] memory cTokens) public { //<----------anyone can call it\\n        address[] memory holders = new address[](1);\\n        holders[0] = holder;\\n        claimComp(holders, cTokens, true, true);\\n    }\\n```\\n

Impact:
`COMP` may be locked into the contract

Recommended mitigation:
Transfer all balances, not using `netBalance`
Vulnerability: StrategyVault can perform a full exit without repaying all secondary debt
Severity: high

Description:
StrategyVault can perform a full exit without repaying all secondary debt, leaving bad debt with the protocol.\\nNoted from the codebase's comment that:\\nVaults can borrow up to the capacity using the `borrowSecondaryCurrencyToVault` and `repaySecondaryCurrencyToVault` methods. Vaults that use a secondary currency must ALWAYS repay the secondary debt during redemption and handle accounting for the secondary currency themselves.\\nThus, when the StrategyVault-side performs a full exit for a vault account, Notional-side does not check that all secondary debts of that vault account are cleared (= zero) and will simply trust StrategyVault-side has already handled them properly.\\nLine 271 below shows that only validates the primary debt but not the secondary debt during a full exit.\\n```\\nFile: VaultAccountAction.sol\\n        if (vaultAccount.accountDebtUnderlying == 0 && vaultAccount.vaultShares == 0) {\\n            // If the account has no position in the vault at this point, set the maturity to zero as well\\n            vaultAccount.maturity = 0;\\n        }\\n        vaultAccount.setVaultAccount({vaultConfig: vaultConfig, checkMinBorrow: true});\\n\\n        // It's possible that the user redeems more vault shares than they lend (it is not always the case\\n        // that they will be increasing their collateral ratio here, so we check that this is the case). No\\n        // need to check if the account has exited in full (maturity == 0).\\n        if (vaultAccount.maturity != 0) {\\n            IVaultAccountHealth(address(this)).checkVaultAccountCollateralRatio(vault, account);\\n        }\\n```\\n

Impact:
Leveraged vaults are designed to be as isolated as possible to mitigate the risk to the Notional protocol and its users. However, the above implementation seems to break this principle. As such, if there is a vulnerability in the leverage vault that allows someone to exploit this issue and bypass the repayment of the secondary debt, the protocol will be left with a bad debt which affects the insolvency of the protocol.

Recommended mitigation:
Consider checking that all secondary debts of a vault account are cleared before executing a full exit.\\n```\\n// Add the line below\\n int256 accountDebtOne;\\n// Add the line below\\n int256 accountDebtTwo;\\n\\n// Add the line below\\n if (vaultConfig.hasSecondaryBorrows()) {\\n// Add the line below\\n  (/* */, accountDebtOne, accountDebtTwo) = VaultSecondaryBorrow.getAccountSecondaryDebt(vaultConfig, account, pr);\\n// Add the line below\\n }\\n\\n// Remove the line below\\n if (vaultAccount.accountDebtUnderlying == 0 && vaultAccount.vaultShares == 0) {\\n// Add the line below\\n if (vaultAccount.accountDebtUnderlying == 0 && vaultAccount.vaultShares == 0 && accountDebtOne == 0 && accountDebtTwo == 0) {\\n // If the account has no position in the vault at this point, set the maturity to zero as well\\n vaultAccount.maturity = 0;\\n}\\nvaultAccount.setVaultAccount({vaultConfig: vaultConfig, checkMinBorrow: true});\\n```\\n
Vulnerability: Unable to transfer fee reserve assets to treasury
Severity: high

Description:
Transferring fee reserve assets to the treasury manager contract will result in a revert, leading to a loss of rewards for NOTE stakers.\\n```\\nFile: TreasuryAction.sol\\n    /// @notice redeems and transfers tokens to the treasury manager contract\\n    function _redeemAndTransfer(uint16 currencyId, int256 primeCashRedeemAmount) private returns (uint256) {\\n        PrimeRate memory primeRate = PrimeRateLib.buildPrimeRateStateful(currencyId);\\n        int256 actualTransferExternal = TokenHandler.withdrawPrimeCash(\\n            treasuryManagerContract,\\n            currencyId,\\n            primeCashRedeemAmount.neg(),\\n            primeRate,\\n            true // if ETH, transfers it as WETH\\n        );\\n\\n        require(actualTransferExternal > 0);\\n        return uint256(actualTransferExternal);\\n    }\\n```\\n\\nThe value returned by the `TokenHandler.withdrawPrimeCash` function is always less than or equal to zero. Thus, the condition `actualTransferExternal > 0` will always be false, and the `_redeemAndTransfer` function will always revert.\\nThe `transferReserveToTreasury` function depends on `_redeemAndTransfer` function. Thus, it is not possible to transfer any asset to the treasury manager contract.

Impact:
The fee collected by Notional is stored in the Fee Reserve. The fee reserve assets will be transferred to Notional's Treasury to be invested into the sNOTE pool. Without the ability to do so, the NOTE stakers will not receive their rewards.

Recommended mitigation:
Negate the value returned by the `TokenHandler.withdrawPrimeCash` function.\\n```\\n        int256 actualTransferExternal = TokenHandler.withdrawPrimeCash(\\n            treasuryManagerContract,\\n            currencyId,\\n            primeCashRedeemAmount.neg(),\\n            primeRate,\\n            true // if ETH, transfers it as WETH\\n// Remove the line below\\n       );\\n// Add the line below\\n       ).neg();\\n```\\n
Vulnerability: Excess funds withdrawn from the money market
Severity: high

Description:
Excessive amounts of assets are being withdrawn from the money market.\\n```\\nFile: TokenHandler.sol\\n    function _redeemMoneyMarketIfRequired(\\n        uint16 currencyId,\\n        Token memory underlying,\\n        uint256 withdrawAmountExternal\\n    ) private {\\n        // If there is sufficient balance of the underlying to withdraw from the contract\\n        // immediately, just return.\\n        mapping(address => uint256) storage store = LibStorage.getStoredTokenBalances();\\n        uint256 currentBalance = store[underlying.tokenAddress];\\n        if (withdrawAmountExternal <= currentBalance) return;\\n\\n        IPrimeCashHoldingsOracle oracle = PrimeCashExchangeRate.getPrimeCashHoldingsOracle(currencyId);\\n        // Redemption data returns an array of contract calls to make from the Notional proxy (which\\n        // is holding all of the money market tokens).\\n        (RedeemData[] memory data) = oracle.getRedemptionCalldata(withdrawAmountExternal);\\n\\n        // This is the total expected underlying that we should redeem after all redemption calls\\n        // are executed.\\n        uint256 totalUnderlyingRedeemed = executeMoneyMarketRedemptions(underlying, data);\\n\\n        // Ensure that we have sufficient funds before we exit\\n        require(withdrawAmountExternal <= currentBalance.add(totalUnderlyingRedeemed)); // dev: insufficient redeem\\n    }\\n```\\n\\nIf the `currentBalance` is `999,900` USDC and the `withdrawAmountExternal` is `1,000,000` USDC, then there is insufficient balance in the contract, and additional funds need to be withdrawn from the money market (e.g. Compound).\\nSince the contract already has `999,900` USDC, only an additional `100` USDC needs to be withdrawn from the money market to fulfill the withdrawal request of `1,000,000` USDC\\nHowever, instead of withdrawing `100` USDC from the money market, Notional withdraw `1,000,000` USDC from the market as per the `oracle.getRedemptionCalldata(withdrawAmountExternal)` function. As a result, an excess of `990,000` USDC is being withdrawn from the money market

Impact:
This led to an excessive amount of assets idling in Notional and not generating any returns or interest in the money market, which led to a loss of assets for the users as they would receive a lower interest rate than expected and incur opportunity loss.\\nAttackers could potentially abuse this to pull the funds Notional invested in the money market leading to griefing and loss of returns/interest for the protocol.

Recommended mitigation:
Consider withdrawing only the shortfall amount from the money market.\\n```\\n// Remove the line below\\n (RedeemData[] memory data) = oracle.getRedemptionCalldata(withdrawAmountExternal);\\n// Add the line below\\n (RedeemData[] memory data) = oracle.getRedemptionCalldata(withdrawAmountExternal // Remove the line below\\n currentBalance);\\n```\\n
Vulnerability: convertFromStorage() fails to use rounding-up when converting a negative storedCashBalance into signedPrimeSupplyValue.
Severity: medium

Description:
`convertFromStorage()` fails to use rounding-up when converting a negative `storedCashBalance` into `signedPrimeSupplyValue`.\\n`convertFromStorage()` is used to convert `storedCashBalance` into `signedPrimeSupplyValue`. When `storedCashBalance` is negative, it represents a debt - positive prime cash owed.\\nUnfortunately, when converting a negative `storedCashBalance` into `signedPrimeSupplyValue`, the following division will apply a rounding-down (near zero) mode, leading to a user to owe less than it is supposed to be.\\n```\\nreturn storedCashBalance.mul(pr.debtFactor).div(pr.supplyFactor);\\n```\\n\\nThis is not acceptable. Typically, rounding should be in favor of the protocol, not in favor of the user to prevent draining of the protocol and losing funds of the protocol.\\nThe following POC shows a rounding-down will happen for a negative value division. The result of the following test is -3.\\n```\\nfunction testMod() public {\\n         \\n         int256 result = -14;\\n         result = result / 4;\\n         console2.logInt(result);\\n    }\\n```\\n

Impact:
`convertFromStorage()` fails to use rounding-up when converting a negative `storedCashBalance` into `signedPrimeSupplyValue`. The protocol is losing some dusts amount, but it can be accumulative or a vulnerability that can be exploited.

Recommended mitigation:
Use rounding-up instead.\\n```\\nfunction convertFromStorage(\\n        PrimeRate memory pr,\\n        int256 storedCashBalance\\n    ) internal pure returns (int256 signedPrimeSupplyValue) {\\n        if (storedCashBalance >= 0) {\\n            return storedCashBalance;\\n        } else {\\n            // Convert negative stored cash balance to signed prime supply value\\n            // signedPrimeSupply = (negativePrimeDebt * debtFactor) / supplyFactor\\n\\n            // cashBalance is stored as int88, debt factor is uint80 * uint80 so there\\n            // is no chance of phantom overflow (88 // Add the line below\\n 80 // Add the line below\\n 80 = 248) on mul\\n// Remove the line below\\n           return storedCashBalance.mul(pr.debtFactor).div(pr.supplyFactor);\\n// Add the line below\\n         return (storedCashBalance.mul(pr.debtFactor).sub(pr.supplyFactor// Remove the line below\\n1)).div(pr.supplyFactor);\\n        }\\n    }\\n```\\n
Vulnerability: getAccountPrimeDebtBalance() always return 0
Severity: medium

Description:
Spelling errors that result in `getAccountPrimeDebtBalance()` Always return 0\\n`getAccountPrimeDebtBalance()` use for Show current debt\\n```\\n    function getAccountPrimeDebtBalance(uint16 currencyId, address account) external view override returns (\\n        int256 debtBalance\\n    ) {\\n        mapping(address => mapping(uint256 => BalanceStorage)) storage store = LibStorage.getBalanceStorage();\\n        BalanceStorage storage balanceStorage = store[account][currencyId];\\n        int256 cashBalance = balanceStorage.cashBalance;\\n\\n        // Only return cash balances less than zero\\n        debtBalance = cashBalance < 0 ? debtBalance : 0;   //<------@audit wrong, Always return 0\\n    }\\n```\\n\\nIn the above code we can see that due to a spelling error, `debtBalance` always ==0 should use `debtBalance = cashBalance < 0 ? cashBalance : 0;`

Impact:
`getAccountPrimeDebtBalance()` is the external method to check the debt If a third party integrates with notional protocol, this method will be used to determine whether the user has debt or not and handle it accordingly, which may lead to serious errors in the third party's business

Recommended mitigation:
```\\n    function getAccountPrimeDebtBalance(uint16 currencyId, address account) external view override returns (\\n        int256 debtBalance\\n    ) {\\n        mapping(address => mapping(uint256 => BalanceStorage)) storage store = LibStorage.getBalanceStorage();\\n        BalanceStorage storage balanceStorage = store[account][currencyId];\\n        int256 cashBalance = balanceStorage.cashBalance;\\n\\n        // Only return cash balances less than zero\\n-       debtBalance = cashBalance < 0 ? debtBalance : 0;\\n+       debtBalance = cashBalance < 0 ? cashBalance : 0;\\n    }\\n```\\n
Vulnerability: Inconsistent use of `VAULT_ACCOUNT_MIN_TIME` in vault implementation
Severity: medium

Description:
There is a considerable difference in implementation behaviour when a vault has yet to mature compared to after vault settlement.\\nThere is some questionable functionality with the following `require` statement:\\n```\\nFile: VaultAccountAction.sol\\n    require(vaultAccount.lastUpdateBlockTime + Constants.VAULT_ACCOUNT_MIN_TIME <= block.timestamp)\\n```\\n\\nThe `lastUpdateBlockTime` variable is updated in two cases:\\nA user enters a vault position, updating the vault state; including `lastUpdateBlockTime`. This is a proactive measure to prevent users from quickly entering and exiting the vault.\\nThe vault has matured and as a result, each time vault fees are assessed for a given vault account, `lastUpdateBlockTime` is updated to `block.timestamp` after calculating the pro-rated fee for the prime cash vault.\\nTherefore, before a vault has matured, it is not possible to quickly enter and exit a vault. But after `Constants.VAULT_ACCOUNT_MIN_TIME` has passed, the user can exit the vault as many times as they like. However, the same does not hold true once a vault has matured. Each time a user exits the vault, they must wait `Constants.VAULT_ACCOUNT_MIN_TIME` time again to re-exit. This seems like inconsistent behaviour.

Impact:
The `exitVault()` function will ultimately affect prime and non-prime vault users differently. It makes sense for the codebase to be written in such a way that functions execute in-line with user expectations.

Recommended mitigation:
It might be worth adding an exception to `VaultConfiguration.settleAccountOrAccruePrimeCashFees()` so that when vault fees are calculated, `lastUpdatedBlockTime` is not updated to `block.timestamp`.
Vulnerability: Return data from the external call not verified during deposit and redemption
Severity: medium

Description:
The deposit and redemption functions did not verify the return data from the external call, which might cause the contract to wrongly assume that the deposit/redemption went well although the action has actually failed in the background.\\n```\\nFile: GenericToken.sol\\n    function executeLowLevelCall(\\n        address target,\\n        uint256 msgValue,\\n        bytes memory callData\\n    ) internal {\\n        (bool status, bytes memory returnData) = target.call{value: msgValue}(callData);\\n        require(status, checkRevertMessage(returnData));\\n    }\\n```\\n\\nWhen the external call within the `GenericToken.executeLowLevelCall` function reverts, the `status` returned from the `.call` will be `false`. In this case, Line 69 above will revert.\\n```\\nFile: TreasuryAction.sol\\n            for (uint256 j; j < depositData.targets.length; ++j) {\\n                // This will revert if the individual call reverts.\\n                GenericToken.executeLowLevelCall(\\n                    depositData.targets[j], \\n                    depositData.msgValue[j], \\n                    depositData.callData[j]\\n                );\\n            }\\n```\\n\\nFor deposit and redeem, Notional assumes that all money markets will revert if the deposit/mint and redeem/burn has an error. Thus, it does not verify the return data from the external call. Refer to the comment in Line 317 above.\\nHowever, this is not always true due to the following reasons:\\nSome money markets might not revert when errors occur but instead return `false (0)`. In this case, the current codebase will wrongly assume that the deposit/redemption went well although the action has failed.\\nCompound might upgrade its contracts to return errors instead of reverting in the future.

Impact:
The gist of prime cash is to integrate with multiple markets. Thus, the codebase should be written in a manner that can handle multiple markets. Otherwise, the contract will wrongly assume that the deposit/redemption went well although the action has actually failed in the background, which might potentially lead to some edge cases where assets are sent to the users even though the redemption fails.

Recommended mitigation:
Consider checking the `returnData` to ensure that the external money market returns a successful response after deposit and redemption.\\nNote that the successful response returned from various money markets might be different. Some protocols return `1` on a successful action, while Compound return zero (NO_ERROR).
Vulnerability: Vault account might not be able to exit after liquidation
Severity: medium

Description:
The vault exit might fail after a liquidation event, leading to users being unable to main their positions.\\nAssume that a large portion of the vault account gets liquidated which results in a large amount of cash deposited into the vault account's cash balance. In addition, interest will also start accruing within the vault account's cash balance.\\nLet $x$ be the `primaryCash` of a vault account after a liquidation event and interest accrual.\\nThe owner of the vault account decided to exit the vault by calling `exitVault`. Within the `exitVault` function, the `vaultAccount.tempCashBalance` will be set to $x$.\\nNext, the `lendToExitVault` function is called. Assume that the cost in prime cash terms to lend an offsetting fCash position is $-y$ (primeCashCostToLend). The `updateAccountDebt` function will be called, and the `vaultAccount.tempCashBalance` will be updated to $x + (-y) \\Rightarrow x - y$. If $x > y$, then the new `vaultAccount.tempCashBalance` will be more than zero.\\nSubsequently, the `redeemWithDebtRepayment` function will be called. However, since `vaultAccount.tempCashBalance` is larger than zero, the transaction will revert, and the owner cannot exit the vault.\\n```\\nFile: VaultConfiguration.sol\\n            if (vaultAccount.tempCashBalance < 0) {\\n                int256 x = vaultConfig.primeRate.convertToUnderlying(vaultAccount.tempCashBalance).neg();\\n                underlyingExternalToRepay = underlyingToken.convertToUnderlyingExternalWithAdjustment(x).toUint();\\n            } else {\\n                // Otherwise require that cash balance is zero. Cannot have a positive cash balance in this method\\n                require(vaultAccount.tempCashBalance == 0);\\n            }\\n```\\n

Impact:
The owner of the vault account would not be able to exit the vault to main their position. As such, their assets are stuck within the protocol.

Recommended mitigation:
Consider refunding the excess positive `vaultAccount.tempCashBalance` to the users so that `vaultAccount.tempCashBalance` will be cleared (set to zero) before calling the `redeemWithDebtRepayment` function.
Vulnerability: Inaccurate settlement reserve accounting
Severity: medium

Description:
The off-chain accounting of fCash debt or prime cash in the settlement reserve will be inaccurate due to an error when handling the conversion between signed and unsigned integers.\\nEvents will be emitted to reconcile off-chain accounting for the edge condition when leveraged vaults lend at zero interest. This event will be emitted if there is fCash debt or prime cash in the settlement reserve.\\nIn an event where `s.fCashDebtHeldInSettlementReserve > 0` and `s.primeCashHeldInSettlementReserve <= 0`, no event will be emitted. As a result, the off-chain accounting of fCash debt or prime cash in the settlement reserve will be off.\\nThe reason is that since `fCashDebtInReserve` is the negation of `s.fCashDebtHeldInSettlementReserve`, which is an unsigned integer, `fCashDebtInReserve` will always be less than or equal to 0. Therefore, `fCashDebtInReserve` > 0 will always be false and is an unsatisfiable condition.\\n```\\nFile: PrimeRateLib.sol\\n        // This is purely done to fully reconcile off chain accounting with the edge condition where\\n        // leveraged vaults lend at zero interest.\\n        int256 fCashDebtInReserve = -int256(s.fCashDebtHeldInSettlementReserve);\\n        int256 primeCashInReserve = int256(s.primeCashHeldInSettlementReserve);\\n        if (fCashDebtInReserve > 0 || primeCashInReserve > 0) {\\n            int256 settledPrimeCash = convertFromUnderlying(settlementRate, fCashDebtInReserve);\\n            int256 excessCash;\\n            if (primeCashInReserve > settledPrimeCash) {\\n                excessCash = primeCashInReserve - settledPrimeCash;\\n                BalanceHandler.incrementFeeToReserve(currencyId, excessCash);\\n            } \\n\\n            Emitter.emitSettlefCashDebtInReserve(\\n                currencyId, maturity, fCashDebtInReserve, settledPrimeCash, excessCash\\n            );\\n        }\\n```\\n

Impact:
The off-chain accounting of fCash debt or prime cash in the settlement reserve will be inaccurate. Users who rely on inaccurate accounting information to conduct any form of financial transaction will expose themselves to unintended financial risks and make ill-informed decisions.

Recommended mitigation:
It is recommended to implement the following fix:\\n```\\n// Remove the line below\\n int256 fCashDebtInReserve = // Remove the line below\\nint256(s.fCashDebtHeldInSettlementReserve);\\n// Add the line below\\n int256 fCashDebtInReserve = int256(s.fCashDebtHeldInSettlementReserve);\\n```\\n