msc-smart-contract-auditing/vulnerable-functions-base huggingface dataset:
Vulnerability: The liquidated person can make the liquidator lose premium by adding collateral in advance
Severity: high

Description:
When the position with debtRatioE18 >= 1e18 or startLiqTimestamp ! = 0, the position can be liquidated. On liquidation, the liquidator needs to pay premium, but the profit is related to the position's health factor and deltaTime, and when discount == 0, the liquidator loses premium.\\n```\\n            uint deltaTime;\\n            // 1.1 check the amount of time since position is marked\\n            if (pos.startLiqTimestamp > 0) {\\n                 deltaTime = Math.max(deltaTime, block.timestamp - pos.startLiqTimestamp);\\n            }\\n            // 1.2 check the amount of time since position is past the deadline\\n             if (block.timestamp > pos.positionDeadline) {\\n                    deltaTime = Math.max(deltaTime, block.timestamp - pos.positionDeadline);\\n            }\\n            // 1.3 cap time-based discount, as configured\\n              uint timeDiscountMultiplierE18 = Math.max(\\n                IConfig(config).minLiquidateTimeDiscountMultiplierE18(),\\n                     ONE_E18 - deltaTime * IConfig(config).liquidateTimeDiscountGrowthRateE18()\\n            );\\n            // 2. calculate health-based discount factor\\n            uint curHealthFactorE18 = (ONE_E18 * ONE_E18) /\\n             getPositionDebtRatioE18(_positionManager, _user, _posId);\\n                 uint minDesiredHealthFactorE18 = IConfig(config).minDesiredHealthFactorE18s(strategy);\\n            // 2.1 interpolate linear health discount factor (according to the diagram in documentation)\\n            uint healthDiscountMultiplierE18 = ONE_E18;\\n             if (curHealthFactorE18 < ONE_E18) {\\n                 healthDiscountMultiplierE18 = curHealthFactorE18 > minDesiredHealthFactorE18\\n                     ? ((curHealthFactorE18 - minDesiredHealthFactorE18) * ONE_E18) /\\n            (ONE_E18 - minDesiredHealthFactorE18)\\n            : 0;\\n            }\\n            // 3. final liquidation discount = apply the two discount methods together\\n            liquidationDiscountMultiplierE18 =\\n            (timeDiscountMultiplierE18 * healthDiscountMultiplierE18) /\\n            ONE_E18;\\n```\\n\\nConsider the following scenario.\\nAlice notices Bob's position with debtRatioE18 >= 1e18 and calls `liquidatePosition()` to liquidate.\\nBob observes Alice's transaction, frontruns a call `markLiquidationStatus()` to make startLiqTimestamp == block.timestamp, and calls `adjustExtraColls()` to bring the position back to the health state.\\nAlice's transaction is executed, and since the startLiqTimestamp of Bob's position.startLiqTimestamp ! = 0, it can be liquidated, but since discount = 0, Alice loses premium. This breaks the protocol's liquidation mechanism and causes the liquidator not to launch liquidation for fear of losing assets, which will lead to more bad debts

Impact:

Recommended mitigation:
Consider having the liquidated person bear the premium, or at least have the liquidator use the minDiscount parameter to set the minimum acceptable discount.
Vulnerability: First depositor can steal asset tokens of others
Severity: high

Description:
The first depositor can be front run by an attacker and as a result will lose a considerable part of the assets provided. When the pool has no share supply, in `_mintInternal()`, the amount of shares to be minted is equal to the assets provided. An attacker can abuse of this situation and profit of the rounding down operation when calculating the amount of shares if the supply is non-zero.\\n```\\n        function _mintInternal(address _receiver, uint _balanceIncreased, uint _totalAsset\\n             ) internal returns (uint mintShares) {\\n                unfreezeTime[_receiver] = block.timestamp + mintFreezeInterval;\\n        if (freezeBuckets.interval > 0) {\\n             FreezeBuckets.addToFreezeBuckets(freezeBuckets, _balanceIncreased.toUint96());\\n        }\\n                 uint _totalSupply = totalSupply();\\n                    if (_totalAsset == 0 || _totalSupply == 0) {\\n                     mintShares = _balanceIncreased + _totalAsset;\\n                 } else {\\n             mintShares = (_balanceIncreased * _totalSupply) / _totalAsset;\\n             }\\n            if (mintShares == 0) {\\n        revert ZeroAmount();\\n        }\\n        _mint(_receiver, mintShares);\\n        }\\n```\\n\\nConsider the following scenario.\\nAlice wants to deposit 2M * 1e6 USDC to a pool.\\nBob observes Alice's transaction, frontruns to deposit 1 wei USDC to mint 1 wei share, and transfers 1 M * 1e6 USDC to the pool.\\nAlice's transaction is executed, since _totalAsset = 1M * 1e6 + 1 and totalSupply = 1, Alice receives 2M * 1e6 * 1 / (1M * 1e6 + 1) = 1 share.\\nThe pool now has 3M*1e6 +1 assets and distributed 2 shares. Bob profits 0.5 M and Alice loses 0.5 M USDC.

Impact:

Recommended mitigation:
When _totalSupply == 0, send the first min liquidity LP tokens to the zero address to enable share dilution Another option is to use the ERC4626 implementation(https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/extensions/ERC4626.sol#L199C14-L208) from OZ.
Vulnerability: The attacker can use larger dust when opening a position to perform griefing attacks
Severity: high

Description:
When opening a position, unused assets are sent to dustVault as dust, but since these dust are not subtracted from inputAmt, they are included in the calculation of positionOpenUSDValueE36, resulting in a small netPnLE36, which can be used by an attacker to perform a griefing attack.\\n```\\n            uint inputTotalUSDValueE36;\\n                for (uint i; i < openTokenInfos.length; ) {\\n                 inputTotalUSDValueE36 += openTokenInfos[i].inputAmt * tokenPriceE36s[i];\\n                      borrowTotalUSDValueE36 += openTokenInfos[i].borrowAmt * tokenPriceE36s[i];\\n                 unchecked {\\n            ++i;\\n                }\\n            }\\n                // 1.3 calculate net pnl (including strategy users & borrow profit)\\n            positionOpenUSDValueE36 = inputTotalUSDValueE36 + borrowTotalUSDValueE36;\\n            netPnLE36 = positionCurUSDValueE36.toInt256() - positionOpenUSDValueE36.toInt256();\\n```\\n

Impact:

Recommended mitigation:
Consider subtracting dust from inputAmt when opening a position.
Vulnerability: SwapHelper.getCalldata should check whitelistedRouters[_router]
Severity: medium

Description:
`SwapHelper.getCalldata()` returns data for swap based on the input, and uses whitelistedRouters to limit the _router param. The issue here is that when `setWhitelistedRouters()` sets the _routers state to false, it does not reset the data in routerTypes and swapInfos, which results in the router still being available in `getCalldata()`. As a result, users can still swap with invalid router data.\\n```\\n        for (uint i; i < _statuses.length; ) {\\n             whitelistedRouters[_routers[i]] = _statuses[i];\\n                 if (_statuses[i]) {\\n                 routerTypes[_routers[i]] = _types[i];\\n        emit SetRouterType(_routers[i], _types[i]);\\n        }\\n              emit SetWhitelistedRouter(_routers[i], _statuses[i]);\\n           unchecked {\\n        ++i;\\n        }\\n        }\\n```\\n

Impact:

Recommended mitigation:
Consider checking whitelistedRouters[_router] in SwapHelper.getCalldata()
Vulnerability: The swap when closing a position does not consider shareProfitAmts
Severity: medium

Description:
When closing a position, token swap is performed to ensure that the closer can repay the debt, for example, when operation == EXACT_IN, tokens of borrowAmt are required to be excluded from the swap, and when operation == EXACT_OUT, tokens of borrowAmt are required to be swapped. The issue here is that the closer needs to pay not only the borrowAmt but also the shareProfitAmts, which causes the closure to fail when percentSwapE18 = 100% due to insufficient tokens. Although the closer can adjust the percentSwapE18 to make the closure successful, it greatly increases the complexity.\\n```\\n        for (uint i; i < swapParams.length; ) {\\n        // find excess amount after repay\\n        uint swapAmt = swapParams[i].operation == SwapOperation.EXACT_IN\\n          ? IERC20(swapParams[i].tokenIn).balanceOf(address(this)) - openTokenInfos[i].borrowAmt\\n             : openTokenInfos[i].borrowAmt - IERC20(swapParams[i].tokenOut).balanceOf(address(this));\\n                 swapAmt = (swapAmt * swapParams[i].percentSwapE18) / ONE_E18\\n                 if (swapAmt == 0) {\\n              revert SwapZeroAmount();\\n             }\\n```\\n

Impact:

Recommended mitigation:
Consider taking shareProfitAmts into account when calculating swapAmt
Vulnerability: The freeze mechanism reduces the borrowableAmount, which reduces Lender's yield
Severity: medium

Description:
The contract has two freeze intervals, mintFreezeInterval and freezeBuckets.interval, the former to prevent users from making flash accesses and the latter to prevent borrowers from running out of funds. Both freeze intervals are applied when a user deposits, and due to the difference in unlocking time, it significantly reduces borrowableAmount and thus reduces Lender's yield.\\n```\\n        function _mintInternal(address _receiver,uint _balanceIncreased, uint _totalAsset\\n          ) internal returns (uint mintShares) {\\n             unfreezeTime[_receiver] = block.timestamp + mintFreezeInterval;\\n          if (freezeBuckets.interval > 0) {\\n         FreezeBuckets.addToFreezeBuckets(freezeBuckets, _balanceIncreased.toUint96());\\n        }\\n```\\n\\nConsider freezeBuckets.interval == mintFreezeInterval = 1 day, 100 ETH in the LendingPool, and borrowableAmount = 100 ETH. At day 0 + 1s, Alice deposits 50 ETH, borrowableAmount = 150 ETH**-** lockedAmount(50 ETH) = 100 ETH, the 50 ETH frozen in freezeBuckets will be unlocked on day 2, while unfreezeTime[alice] = day 1 + 1s. At day 1 + 1s, unfreezeTime[Alice] is reached, Alice can withdraw 50 ETH, borrowableAmount = 100 ETH - LockedAmount(50 ETH) = 50 ETH. If Bob wants to borrow the available funds in the Pool at this time, Bob can only borrow 50 ETH, while the available funds are actually 100 ETH, which will reduce Lender's yield by half. At day 2 + 1s, freezeBuckets is unfrozen and borrowableAmount = 100 ETH -LockedAmount(0 ETH) = 100 ETH.

Impact:

Recommended mitigation:
Consider making mintFreezeInterval >= 2 * freezeBuckets.interval, which makes unfreezeTime greater than the unfreeze time of freezeBuckets.
Vulnerability: A malicious operator can drain the vault funds in one transaction
Severity: high

Description:
The vault operator can swap tokens using the `trade()` function. They pass the following structure for each trade:\\n```\\n         struct tradeInput { \\n             address spendToken;\\n               address receiveToken;\\n                 uint256 spendAmt;\\n                   uint256 receiveAmtMin;\\n                address routerAddress;\\n         uint256 pathIndex;\\n         }\\n```\\n\\nNotably, receiveAmtMin is used to guarantee acceptable slippage. An operator can simply pass 0 to make sure the trade is executed. This allows an operator to steal all the funds in the vault by architecting a sandwich attack.\\nFlashloan a large amount of funds\\nSkew the token proportions in a pool which can be used for trading, by almost completely depleting the target token.\\nPerform the trade at >99% slippage\\nSell target tokens for source tokens on the manipulated pool, returning to the original ratio.\\nPay off the flashloan, and keep the tokens traded at 99% slippage. In fact, this attack can be done in one TX, different to most sandwich attacks.

Impact:

Recommended mitigation:
The contract should enforce sensible slippage parameters.
Vulnerability: Removing a trade path in router will cause serious data corruption
Severity: medium

Description:
The RouterInfo represents a single UniV3-compatible router which supports a list of token paths. It uses the following data structures:\\n```\\n         mapping(address => mapping(address => listInfo)) private allowedPairsMap;\\n                  pair[] private allowedPairsList;\\n```\\n\\n```\\n          struct listInfo {\\n               bool allowed;\\n                uint256 listPosition;\\n         }\\n         struct pair {\\n            address token0;\\n               address token1;\\n                  uint256 numPathsAllowed;\\n          }\\n```\\n\\nWhen an admin specifies a new path from token0 to token1, `_increasePairPaths()` is called.\\n```\\n               function _increasePairPaths(address token0, address token1) private {\\n                     listInfo storage LI = allowedPairsMap[token0][token1];\\n                  if (!LI.allowed){\\n                  LI.allowed = true;\\n                  LI.listPosition = allowedPairsList.length;\\n                      allowedPairsList.push(pair(token0, token1, 0));\\n                   }\\n                      allowedPairsList[LI.listPosition].numPathsAllowed++;\\n                   }\\n```\\n\\nWhen a path is removed, the complementary function is called.\\n```\\n      function _decreasePairPaths(address token0, address token1) private {\\n             listInfo storage LI = allowedPairsMap[token0][token1];\\n                require(LI.allowed, "RouterInfo: pair not allowed");\\n                   allowedPairsList[LI.listPosition].numPathsAllowed--;\\n            if (allowedPairsList[LI.listPosition].numPathsAllowed == 0){\\n         allowedPairsList[LI.listPosition] = \\n      allowedPairsList[allowedPairsList.length - 1];\\n         allowedPairsList.pop();\\n         LI.allowed = false;\\n      }\\n      }\\n```\\n\\nWhen the last path is removed, the contract reuses the index of the removed pair, to store the last pair in the list. It then removes the last pair, having already copied it. The issue is that the corresponding listInfo structure is not updated, to keep track of index in the pairs list. Future usage of the last pair will use a wrong index, which at this moment, is over the array bounds. When a new pair will be created, it will share the index with the corrupted pair. This can cause a variety of serious issues. For example, it will not be possible to remove paths from the corrupted pair until a new pair is created, at which point the new pair will have a wrong numPathsAllowed as it is shared.

Impact:

Recommended mitigation:
Update the listPosition member of the last pair in the list, before repositioning it.
Vulnerability: Attacker can DOS deposit transactions due to strict verifications
Severity: medium

Description:
When users deposit funds to the Vault, it verifies that the proportion between the tokens inserted to the vault matches the current vault token balances.\\n```\\n      uint256[] memory balances = vlt.balances();\\n          //ensure deposits are in the same ratios as the vault's current balances\\n          require(functions.ratiosMatch(balances, amts), "ratios don't match");\\n```\\n\\nThe essential part of the check is below:\\n```\\n         for (uint256 i = 0; i < sourceRatios.length; i++) {\\n         // if (targetRatios[i] != (targetRatios[greatestIndex] * \\n                  sourceRatios[i]) / greatest) {\\n               if (targetRatios[i] != \\n         Arithmetic.overflowResistantFraction(targetRatios[greatestIndex], sourceRatios[i], greatest)) {\\n         return false;\\n            }\\n         }\\n```\\n\\nThe exact logic here is not important, but note that a small change in the balance of one of the vault tokens will affect the expected number of tokens that need to be inserted to maintain correct ratio. The exact amounts to be deposited are passed as targetRatios, and sourceRatios is the current balances. Therefore, an attacker can directly transfer a negligible amount of some vault token to the contract to make the amount the user specified in targetRatios not line up with the expected proportion. As a result, the deposit would revert. Essentially it is an abuse of the over-granular verification of ratios, leading to a DOS of any deposit in the mempool.

Impact:

Recommended mitigation:
Loosen the restriction on deposit ratios. A DOS attack should cost an amount that the vault creditors would be happy to live with.
Vulnerability: User deposits can fail despite using the correct method for calculation of deposit amounts
Severity: medium

Description:
Users can use the `getAmtsNeededForDeposit()` function to get the amount of tokens that maintain the desired proportion for vault deposits. It will perform a calculation very similar to the one in `ratiosMatch()`, which will verify the deposit.\\n```\\n         for (uint256 i = 0; i < balances.length; i++) {\\n               if (i == indexOfReferenceToken) {\\n                amtsNeeded[i] = amtIn;\\n         } else {\\n         // amtsNeeded[i] = (amtIn * balances[i]) / \\n                  balances[indexOfReferenceToken];\\n                     amtsNeeded[i] = Arithmetic.overflowResistantFraction(amtIn, \\n                  balances[i], balances[indexOfReferenceToken]);\\n               }\\n            }\\n```\\n\\nHowever, a difference between the verification function and the getter function is that the getter receives any reference token, while the verification will use proportions based on the deposit amount in the largest balance in the vault. Indeed, these fractions may differ by a small amount. This could cause the `getAmtsNeededForDeposit()` function to respond with values which will not be accepted at deposit, since they will be rounded differently.

Impact:

Recommended mitigation:
Calculation amounts needed using the ratio between largest balance and the deposit amount. This would line up the numbers as verification would expect.
Vulnerability: Several popular ERC20 tokens are incompatible with the vault due to MAX approve
Severity: low

Description:
There are several instances where the vault approves use of funds to the manager or a trade router. It will set approval to MAX_UINT256.\\n```\\n         for (uint i = 0; i < tokens.length; i++) {\\n         //allow vault manager to withdraw tokens\\n                   IERC20(tokens[i]).safeIncreaseAllowance(ownerIn, \\n         type(uint256).max); \\n         }\\n```\\n\\nThe issue is that there are several popular tokens(https://github.com/d-xo/weird-erc20#revert-on-large-approvals--transfers) (UNI, COMP and others) which do not support allowances of above UINT_96. The contract will not be able to interoperate with them.

Impact:

Recommended mitigation:
Consider setting allowance to UINT_96. Whenever the allowance is consumed, perform re-approval up to UINT_96.
Vulnerability: Attacker can freeze deposits and withdrawals indefinitely by submitting a bad withdrawal
Severity: high

Description:
Users request to queue a withdrawal using the function below in Vault.\\n```\\n        function addWithdrawRequest(uint256 _amountMLP, address _token) external {\\n            require(isAcceptingToken(_token), "ERROR: Invalid token");\\n                require(_amountMLP != 0, "ERROR: Invalid amount");\\n        \\n        address _withdrawer = msg.sender;\\n        // Get the pending buffer and staged buffer.\\n             RequestBuffer storage _pendingBuffer = _requests(false);\\n             RequestBuffer storage _stagedBuffer = _requests(true);\\n        // Check if the withdrawer have enough balance to withdraw.\\n        uint256 _bookedAmountMLP =  _stagedBuffer.withdrawAmountPerUser[_withdrawer] + \\n       _pendingBuffer.withdrawAmountPerUser[_withdrawer];\\n            require(_bookedAmountMLP + _amountMLP <= \\n                MozaicLP(mozLP).balanceOf(_withdrawer), "Withdraw amount > amount  MLP");\\n        …\\n        emit WithdrawRequestAdded(_withdrawer, _token, chainId, _amountMLP);\\n        }\\n```\\n\\nNotice that the function only validates that the user has a sufficient LP token balance to withdraw at the moment of execution. After it is queued up, a user can move their tokens to another wallet. Later in `_settleRequests()`, the Vault will attempt to burn user's tokens:\\n```\\n                // Burn moazic LP token.\\n            MozaicLP(mozLP).burn(request.user, _mlpToBurn);\\n```\\n\\nThis would revert and block any other settlements from occurring. Therefore, users can block the entire settlement process by requesting a tiny withdrawal amount in every epoch and moving funds to another wallet.

Impact:

Recommended mitigation:
Vault should take custody of user's LP tokens when they request withdrawals. If the entire withdrawal cannot be satisfied, it can refund some tokens back to the user.
Vulnerability: Removal of Multisig members will corrupt data structures
Severity: medium

Description:
The Mozaic Multisig (the senate) can remove council members using the TYPE_DEL_OWNER operation:\\n```\\n        if(proposals[_proposalId].actionType == TYPE_DEL_OWNER) {\\n                (address _owner) = abi.decode(proposals[_proposalId].payload, (address));\\n        require(contains(_owner) != 0, "Invalid owner address");\\n            uint index = contains(_owner);\\n                for (uint256 i = index; i < councilMembers.length - 1; i++) {\\n            councilMembers[i] = councilMembers[i + 1];\\n        }\\n            councilMembers.pop();\\n                 proposals[_proposalId].executed = true;\\n                     isCouncil[_owner] = false;\\n          }\\n```\\n\\nThe code finds the owner's index in the councilMembers array, copies all subsequent members downwards, and deletes the last element. Finally, it deletes the isCouncil[_owner] entry. The issue is actually in the contains() function.\\n```\\n       function contains(address _owner) public view returns (uint) {\\n             for (uint i = 1; i <= councilMembers.length; i++) {\\n       if (councilMembers[i - 1] == _owner) {\\n                return i;\\n            }\\n        }\\n        return 0;\\n        }\\n```\\n\\nThe function returns the index following the owner's index. Therefore, the intended owner is not deleted from councilMembers, instead the one after it is. The `submitProposal()` and `confirmTransaction()` privileged functions will not be affected by the bug, as they filter by isCouncil. However, the corruption of councilMembers will make deleting the member following the currently deleted owner fail, as deletion relies on finding the member in councilMembers.

Impact:

Recommended mitigation:
Fix the `contains()` function to return the correct index of _owner
Vulnerability: Attacker could abuse victim's vote to pass their own proposal
Severity: medium

Description:
Proposals are created using submitProposal():\\n```\\n        function submitProposal(uint8 _actionType, bytes memory _payload)  public onlyCouncil {\\n             uint256 proposalId = proposalCount;\\n                 proposals[proposalId] = Proposal(msg.sender,_actionType, \\n                    _payload, 0, false);\\n                proposalCount += 1;\\n         emit ProposalSubmitted(proposalId, msg.sender);\\n        }\\n```\\n\\nAfter submission, council members approve them by calling confirmTransaction():\\n```\\n        function confirmTransaction(uint256 _proposalId) public onlyCouncil \\n            notConfirmed(_proposalId) {\\n             confirmations[_proposalId][msg.sender] = true;\\n             proposals[_proposalId].confirmation += 1;\\n        emit Confirmation(_proposalId, msg.sender);\\n        }\\n```\\n\\nNotably, the _proposalId passed to `confirmTransaction()` is simply the proposalCount at time of submission. This design allows the following scenario to occur:\\nUser A submits proposal P1\\nUser B is interested in the proposal and confirms it\\nAttacker submits proposal P2\\nA blockchain re-org occurs. Submission of P1 is dropped in place of P2.\\nUser B's confirmation is applied on top of the re-orged blockchain. Attacker gets their vote. We've seen very large re-orgs in top blockchains such as Polygon, so this threat remains a possibility to be aware of.

Impact:

Recommended mitigation:
Calculate proposalId as a hash of the proposal properties. This way, votes cannot be misdirected.