msc-smart-contract-auditing/vulnerable-functions-base huggingface dataset:
Vulnerability: Staking#_unstake removes votes from wrong person if msg.sender != owner
Severity: high

Description:
Staking#_unstake allows any msg.sender to unstake tokens for any owner that has approved them. The issue is that even when msg.sender != owner the votes are removed from msg.sender instead of owner. The result is that the owner keeps their votes and msg.sender loses theirs. This could be abused to hijack or damage voting.\\n```\\naddress owner = ownerOf(_tokenId);\\nif (msg.sender != owner && !isApprovedForAll[owner][msg.sender] && msg.sender != getApproved[_tokenId]) revert NotAuthorized();\\n```\\n\\nStaking#_unstake allows any msg.sender to unstake tokens for any owner that has approved them.\\n```\\nuint lostVotingPower;\\nfor (uint i = 0; i < numTokens; i++) {\\n    lostVotingPower += _unstakeToken(_tokenIds[i], _to);\\n}\\n\\nvotesFromOwnedTokens[msg.sender] -= lostVotingPower;\\n// Since the delegate currently has the voting power, it must be removed from their balance\\n// If the user doesn't delegate, delegates(msg.sender) will return self\\ntokenVotingPower[getDelegate(msg.sender)] -= lostVotingPower;\\ntotalTokenVotingPower -= lostVotingPower;\\n```\\n\\nAfter looping through _unstakeToken all accumulated votes are removed from msg.sender. The problem with this is that msg.sender is allowed to unstake tokens for users other than themselves and in these cases they will lose votes rather than the user who owns the token.\\nExample: User A and User B both stake tokens and have 10 votes each. User A approves User B to unstake their tokens. User B calls unstake for User A. User B is msg.sender and User A is owner. The votes should be removed from owner but instead are removed from msg.sender. The result is that after unstaking User B has a vote balance of 0 while still having their locked token and User B has a vote balance of 10 and their token back. Now User B is unable to unstake their token because their votes will underflow on unstake, permanently trapping their NFT.

Impact:
Votes are removed incorrectly if msg.sender != owner. By extension this would forever trap msg.sender tokens in the contract.

Recommended mitigation:
Remove the ability for users to unstake for other users
Vulnerability: Delegate can keep can keep delegatee trapped indefinitely
Severity: medium

Description:
Users are allowed to delegate their votes to other users. Since staking does not implement checkpoints, users are not allowed to delegate or unstake during an active proposal if their delegate has already voted. A malicious delegate can abuse this by creating proposals so that there is always an active proposal and their delegatees are always locked to them.\\n```\\nmodifier lockedWhileVotesCast() {\\n  uint[] memory activeProposals = governance.getActiveProposals();\\n  for (uint i = 0; i < activeProposals.length; i++) {\\n    if (governance.getReceipt(activeProposals[i], getDelegate(msg.sender)).hasVoted) revert TokenLocked();\\n    (, address proposer,) = governance.getProposalData(activeProposals[i]);\\n    if (proposer == getDelegate(msg.sender)) revert TokenLocked();\\n  }\\n  _;\\n}\\n```\\n\\nThe above modifier is applied when unstaking or delegating. This reverts if the delegate of msg.sender either has voted or currently has an open proposal. The result is that under those conditions, the delgatee cannot unstake or delegate. A malicious delegate can abuse these conditions to keep their delegatees forever delegated to them. They would keep opening proposals so that delegatees could never unstake or delegate. A single users can only have a one proposal opened at the same time so they would use a secondary account to alternate and always keep an active proposal.

Impact:
Delegatees can never unstake or delegate to anyone else

Recommended mitigation:
There should be a function to emergency eject the token from staking. To prevent abuse a token that has been emergency ejected should be blacklisted from staking again for a certain cooldown period, such as the length of current voting period.
Vulnerability: If a user approves junior vault tokens to WithdrawPeriphery, anyone can withdraw/redeem his/her token
Severity: high

Description:
If users want to withdraw/redeem tokens by WithdrawPeriphery, they should approve token approval to WithdrawPeriphery, then call `withdrawToken()` or `redeemToken()`. But if users approve `dnGmxJuniorVault` to WithdrawPeriphery, anyone can withdraw/redeem his/her token.\\nUsers should approve `dnGmxJuniorVault` before calling `withdrawToken()` or redeemToken():\\n```\\n    function withdrawToken(\\n        address from,\\n        address token,\\n        address receiver,\\n        uint256 sGlpAmount\\n    ) external returns (uint256 amountOut) {\\n        // user has approved periphery to use junior vault shares\\n        dnGmxJuniorVault.withdraw(sGlpAmount, address(this), from);\\n// rest of code\\n\\n    function redeemToken(\\n        address from,\\n        address token,\\n        address receiver,\\n        uint256 sharesAmount\\n    ) external returns (uint256 amountOut) {\\n        // user has approved periphery to use junior vault shares\\n        dnGmxJuniorVault.redeem(sharesAmount, address(this), from);\\n// rest of code\\n```\\n\\nFor better user experience, we always use `approve(WithdrawPeriphery, type(uint256).max)`. It means that if Alice approves the max amount, anyone can withdraw/redeem her tokens anytime. Another scenario is that if Alice approves 30 amounts, she wants to call `withdrawToken` to withdraw 30 tokens. But in this case Alice should send two transactions separately, then an attacker can frontrun `withdrawToken` transaction and withdraw Alice's token.

Impact:
Attackers can frontrun withdraw/redeem transactions and steal tokens. And some UI always approves max amount, which means that anyone can withdraw users tokens.

Recommended mitigation:
Replace `from` parameter by `msg.sender`.\\n```\\n        // user has approved periphery to use junior vault shares\\n        dnGmxJuniorVault.withdraw(sGlpAmount, address(this), msg.sender);\\n\\n        // user has approved periphery to use junior vault shares\\n        dnGmxJuniorVault.redeem(sharesAmount, address(this), msg.sender);\\n```\\n
Vulnerability: WithdrawPeriphery#_convertToToken slippage control is broken for any token other than USDC
Severity: medium

Description:
WithdrawPeriphery allows the user to redeem junior share vaults to any token available on GMX, applying a fixed slippage threshold to all redeems. The slippage calculation always returns the number of tokens to 6 decimals. This works fine for USDC but for other tokens like WETH or WBTC that are 18 decimals the slippage protection is completely ineffective and can lead to loss of funds for users that are withdrawing.\\n```\\nfunction _convertToToken(address token, address receiver) internal returns (uint256 amountOut) {\\n    // this value should be whatever glp is received by calling withdraw/redeem to junior vault\\n    uint256 outputGlp = fsGlp.balanceOf(address(this));\\n\\n    // using min price of glp because giving in glp\\n    uint256 glpPrice = _getGlpPrice(false);\\n\\n    // using max price of token because taking token out of gmx\\n    uint256 tokenPrice = gmxVault.getMaxPrice(token);\\n\\n    // apply slippage threshold on top of estimated output amount\\n    uint256 minTokenOut = outputGlp.mulDiv(glpPrice * (MAX_BPS - slippageThreshold), tokenPrice * MAX_BPS);\\n\\n    // will revert if atleast minTokenOut is not received\\n    amountOut = rewardRouter.unstakeAndRedeemGlp(address(token), outputGlp, minTokenOut, receiver);\\n}\\n```\\n\\nWithdrawPeriphery allows the user to redeem junior share vaults to any token available on GMX. To prevent users from losing large amounts of value to MEV the contract applies a fixed percentage slippage. minToken out is returned to 6 decimals regardless of the token being requested. This works for tokens with 6 decimals like USDC, but is completely ineffective for the majority of tokens that aren't.

Impact:
Users withdrawing tokens other than USDC can suffer huge loss of funds due to virtually no slippage protection

Recommended mitigation:
Adjust minTokenOut to match the decimals of the token:\\n```\\n    uint256 minTokenOut = outputGlp.mulDiv(glpPrice * (MAX_BPS - slippageThreshold), tokenPrice * MAX_BPS);\\n+   minTokenOut = minTokenOut * 10 ** (token.decimals() - 6);\\n```\\n
Vulnerability: WithdrawPeriphery uses incorrect value for MAX_BPS which will allow much higher slippage than intended
Severity: medium

Description:
WithdrawPeriphery accidentally uses an incorrect value for MAX_BPS which will allow for much higher slippage than intended.\\n```\\nuint256 internal constant MAX_BPS = 1000;\\n```\\n\\nBPS is typically 10,000 and using 1000 is inconsistent with the rest of the ecosystem contracts and tests. The result is that slippage values will be 10x higher than intended.

Impact:
Unexpected slippage resulting in loss of user funds, likely due to MEV

Recommended mitigation:
Correct MAX_BPS:\\n```\\n-   uint256 internal constant MAX_BPS = 1000;\\n+   uint256 internal constant MAX_BPS = 10_000;\\n```\\n
Vulnerability: Early depositors to DnGmxSeniorVault can manipulate exchange rates to steal funds from later depositors
Severity: medium

Description:
To calculate the exchange rate for shares in DnGmxSeniorVault it divides the total supply of shares by the totalAssets of the vault. The first deposit can mint a very small number of shares then donate aUSDC to the vault to grossly manipulate the share price. When later depositor deposit into the vault they will lose value due to precision loss and the adversary will profit.\\n```\\nfunction convertToShares(uint256 assets) public view virtual returns (uint256) {\\n    uint256 supply = totalSupply(); // Saves an extra SLOAD if totalSupply is non-zero.\\n\\n    return supply == 0 ? assets : assets.mulDivDown(supply, totalAssets());\\n}\\n```\\n\\nShare exchange rate is calculated using the total supply of shares and the totalAsset. This can lead to exchange rate manipulation. As an example, an adversary can mint a single share, then donate 1e8 aUSDC. Minting the first share established a 1:1 ratio but then donating 1e8 changed the ratio to 1:1e8. Now any deposit lower than 1e8 (100 aUSDC) will suffer from precision loss and the attackers share will benefit from it.\\nThis same vector is present in DnGmxJuniorVault.

Impact:
Adversary can effectively steal funds from later users

Recommended mitigation:
Initialize should include a small deposit, such as 1e6 aUSDC that mints the share to a dead address to permanently lock the exchange rate:\\n```\\n    aUsdc.approve(address(pool), type(uint256).max);\\n    IERC20(asset).approve(address(pool), type(uint256).max);\\n\\n+   deposit(1e6, DEAD_ADDRESS);\\n```\\n
Vulnerability: The total community voting power is updated incorrectly when a user delegates.
Severity: high

Description:
When a user delegates their voting power from staked tokens, the total community voting power should be updated. But the update logic is not correct, the the total community voting power could be wrong values.\\n```\\n    tokenVotingPower[currentDelegate] -= amount;\\n    tokenVotingPower[_delegatee] += amount; \\n\\n    // If a user is delegating back to themselves, they regain their community voting power, so adjust totals up\\n    if (_delegator == _delegatee) {\\n      _updateTotalCommunityVotingPower(_delegator, true);\\n\\n    // If a user delegates away their votes, they forfeit their community voting power, so adjust totals down\\n    } else if (currentDelegate == _delegator) {\\n      _updateTotalCommunityVotingPower(_delegator, false);\\n    }\\n```\\n\\nWhen the total community voting power is increased in the first if statement, _delegator's token voting power might be positive already and community voting power might be added to total community voting power before.\\nAlso, currentDelegate's token voting power might be still positive after delegation so we shouldn't remove the communitiy voting power this time.

Impact:
The total community voting power can be incorrect.

Recommended mitigation:
Add more conditions to check if the msg.sender delegated or not.\\n```\\n    if (_delegator == _delegatee) {\\n        if(tokenVotingPower[_delegatee] == amount) {\\n            _updateTotalCommunityVotingPower(_delegator, true);\\n        }\\n        if(tokenVotingPower[currentDelegate] == 0) {\\n            _updateTotalCommunityVotingPower(currentDelegate, false);      \\n        }\\n    } else if (currentDelegate == _delegator) {\\n        if(tokenVotingPower[_delegatee] == amount) {\\n            _updateTotalCommunityVotingPower(_delegatee, true);\\n        }\\n        if(tokenVotingPower[_delegator] == 0) {\\n            _updateTotalCommunityVotingPower(_delegator, false);      \\n        }\\n    }\\n```\\n
Vulnerability: Staking#changeStakeTime and changeStakeAmount are problematic given current staking design
Severity: medium

Description:
Staking#changeStakeTime and changeStakeAmount allow the locking bonus to be modified. Any change to this value will cause voting imbalance in the system. If changes result in a higher total bonus then existing stakers will be given a permanent advantage over new stakers. If the bonus is increased then existing stakers will be at a disadvantage because they will be locked and unable to realize the new staking bonus.\\n```\\nfunction _stakeToken(uint _tokenId, uint _unlockTime) internal returns (uint) {\\n  if (_unlockTime > 0) {\\n    unlockTime[_tokenId] = _unlockTime;\\n    uint fullStakedTimeBonus = ((_unlockTime - block.timestamp) * stakingSettings.maxStakeBonusAmount) / stakingSettings.maxStakeBonusTime;\\n    stakedTimeBonus[_tokenId] = _tokenId < 10000 ? fullStakedTimeBonus : fullStakedTimeBonus / 2;\\n  }\\n```\\n\\nWhen a token is staked their stakeTimeBonus is stored. This means that any changes to stakingSettings.maxStakeBonusAmount or stakingSettings.maxStakeBonusTime won't affect tokens that are already stored. Storing the value is essential to prevent changes to the values causing major damage to the voting, but it leads to other more subtle issue when it is changed that will put either existing or new stakers at a disadvantage.\\nExample: User A stake when maxStakeBonusAmount = 10 and stake long enough to get the entire bonus. Now maxStakeBonusAmount is changed to 20. User A is unable to unstake their token right away because it is locked. They are now at a disadvantage because other users can now stake and get a bonus of 20 while they are stuck with only a bonus of 10. Now maxStakeBonusAmount is changed to 5. User A now has an advantage because other users can now only stake for a bonus of 5. If User A never unstakes then they will forever have that advantage over new users.

Impact:
Voting power becomes skewed for users when Staking#changeStakeTime and changeStakeAmount are used

Recommended mitigation:
I recommend implementing a poke function that can be called by any user on any user. This function should loop through all tokens (or the tokens specified) and recalculate their voting power based on current multipliers, allowing all users to be normalized to prevent any abuse.
Vulnerability: Adversary can abuse delegating to lower quorum
Severity: medium

Description:
When a user delegates to another user they surrender their community voting power. The quorum threshold for a vote is determined when it is created. Users can artificially lower quorum by delegating to other users then creating a proposal. After it's created they can self delegate and regain all their community voting power to reach quorum easier.\\n```\\n// If a user is delegating back to themselves, they regain their community voting power, so adjust totals up\\nif (_delegator == _delegatee) {\\n  _updateTotalCommunityVotingPower(_delegator, true);\\n\\n// If a user delegates away their votes, they forfeit their community voting power, so adjust totals down\\n} else if (currentDelegate == _delegator) {\\n  _updateTotalCommunityVotingPower(_delegator, false);\\n}\\n```\\n\\nWhen a user delegates to user other than themselves, they forfeit their community votes and lowers the total number of votes. When they self delegate again they will recover all their community voting power.\\n```\\n    newProposal.id = newProposalId.toUint96();\\n    newProposal.proposer = msg.sender;\\n    newProposal.targets = _targets;\\n    newProposal.values = _values;\\n    newProposal.signatures = _signatures;\\n    newProposal.calldatas = _calldatas;\\n\\n    //@audit quorum votes locked at creation\\n\\n    newProposal.quorumVotes = quorumVotes().toUint24();\\n    newProposal.startTime = (block.timestamp + votingDelay).toUint32();\\n    newProposal.endTime = (block.timestamp + votingDelay + votingPeriod).toUint32();\\n```\\n\\nWhen a proposal is created the quorum is locked at the time at which it's created. Users can combine these two quirks to abuse the voting.\\nExample:\\nAssume there is 1000 total votes and quorum is 20%. Assume 5 users each have 35 votes, 10 base votes and 25 community votes. In this scenario quorum is 200 votes which they can't achieve. Each user delegates to other users, reducing each of their votes by 25 and reducing the total number of votes of 875. Now they can create a proposal and quorum will now be 175 votes (875*20%). They all self delegate and recover their community votes. Now they can reach quorum and pass their proposal.

Impact:
Users can collude to lower quorum and pass proposal easier

Recommended mitigation:
One solution would be to add a vote cooldown to users after they delegate, long enough to make sure all active proposals have expired before they're able to vote. The other option would be to implement checkpoints.
Vulnerability: [Tomo-M3] Use safeMint instead of mint for ERC721
Severity: medium

Description:
Use safeMint instead of mint for ERC721\\nThe `msg.sender` will be minted as a proof of staking NFT when `_stakeToken()` is called.\\nHowever, if `msg.sender` is a contract address that does not support ERC721, the NFT can be frozen in the contract.\\nAs per the documentation of EIP-721:\\nA wallet/broker/auction application MUST implement the wallet interface if it will accept safe transfers.\\nAs per the documentation of ERC721.sol by Openzeppelin\\n```\\n/**\\n * @dev Mints `tokenId` and transfers it to `to`.\\n *\\n * WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible\\n *\\n * Requirements:\\n *\\n * - `tokenId` must not exist.\\n * - `to` cannot be the zero address.\\n *\\n * Emits a {Transfer} event.\\n */\\nfunction _mint(address to, uint256 tokenId) internal virtual {\\n```\\n

Impact:
Users possibly lose their NFTs

Recommended mitigation:
Use `safeMint` instead of `mint` to check received address support for ERC721 implementation.
Vulnerability: [Medium-1] Hardcoded `monsterMultiplier` in case of `stakedTimeBonus` disregards the updates done to `monsterMultiplier` through `setMonsterMultiplier()`
Severity: medium

Description:
[Medium-1] Hardcoded `monsterMultiplier` in case of `stakedTimeBonus` disregards the updates done to `monsterMultiplier` through `setMonsterMultiplier()`\\nFrankenDAO allows users to stake two types of NFTs, `Frankenpunks` and `Frankenmonsters` , one of which is considered more valuable, ie: `Frankenpunks`,\\nThis is achieved by reducing votes applicable for `Frankenmonsters` by `monsterMultiplier`.\\n```\\nfunction getTokenVotingPower(uint _tokenId) public override view returns (uint) {\\n      if (ownerOf(_tokenId) == address(0)) revert NonExistentToken();\\n\\n      // If tokenId < 10000, it's a FrankenPunk, so 100/100 = a multiplier of 1\\n      uint multiplier = _tokenId < 10_000 ? PERCENT : monsterMultiplier;\\n      \\n      // evilBonus will return 0 for all FrankenMonsters, as they are not eligible for the evil bonus\\n      return ((baseVotes * multiplier) / PERCENT) + stakedTimeBonus[_tokenId] + evilBonus(_tokenId);\\n    }\\n```\\n\\nThis `monsterMultiplier` is initially set as 50 and could be changed by governance proposal.\\n```\\nfunction setMonsterMultiplier(uint _monsterMultiplier) external onlyExecutor {\\n    emit MonsterMultiplierChanged(monsterMultiplier = _monsterMultiplier); \\n  }\\n```\\n\\nHowever, one piece of code inside the FrakenDAO staking contract doesn't consider this and has a monster multiplier hardcoded.\\n```\\nfunction stake(uint[] calldata _tokenIds, uint _unlockTime) \\n----\\nfunction _stakeToken(uint _tokenId, uint _unlockTime) internal returns (uint) {\\n   if (_unlockTime > 0) {\\n     --------\\n      stakedTimeBonus[_tokenId] = _tokenId < 10000 ? **fullStakedTimeBonus : fullStakedTimeBonus / 2;** \\n    }\\n--------\\n```\\n\\nHence any update done to `monsterMultiplier` would not reflect in the calculation of `stakedTimeBonus`, and thereby votes.

Impact:
Any update done to monsterMultiplier would not be reflected in stakedTimeBonus; it would always remain as /2 or 50%.\\nLikelihood: Medium\\nOne needs to pass a governance proposal to change the monster multiplier, so this is definitely not a high likelihood; it's not low as well, as there is a clear provision in spec regarding this.

Recommended mitigation:
Consider replacing the hardcoded value with monsterMultiplier
Vulnerability: `getCommunityVotingPower` doesn't calculate voting Power correctly due to precision loss
Severity: medium

Description:
In `Staking.sol`, the getCommunityVotingPower function, doesn't calculate the votes correctly due to precision loss.\\nIn getCommunityVotingPower function, the `return` statement is where the mistake lies in:\\n```\\n return \\n        (votes * cpMultipliers.votes / PERCENT) + \\n        (proposalsCreated * cpMultipliers.proposalsCreated / PERCENT) + \\n        (proposalsPassed * cpMultipliers.proposalsPassed / PERCENT);\\n```\\n\\nHere, after each multiplication by the `Multipliers`, we immediately divide it by `PERCENT`. Every time we do a division, there is a certain amount of precision loss. And when its done thrice, the loss just accumulates. So instead, the division by `PERCENT` should be done after all 3 terms are added together.\\nNote that this loss is not there, if the `Multipliers` are a multiple of `PERCENT`. But these values can be changed through governance later. So its better to be careful assuming that they may not always be a multiple of `PERCENT`.

Impact:
The community voting power of the user is calculated wrongly.

Recommended mitigation:
Do the division once after all terms are added together:\\n```\\n      return \\n        ( (votes * cpMultipliers.votes) + \\n        (proposalsCreated * cpMultipliers.proposalsCreated) + \\n        (proposalsPassed * cpMultipliers.proposalsPassed) ) / PERCENT;\\n    }\\n```\\n