msc-smart-contract-auditing/vulnerable-functions-base huggingface dataset:
Vulnerability: The _removeFromQueue is very gas greedy
Severity: medium

Description:
The `_removeFromQueue` function is supposed to remove `_countToRemove` elements from the queue:\\n```\\nfunction \\_removeFromQueue(uint256 \\_countToRemove) internal {\\n  for (uint256 i = 0; i < \\_countToRemove; i++) {\\n    delete withdrawalsInfo[withdrawalQueue[i]];\\n  }   \\n\\n  if (\\_countToRemove == withdrawalQueue.length) {\\n    delete withdrawalQueue;\\n  } else {\\n    uint256 \\_remainingArrLength = withdrawalQueue.length.sub(\\_countToRemove);\\n    address[] memory \\_remainingArr = new address[](\\_remainingArrLength);\\n\\n    for (uint256 i = 0; i < \\_remainingArrLength; i++) {\\n      \\_remainingArr[i] = withdrawalQueue[i.add(\\_countToRemove)];\\n    }\\n\\n    withdrawalQueue = \\_remainingArr;\\n  }\\n}\\n```\\n\\nThis function uses too much gas, which makes it easier to make attacks on the system. Even if only one request is removed and executed, this function rewrites all the requests to the storage.

Impact:

Recommended mitigation:
The data structure should be changed so this function shouldn't rewrite the requests that did not change. For example, it can be a mapping `(unit => address)` with 2 indexes `(start, end)` that are only increasing.
Vulnerability: Withdrawal with zero amount is possible
Severity: medium

Description:
When creating a withdrawal request, the amount of tokens to withdraw is passed as a parameter:\\n```\\nfunction requestWithdrawal(uint256 \\_tokensToWithdraw) external override {\\n```\\n\\nThe problem is that this parameter can be zero, and the function will be successfully executed. Moreover, this request can then be added to the queue, and the actual withdrawal will also be executed with zero value. Addresses that never added any liquidity could spam the system with these requests.

Impact:

Recommended mitigation:
Do not allow withdrawals of zero tokens.
Vulnerability: The withdrawal queue is only updated when the liquidity is added
Severity: medium

Description:
Sometimes when the amount of liquidity is not much higher than the number of tokens locked for the collateral, it's impossible to withdraw liquidity. For a user that wants to withdraw liquidity, a withdrawal request is created. If the request can't be executed, it's added to the withdrawal queue, and the user needs to wait until there's enough collateral for withdrawal. There are potentially 2 ways to achieve that: either someone adds more liquidity or some existing policies expire.\\nCurrently, the queue can only be cleared when the internal `_updateWithdrawalQueue` function is called. And it is only called in one place while adding liquidity:\\n```\\nfunction \\_addLiquidityFor(address \\_liquidityHolderAddr, uint256 \\_liquidityAmount, bool \\_isLM) internal {\\n  daiToken.transferFrom(\\_liquidityHolderAddr, address(this), \\_liquidityAmount);   \\n  \\n  uint256 \\_amountToMint = \\_liquidityAmount.mul(PERCENTAGE\\_100).div(getDAIToDAIxRatio());\\n  totalLiquidity = totalLiquidity.add(\\_liquidityAmount);\\n  \\_mintERC20(\\_liquidityHolderAddr, \\_amountToMint);\\n\\n  if (\\_isLM) {\\n    liquidityFromLM[\\_liquidityHolderAddr] = liquidityFromLM[\\_liquidityHolderAddr].add(\\_liquidityAmount);\\n  }\\n\\n  \\_updateWithdrawalQueue();\\n\\n  emit AddLiquidity(\\_liquidityHolderAddr, \\_liquidityAmount, totalLiquidity);\\n}\\n```\\n

Impact:

Recommended mitigation:
It would be better if the queue could be processed when some policies expire without adding new liquidity. For example, there may be an external function that allows users to process the queue.
Vulnerability: Optimize gas usage when checking max length of arrays
Severity: low

Description:
There are a few cases where some arrays have to be limited to a number of items.\\nAnd the max size is enforced by removing the last item if the array reached max size + 1.\\n```\\nif (leaderboard.length == MAX\\_LEADERBOARD\\_SIZE.add(1)) {\\n    leaderboard.pop();\\n}\\n```\\n\\n```\\nif (topUsers.length == MAX\\_TOP\\_USERS\\_SIZE.add(1)) {\\n    topUsers.pop();\\n}\\n```\\n\\n```\\nif (\\_addresses.length == MAX\\_GROUP\\_LEADERS\\_SIZE.add(1)) {\\n    groupsLeaders[\\_referralLink].pop();\\n}\\n```\\n\\nA simpler and cheaper way to check if an item should be removed is to change the condition to\\n```\\nif (limitedSizedArray.length > MAX\\_DEFINED\\_SIZE\\_FOR\\_ARRAY) {\\n    limitedSizedArray.pop();\\n}\\n```\\n\\nThis check does not need or do a SafeMath call (which is more expensive), and because of the limited number of items, as well as a practical impossibility to add enough items to overflow the limit, makes it a preferred way to check the maximum limit.

Impact:

Recommended mitigation:
Rewrite the checks and remove SafeMath operations, as well as the addition by 1 and change the check to a “greater than” verification.
Vulnerability: Methods return values that are never used
Severity: low

Description:
When a user calls `investDAI` these 3 methods are called internally:\\n```\\n\\_updateTopUsers();\\n\\_updateLeaderboard(\\_userTeamInfo.teamAddr);\\n\\_updateGroupLeaders(\\_userTeamInfo.teamAddr);\\n```\\n\\nEach method returns a boolean, but the value is never used. It is also unclear what the value should represent.

Impact:

Recommended mitigation:
Remove the returned variable or use it in method `investDAI`.
Vulnerability: Save some gas when looping over state arrays
Severity: low

Description:
There are a few loops over state arrays in `LiquidutyMining`.\\n```\\nfor (uint256 i = 0; i < leaderboard.length; i++) {\\n```\\n\\n```\\nfor (uint256 i = 0; i < topUsers.length; i++) {\\n```\\n\\nConsider caching the length in a local variable to reduce gas costs.\\nSimilar to\\n```\\nuint256 \\_usersNumber = allUsers.length;\\n```\\n\\n```\\nfor (uint256 i = 0; i < \\_usersNumber; i++) {\\n```\\n

Impact:

Recommended mitigation:
Reduce gas cost by caching array state length in a local variable.
Vulnerability: Optimize gas costs when handling liquidity start and end times
Severity: low

Description:
When the `LiquidityMining` contract is deployed, `startLiquidityMiningTime` saves the current block timestamp.\\n```\\nstartLiquidityMiningTime = block.timestamp;       \\n```\\n\\nThis value is never changed.\\nThere also exists an end limit calculated by `getEndLMTime`.\\n```\\nfunction getEndLMTime() public view override returns (uint256) {\\n    return startLiquidityMiningTime.add(2 weeks);\\n}\\n```\\n\\nThis value is also fixed, once the start was defined.\\nNone of the values change after the contract was deployed. This is why you can use the immutable feature provided by Solidity.\\nIt will reduce costs significantly.\\n```\\ncontract A {\\n    uint public immutable start;\\n    uint public immutable end;\\n   \\n    constructor() {\\n        start = block.timestamp;\\n        end = block.timestamp + 2 weeks;\\n    }\\n}\\n```\\n\\nThis contract defines 2 variables: `start` and `end` and their value is fixed on deploy and cannot be changed.\\nIt does not need to use `SafeMath` because there's no risk of overflowing.\\nSetting `public` on both variables creates getters, and calling `A.start()` and `A.end()` returns the respective values.\\nHaving set as immutable does not request EVM storage and makes them very cheap to access.

Impact:

Recommended mitigation:
Use Solidity's immutable feature to reduce gas costs and rename variables for consistency.\\nUse the example for inspiration.
Vulnerability: Computing the quote should be done for a positive amount of tokens
Severity: low

Description:
When a policy is bought, a quote is requested from the `PolicyQuote` contract.\\n```\\nfunction \\_buyPolicyFor(\\n  address \\_policyHolderAddr,\\n  uint256 \\_epochsNumber,\\n  uint256 \\_coverTokens\\n) internal {\\n```\\n\\n```\\nuint256 \\_totalPrice = policyQuote.getQuote(\\_totalSeconds, \\_coverTokens, address(this));\\n```\\n\\nThe `getQuote` call is then forwarded to an internal function\\n```\\nfunction getQuote(uint256 \\_durationSeconds, uint256 \\_tokens, address \\_policyBookAddr)\\n  external view override returns (uint256 \\_daiTokens)\\n{\\n  \\_daiTokens = \\_getQuote(\\_durationSeconds, \\_tokens, \\_policyBookAddr);\\n}\\n```\\n\\n```\\nfunction \\_getQuote(uint256 \\_durationSeconds, uint256 \\_tokens, address \\_policyBookAddr)\\n  internal view returns (uint256)\\n{\\n```\\n\\nThere are some basic checks that make sure the total covered tokens with the requested quote do not exceed the total liquidity. On top of that check, it makes sure the total liquidity is positive.\\n```\\nrequire(\\_totalCoverTokens.add(\\_tokens) <= \\_totalLiquidity, "PolicyBook: Requiring more than there exists");\\nrequire(\\_totalLiquidity > 0, "PolicyBook: The pool is empty");\\n```\\n\\nBut there is no check for the number of quoted tokens. It should also be positive.

Impact:

Recommended mitigation:
Add an additional check for the number of quoted tokens to be positive. The check could fail or return 0, depending on your use case.\\nIf you add a check for the number of quoted tokens to be positive, the check for `_totalLiquidity` to be positive becomes obsolete and can be removed.
Vulnerability: Anyone can win all the funds from the LiquidityMining without investing any DAI
Severity: high

Description:
When a user decides to `investDAI` in the `LiquidityMining` contract, the policy book address is passed as a parameter:\\n```\\nfunction investDAI(uint256 \\_tokensAmount, address \\_policyBookAddr) external override {\\n```\\n\\nBut this parameter is never checked and only used at the end of the function:\\n```\\nIPolicyBook(\\_policyBookAddr).addLiquidityFromLM(msg.sender, \\_tokensAmount);\\n```\\n\\nThe attacker can pass the address of a simple multisig that will process this transaction successfully without doing anything. And pretend to invest a lot of DAI without actually doing that to win all the rewards in the `LiquidityMining` contract.

Impact:

Recommended mitigation:
Check that the pool address is valid.
Vulnerability: Liquidity withdrawal can be blocked
Severity: high

Description:
The main problem in that issue is that the liquidity provider may face many potential issues when withdrawing the liquidity. Under some circumstances, a normal user will never be able to withdraw the liquidity. This issue consists of multiple factors that are interconnected and share the same solution.\\nThere are no partial withdrawals when in the queue. When the withdrawal request is added to the queue, it can only be processed fully:\\n```\\naddress \\_currentAddr = withdrawalQueue.head();\\nuint256 \\_tokensToWithdraw = withdrawalsInfo[\\_currentAddr].withdrawalAmount;\\n \\nuint256 \\_amountInDAI = convertDAIXtoDAI(\\_tokensToWithdraw);\\n \\nif (\\_availableLiquidity < \\_amountInDAI) {\\n  break;\\n}\\n```\\n\\nBut when the request is not in the queue, it can still be processed partially, and the rest of the locked tokens will wait in the queue.\\n```\\n} else if (\\_availableLiquidity < convertDAIXtoDAI(\\_tokensToWithdraw)) {\\n  uint256 \\_availableDAIxTokens = convertDAIToDAIx(\\_availableLiquidity);\\n  uint256 \\_currentWithdrawalAmount = \\_tokensToWithdraw.sub(\\_availableDAIxTokens);\\n  withdrawalsInfo[\\_msgSender()].withdrawalAmount = \\_currentWithdrawalAmount;\\n \\n  aggregatedQueueAmount = aggregatedQueueAmount.add(\\_currentWithdrawalAmount);\\n  withdrawalQueue.push(\\_msgSender());\\n \\n  \\_withdrawLiquidity(\\_msgSender(), \\_availableDAIxTokens);\\n} else {\\n```\\n\\nIf there's a huge request in the queue, it can become a bottleneck that does not allow others to withdraw even if there is enough free liquidity.\\nWithdrawals can be blocked forever by the bots.\\nThe withdrawal can only be requested if there are enough free funds in the contract. But once these funds appear, the bots can instantly buy a policy, and for the normal users, it will be impossible to request the withdrawal. Even when a withdrawal is requested and then in the queue, the same problem appears at that stage.\\nThe policy can be bought even if there are pending withdrawals in the queue.

Impact:

Recommended mitigation:
One of the solutions would be to implement the following changes, but the team should thoroughly consider them:\\nAllow people to request the withdrawal even if there is not enough liquidity at the moment.\\nDo not allow people to buy policies if there are pending withdrawals in the queue and cannot be executed.\\n(Optional) Even when the queue is empty, do not allow people to buy policies if there is not enough liquidity for the pending requests (that are not yet in the queue).\\n(Optional if the points above are implemented) Allow partial executions of the withdrawals in the queue.
Vulnerability: The totalCoverTokens can be decreased before the claim is committed
Severity: high

Description:
The `totalCoverTokens` is decreased right after the policy duration ends (_endEpochNumber). When that happens, the liquidity providers can withdraw their funds:\\n```\\npolicyHolders[\\_msgSender()] = PolicyHolder(\\_coverTokens, currentEpochNumber,\\n  \\_endEpochNumber, \\_totalPrice, \\_reinsurancePrice);\\n\\nepochAmounts[\\_endEpochNumber] = epochAmounts[\\_endEpochNumber].add(\\_coverTokens);\\n```\\n\\n```\\nuint256 \\_countOfPassedEpoch = block.timestamp.sub(epochStartTime).div(EPOCH\\_DURATION);\\n\\nnewTotalCoverTokens = totalCoverTokens;\\nlastEpochUpdate = currentEpochNumber;\\nnewEpochNumber = \\_countOfPassedEpoch.add(1);\\n\\nfor (uint256 i = lastEpochUpdate; i < newEpochNumber; i++) {\\n  newTotalCoverTokens = newTotalCoverTokens.sub(epochAmounts[i]);     \\n}\\n```\\n\\nOn the other hand, the claim can be created while the policy is still “active”. And is considered active until one week after the policy expired:\\n```\\nfunction isPolicyActive(address \\_userAddr, address \\_policyBookAddr) public override view returns (bool) {\\n  PolicyInfo storage \\_currentInfo = policyInfos[\\_userAddr][\\_policyBookAddr];\\n\\n  if (\\_currentInfo.endTime == 0) {\\n    return false;\\n  }\\n\\n  return \\_currentInfo.endTime.add(STILL\\_CLAIMABLE\\_FOR) > block.timestamp;\\n}\\n```\\n\\nBy the time when the claim is created + voted, the liquidity provider can potentially withdraw all of their funds already, and the claim will fail.

Impact:

Recommended mitigation:
Make sure that there will always be enough funds for the claim.
Vulnerability: The totalCoverTokens is not decreased after the claim happened
Severity: high

Description:
When the claim happens and the policy is removed, the `totalCoverTokens` should be decreased instantly, that's why the scheduled reduction value is removed:\\n```\\nPolicyHolder storage holder = policyHolders[claimer];\\n\\nepochAmounts[holder.endEpochNumber] = epochAmounts[holder.endEpochNumber].sub(holder.coverTokens);\\ntotalLiquidity = totalLiquidity.sub(claimAmount);\\n\\ndaiToken.transfer(claimer, claimAmount);\\n               \\ndelete policyHolders[claimer];\\npolicyRegistry.removePolicy(claimer);\\n```\\n\\nBut the `totalCoverTokens` is not changed and will have the coverage from the removed policy forever.

Impact:

Recommended mitigation:
Decrease the `totalCoverTokens` inside the `commitClaim` function.
Vulnerability: The Queue remove function does not remove the item completely
Severity: high

Description:
When removing an item in a queue, the following function is used:\\n```\\nfunction remove(UniqueAddressQueue storage baseQueue, address addrToRemove) internal returns (bool) {\\n    if (!contains(baseQueue, addrToRemove)) {\\n        return false;\\n    }\\n\\n    if (baseQueue.HEAD == addrToRemove) {\\n        return removeFirst(baseQueue);\\n    }\\n\\n    if (baseQueue.TAIL == addrToRemove) {\\n        return removeLast(baseQueue);\\n    }\\n\\n    address prevAddr = baseQueue.queue[addrToRemove].prev;\\n    address nextAddr = baseQueue.queue[addrToRemove].next;\\n    baseQueue.queue[prevAddr].next = nextAddr;\\n    baseQueue.queue[nextAddr].prev = prevAddr;\\n    baseQueue.queueLength--;\\n\\n    return true;\\n}\\n```\\n\\nAs the result, the `baseQueue.queue[addrToRemove]` is not deleted, so the `contains` function will still return `True` after the removal.

Impact:

Recommended mitigation:
Remove the element from the queue completely.
Vulnerability: The aggregatedQueueAmount value is used inconsistently
Severity: medium

Description:
The `aggregatedQueueAmount` variable represents the cumulative DAIx amount in the queue that is waiting for the withdrawal. When requesting the withdrawal, this value is used as the amount of DAI that needs to be withdrawn, which may be significantly different:\\n```\\nrequire(totalLiquidity >= totalCoverTokens.add(aggregatedQueueAmount).add(\\_daiTokensToWithdraw),\\n  "PB: Not enough available liquidity");\\n```\\n\\nThat may lead to allowing the withdrawal request even if it shouldn't be allowed and the opposite.

Impact:

Recommended mitigation:
Convert `aggregatedQueueAmount` to DAI in the `_requestWithdrawal`.
Vulnerability: The claim can only be done once
Severity: medium

Description:
When the claim happens, the policy is removed afterward:\\n```\\nfunction commitClaim(address claimer, uint256 claimAmount)\\n  external \\n  override\\n  onlyClaimVoting\\n  updateBMIDAIXStakingReward\\n{\\n  PolicyHolder storage holder = policyHolders[claimer];\\n\\n  epochAmounts[holder.endEpochNumber] = epochAmounts[holder.endEpochNumber].sub(holder.coverTokens);\\n  totalLiquidity = totalLiquidity.sub(claimAmount);\\n \\n  daiToken.transfer(claimer, claimAmount);\\n                 \\n  delete policyHolders[claimer];\\n  policyRegistry.removePolicy(claimer);\\n}\\n```\\n\\nIf the claim amount is much lower than the coverage, the users are incentivized not to submit it and wait until the end of the coverage period to accumulate all the claims into one.

Impact:

Recommended mitigation:
Allow the policyholders to submit multiple claims until the `coverTokens` is not reached.
Vulnerability: iETH.exchangeRateStored may not be accurate when invoked from external contracts
Severity: high

Description:
`iETH.exchangeRateStored` returns the exchange rate of the contract as a function of the current cash of the contract. In the case of `iETH`, current cash is calculated as the contract's ETH balance minus msg.value:\\n```\\n/\\*\\*\\n \\* @dev Gets balance of this contract in terms of the underlying\\n \\*/\\nfunction \\_getCurrentCash() internal view override returns (uint256) {\\n    return address(this).balance.sub(msg.value);\\n}\\n```\\n\\n`msg.value` is subtracted because the majority of `iETH` methods are payable, and `msg.value` is implicitly added to a contract's balance before execution begins. If `msg.value` were not subtracted, the value sent with a call could be used to inflate the contract's exchange rate artificially.\\nAs part of execution, `iETH` makes calls to the `Controller`, which performs important checks using (among other things) the stored exchange rate. When `exchangeRateStored` is invoked from the `Controller`, the call context has a `msg.value` of 0. However, the `msg.value` sent by the initial `iETH` execution is still included in the contract's balance. This means that the `Controller` receives an exchange rate inflated by the initial call's `msg.value`.\\nThis problem occurs in multiple locations in the Controller:\\n`beforeMint` uses the exchange rate to ensure the supply capacity of the market is not reached. In this case, inflation would prevent the entire supply capacity of the market from being utilized:\\n```\\n// Check the iToken's supply capacity, -1 means no limit\\nuint256 \\_totalSupplyUnderlying =\\n    IERC20Upgradeable(\\_iToken).totalSupply().rmul(\\n        IiToken(\\_iToken).exchangeRateStored()\\n    );\\nrequire(\\n    \\_totalSupplyUnderlying.add(\\_mintAmount) <= \\_market.supplyCapacity,\\n    "Token supply capacity reached"\\n);\\n```\\n\\n`beforeLiquidateBorrow` uses the exchange rate via `calcAccountEquity` to calculate the value of the borrower's collateral. In this case, inflation would increase the account's equity, which could prevent the liquidator from liquidating:\\n```\\n(, uint256 \\_shortfall, , ) = calcAccountEquity(\\_borrower);\\n\\nrequire(\\_shortfall > 0, "Account does not have shortfall");\\n```\\n

Impact:

Recommended mitigation:
Resolution\\nThis issue was addressed in commit `9876e3a` by using a modifier to track the current `msg.value` of payable functions.\\nRather than having the `Controller` query the `iETH.exchangeRateStored`, the exchange rate could be passed-in to `Controller` methods as a parameter.\\nEnsure no other components in the system rely on `iETH.exchangeRateStored` after being called from `iETH`.
Vulnerability: Fix utilization rate computation and respect reserves when lending
Severity: medium

Description:
The utilization rate `UR` of an asset forms the basis for interest calculations and is defined as `borrows / ( borrows + cash - reserves)`.\\n```\\n/\\*\\*\\n \\* @notice Calculate the utilization rate: `\\_borrows / (\\_cash + \\_borrows - \\_reserves)`\\n \\* @param \\_cash Asset balance\\n \\* @param \\_borrows Asset borrows\\n \\* @param \\_reserves Asset reserves\\n \\* @return Asset utilization [0, 1e18]\\n \\*/\\nfunction utilizationRate(\\n    uint256 \\_cash,\\n    uint256 \\_borrows,\\n    uint256 \\_reserves\\n) internal pure returns (uint256) {\\n    // Utilization rate is 0 when there are no borrows\\n    if (\\_borrows == 0) return 0;\\n\\n    return \\_borrows.mul(BASE).div(\\_cash.add(\\_borrows).sub(\\_reserves));\\n}\\n```\\n\\nThe implicit assumption here is that `reserves` <= cash; in this case — and if we define `UR` as `0` for `borrows == 0` — we have `0` <= `UR` <=1. We can view `cash` - `reserves` as “available cash”. However, the system does not guarantee that `reserves` never exceeds `cash`. If `reserves` > `cash` (and borrows + `cash` - `reserves` > 0), the formula for `UR` above gives a utilization rate above `1`. This doesn't make much sense conceptually and has undesirable technical consequences; an especially severe one is analyzed in issue 4.4.

Impact:

Recommended mitigation:
If `reserves > cash` — or, in other words, available cash is negative — this means part of the `reserves` have been borrowed, which ideally shouldn't happen in the first place. However, the `reserves` grow automatically over time, so it might be difficult to avoid this entirely. We recommend (1) avoiding this situation whenever it is possible and (2) fixing the `UR` computation such that it deals more gracefully with this scenario. More specifically:\\nLoan amounts should not be checked to be smaller than or equal to `cash` but `cash - reserves` (which might be negative). Note that the current check against `cash` happens more or less implicitly because the transfer just fails for insufficient `cash`.\\nMake the utilization rate computation return `1` if `reserves > cash` (unless borrows == `0`, in which case return `0` as is already the case).\\nRemark\\nInternally, the utilization rate and other fractional values are scaled by `1e18`. The discussion above has a more conceptual than technical perspective, so we used unscaled numbers. When making changes to the code, care must be taken to apply the scaling.