msc-smart-contract-auditing/vulnerable-functions-base huggingface dataset:
Vulnerability: Harvester.rewardTokens doesn't account for LDO tokens
Severity: medium

Description:
As part of the vault's strategy, the reward tokens for participating in Curve's ETH-stETH pool and Convex staking are claimed and swapped for ETH. This is done by having the `ConvexPositionHandler` contract call the reward claims API from Convex via `baseRewardPool.getReward()`, which transfers the reward tokens to the handler's address. Then, the tokens are iterated through and sent to the harvester to be swapped from `ConvexPositionHandler` by getting their list from `harvester.rewardTokens()` and calling `harvester.harvest()`\\n```\\n// get list of tokens to transfer to harvester\\naddress[] memory rewardTokens = harvester.rewardTokens();\\n//transfer them\\nuint256 balance;\\nfor (uint256 i = 0; i < rewardTokens.length; i++) {\\n    balance = IERC20(rewardTokens[i]).balanceOf(address(this));\\n\\n    if (balance > 0) {\\n        IERC20(rewardTokens[i]).safeTransfer(\\n            address(harvester),\\n            balance\\n        );\\n    }\\n}\\n\\n// convert all rewards to WETH\\nharvester.harvest();\\n```\\n\\nHowever, `harvester.rewardTokens()` doesn't have the LDO token's address in its list, so they will not be transferred to the harvester to be swapped.\\n```\\nfunction rewardTokens() external pure override returns (address[] memory) {\\n    address[] memory rewards = new address[](2);\\n    rewards[0] = address(crv);\\n    rewards[1] = address(cvx);\\n    return rewards;\\n}\\n```\\n\\nAs a result, `harvester.harvest()` will not be able to execute its `_swapLidoForWETH()` function since its `ldoBalance` will be 0. This results in missed rewards and therefore yield for the vault as part of its normal flow.\\nThere is a possible mitigation in the current state of the contract that would require governance to call `sweep()` on the LDO balance from the `BaseTradeExecutor` contract (that `ConvexPositionHandler` inherits) and then transferring those LDO tokens to the harvester contract to perform the swap at a later rewards claim. This, however, requires transactions separate from the intended flow of the system as well as governance intervention.

Impact:

Recommended mitigation:
Add the LDO token address to the `rewardTokens()` function by adding the following line `rewards[2] = address(ldo);`
Vulnerability: Approving MAX_UINT amount of ERC20 tokens
Severity: low

Description:
Approving the maximum value of uint256 is a known practice to save gas. However, this pattern was proven to increase the impact of an attack many times in the past, in case the approved contract gets hacked.\\n```\\nIERC20(vaultWantToken()).approve(vault, MAX\\_INT);\\n```\\n\\n```\\nIERC20(vaultInfo.tokenAddress).approve(vaultAddress, type(uint256).max);\\n```\\n\\n```\\nIERC20(LP\\_TOKEN).safeApprove(ETH\\_STETH\\_POOL, type(uint256).max);\\n\\n// Approve max LP tokens to convex booster\\nIERC20(LP\\_TOKEN).safeApprove(\\n    address(CONVEX\\_BOOSTER),\\n    type(uint256).max\\n);\\n```\\n\\n```\\ncrv.safeApprove(address(crveth), type(uint256).max);\\n// max approve CVX to CVX/ETH pool on curve\\ncvx.safeApprove(address(cvxeth), type(uint256).max);\\n// max approve LDO to uniswap swap router\\nldo.safeApprove(address(uniswapRouter), type(uint256).max);\\n```\\n\\n```\\nIERC20(wantTokenL2).safeApprove(\\n    address(UniswapV3Controller.uniswapRouter),\\n    type(uint256).max\\n);\\n// approve max susd balance to uniV3 router\\nLyraController.sUSD.safeApprove(\\n    address(UniswapV3Controller.uniswapRouter),\\n    type(uint256).max\\n);\\n```\\n

Impact:

Recommended mitigation:
Consider approving the exact amount that's needed to be transferred, or alternatively, add an external function that allows the revocation of approvals.
Vulnerability: Batcher.depositFunds may allow for more deposits than vaultInfo.maxAmount
Severity: low

Description:
As part of a gradual rollout strategy, the Brahma-fi system of contracts has a limit of how much can be deposited into the protocol. This is implemented through the `Batcher` contract that allows users to deposit into it and keep the amount they have deposited in the `depositLedger[recipient]` state variable. In order to cap how much is deposited, the user's input `amountIn` is evaluated within the following statement:\\n```\\nrequire(\\n    IERC20(vaultInfo.vaultAddress).totalSupply() +\\n        pendingDeposit -\\n        pendingWithdrawal +\\n        amountIn <=\\n        vaultInfo.maxAmount,\\n    "MAX\\_LIMIT\\_EXCEEDED"\\n);\\n```\\n\\nHowever, while `pendingDeposit`, `amountIn`, and `vaultInfo.maxAmount` are denominated in the vault asset token (WETH in our case), `IERC20(vaultInfo.vaultAddress).totalSupply()` and `pendingWithdrawal` represent vault shares tokens, creating potential mismatches in this evaluation.\\nAs the yield brings in more and more funds to the vault, the amount of share minted for each token deposited in decreases, so `totalSupply()` becomes less than the total deposited amount (not just vault funds) as the strategy succeeds over time. For example, at first `X` deposited tokens would mint `X` shares. After some time, this would create additional funds in the vault through yield, and another `X` deposit of tokens would mint less than `X` shares, say `X-Y`, where `Y` is some number greater than 0 representing the difference in the number of shares minted. So, while there were `2*X` deposited tokens, `totalSupply()=(2*X-Y)` shares would have been minted in total. However, at the time of the next deposit, a user's `amountIn` will be added with `totalSupply()=(2*X-Y)` number of shares instead of a greater `2*X` number of deposited tokens. So, this will undershoot the actual amount of tokens deposited after this user's deposit, thus potentially evaluating it less than `maxAmount`, and letting more user deposits get inside the vault than what was intended.

Impact:

Recommended mitigation:
Consider either documenting this potential discrepancy or keeping track of all deposits in a state variable and using that inside the `require` statement..
Vulnerability: BaseTradeExecutor.confirmDeposit | confirmWithdraw - Violation of the “checks-effects-interactions” pattern
Severity: low

Description:
Both `confirmDeposit, confirmWithdraw` might be re-entered by the keeper (in case it is a contract), in case the derived contract allows the execution of untrusted code.\\n```\\nfunction confirmDeposit() public override onlyKeeper {\\n    require(depositStatus.inProcess, "DEPOSIT\\_COMPLETED");\\n    \\_confirmDeposit();\\n    depositStatus.inProcess = false;\\n}\\n```\\n\\n```\\nfunction confirmWithdraw() public override onlyKeeper {\\n    require(withdrawalStatus.inProcess, "WIHDRW\\_COMPLETED");\\n    \\_confirmWithdraw();\\n    withdrawalStatus.inProcess = false;\\n}\\n```\\n

Impact:

Recommended mitigation:
Although the impact is very limited, it is recommended to implement the “checks-effects-interactions” in both functions.
Vulnerability: Reactivated gauges can't queue up rewards
Severity: high

Description:
Active gauges as set in `ERC20Gauges.addGauge()` function by authorised users get their rewards queued up in the `FlywheelGaugeRewards._queueRewards()` function. As part of it, their associated struct `QueuedRewards` updates its `storedCycle` value to the cycle in which they get queued up:\\n```\\ngaugeQueuedRewards[gauge] = QueuedRewards({\\n    priorCycleRewards: queuedRewards.priorCycleRewards + completedRewards,\\n    cycleRewards: uint112(nextRewards),\\n    storedCycle: currentCycle\\n});\\n```\\n\\nHowever, these gauges may be deactivated in `ERC20Gauges.removeGauge()`, and they will now be ignored in either `FlywheelGaugeRewards.queueRewardsForCycle()` or `FlywheelGaugeRewards.queueRewardsForCyclePaginated()` because both use `gaugeToken.gauges()` to get the set of gauges for which to queue up rewards for the cycle, and that only gives active gauges. Therefore, any updates `FlywheelGaugeRewards` makes to its state will not be done to deactivated gauges' `QueuedRewards` structs. In particular, the `gaugeCycle` contract state variable will keep advancing throughout its cycles, while `QueuedRewards.storedCycle` will retain its previously set value, which is the cycle where it was queued and not 0.\\nOnce reactivated later with at least 1 full cycle being done without it, it will produce issues. It will now be returned by `gaugeToken.gauges()` to be processed in either FlywheelGaugeRewards.queueRewardsForCycle()or `FlywheelGaugeRewards.queueRewardsForCyclePaginated()`, but, once the reactivated gauge is passed to `_queueRewards()`, it will fail an assert:\\n```\\nassert(queuedRewards.storedCycle == 0 || queuedRewards.storedCycle >= lastCycle);\\n```\\n\\nThis is because it already has a set value from the cycle it was processed in previously (i.e. storedCycle>0), and, since that cycle is at least 1 full cycle behind the state contract, it will also not pass the second condition `queuedRewards.storedCycle >= lastCycle`.\\nThe result is that this gauge is locked out of queuing up for rewards because `queuedRewards.storedCycle` is only synchronised with the contract's cycle later in `_queueRewards()` which will now always fail for this gauge.

Impact:

Recommended mitigation:
Account for the reactivated gauges that previously went through the rewards queue process, such as introducing a separate flow for newly activated gauges. However, any changes such as removing the above mentioned `assert()` should be carefully validated for other downstream logic that may use the `QueuedRewards.storedCycle` value. Therefore, it is recommended to review the state transitions as opposed to only passing this specific check.
Vulnerability: Reactivated gauges have incorrect accounting for the last cycle's rewards
Severity: medium

Description:
As described in https://github.com/ConsenSysDiligence/fei-labs-audit-2022-04/issues/3, reactivated gauges that previously had queued up rewards have a mismatch between their `storedCycle` and contract's `gaugeCycle` state variable.\\nDue to this mismatch, there is also a resulting issue with the accounting logic for its completed rewards:\\n```\\nuint112 completedRewards = queuedRewards.storedCycle == lastCycle ? queuedRewards.cycleRewards : 0;\\n```\\n\\nConsequently, this then produces an incorrect value for QueuedRewards.priorCycleRewards:\\n```\\npriorCycleRewards: queuedRewards.priorCycleRewards + completedRewards,\\n```\\n\\nAs now `completedRewards` will be equal to 0 instead of the previous cycle's rewards for that gauge. This may cause a loss of rewards accounted for this gauge as this value is later used in `getAccruedRewards()`.

Impact:

Recommended mitigation:
Consider changing the logic of the check so that `storedCycle` values further in the past than `lastCycle` may produce the right rewards return for this expression, such as using `<=` instead of `==` and adding an explicit check for `storedCycle` `==` 0 to account for the initial scenario.
Vulnerability: Lack of input validation in delegateBySig
Severity: low

Description:
```\\nfunction delegateBySig(\\n    address delegatee,\\n    uint256 nonce,\\n    uint256 expiry,\\n    uint8 v,\\n    bytes32 r,\\n    bytes32 s\\n) public {\\n    require(block.timestamp <= expiry, "ERC20MultiVotes: signature expired");\\n    address signer = ecrecover(\\n        keccak256(\\n            abi.encodePacked(\\n                "\\x19\\x01",\\n                DOMAIN\\_SEPARATOR(),\\n                keccak256(abi.encode(DELEGATION\\_TYPEHASH, delegatee, nonce, expiry))\\n            )\\n        ),\\n        v,\\n        r,\\n        s\\n    );\\n    require(nonce == nonces[signer]++, "ERC20MultiVotes: invalid nonce");\\n    \\_delegate(signer, delegatee);\\n}\\n```\\n

Impact:

Recommended mitigation:
Introduce a zero address check i.e `require signer!=address(0)` and check if the recovered signer is an expected address. Refer to ERC20's permit for inspiration.
Vulnerability: Decreasing maxGauges does not account for users' previous gauge list size.
Severity: low

Description:
`ERC20Gauges` contract has a `maxGauges` state variable meant to represent the maximum amount of gauges a user can allocate to. As per the natspec, it is meant to protect against gas DOS attacks upon token transfer to allow complicated transactions to fit in a block. There is also a function `setMaxGauges` for authorised users to decrease or increase this state variable.\\n```\\nfunction setMaxGauges(uint256 newMax) external requiresAuth {\\n    uint256 oldMax = maxGauges;\\n    maxGauges = newMax;\\n\\n    emit MaxGaugesUpdate(oldMax, newMax);\\n}\\n```\\n\\nHowever, if it is decreased and there are users that have already reached the previous maximum that was larger, there may be unexpected behavior. All of these users' gauges will remain active and manageable, such as have user gauge weights incremented or decremented. So it could be possible that for such a user address `user_address`, numUserGauges(user_address) > `maxGauges`. While in the current contract logic this does not cause issues, `maxGauges` is a public variable that may be used by other systems. If unaccounted for, this discrepancy between the contract's `maxGauges` and the users' actual number of gauges given by `numUserGauges()` could, for example, cause gauges to be skipped or fail loops bounded by `maxGauges` in other systems' logic that try and go through all user gauges.

Impact:

Recommended mitigation:
Either document the potential discrepancy between the user gauges size and the `maxGauges` state variable, or limit `maxGauges` to be only called within the contract thereby forcing other contracts to retrieve user gauge list size through `numUserGauges()`.
Vulnerability: Decrementing a gauge by 0 that is not in the user gauge list will fail an assert.
Severity: low

Description:
`ERC20Gauges._decrementGaugeWeight` has an edge case scenario where a user can attempt to decrement a `gauge` that is not in the user `gauge` list by 0 `weight`, which would trigger a failure in an assert.\\n```\\nfunction \\_decrementGaugeWeight(\\n    address user,\\n    address gauge,\\n    uint112 weight,\\n    uint32 cycle\\n) internal {\\n    uint112 oldWeight = getUserGaugeWeight[user][gauge];\\n\\n    getUserGaugeWeight[user][gauge] = oldWeight - weight;\\n    if (oldWeight == weight) {\\n        // If removing all weight, remove gauge from user list.\\n        assert(\\_userGauges[user].remove(gauge));\\n    }\\n```\\n\\nAs `_decrementGaugeWeight`, `decrementGauge`, or `decrementGauges` don't explicitly check that a `gauge` belongs to the user, the contract logic continues with its operations in `_decrementGaugeWeight` for any gauges passed to it. In general this is fine because if a user tries to decrement non-zero `weight` from a `gauge` they have no allocation to, thus getting `getUserGaugeWeight[user][gauge]=0`, there would be a revert due to a negative value being passed to `getUserGaugeWeight[user][gauge]`\\n```\\nuint112 oldWeight = getUserGaugeWeight[user][gauge];\\n\\ngetUserGaugeWeight[user][gauge] = oldWeight - weight;\\n```\\n\\nHowever, passing a `weight=0` parameter with a `gauge` that doesn't belong to the user, would successfully process that line. This would then be followed by an evaluation `if (oldWeight == weight)`, which would also succeed since both are 0, to finally reach an assert that will verify a remove of that `gauge` from the user `gauge` list. However, it will fail since it was never there in the first place.\\n```\\nassert(\\_userGauges[user].remove(gauge));\\n```\\n\\nAlthough an edge case with no effect on contract state's health, it may happen with front end bugs or incorrect user transactions, and it is best not to have asserts fail.

Impact:

Recommended mitigation:
Replace `assert()` with a `require()` or verify that the gauge belongs to the user prior to performing any operations.
Vulnerability: Undelegating 0 votes from an address who is not a delegate of a user will fail an assert.
Severity: low

Description:
Similar scenario with issue 5.5. `ERC20MultiVotes._undelegate` has an edge case scenario where a user can attempt to undelegate from a `delegatee` that is not in the user delegates list by 0 `amount`, which would trigger a failure in an assert.\\n```\\nfunction \\_undelegate(\\n    address delegator,\\n    address delegatee,\\n    uint256 amount\\n) internal virtual {\\n    uint256 newDelegates = \\_delegatesVotesCount[delegator][delegatee] - amount;\\n\\n    if (newDelegates == 0) {\\n        assert(\\_delegates[delegator].remove(delegatee)); // Should never fail.\\n    }\\n```\\n\\nAs `_undelegate`, or `undelegate` don't explicitly check that a `delegatee` belongs to the user, the contract logic continues with its operations in `_undelegate` for the `delegatee` passed to it. In general this is fine because if a user tries to `undelegate` non-zero `amount` from a `delegatee` they have no votes delegated to, thus getting `_delegatesVotesCount[delegator][delegatee]=0`, there would be a revert due to a negative value being passed to `uint256 newDelegates`\\n```\\nuint256 newDelegates = \\_delegatesVotesCount[delegator][delegatee] - amount;\\n```\\n\\nHowever, passing a `amount=0` parameter with a `delegatee` that doesn't belong to the user, would successfully process that line. This would then be followed by an evaluation `if (newDelegates == 0)`, which would succeed, to finally reach an assert that will verify a remove of that `delegatee` from the user delegates list. However, it will fail since it was never there in the first place.\\n```\\nassert(\\_delegates[delegator].remove(delegatee)); // Should never fail.\\n```\\n\\nAlthough an edge case with no effect on contract state's health, it may happen with front end bugs or incorrect user transactions, and it is best not to have asserts fail, as per the dev comment in that line “// Should never fail”.

Impact:

Recommended mitigation:
Replace `assert()` with a `require()` or verify that the delegatee belongs to the user prior to performing any operations.
Vulnerability: xTRIBE.emitVotingBalances - DelegateVotesChanged event can be emitted by anyone
Severity: medium

Description:
`xTRIBE.emitVotingBalances` is an external function without authentication constraints. It means anyone can call it and emit `DelegateVotesChanged` which may impact other layers of code that rely on these events.\\n```\\nfunction emitVotingBalances(address[] calldata accounts) external {\\n    uint256 size = accounts.length;\\n\\n    for (uint256 i = 0; i < size; ) {\\n        emit DelegateVotesChanged(accounts[i], 0, getVotes(accounts[i]));\\n\\n        unchecked {\\n            i++;\\n        }\\n    }\\n}\\n```\\n

Impact:

Recommended mitigation:
Consider restricting access to this function for allowed accounts only.
Vulnerability: Decreasing maxGauges does not account for users' previous gauge list size.
Severity: low

Description:
`ERC20Gauges` contract has a `maxGauges` state variable meant to represent the maximum amount of gauges a user can allocate to. As per the natspec, it is meant to protect against gas DOS attacks upon token transfer to allow complicated transactions to fit in a block. There is also a function `setMaxGauges` for authorised users to decrease or increase this state variable.\\n```\\nfunction setMaxGauges(uint256 newMax) external requiresAuth {\\n uint256 oldMax = maxGauges;\\n maxGauges = newMax;\\n\\n emit MaxGaugesUpdate(oldMax, newMax);\\n}\\n```\\n\\nHowever, if it is decreased and there are users that have already reached the previous maximum that was larger, there may be unexpected behavior. All of these users' gauges will remain active and manageable, such as have user gauge weights incremented or decremented. So it could be possible that for such a user address `user_address`, numUserGauges(user_address) > `maxGauges`. While in the current contract logic this does not cause issues, `maxGauges` is a public variable that may be used by other systems. If unaccounted for, this discrepancy between the contract's `maxGauges` and the users' actual number of gauges given by `numUserGauges()` could, for example, cause gauges to be skipped or fail loops bounded by `maxGauges` in other systems' logic that try and go through all user gauges.

Impact:

Recommended mitigation:
Either document the potential discrepancy between the user gauges size and the `maxGauges` state variable, or limit `maxGauges` to be only called within the contract thereby forcing other contracts to retrieve user gauge list size through `numUserGauges()`.
Vulnerability: Accounts that claim incentives immediately before the migration will be stuck
Severity: medium

Description:
For accounts that existed before the migration to the new incentive calculation, the following happens when they claim incentives for the first time after the migration: First, the incentives that are still owed from before the migration are computed according to the old formula; the incentives since the migration are calculated according to the new logic, and the two values are added together. The first part - calculating the pre-migration incentives according to the old formula - happens in function MigrateIncentives.migrateAccountFromPreviousCalculation; the following lines are of particular interest in the current context:\\n```\\nuint256 timeSinceMigration = finalMigrationTime - lastClaimTime;\\n\\n// (timeSinceMigration \\* INTERNAL\\_TOKEN\\_PRECISION \\* finalEmissionRatePerYear) / YEAR\\nuint256 incentiveRate =\\n    timeSinceMigration\\n        .mul(uint256(Constants.INTERNAL\\_TOKEN\\_PRECISION))\\n        // Migration emission rate is stored as is, denominated in whole tokens\\n        .mul(finalEmissionRatePerYear).mul(uint256(Constants.INTERNAL\\_TOKEN\\_PRECISION))\\n        .div(Constants.YEAR);\\n\\n// Returns the average supply using the integral of the total supply.\\nuint256 avgTotalSupply = finalTotalIntegralSupply.sub(lastClaimIntegralSupply).div(timeSinceMigration);\\n```\\n\\nThe division in the last line will throw if `finalMigrationTime` and `lastClaimTime` are equal. This will happen if an account claims incentives immediately before the migration happens - where “immediately” means in the same block. In such a case, the account will be stuck as any attempt to claim incentives will revert.

Impact:

Recommended mitigation:
The function should return `0` if `finalMigrationTime` and `lastClaimTime` are equal. Moreover, the variable name `timeSinceMigration` is misleading, as the variable doesn't store the time since the migration but the time between the last incentive claim and the migration.
Vulnerability: type(T).max is inclusive
Severity: low

Description:
Throughout the codebase, there are checks whether a number can be represented by a certain type.\\n```\\nrequire(accumulatedNOTEPerNToken < type(uint128).max); // dev: accumulated NOTE overflow\\n```\\n\\n```\\nrequire(blockTime < type(uint32).max); // dev: block time overflow\\n```\\n\\n```\\nrequire(totalSupply <= type(uint96).max);\\nrequire(blockTime <= type(uint32).max);\\n```\\n\\nSometimes these checks use `<=`, sometimes they use `<`.

Impact:

Recommended mitigation:
`type(T).max` is inclusive, i.e., it is the greatest number that can be represented with type `T`. Strictly speaking, it can and should therefore be used consistently with `<=` instead of `<`.
Vulnerability: Fliquidator - excess funds of native tokens are not returned
Severity: medium

Description:
`FliquidatorFTM.batchLiquidate` accepts the `FTM` native token and checks if at least an amount of `debtTotal` was provided with the call. The function continues using the `debtTotal` value. If a caller provides msg.value > `debtTotal`, excess funds are not returned and remain in the contract. `FliquidatorFTM` is not upgradeable, and there is no way to recover the surplus funds.\\n```\\nif (vAssets.borrowAsset == FTM) {\\n require(msg.value >= debtTotal, Errors.VL\\_AMOUNT\\_ERROR);\\n} else {\\n```\\n

Impact:

Recommended mitigation:
Consider returning excess funds. Consider making `_constructParams` public to allow the caller to pre-calculate the `debtTotal` that needs to be provided with the call.\\nConsider removing support for native token `FTM` entirely to reduce the overall code complexity. The wrapped equivalent can be used instead.