msc-smart-contract-auditing/vulnerable-functions-base huggingface dataset:
Vulnerability: A stake that has just been locked gets full reward multiplier
Severity: medium

Description:
A staker gets rewarded with full multiplier even if its stake has just been locked. Multiplier calculation should take into account the duration of the lock.\\nA staker stakes an amount of tokens.\\nThe staker waits for some time\\nThe staker has control of another member (bribe, ...)\\nThe staker vouches this other member\\nThe member borrows\\nThe staker calls `Comptroller:withdrawRewards` and gets an amount of rewards with a multiplier corresponding to a locked stake\\nThe member repays the loan\\nNote that steps 4 to 7 can be made in one tx, so no interest is paid at step 7.\\nThe result is that the staker can always get the full multiplier for rewards, without ever putting any funds at risk, nor any interest being paid. This is done at the expense of other honest stakers, who get proprotionally less of the rewards dripped into the comptroller.\\nFor a coded PoC replace the test `"staker with locked balance gets more rewards"` in `staking.ts` with the following\\n```\\n        it("PoC: staker with locked balance gets more rewards even when just locked", async () => {\\n            const trustAmount = parseUnits("2000");\\n            const borrowAmount = parseUnits("1800");\\n            const [account, staker, borrower] = members;\\n\\n            const [accountStaked, borrowerStaked, stakerStaked] = await helpers.getStakedAmounts(\\n                account,\\n                staker,\\n                borrower\\n            );\\n\\n            expect(accountStaked).eq(borrowerStaked);\\n            expect(borrowerStaked).eq(stakerStaked);\\n\\n            await helpers.updateTrust(staker, borrower, trustAmount);\\n            \\n            await roll(10);\\n            await helpers.borrow(borrower, borrowAmount); // borrows just after withdrawing\\n            \\n            const [accountMultiplier, stakerMultiplier] = await helpers.getRewardsMultipliers(account, staker);\\n            console.log("accountMultiplier: ", accountMultiplier);\\n            console.log("StakerMultiplier: ", stakerMultiplier);\\n            expect(accountMultiplier).lt(stakerMultiplier);  // the multiplier is larger even if just locked\\n        });\\n```\\n

Impact:
A staker can get larger rewards designed for locked stakes by locking and unlocking in the same tx.

Recommended mitigation:
Issue A stake that has just been locked gets full reward multiplier\\nShould introduce the accounting of the duration of a lock into the rewards calculation, so that full multiplier is given only to a lock that is as old as the stake itself.
Vulnerability: updateTrust() vouchers also need check maxVouchers
Severity: medium

Description:
maxVouchers is to prevent the “vouchees“ array from getting too big and the loop will have the GAS explosion problem, but “vouchers“have the same problem, if you don't check the vouchers array, it is also possible that vouchers are big and cause updateLocked() to fail\\nvouchees check < maxVouchers ,but vouchers don't check\\n```\\n    function updateTrust(address borrower, uint96 trustAmount) external onlyMember(msg.sender) whenNotPaused {\\n// rest of code\\n            uint256 voucheesLength = vouchees[staker].length;\\n            if (voucheesLength >= maxVouchers) revert MaxVouchees();\\n\\n\\n            uint256 voucherIndex = vouchers[borrower].length;\\n            voucherIndexes[borrower][staker] = Index(true, uint128(voucherIndex));\\n            vouchers[borrower].push(Vouch(staker, trustAmount, 0, 0)); /**** don't check maxVouchers****/\\n```\\n

Impact:
it is also possible that vouchers are big and cause updateLocked() to fail

Recommended mitigation:
```\\n   function updateTrust(address borrower, uint96 trustAmount) external onlyMember(msg.sender) whenNotPaused {\\n// rest of code\\n            uint256 voucheesLength = vouchees[staker].length;\\n            if (voucheesLength >= maxVouchers) revert MaxVouchees();\\n\\n\\n            uint256 voucherIndex = vouchers[borrower].length;\\n+         if (voucherIndex >= maxVouchers) revert MaxVouchees();\\n            voucherIndexes[borrower][staker] = Index(true, uint128(voucherIndex));\\n            vouchers[borrower].push(Vouch(staker, trustAmount, 0, 0)); \\n```\\n
Vulnerability: getUserInfo() returns incorrect values for locked and stakedAmount
Severity: medium

Description:
The `getUserInfo()` function mixes up the values for `locked` and `stakedAmount`, so the value for each of these is returned for the other.\\nIn UnionLens.sol, the `getUserInfo()` function is used to retrieve information about a given user.\\nIn order to pull the user's staking information, the following function is called:\\n```\\n(bool isMember, uint96 locked, uint96 stakedAmount) = userManager.stakers(user);\\n```\\n\\nThis function is intended to return these three values from the UserManager.sol contract. However, in that contract, the function being called returns a Staker struct, which has the following values:\\n```\\nstruct Staker {\\n    bool isMember;\\n    uint96 stakedAmount;\\n    uint96 locked;\\n}\\n```\\n\\nBecause both `locked` and `stakedAmount` have the type `uint96`, the function does not revert, and simply returns the incorrect values to the caller.

Impact:
Any user or front end calling the `getUserInfo()` function will be given incorrect values, which could lead to wrong decisions.

Recommended mitigation:
Reverse the order of return values in the `getUserInfo()` function, so that it reads:\\n```\\n(bool isMember, uint96 stakedAmount, uint96 locked) = userManager.stakers(user);\\n```\\n
Vulnerability: `AssetManager.rebalance()` will revert when the balance of `tokenAddress` in the money market is 0.
Severity: medium

Description:
`AssetManager.rebalance()` will revert when the balance of `tokenAddress` in the money market is 0.\\nAssetManager.rebalance() tries to withdraw tokens from each money market for rebalancing here.\\n```\\n    // Loop through each money market and withdraw all the tokens\\n    for (uint256 i = 0; i < moneyMarketsLength; i++) {\\n        IMoneyMarketAdapter moneyMarket = moneyMarkets[i];\\n        if (!moneyMarket.supportsToken(tokenAddress)) continue;\\n        moneyMarket.withdrawAll(tokenAddress, address(this));\\n\\n        supportedMoneyMarkets[supportedMoneyMarketsSize] = moneyMarket;\\n        supportedMoneyMarketsSize++;\\n    }\\n```\\n\\nWhen the balance of the `tokenAddress` is 0, we don't need to call `moneyMarket.withdrawAll()` but it still tries to call.\\nBut this will revert because Aave V3 doesn't allow to withdraw 0 amount here.\\n```\\n  function validateWithdraw(\\n    DataTypes.ReserveCache memory reserveCache,\\n    uint256 amount,\\n    uint256 userBalance\\n  ) internal pure {\\n    require(amount != 0, Errors.INVALID_AMOUNT);\\n```\\n\\nSo `AssetManager.rebalance()` will revert if one money market has zero balance of `tokenAddress`.

Impact:
The money markets can't be rebalanced if there is no balance in at least one market.

Recommended mitigation:
Issue `AssetManager.rebalance()` will revert when the balance of `tokenAddress` in the money market is 0.\\nI think we can modify AaveV3Adapter.withdrawAll() to work only when the balance is positive.\\n```\\n    function withdrawAll(address tokenAddress, address recipient)\\n        external\\n        override\\n        onlyAssetManager\\n        checkTokenSupported(tokenAddress)\\n    {\\n        address aTokenAddress = tokenToAToken[tokenAddress];\\n        IERC20Upgradeable aToken = IERC20Upgradeable(aTokenAddress);\\n        uint256 balance = aToken.balanceOf(address(this));\\n\\n        if (balance > 0) {\\n            lendingPool.withdraw(tokenAddress, type(uint256).max, recipient);\\n        }\\n    }\\n```\\n
Vulnerability: gas limit DoS via unbounded operations
Severity: medium

Description:
Only one attack will lead to two types of vulnerabilities in `UserManager.sol` and `UToken.sol`\\nOn `UserManager.sol` ==> `updateTrust()` Case one: malicious users (members) can keep `vouching` Alice with `trustAmount == 0` until his `vouchers` array achieves the max limit (2**256-1) So when a normal member tries to give `vouching` to Alice with `trustAmount != 0` he will find because the `vouchers` array completely full.\\nCase two (which is more realistic ): malicious users (members) can keep `vouching` Alice with `trustAmount == 0` until his `vouchers` array achieves late's say 20% of max limit (2**256-1) The problem is when Alice invoke `borrow()` or `repayBorrow()` on `UToken.sol`\\n```\\n   IUserManager(userManager).updateLocked(msg.sender, uint96(amount + fee), true);\\n  …\\n  IUserManager(userManager).updateLocked(borrower, uint96(repayAmount - interest), false);\\n```\\n\\nIt will call `updateLocked()` on `UserManager.sol`\\n```\\n   function updateLocked(\\n        address borrower,\\n        uint96 amount,\\n        bool lock\\n    ) external onlyMarket {\\n        uint96 remaining = amount;\\n\\n        for (uint256 i = 0; i < vouchers[borrower].length; i++) {\\n \\n```\\n\\nThe for loop could go through `vouchers[]` which could be long enough to lead to a "gas limit DoS via unbounded operations" And the same thing with `registerMember()`, any user could lose all their fund in this transaction\\n```\\n       function registerMember(address newMember) public virtual whenNotPaused {\\n        if (stakers[newMember].isMember) revert NoExistingMember();\\n\\n        uint256 count = 0;\\n        uint256 vouchersLength = vouchers[newMember].length;\\n\\n        // Loop through all the vouchers to count how many active vouches there\\n        // are that are greater than 0. Vouch is the min of stake and trust\\n        for (uint256 i = 0; i < vouchersLength; i++) {\\n```\\n

Impact:
1- The user couldn't get any more `vouching` 2- The user will be not able to `borrow()` or `repayBorrow()` 3- No one can in invokeregisterMember() successfully for a specific user

Recommended mitigation:
Add check for `trustAmount == 0`
Vulnerability: Template implementations doesn't validate configurations properly
Severity: medium

Description:
In past audits, we have seen contract admins claim that invalidated configuration setters are fine since “admins are trustworthy”. However, cases such as Nomad got drained for over $150M and Misconfiguration in the Acala stablecoin project allows attacker to steal 1.2 billion aUSD have shown again and again that even trustable entities can make mistakes. Thus any fields that might potentially result in insolvency of protocol should be thoroughly checked.\\nNftPort template implementations often ignore checks for config fields. For the rest of the issue, we take `royalty` related fields as an example to illustrate potential consequences of misconfigurations. Notably, lack of check is not limited to `royalty`, but exists among most config fields.\\nAdmins are allowed to set a wrong `royaltiesBps` which is higher than `ROYALTIES_BASIS`. `royaltyInfo()` will accept this invalid `royaltiesBps` and users will pay a large amount of royalty.\\nEIP-2981 (NFT Royalty Standard) defines `royaltyInfo()` function that specifies how much to pay for a given sale price. In general, royalty should not be higher than 100%. NFTCollection.sol checks that admins can't set royalties to more than 100%:\\n```\\n    /// Validate a runtime configuration change\\n    function _validateRuntimeConfig(RuntimeConfig calldata config)\\n        internal\\n        view\\n    {\\n        // Can't set royalties to more than 100%\\n        require(config.royaltiesBps <= ROYALTIES_BASIS, "Royalties too high");\\n\\n        // rest of code\\n```\\n\\nBut `NFTCollection` only check `royaltiesBps` when admins call `updateConfig()`, it doesn't check `royaltiesBps` in `initialize()` function, leading to admins could set an invalid `royaltiesBps` (higher than 100%) when initializing contracts.\\nThe same problem exists in ERC721NFTProduct and ERC1155NFTProduct. Both ERC721NFTProduct and ERC1155NFTProduct don't check `royaltiesBasisPoints` in `initialize()` function. Furthermore, these contracts also don't check `royaltiesBasisPoints` when admins call `update()` function. It means that admins could set an invalid `royaltiesBasisPoints` which may be higher than 100% in any time.

Impact:
EIP-2981 only defines `royaltyInfo()` that it should return royalty amount rather than royalty percentage. It means that if the contract has an invalid royalty percentage which is higher than 100%, `royaltyInfo()` doesn't revert and users will pay a large amount of royalty.

Recommended mitigation:
Issue Template implementations doesn't validate configurations properly\\nCheck `royaltiesBps <= ROYALTIES_BASIS` both in `initialize()` and `update()` functions.
Vulnerability: Freezing roles in ERC721NFTProduct and ERC1155NFTProduct is moot
Severity: medium

Description:
In ERC721NFTProduct and ERC1155NFTProduct roles can be frozen which is supposed to lock role to current addresses and not allow any changes. The problem is that admin can still use AccessControlUpgradable#grantRole and revokeRole to grant and remove roles to addresses because hasRole allows "ADMIN_ROLE" to bypass all role restrictions even "DEFAULT_ADMIN_ROLE".\\n```\\nfunction hasRole(bytes32 role, address account)\\n    public\\n    view\\n    virtual\\n    override\\n    returns (bool)\\n{\\n    return\\n        super.hasRole(ADMIN_ROLE, account) || super.hasRole(role, account);\\n}\\n```\\n\\nIn GranularRoles.sol and AccessControlUpgradable.sol, developers are careful to never grant the "DEFAULT_ADMIN_ROLE" to any user. Additionally they never set the admin role of any role so that it's admin will remain "DEFAULT_ADMIN_ROLE". In theory this should make so that there is no way to grant or revoke roles outside of GranularRoles#_initRoles and updateRoles. The issue is that the override by GranularRoles#hasRole allows "ADMIN_ROLE" to bypass any role restriction including "DEFAULT_ADMIN_ROLE". This allows "ADMIN_ROLE" to directly call AccessControlUpgradable#grantRole and revokeRole, which makes the entire freezing system useless as it doesn't actually stop any role modification.

Impact:
Freezing roles doesn't actually prevent "ADMIN_ROLE" from modifying roles as intended. Submitting as high due to gross over-extension of admin authority clearly violating intended guardrails.

Recommended mitigation:
Override AccessControlUpgradable#grantRole and revokeRole in GranularRoles.sol to revert when called:\\n```\\n GranularRoles.sol\\n\\n+   function grantRole(bytes32 role, address account) public virtual override {\\n+       revert();\\n+   }\\n\\n+   function revokeRole(bytes32 role, address account) public virtual override {\\n+       revert();\\n+   }\\n```\\n
Vulnerability: registerTemplate() can't handle properly when ITemplate version is 0
Severity: medium

Description:
Factory.sol when register one template , and template ' s version is 0, the latestImplementation[templateName] will be address(0) and add other version, "_templateNames" will duplicate\\nWhen version is equal 0 latestImplementation[templateName] don't set\\n```\\n   function _setTemplate(\\n        string memory templateName,\\n        uint256 templateVersion,\\n        address implementationAddress\\n    ) internal {\\n// rest of code\\n\\n        if (latestImplementation[templateName] == address(0)) { /****add other version, _templateNames will duplicate ****/\\n            _templateNames.push(templateName);\\n        }\\n\\n        if (templateVersion > latestVersion[templateName]) {\\n            latestVersion[templateName] = templateVersion;\\n            latestImplementation[templateName] = implementationAddress; /****templateVersion==0 ,  don't set ****/\\n        }\\n\\n    }\\n```\\n

Impact:
latestImplementation[templateName] and _templateNames will error. external contracts may think there is no setup, resulting in duplicate setups that keep failing

Recommended mitigation:
```\\n    function _setTemplate(\\n        string memory templateName,\\n        uint256 templateVersion,\\n        address implementationAddress\\n    ) internal {\\n\\n -      if (templateVersion > latestVersion[templateName]) {\\n +      if (templateVersion > = latestVersion[templateName]) {\\n            latestVersion[templateName] = templateVersion;\\n            latestImplementation[templateName] = implementationAddress; \\n        }\\n```\\n
Vulnerability: Factory uses signature that do not have expiration
Severity: medium

Description:
NftPort can't remove license from user, once the signature was provided to it, without changing `SIGNER_ROLE` address.\\nIn Factory contract there are few methods that are called when signed by trusted signer.\\nThis is how the signature is checked\\n```\\nsignedOnly(abi.encodePacked(msg.sender, instance, data), signature)\\n```\\n\\nAs you can see there is no any expiration time. That means that once, the signer has signed the signature for the user it can use it for the end of life. It's like lifetime license. The only option to remove the license from user is to revoke `SIGNER_ROLE` and set it to another account. But it's possible that the NFTPort will have a need to do that with current signer.

Impact:
License can't be removed.

Recommended mitigation:
Add expiration param to the signature.
Vulnerability: Users can avoid performance fees by withdrawing before the end of the epoch forcing other users to pay their fees
Severity: medium

Description:
No performance fees are taken when user withdraws early from the vault but their withdrawal value will be used to take fees, which will be taken from other users.\\n```\\nuint256 adjustedTotalAssets = _totalAssets() + l.totalWithdrawals;\\n\\nif (adjustedTotalAssets > l.lastTotalAssets) {\\n    netIncome = adjustedTotalAssets - l.lastTotalAssets;\\n\\n    feeInCollateral = l.performanceFee64x64.mulu(netIncome);\\n\\n    ERC20.safeTransfer(l.feeRecipient, feeInCollateral);\\n}\\n```\\n\\nWhen taking the performance fees, it factors in both the current assets of the vault as well as the total value of withdrawals that happened during the epoch. Fees are paid from the collateral tokens in the vault, at the end of the epoch. Paying the fees like this reduces the share price of all users, which effectively works as a fee applied to all users. The problem is that withdraws that take place during the epoch are not subject to this fee and the total value of all their withdrawals are added to the adjusted assets of the vault. This means that they don't pay any performance fee but the fee is still taken from the vault collateral. In effect they completely avoid the fee force all there other users of the vault to pay it for them.

Impact:
User can avoid performance fees and force other users to pay them

Recommended mitigation:
Fees should be taken on withdrawals that occur before vault is settled
Vulnerability: processAuction() in VaultAdmin.sol can be called multiple times by keeper if the auction is canceled.
Severity: medium

Description:
processAuction() in VaultAdmin.sol can be called multiple times by keeper if the auction is canceled.\\nprocessAuction() in VaultAdmin.sol can be called multiple times by keeper, the code below would execute more than one times if the auction is canceled.\\nbecause it is the line of code inside the function processAuction in VaultAdmin.sol below that can change the auction status to PROCESSED.\\nthis code only runs when the auction is finalized, it not finalized, the auction is in Canceled State and\\n```\\n   bool cancelled = l.Auction.isCancelled(lastEpoch);\\n        bool finalized = l.Auction.isFinalized(lastEpoch);\\n\\n        require(\\n            (!finalized && cancelled) || (finalized && !cancelled),\\n            "auction is not finalized nor cancelled"\\n        );\\n```\\n\\nwould always pass because the auction is in cancel state.

Impact:
Why the processAuction should not be called multiple times?\\nIn the first time it is called, the withdrawal lock is released so user can withdraw fund,\\n```\\n // deactivates withdrawal lock\\n  l.auctionProcessed = true;\\n```\\n\\nthen if we called again, the lastTotalAssets can be updated multiple times.\\n```\\n        // stores the last total asset amount, this is effectively the amount of assets held\\n        // in the vault at the start of the auction\\n        l.lastTotalAssets = _totalAssets();\\n```\\n\\nthe total asset can be lower and lower because people are withdrawing their fund.\\nthen when _collectPerformanceFee is called, the performance may still be collected

Recommended mitigation:
Issue processAuction() in VaultAdmin.sol can be called multiple times by keeper if the auction is canceled.\\nWe recommend the project lock the epoch and make it impossible for keeper to call the processAuction again.
Vulnerability: Settlement slippage is not implemented correctly which may lead to some vaults being impossible to settle
Severity: high

Description:
The contract is supposed to implement a different max slippage value depending on the settlement type, but these values have no impact because they are never actually applied. Instead, regardless of settlement type or function inputs, max slippage will always be limited to the value of balancerPoolSlippageLimitPercent. This can be problematic because the default value allows only 1% slippage. If settlement slippage goes outside of 1% then settlement of any kind will become impossible.\\nBoosted3TokenAuraHelper.sol#L95-L99\\n```\\n    params.minPrimary = poolContext._getTimeWeightedPrimaryBalance(\\n        oracleContext, strategyContext, bptToSettle\\n    );\\n\\n    params.minPrimary = params.minPrimary * strategyContext.vaultSettings.balancerPoolSlippageLimitPercent / \\n        uint256(BalancerConstants.VAULT_PERCENT_BASIS);\\n```\\n\\nBoosted3TokenAuraHelper#_executeSettlement first sets params.minPrimary overwriting any value from function input. Next it adjusts minPrimary by balancerPoolSlippageLimitPercent, which is a constant set at pool creation; however it doesn't ever adjust it by Params.DynamicTradeParams.oracleSlippagePercent. This means that the max possible slippage regardless of settlement type is limited to the slippage allowed by balancerPoolSlippageLimitPercent. If the max slippage ever goes outside of this range, then settlement of any kind will become impossible.

Impact:
Settlement may become impossible

Recommended mitigation:
Params.DynamicTradeParams.oracleSlippagePercent is validated in every scenario before Boosted3TokenAuraHelper#_executeSettlement is called, so we can apply these values directly when calculating minPrimary:\\n```\\n    params.minPrimary = poolContext._getTimeWeightedPrimaryBalance(\\n        oracleContext, strategyContext, bptToSettle\\n    );\\n\\n+   DynamicTradeParams memory callbackData = abi.decode(\\n+       params.secondaryTradeParams, (DynamicTradeParams)\\n+   );\\n\\n-   params.minPrimary = params.minPrimary * strategyContext.vaultSettings.balancerPoolSlippageLimitPercent / \\n+   params.minPrimary = params.minPrimary * \\n+      (strategyContext.vaultSettings.balancerPoolSlippageLimitPercent - callbackData.oracleSlippagePercent) / \\n       uint256(BalancerConstants.VAULT_PERCENT_BASIS);\\n```\\n